I recently updated my package deps, which included incorporating the guidance regarding ITensorMPS. I noticed though that some interfaces seem to have changed.
In my calculations I use linsolve from ITensorTDVP. I need to be able to access the state from the previous sweep and compare it to the state from the current sweep. In prior versions of ITensorsTDVP this was possible because I could customize my Observer and then overload checkdone! (as described here).
For example, previously I was doing something like this.
mutable struct MyObserver <: AbstractObserver
tol::Float64 # some tolerance i use to check for convergence
previous_state::MPS
end
function ITensors.checkdone!(obs::MyObserver ; kwargs...)
approx = kwargs[:psi]
sweep = kwargs[:sweep]
if sweep == 1
obs.previous_state = deepcopy(approx)
return false
else
err = some_convergence_check(obs.previous_state, approx)
if err <= obs.tol
return true
else
obs.previous_state = deepcopy(approx)
return false
end
end
end
The interface has appeared to have changed based on an inspection of the code here. Given my read (which could be wrong), it seems we now explicitly have to specify checkdone and not only that but checkdone now has a fixed interface that only accepts state, sweep, and outputlevel (see line 108 at above link). I am not sure how to achieve the behavior shown above with this new interface. Possibly defining my own checkdone that is a some type of closure that closes over state with each iteration so that I can access in the next iteration?
Any hints or thoughts on how I can use the current interface to achieve my goals here? Or am I missing something?
Yes, the new interface is to pass a checkdone function that accepts those keyword arguments and outputs true or false. It was slightly arbitrary that we only pass state, sweep, outputlevel as keyword argument to checkdone, but we can easily pass more objects as keyword arguments to checkdone. It seems like you’re only using psi and sweep, can you just change your function over to use state and sweep?
Unfortunately no - at least not as far as I tell. I have to be able to access the state from the previous sweep and I think what you are saying is I have to make this work using only the state from the current sweep.
What I mean is
# How do I get state from previous sweep into this? I need state from sweep-1
isdone = checkdone(; state, sweep, outputlevel)
Previously my method worked because I could store the state from sweep-1 in the observer which was updated every sweep and then the observer was passed to checkdone.
Could the interface be modified so that it always accepts the observer as the first argument? If so I’d be happy to generate a pull request.
Edit: In short, I am trying to use the L2 distance between the previous and current state as the stopping criteria.
Could your implementation of checkdone store an array of the previous states it encountered? For example, your checkdone function could be a “closure” over an array that is made in the scope surrounding the function definition. Or you could make checkdone to be a callable object which could store internal data. Let me know if you need more clarification on one of these approaches.
Thanks for the clarification, after I posted I realized the nontrivial part of your checkdone definition involves accessing previous states.
The suggestion by Miles of creating a closure/callable that stores previous states is a clever way to achieve that.
As an alternative, we can pass the observer(s) to the checkdone function as keyword arguments so users can access those, say if you are storing previous states in the observer anyway. I’ll make a PR adding that functionality.