ITensorTDVP.jl
ITensorTDVP.jl copied to clipboard
Reuse Projected Hamiltonian Tensors
It occurs to me the current setup of using TDVP has a major inefficiency: after calling the tdvp
function on a given timestep, for the next timestep the exact same MPS that was returned is often reused as the starting state for the next timestep. This means that many of the "environment" tensors representing the Hamiltonian projected into the MPS basis are being recomputed two times when they could be computed just once.
Some solutions could be:
- enable saving the projection tensors and passing them back to the algorithm each time
- making a second, higher-level interface where one specifies the total time value and then that code runs TDVP where a certain amount of time evolution is done each
nsweeps
sweeps
Could you be more explicit with some code (for example pointing out problematic lines of code or showing a minimal example)? Is this an issue within the tdvp
function, or when you call the tdvp
function multiple times?
For example you should be able to do:
tdvp(H, ψ0, -0.1im; nsweeps=10)
in order to evolve to time 1.0
, does that address the problem you are seeing?
Currently the time that is input is the timestep per sweep, which was an arbitrary choice. We can change it so that the input time t
is the total time, and the timestep is t/nsweeps
.
Agreed it would be nice to split out the code that happens within a sweep into a separate function (say tdvp_iteration
or tdvp_sweep
). That could accept and return the environment tensors to make it more efficient, as you suggest. That is the design of the TDVP/VUMPS code in ITensorInfiniteMPS.jl
.
Thanks for clarifying these things. I was actually thinking it could be a bug that the total time is t*nsweeps
in the current code, and that one might have reasons to do more than one sweep per time step.
But perhaps that is a rare, if ever needed feature and we should leave it as it is now where the total time evolved is t*nsweeps
.
In that case, then yes mainly what would be needed is an interface change or a second interface where you input a total time and then it figures out the number of sweeps to do.
Finally, one drawback of that sort of thing is that if one wants to "monitor" some property as the time evolution is proceeding, then one has to use an observer or that sort of thing. This can actually get rather complicated: one algorithm I'm currently doing involves time-evolving 4 MPS at once, and overlapping them every time step to get the desired result.
So then one idea is that we can think of ways to make "anonymous observers" – something like this:
Sz = Float64[]
ttotal = 10.0
psi = tdvp(H,psi,-im*ttotal; cutoff) do
push!(Sz,expect(psi,"Sz"; site_range=j:j)
end
(That's not the best example because of the overhead of expect
there but hopefully conveys the idea.)
To make sure I answered your question above, I was only referring to the case of calling tdvp
multiple times in a loop.
Right, it sounds like a simple way to go is to have a high level interface tdvp
that does multiple sweeps of TDVP up to a desired number of sweeps of a specified time step (like the current version) and/or up to a total time, with the possibility of passing an observer like we do with DMRG. That would be the high level interface.
Then we could have a version which just calls a single sweep of TDVP (maybe called tdvp_iteration
or tdvp_sweep
) which is the "low level" interface for more advanced requirements like the one you mentioned of time evolving 4 different states at the same time. This version would output the projected MPOs so they can be passed to the next iteration. Additionally, the high level tdvp
function would be implemented just by looping over tdvp_iteration
/tdvp_sweep
. Does that sound reasonable?
Yes, let's go for that design. Sounds like the right choice.
Seperately, let's think about how to make really simple observers in an easy way, like that anonymous example above. I know you and Giacomo had worked on a more simple observer design in the context of PastaQ.
We have a package: https://github.com/GTorlai/Observers.jl which I think is pretty simple to use. Basically you just create an Observer
from a list of functions you want called at each iteration, and then you retrieve the results with a dictionary-like interface.
The goal was to generalize the observer functionality in ITensor with just a single type, and ultimately the hope was to just use that Observer
type in dmrg
. So tdvp
in this package would be a good testing ground for that.