ITensor / ITensorTDVP.jl

Time dependent variational principle (TDVP) of MPS based on ITensors.jl.
MIT License
49 stars 13 forks source link

Reuse Projected Hamiltonian Tensors #7

Open emstoudenmire opened 2 years ago

emstoudenmire commented 2 years ago

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:

mtfishman commented 2 years ago

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?

mtfishman commented 2 years ago

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?

mtfishman commented 2 years ago

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.

emstoudenmire commented 2 years ago

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.)

emstoudenmire commented 2 years ago

To make sure I answered your question above, I was only referring to the case of calling tdvp multiple times in a loop.

mtfishman commented 2 years ago

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?

emstoudenmire commented 2 years ago

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.

mtfishman commented 2 years ago

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.

mtfishman commented 2 years ago

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.