├── .gitignore ├── README.md ├── basisextension.h ├── sample ├── Makefile.sample ├── README.md └── run.cc └── tdvp.h /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.swp 3 | sample/Makefile 4 | sample/run 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TDVP 2 | ITensor implementation of the time dependent variational principle (TDVP) algorithm for finite MPS. 3 | It also includes a global subspace expansion algorithm to enlarge the bond dimension of the MPS for TDVP according to the paper [arXiv:2005.06104](https://arxiv.org/abs/2005.06104) or [Phys. Rev. B 102, 094315 (2020)](https://journals.aps.org/prb/abstract/10.1103/PhysRevB.102.094315), which would be useful for reliable time evolution of two-dimensional systems and one-dimensional systems with long-range interactions. 4 | 5 | Requires a working version of the ITensor library on your system. 6 | 7 | See the sample/ directory for an example of how to run it. 8 | 9 | -------------------------------------------------------------------------------- /basisextension.h: -------------------------------------------------------------------------------- 1 | #include "itensor/decomp.h" 2 | #include "itensor/mps/mps.h" 3 | #include "itensor/mps/mpo.h" 4 | #include "itensor/mps/mpsalgs.cc" 5 | #include "itensor/mps/localop.h" 6 | #include "itensor/util/print_macro.h" 7 | #include "itensor/util/cputime.h" 8 | #include "itensor/tensor/slicemat.h" 9 | 10 | namespace itensor{ 11 | 12 | void 13 | denmatSumDecomp(std::vector const& psis, 14 | MPS & res, 15 | std::vector & Bs, 16 | int b, 17 | Direction dir, 18 | Args args = Args::global()) 19 | { 20 | // NumCenter can only be 1 if not want to treat res exactly 21 | const int numCenter = args.getInt("NumCenter",1); 22 | const bool quiet = args.getBool("Quiet",false); 23 | 24 | // SVD site tensor of res without truncaion 25 | auto [V1,S1,U1] = svd(Bs.front(), dir == Fromleft? rightLinkIndex(res,b): leftLinkIndex(res,b)); 26 | 27 | // Find the indices to be left to the density matrix 28 | auto& to_orth = Bs.front(); 29 | auto& newoc = (dir==Fromleft? res(b+1) : res(b-1)); 30 | auto& activeInds = to_orth.inds(); 31 | auto cinds = stdx::reserve_vector(activeInds.r()); 32 | for(auto& I : activeInds) 33 | { 34 | if(!hasIndex(newoc,I)) cinds.push_back(I); 35 | } 36 | 37 | auto [cmb,mid] = combiner(std::move(cinds)); 38 | 39 | if(dim(mid) <= dim(commonIndex(U1,S1))) 40 | { 41 | res.ref(b) = U1; 42 | if(!quiet) 43 | printfln("warning: at bond %d, already reach maximum bond dimension.", b); 44 | } 45 | else 46 | { 47 | // Density matrix summation to be truncated 48 | ITensor rho2; 49 | if(numCenter == 1) 50 | { 51 | auto psi = psis.begin(); 52 | for(auto B = Bs.begin()+1; B != Bs.end(); ++B, ++psi) 53 | { 54 | rho2 += prime(*B)*dag(prime(*B,dir == Fromleft? rightLinkIndex(*psi,b): leftLinkIndex(*psi,b))); 55 | } 56 | rho2.swapPrime(0,1); 57 | } 58 | else 59 | { 60 | Error("numCenter can only be one"); 61 | } 62 | 63 | // Form the density matrix with only 2 indices 64 | U1 *= cmb; 65 | rho2 *= cmb; 66 | cmb.prime(); 67 | cmb.dag(); 68 | rho2 *= cmb; 69 | cmb.noPrime(); 70 | 71 | // Project rho2c to the orthogonal complement of U1 72 | auto proj2 = toDense(delta(dag(mid),prime(mid)))-dag(U1)*prime(U1,mid); 73 | auto normrho2 = norm(rho2); 74 | rho2 *= mapPrime(proj2,0,2); 75 | rho2 *= proj2; 76 | rho2.mapPrime(2,0); 77 | rho2.swapPrime(0,1); 78 | auto normPrho2P = norm(rho2); 79 | if(normPrho2P/normrho2 < 1E-12)// TODO: changed to calculate the trace will have less complexity and have the same effect! 80 | { 81 | res.ref(b) = cmb * U1; 82 | if(!quiet) 83 | printfln("warning: at bond %d, not adding any new basis.", b); 84 | } 85 | else 86 | { 87 | // Diagonalize rho2c to obtain U2 88 | ITensor U2, D2; 89 | args.add("Truncate",true); 90 | diag_hermitian(rho2,U2,D2,args);// T==prime(U)*D*dag(U) 91 | U2.dag(); 92 | 93 | // Direct sum of U1 and U2 94 | auto i1 = commonIndex(U1,S1); 95 | auto i2 = commonIndex(U2,D2); 96 | auto sumind = Index(dim(i1)+dim(i2),"Link"); 97 | sumind.setDir(i1.dir()); 98 | ITensor expand1,expand2; 99 | plussers(i1,i2,sumind,expand1,expand2); 100 | auto U = U1*expand1 + U2*expand2; 101 | 102 | res.ref(b) = cmb * U; 103 | } 104 | 105 | } 106 | 107 | // Obtain the new Bs for the operation of the next site 108 | Bs.front() *= dag(res(b)); 109 | Bs.front() *= (dir == Fromleft? res(b+1): res(b-1)); 110 | auto psi = psis.begin(); 111 | for(auto B = Bs.begin()+1; B != Bs.end(); ++B, ++psi) 112 | { 113 | (*B) *= dag(res(b)); 114 | (*B) *= (dir == Fromleft? (*psi).A(b+1): (*psi).A(b-1)); 115 | } 116 | 117 | } 118 | 119 | void 120 | addBasisWorker(std::vector const& psis, 121 | MPS & res, 122 | Direction dir, 123 | const Args & args = Args::global()) 124 | { 125 | int N = length(res); 126 | int nt = psis.size()+1; 127 | 128 | if(dir == Fromleft) 129 | { 130 | if(orthoCenter(res) != 1) 131 | Error("OC need set to be 1"); 132 | for(auto& psi : psis) 133 | { 134 | if(orthoCenter(psi) != 1) 135 | Error("OC need set to be 1"); 136 | } 137 | 138 | auto Bs = std::vector(nt); 139 | Bs.front() = res(1); 140 | auto psi = psis.begin(); 141 | for(auto B = Bs.begin()+1; B != Bs.end(); ++B, ++psi) 142 | { 143 | (*B) = (*psi).A(1); 144 | } 145 | 146 | for(int b = 1; b < N ; ++b) 147 | { 148 | denmatSumDecomp(psis,res,Bs,b,Fromleft,args); 149 | } 150 | 151 | res.Aref(N) = Bs.front(); 152 | } 153 | else 154 | { 155 | if(orthoCenter(res) != N) 156 | Error("OC need set to be N"); 157 | for(auto& psi : psis) 158 | { 159 | if(orthoCenter(psi) != N) 160 | Error("OC need set to be N"); 161 | } 162 | 163 | auto Bs = std::vector(nt); 164 | Bs.front() = res(N); 165 | auto psi = psis.begin(); 166 | for(auto B = Bs.begin()+1; B != Bs.end(); ++B, ++psi) 167 | { 168 | (*B) = (*psi).A(N); 169 | } 170 | 171 | for(int b = N; b > 1 ; --b) 172 | { 173 | denmatSumDecomp(psis,res,Bs,b,Fromright,args); 174 | } 175 | 176 | res.Aref(1) = Bs.front(); 177 | } 178 | } 179 | 180 | void addBasis(MPS& phi, 181 | const MPO& H, 182 | std::vector const& truncK, 183 | const Args& args0 = Args::global()) 184 | { 185 | auto quiet = args0.getBool("Quiet",false); 186 | auto dk = args0.getInt("KrylovOrd",2); 187 | auto method = args0.getString("Method","DensityMatrix"); 188 | auto nsw = args0.getInt("Nsweep",2); 189 | auto donormalize = args0.getBool("DoNormalize",false);//TODO: add a function to construct general 1-tauH 190 | 191 | auto psis = std::vector(dk-1); 192 | 193 | cpu_time expand_time; 194 | for(int i = 0; i < dk-1; ++i) 195 | { 196 | auto args1 = Args("Method=",method,"Cutoff=",truncK.at(i),"Nsweep=",nsw); 197 | if(args0.defined("WriteDim")) 198 | { 199 | args1.add("WriteDim",args0.getInt("WriteDim")); 200 | if(args0.defined("WriteDir")) args1.add("WriteDir",args0.getString("WriteDir")); 201 | } 202 | 203 | if(i==0) 204 | psis.at(i) = applyMPO(H,phi,args1); 205 | else 206 | psis.at(i) = applyMPO(H,psis.at(i-1),args1); 207 | 208 | psis.at(i).noPrime(); 209 | if(donormalize) 210 | psis.at(i).normalize(); 211 | 212 | if(!quiet) 213 | { 214 | printfln("norm(psi%d)=%.20f",i+1,norm(psis.at(i))); 215 | printfln("maxLinkDim(psi%d) = %d",i+1,maxLinkDim(psis.at(i))); 216 | } 217 | } 218 | 219 | int N = length(phi); 220 | for(int i = 0; i < dk-1; ++i) 221 | { 222 | psis.at(i).position(N); 223 | } 224 | phi.position(N); 225 | 226 | //TODO: adjustable weight for each psi 227 | addBasisWorker(psis,phi,Fromright,args0); 228 | 229 | auto sm = expand_time.sincemark(); 230 | printfln("\nmaxLinkDim after global subspace expansion = %d",maxLinkDim(phi)); 231 | printfln("Global subspace expansion: cputime = %s, walltime = %s",showtime(sm.time),showtime(sm.wall)); 232 | } 233 | 234 | void addBasis(MPS& phi, 235 | const MPO& H, 236 | std::vector const& maxdimK, 237 | const Args& args0 = Args::global()) 238 | { 239 | auto dk = args0.getInt("KrylovOrd",2); 240 | auto method = args0.getString("Method","DensityMatrix"); 241 | auto nsw = args0.getInt("Nsweep",2); 242 | auto donormalize = args0.getBool("DoNormalize",false); 243 | 244 | auto psis = std::vector(dk-1); 245 | 246 | cpu_time expand_time; 247 | for(int i = 0; i < dk-1; ++i) 248 | { 249 | auto args1 = Args("Method=",method,"MaxDim=",maxdimK.at(i),"Nsweep=",nsw); 250 | if(args0.defined("WriteDim")) 251 | { 252 | args1.add("WriteDim",args0.getInt("WriteDim")); 253 | if(args0.defined("WriteDir")) args1.add("WriteDir",args0.getString("WriteDir")); 254 | } 255 | 256 | if(i==0) 257 | psis.at(i) = applyMPO(H,phi,args1); 258 | else 259 | psis.at(i) = applyMPO(H,psis.at(i-1),args1); 260 | 261 | psis.at(i).noPrime(); 262 | if(donormalize) 263 | psis.at(i).normalize(); 264 | 265 | printfln("norm(psi%d)=%.20f",i+1,norm(psis.at(i))); 266 | printfln("maxLinkDim(psi%d) = %d",i+1,maxLinkDim(psis.at(i))); 267 | } 268 | 269 | int N = length(phi); 270 | for(int i = 0; i < dk-1; ++i) 271 | { 272 | psis.at(i).position(N); 273 | } 274 | phi.position(N); 275 | 276 | addBasisWorker(psis,phi,Fromright,args0); 277 | 278 | auto sm = expand_time.sincemark(); 279 | printfln("\nmaxLinkDim after global subspace expansion = %d",maxLinkDim(phi)); 280 | printfln("Global subspace expansion: cputime = %s, walltime = %s",showtime(sm.time),showtime(sm.wall)); 281 | } 282 | 283 | }// namespace itensor 284 | -------------------------------------------------------------------------------- /sample/Makefile.sample: -------------------------------------------------------------------------------- 1 | # 1. Put this file in the same folder as your 'driver' code 2 | # (the code containing the 'main' function). 3 | 4 | # 2. Edit LIBRARY_DIR to point at the location of your ITensor Library 5 | # source folder (this is the folder that has options.mk in it). 6 | # Also, edit TDVP_DIR to point at the location of the TDVP source files. 7 | LIBRARY_DIR=$(HOME)/itensor 8 | TDVP_DIR=$(HOME)/TDVP 9 | 10 | # 3. If your 'main' function is in a file called 'myappname.cc', then 11 | # set APP to 'myappname'. Running 'make' will compile the app. 12 | # Running 'make debug' will make a program called 'myappname-g' 13 | # which includes debugging symbols and can be used in gdb (Gnu debugger); 14 | APP=run 15 | 16 | # 4. Add any headers your program depends on here. The make program 17 | # will auto-detect if these headers have changed and recompile your app. 18 | HEADERS=$(TDVP_DIR)/tdvp.h 19 | 20 | # 5. For any additional .cc (source) files making up your project, 21 | # add their full filenames here. 22 | CCFILES=$(APP).cc 23 | 24 | ################################################################# 25 | ################################################################# 26 | ################################################################# 27 | ################################################################# 28 | 29 | 30 | include $(LIBRARY_DIR)/this_dir.mk 31 | include $(LIBRARY_DIR)/options.mk 32 | 33 | TENSOR_HEADERS=$(LIBRARY_DIR)/itensor/core.h 34 | 35 | CCFLAGS+=-I$(TDVP_DIR) 36 | CCGFLAGS+=-I$(TDVP_DIR) 37 | 38 | #Mappings -------------- 39 | OBJECTS=$(patsubst %.cc,%.o, $(CCFILES)) 40 | GOBJECTS=$(patsubst %,.debug_objs/%, $(OBJECTS)) 41 | 42 | #Rules ------------------ 43 | 44 | %.o: %.cc $(HEADERS) $(TENSOR_HEADERS) 45 | $(CCCOM) -c $(CCFLAGS) -o $@ $< 46 | 47 | .debug_objs/%.o: %.cc $(HEADERS) $(TENSOR_HEADERS) 48 | $(CCCOM) -c $(CCGFLAGS) -o $@ $< 49 | 50 | #Targets ----------------- 51 | 52 | build: $(APP) 53 | debug: $(APP)-g 54 | 55 | $(APP): $(OBJECTS) $(ITENSOR_LIBS) 56 | $(CCCOM) $(CCFLAGS) $(OBJECTS) -o $(APP) $(LIBFLAGS) 57 | 58 | $(APP)-g: mkdebugdir $(GOBJECTS) $(ITENSOR_GLIBS) 59 | $(CCCOM) $(CCGFLAGS) $(GOBJECTS) -o $(APP)-g $(LIBGFLAGS) 60 | 61 | clean: 62 | rm -fr .debug_objs *.o $(APP) $(APP)-g 63 | 64 | mkdebugdir: 65 | mkdir -p .debug_objs 66 | 67 | -------------------------------------------------------------------------------- /sample/README.md: -------------------------------------------------------------------------------- 1 | To run this sample TDVP code, you first need to have ITensor V3 2 | installed. Then, edit the Makefile to point towards your installation 3 | of ITensor as well as the location of the TDVP source code (the tdvp.h header file). 4 | 5 | If you have any questions about this repository, please contact . 6 | 7 | ### TDVP 8 | 9 | #### Function 10 | ``tdvp(MPS psi, MPO H, Cplx t, Sweeps sweeps, Args args) -> Real energy`` 11 | 12 | ``tdvp(MPS psi, MPO H, Cplx t, Sweeps sweeps, DMRGObserver obs, Args args) -> Real energy`` 13 | 14 | Note there are other interfaces available for TDVP, which are similar to their [DMRG counterparts](http://itensor.org/docs.cgi?page=classes/dmrg&vers=cppv3). 15 | 16 | #### Parameters 17 | 18 | `psi`: the MPS to be time evolved. 19 | 20 | `H`: the MPO of the Hamiltonian. Currently only Hermitian Hamiltonians can be treated; for non-Hermitian Hamiltonians, please merge this [pull request](https://github.com/ITensor/ITensor/pull/410) to your ITensor and set the argument `IsHermitian` to be `false` in the `tdvp` function. (In this pull request, the `applyExp` function is modified to include the Arnoldi orthogonalization so as to treat the non-Hermitian Hamiltonians. In addition, it uses a [time-step adjusting technique](https://www.maths.uq.edu.au/expokit/paper.pdf), so if your time step is too large for the `MaxIter` (controlled by `niter` of `sweeps`, see below) argument, the algorithm will automatically reduce the time step and restart the integration for multiple times until either the requested time step is reached or the `MaxRestart` is reached. This strategy is useful when you have a limited memory resource, since you can restrict your `MaxIter` to a smaller number and thus less memory is required, though same effect can be achieved by requesting a smaller time step.) 21 | 22 | `t`: the time step of TDVP. It can be real, imaginary, or complex. The corresponding time evolution operator of a single time step will be . Therefore, to do real time evolution, `t` need to be purely imaginary; to do imaginary time evolution, `t` need to be purely real. 23 | 24 | `sweeps`: Specify the sweep parameters of TDVP (similar to DMRG). `nsweeps` is the number of TDVP sweeps. A TDVP sweep = a sweep from left to right with half time step + a sweep from right to left with half time step. The total evolution time = `t*nsweep`. `maxdim` is the maximum bond dimension of the sweep. `cutoff` is the truncation error of the sweep (to allow truncation for the one-site TDVP, one needs to set the `Truncate` `args` to `true` (see below)). `niter` is the maximum number of lanczos iterations used when solving each local effective TDVP equations. 25 | 26 | `obs`: is the observer one can customize to do measurement after each sweep without recalling the `tdvp` function. Similar to its use in DMRG. 27 | 28 | `args`: `NumCenter` can either be 1 or 2, corresponding to the one-site and two-site TDVP respectively (default is `2`). `Truncate` choose whether or not truncate when doing the SVD (for `NumCenter=1`, default is `false`; for `NumCenter=2`, default is `true`). `DoNormalize` choose whether or not normalize the MPS after a TDVP sweep (default is `true`). `Quiet` choose to whether or not print out the information of each local update. If `WriteDim` is specified, then the environment tensors PH's for the TDVP sweep will be written to disk when the bond dimension of the MPS is larger than `WriteDim`. `WriteDir` gives the directory to write those environment tensors PH's (default is the current directory). 29 | 30 | 31 | ### Global subspace expansion 32 | 33 | #### Function 34 | ``addBasis(MPS phi, MPO H, vector truncK, Args args)`` 35 | 36 | ``addBasis(MPS phi, MPO H, vector maxdimK, Args args)`` 37 | 38 | #### Parameters 39 | `phi`: the MPS will be global subspace expanded after calling the function. 40 | 41 | `H`: the MPO of the operator to be used to construct the subspace, e.g. the Hamiltonian or the operator 42 | 43 | `truncK` `maxdimK`: one can choose to either specify the truncation error `truncK` or the maximum bond dimension `maxdimK` when applying the MPO `H`. Each element in the vector corresponds to each order of application. For example, `truncK={1e-8,1e-6}` means the truncation error of `H` applying to `phi` is 1e-8 and the truncation error of `H` applying to `H*phi`(obtained from previous step) is 1e-6. `maxdimK` is useful to make sure the bond dimension growth when applying the MPO `H` not out of control. When the bond dimension of the MPS `phi` is not small, a **typical strategy** is to set `maxdimK` to be the same as `maxLinkDim(phi)` and tune the `KrylovOrd` and `Cutoff` args in the `addBasis` function and the size of the time step `t` in the `tdvp` function accordingly to get optimal performance. For a Hamiltonian `H` with long-range interactions, usually at some point during the time evolution when the bond dimension becomes large enough (one needs to test what is the minimal bond dimension to turn off GSE), the global subspace expansion could be **turned off** and we can switch to the ordinary two-site TDVP without global subspace expansion. 44 | 45 | `args`: `Cutoff` set the truncation error when diagonalizing the sum of the reduced density matrices (default is `1e-15`). `KrylovOrd` is the dimension of the Krylov subspace (default is `2`). `Method` specify which method is used when applying the MPO, it can be e.g. `DensityMatrix` or `Fit` (default is `DensityMatrix`, the `DensityMatrix` way to apply the MPO will produce more relevant basis than the `Fit` way given the same bond dimension, though at a [higher cost](http://itensor.org/docs.cgi?vers=cppv3&page=classes/mps_mpo_algs)). `DoNormalize` choose whether or not to perform normalization after applying the MPO (default is `false`). `Nsweep` specify the number of sweeps if use the `Fit` method to apply MPO (default is `2`). `Quiet` choose whether or not print out the norm and bond dimension of the Krylov vectors and the warning messages, (default is `false`). If `WriteDim` is provided, `DensityMatrix` way of applying the MPO will write the intermediate environment tensors E's to disk when the bond dimension of the MPS is bigger than `WriteDim`, thus reducing the memory usage. `WriteDir` gives the directory to write those environment tensors E's (default is the current directory). 46 | -------------------------------------------------------------------------------- /sample/run.cc: -------------------------------------------------------------------------------- 1 | #include "itensor/all.h" 2 | #include "tdvp.h" 3 | #include "basisextension.h" 4 | 5 | using namespace itensor; 6 | 7 | int main() 8 | { 9 | // The system will be a 2x20 ladder 10 | int Nx = 20; 11 | int Ny = 2; 12 | int N = Nx*Ny; 13 | auto t = 0.1; 14 | auto tend = 0.5; 15 | int nsw = tend/t; 16 | auto t0 = 0.01; 17 | 18 | // Make N spin 1/2's 19 | auto sites = SpinHalf(N); 20 | 21 | // Make the Hamiltonian for rung-decoupled Heisenberg ladder 22 | auto ampo = AutoMPO(sites); 23 | for(int i = 1; i <= N-2; ++ i) 24 | { 25 | ampo += 0.5,"S+",i,"S-",i+2; 26 | ampo += 0.5,"S-",i,"S+",i+2; 27 | ampo += "Sz",i,"Sz",i+2; 28 | } 29 | auto H = toMPO(ampo); 30 | printfln("Maximum bond dimension of H is %d",maxLinkDim(H)); 31 | 32 | // Set the initial state to be Neel state 33 | auto state = InitState(sites); 34 | state.set(1,"Up"); 35 | for(int i = 2; i < N; i=i+2) 36 | { 37 | if((i/2)%2==1) 38 | { 39 | state.set(i,"Dn"); 40 | state.set(i+1,"Dn"); 41 | } 42 | else 43 | { 44 | state.set(i,"Up"); 45 | state.set(i+1,"Up"); 46 | } 47 | } 48 | state.set(N,"Up"); 49 | 50 | auto psi1 = MPS(state); 51 | auto psi2 = psi1; 52 | 53 | // start TDVP, either one site or two site algorithm can be used by adjusting the "NumCenter" argument 54 | println("----------------------------------------GSE-TDVP---------------------------------------"); 55 | 56 | auto energy = real(innerC(psi1,H,psi1)); 57 | printfln("Initial energy = %.5f", energy); 58 | 59 | auto sweeps = Sweeps(1); 60 | sweeps.maxdim() = 2000; 61 | sweeps.cutoff() = 1E-12; 62 | sweeps.niter() = 10; 63 | 64 | for(int n = 1; n <= nsw; ++n) 65 | { 66 | if(n < 3) 67 | { 68 | // Global subspace expansion 69 | std::vector epsilonK = {1E-12, 1E-12}; 70 | addBasis(psi1,H,epsilonK,{"Cutoff",1E-8, 71 | "Method","DensityMatrix", 72 | "KrylovOrd",3, 73 | "DoNormalize",true, 74 | "Quiet",true}); 75 | } 76 | 77 | // TDVP sweep 78 | energy = tdvp(psi1,H,-t,sweeps,{"Truncate",true, 79 | "DoNormalize",true, 80 | "Quiet",true, 81 | "NumCenter",1, 82 | "ErrGoal",1E-7}); 83 | } 84 | 85 | printfln("\nEnergy after imaginary time evolution = %.10f",energy); 86 | printfln("Using overlap = %.10f", real(innerC(psi1,H,psi1)) ); 87 | 88 | println("-------------------------------------MPO W^I 2nd order---------------------------------------"); 89 | 90 | auto expH1 = toExpH(ampo,(1-1_i)/2*t0); 91 | auto expH2 = toExpH(ampo,(1+1_i)/2*t0); 92 | printfln("Maximum bond dimension of expH1 is %d",maxLinkDim(expH1)); 93 | auto args = Args("Method=","DensityMatrix","Cutoff=",1E-12,"MaxDim=",2000); 94 | for(int n = 1; n <= nsw*std::real(t/t0); ++n) 95 | { 96 | psi2 = applyMPO(expH1,psi2,args); 97 | psi2.noPrime(); 98 | psi2 = applyMPO(expH2,psi2,args); 99 | psi2.noPrime().normalize(); 100 | if(n%int(std::real(t/t0)) == 0) 101 | { 102 | printfln("\nMaximum bond dimension at time %.1f is %d ", n*t0, maxLinkDim(psi2)); 103 | printfln("Energy using overlap at time %.1f is %.10f", n*t0, real(innerC(psi2,H,psi2)) ); 104 | } 105 | } 106 | 107 | return 0; 108 | } 109 | -------------------------------------------------------------------------------- /tdvp.h: -------------------------------------------------------------------------------- 1 | #ifndef __ITENSOR_TDVP_H 2 | #define __ITENSOR_TDVP_H 3 | 4 | #include "itensor/iterativesolvers.h" 5 | #include "itensor/mps/localmposet.h" 6 | #include "itensor/mps/sweeps.h" 7 | #include "itensor/mps/DMRGObserver.h" 8 | #include "itensor/util/cputime.h" 9 | 10 | 11 | namespace itensor { 12 | 13 | template 14 | Real 15 | TDVPWorker(MPS & psi, 16 | LocalOpT& PH, 17 | Cplx t, 18 | const Sweeps& sweeps, 19 | const Args& args = Args::global()); 20 | 21 | template 22 | Real 23 | TDVPWorker(MPS & psi, 24 | LocalOpT& PH, 25 | Cplx t, 26 | const Sweeps& sweeps, 27 | DMRGObserver & obs, 28 | Args args = Args::global()); 29 | 30 | // 31 | // Available TDVP methods: 32 | // second order integrator: sweep left-to-right and right-to-left 33 | // 34 | 35 | // 36 | //TDVP with an MPO 37 | // 38 | Real inline 39 | tdvp(MPS & psi, 40 | MPO const& H, 41 | Cplx t, 42 | const Sweeps& sweeps, 43 | const Args& args = Args::global()) 44 | { 45 | LocalMPO PH(H,args); 46 | Real energy = TDVPWorker(psi,PH,t,sweeps,args); 47 | return energy; 48 | } 49 | 50 | // 51 | //TDVP with an MPO and custom DMRGObserver 52 | // 53 | Real inline 54 | tdvp(MPS & psi, 55 | MPO const& H, 56 | Cplx t, 57 | const Sweeps& sweeps, 58 | DMRGObserver & obs, 59 | const Args& args = Args::global()) 60 | { 61 | LocalMPO PH(H,args); 62 | Real energy = TDVPWorker(psi,PH,t,sweeps,obs,args); 63 | return energy; 64 | } 65 | 66 | // 67 | //TDVP with an MPO and boundary tensors LH, RH 68 | // LH - H1 - H2 - ... - HN - RH 69 | //(ok if one or both of LH, RH default constructed) 70 | // 71 | Real inline 72 | tdvp(MPS & psi, 73 | MPO const& H, 74 | Cplx t, 75 | ITensor const& LH, 76 | ITensor const& RH, 77 | const Sweeps& sweeps, 78 | const Args& args = Args::global()) 79 | { 80 | LocalMPO PH(H,LH,RH,args); 81 | Real energy = TDVPWorker(psi,PH,t,sweeps,args); 82 | return energy; 83 | } 84 | 85 | // 86 | //TDVP with an MPO and boundary tensors LH, RH 87 | //and a custom observer 88 | // 89 | Real inline 90 | tdvp(MPS & psi, 91 | MPO const& H, 92 | Cplx t, 93 | ITensor const& LH, 94 | ITensor const& RH, 95 | const Sweeps& sweeps, 96 | DMRGObserver& obs, 97 | const Args& args = Args::global()) 98 | { 99 | LocalMPO PH(H,LH,RH,args); 100 | Real energy = TDVPWorker(psi,PH,t,sweeps,obs,args); 101 | return energy; 102 | } 103 | 104 | // 105 | //TDVP with a set of MPOs (lazily summed) 106 | //(H vector is 0-indexed) 107 | // 108 | Real inline 109 | tdvp(MPS& psi, 110 | std::vector const& Hset, 111 | Cplx t, 112 | const Sweeps& sweeps, 113 | const Args& args = Args::global()) 114 | { 115 | LocalMPOSet PH(Hset,args); 116 | Real energy = TDVPWorker(psi,PH,t,sweeps,args); 117 | return energy; 118 | } 119 | 120 | // 121 | //TDVP with a set of MPOs and a custom DMRGObserver 122 | //(H vector is 0-indexed) 123 | // 124 | Real inline 125 | tdvp(MPS & psi, 126 | std::vector const& Hset, 127 | Cplx t, 128 | const Sweeps& sweeps, 129 | DMRGObserver& obs, 130 | const Args& args = Args::global()) 131 | { 132 | LocalMPOSet PH(Hset,args); 133 | Real energy = TDVPWorker(psi,PH,t,sweeps,obs,args); 134 | return energy; 135 | } 136 | 137 | 138 | // 139 | // TDVPWorker 140 | // 141 | 142 | template 143 | Real 144 | TDVPWorker(MPS & psi, 145 | LocalOpT& PH, 146 | Cplx t, 147 | Sweeps const& sweeps, 148 | Args const& args) 149 | { 150 | DMRGObserver obs(psi,args); 151 | Real energy = TDVPWorker(psi,PH,t,sweeps,obs,args); 152 | return energy; 153 | } 154 | 155 | template 156 | Real 157 | TDVPWorker(MPS & psi, 158 | LocalOpT& H, 159 | Cplx t, 160 | Sweeps const& sweeps, 161 | DMRGObserver& obs, 162 | Args args) 163 | { 164 | // Truncate blocks of degenerate singular values (or not) 165 | args.add("RespectDegenerate",args.getBool("RespectDegenerate",true)); 166 | 167 | const bool silent = args.getBool("Silent",false); 168 | if(silent) 169 | { 170 | args.add("Quiet",true); 171 | args.add("PrintEigs",false); 172 | args.add("NoMeasure",true); 173 | args.add("DebugLevel",-1); 174 | } 175 | const bool quiet = args.getBool("Quiet",false); 176 | const int debug_level = args.getInt("DebugLevel",(quiet ? -1 : 0)); 177 | const int numCenter = args.getInt("NumCenter",2); 178 | if(numCenter != 1) 179 | args.add("Truncate",args.getBool("Truncate",true)); 180 | else 181 | args.add("Truncate",args.getBool("Truncate",false)); 182 | 183 | const int N = length(psi); 184 | Real energy = NAN; 185 | 186 | if((!isOrtho(psi)) || (psi.leftLim() != 0)) 187 | psi.position(1); 188 | 189 | args.add("DebugLevel",debug_level); 190 | 191 | for(int sw = 1; sw <= sweeps.nsweep(); ++sw) 192 | { 193 | cpu_time sw_time; 194 | args.add("Sweep",sw); 195 | args.add("NSweep",sweeps.nsweep()); 196 | args.add("Cutoff",sweeps.cutoff(sw)); 197 | args.add("MinDim",sweeps.mindim(sw)); 198 | args.add("MaxDim",sweeps.maxdim(sw)); 199 | args.add("MaxIter",sweeps.niter(sw)); 200 | 201 | if(!H.doWrite() 202 | && args.defined("WriteDim") 203 | && maxLinkDim(psi) >= args.getInt("WriteDim")) 204 | { 205 | if(!quiet) 206 | { 207 | println("\nTurning on write to disk, write_dir = ", 208 | args.getString("WriteDir","./")); 209 | } 210 | 211 | //psi.doWrite(true); 212 | H.doWrite(true,args); 213 | } 214 | 215 | // 0, 1 and 2-site wavefunctions 216 | ITensor phi0,phi1; 217 | Spectrum spec; 218 | for(int b = 1, ha = 1; ha <= 2; sweepnext(b,ha,N,{"NumCenter=",numCenter})) 219 | { 220 | if(!quiet) 221 | printfln("Sweep=%d, HS=%d, Bond=%d/%d",sw,ha,b,(N-1)); 222 | 223 | H.numCenter(numCenter); 224 | H.position(b,psi); 225 | 226 | if(numCenter == 2) 227 | phi1 = psi(b)*psi(b+1); 228 | else if(numCenter == 1) 229 | phi1 = psi(b); 230 | 231 | applyExp(H,phi1,t/2,args); 232 | 233 | if(args.getBool("DoNormalize",true)) 234 | phi1 /= norm(phi1); 235 | 236 | if(numCenter == 2) 237 | spec = psi.svdBond(b,phi1,(ha==1 ? Fromleft : Fromright),H,args); 238 | else if(numCenter == 1) 239 | psi.ref(b) = phi1; 240 | 241 | if((ha == 1 && b+numCenter-1 != N) || (ha == 2 && b != 1)) 242 | { 243 | auto b1 = (ha == 1 ? b+1 : b); 244 | 245 | if(numCenter == 2) 246 | { 247 | phi0 = psi(b1); 248 | } 249 | else if(numCenter == 1) 250 | { 251 | Index l; 252 | if(ha == 1) l = commonIndex(psi(b),psi(b+1)); 253 | else l = commonIndex(psi(b-1),psi(b)); 254 | ITensor U,S,V(l); 255 | spec = svd(phi1,U,S,V,args); 256 | psi.ref(b) = U; 257 | phi0 = S*V; 258 | } 259 | 260 | H.numCenter(numCenter-1); 261 | H.position(b1,psi); 262 | 263 | applyExp(H,phi0,-t/2,args); 264 | 265 | if(args.getBool("DoNormalize",true)) 266 | phi0 /= norm(phi0); 267 | 268 | if(numCenter == 2) 269 | { 270 | psi.ref(b1) = phi0; 271 | } 272 | if(numCenter == 1) 273 | { 274 | if(ha == 1) psi.ref(b+1) *= phi0; 275 | else psi.ref(b-1) *= phi0; 276 | } 277 | 278 | // Calculate energy 279 | ITensor H_phi0; 280 | H.product(phi0,H_phi0); 281 | energy = real(eltC(dag(phi0)*H_phi0)); 282 | } 283 | else 284 | { 285 | // Calculate energy 286 | ITensor H_phi1; 287 | H.product(phi1,H_phi1); 288 | energy = real(eltC(dag(phi1)*H_phi1)); 289 | } 290 | 291 | if(!quiet) 292 | { 293 | printfln(" Truncated to Cutoff=%.1E, Min_dim=%d, Max_dim=%d", 294 | sweeps.cutoff(sw), 295 | sweeps.mindim(sw), 296 | sweeps.maxdim(sw) ); 297 | printfln(" Trunc. err=%.1E, States kept: %s", 298 | spec.truncerr(), 299 | showDim(linkIndex(psi,b)) ); 300 | } 301 | 302 | obs.lastSpectrum(spec); 303 | 304 | args.add("AtBond",b); 305 | args.add("HalfSweep",ha); 306 | args.add("Energy",energy); 307 | args.add("Truncerr",spec.truncerr()); 308 | 309 | obs.measure(args); 310 | 311 | } //for loop over b 312 | 313 | if(!silent) 314 | { 315 | auto sm = sw_time.sincemark(); 316 | printfln(" Sweep %d/%d CPU time = %s (Wall time = %s)", 317 | sw,sweeps.nsweep(),showtime(sm.time),showtime(sm.wall)); 318 | } 319 | 320 | if(obs.checkDone(args)) break; 321 | 322 | } //for loop over sw 323 | 324 | psi.rightLim(psi.leftLim()+2); 325 | if(args.getBool("DoNormalize",true)) 326 | psi.normalize(); 327 | 328 | return energy; 329 | } 330 | 331 | } //namespace itensor 332 | 333 | #endif 334 | --------------------------------------------------------------------------------