def calc_B(self, n, set_eta=True): """Generates the B[n] tangent vector corresponding to physical evolution of the state. In other words, this returns B[n][x*] (equiv. eqn. (47) of arXiv:1103.0936v2 [cond-mat.str-el]) with x* the parameter matrices satisfying the Euler-Lagrange equations as closely as possible. In the case of Bc, use the general Bc generated in calc_B_centre(). """ if n == self.N_centre: B, eta_c = self.calc_B_centre() if set_eta: self.eta[self.N_centre] = eta_c else: l_sqrt, r_sqrt, l_sqrt_inv, r_sqrt_inv = self.calc_l_r_roots(n) if n > self.N_centre: Vsh = tm.calc_Vsh(self.A[n], r_sqrt, sanity_checks=self.sanity_checks) x = self.calc_x(n, Vsh, l_sqrt, r_sqrt, l_sqrt_inv, r_sqrt_inv, right=True) B = sp.empty_like(self.A[n]) for s in xrange(self.q[n]): B[s] = mm.mmul(l_sqrt_inv, x, mm.H(Vsh[s]), r_sqrt_inv) if self.sanity_checks: M = tm.eps_r_noop(self.r[n], B, self.A[n]) if not sp.allclose(M, 0): print "Sanity Fail in calc_B!: B_%u does not satisfy GFC!" % n else: Vsh = tm.calc_Vsh_l(self.A[n], l_sqrt, sanity_checks=self.sanity_checks) x = self.calc_x(n, Vsh, l_sqrt, r_sqrt, l_sqrt_inv, r_sqrt_inv, right=False) B = sp.empty_like(self.A[n]) for s in xrange(self.q[n]): B[s] = mm.mmul(l_sqrt_inv, mm.H(Vsh[s]), x, r_sqrt_inv) if self.sanity_checks: M = tm.eps_l_noop(self.l[n - 1], B, self.A[n]) if not sp.allclose(M, 0): print "Sanity Fail in calc_B!: B_%u does not satisfy GFC!" % n if set_eta: self.eta[n] = sp.sqrt(mm.adot(x, x)) return B
def _calc_B_l(self, n, set_eta=True): if self.q[n] * self.D[n - 1] - self.D[n] > 0: l_sqrt, l_sqrt_inv, r_sqrt, r_sqrt_inv = tm.calc_l_r_roots(self.l[n - 1], self.r[n], zero_tol=self.zero_tol, sanity_checks=self.sanity_checks, sc_data=('site', n)) Vsh = tm.calc_Vsh_l(self.A[n], l_sqrt, sanity_checks=self.sanity_checks) x = self.calc_x_l(n, Vsh, l_sqrt, r_sqrt, l_sqrt_inv, r_sqrt_inv) if set_eta: self.eta[n] = sp.sqrt(m.adot(x, x)) B = sp.empty_like(self.A[n]) for s in xrange(self.q[n]): B[s] = m.mmul(l_sqrt_inv, m.H(Vsh[s]), x, r_sqrt_inv) return B else: return None
def _calc_B_l_n(self, n, set_eta=True, l_s_m1=None, l_si_m1=None, r_s=None, r_si=None, Vlh=None): if self.q[n] * self.D[n - 1] - self.D[n] > 0: if l_s_m1 is None: l_s_m1, l_si_m1, r_s, r_si = tm.calc_l_r_roots(self.l[n - 1], self.r[n], zero_tol=self.zero_tol, sanity_checks=self.sanity_checks, sc_data=('site', n)) if Vlh is None: Vlh = tm.calc_Vsh_l(self.A[n], l_s_m1, sanity_checks=self.sanity_checks) x = self.calc_x_l(n, Vlh, l_s_m1, r_s, l_si_m1, r_si) if set_eta: self.eta_sq[n] = m.adot(x, x) B = sp.empty_like(self.A[n]) for s in xrange(self.q[n]): B[s] = m.mmul(l_si_m1, m.H(Vlh[s]), x, r_si) return B else: return None
def take_step(self, dtau, B=None, save_memory=False, calc_Y_2s=False, dynexp=False, dD_max=16, D_max=0, sv_tol=1E-14): """Performs a complete forward-Euler step of imaginary time dtau. The operation is A[n] -= dtau * B[n] with B[n] from self.calc_B(n). If dtau is itself imaginary, real-time evolution results. Second-order corrections to the dynamics can be calculated if desired. If they are, the norm of the second-order contributions is stored in self.eta_BB. For nearest-neighbour Hamiltonians, this captures all errors made by projecting onto the MPS tangent plane. The second-order contributions also form the basis of the dynamical expansion scheme (dynexp), which captures a configurable (dD_max) amount of these contributions by increasing the bond dimension. Parameters ---------- dtau : complex The (imaginary or real) amount of imaginary time (tau) to step. B : sequence of ndarray The direction to step in. Not compatible with dynexp or save_memory. save_memory : bool Whether to save memory by avoiding storing all B[n] at once. calc_Y_2s : bool Whether to calculate the second-order contributions to the dynamics. dynexp : bool Whether to increase the bond dimension to capture more of the dynamics. dD_max : int The maximum amount by which to increase the bond dimension (for any site). D_max : int The maximum bond dimension to allow when expanding. sv_tol : float Only use singular values larger than this for dynamical expansion. """ self.eta_sq.fill(0) self.etaBB_sq.fill(0) if (self.gauge_fixing == 'right' and save_memory and not calc_Y_2s and not dynexp and B is None): B = [None] * (self.N + 1) for n in xrange(1, self.N + self.ham_sites): #V is not always defined (e.g. at the right boundary vector, and possibly before) if n <= self.N: B[n] = self.calc_B(n) #Only change an A after the next B no longer depends on it! if n >= self.ham_sites: m = n - self.ham_sites + 1 if not B[m] is None: self.A[m] += -dtau * B[m] B[m] = None assert all(x is None for x in B), "take_step update incomplete!" else: if B is None or dynexp or calc_Y_2s: l_s = sp.empty((self.N + 1), dtype=sp.ndarray) l_si = sp.empty((self.N + 1), dtype=sp.ndarray) r_s = sp.empty((self.N + 1), dtype=sp.ndarray) r_si = sp.empty((self.N + 1), dtype=sp.ndarray) Vrh = sp.empty((self.N + 1), dtype=sp.ndarray) Vlh = sp.empty((self.N + 1), dtype=sp.ndarray) for n in xrange(1, self.N + 1): l_s[n-1], l_si[n-1], r_s[n], r_si[n] = tm.calc_l_r_roots(self.l[n - 1], self.r[n], zero_tol=self.zero_tol, sanity_checks=self.sanity_checks, sc_data=('site', n)) if dynexp or calc_Y_2s or self.gauge_fixing == 'left': for n in xrange(1, self.N + 1): Vlh[n] = tm.calc_Vsh_l(self.A[n], l_s[n-1], sanity_checks=self.sanity_checks) if dynexp or calc_Y_2s or self.gauge_fixing == 'right': for n in xrange(1, self.N + 1): Vrh[n] = tm.calc_Vsh(self.A[n], r_s[n], sanity_checks=self.sanity_checks) if B is None: B = self.calc_B(set_eta=True, l_s=l_s, l_si=l_si, r_s=r_s, r_si=r_si, Vlh=Vlh, Vrh=Vrh) if calc_Y_2s or dynexp: Y, self.etaBB_sq = self.calc_BB_Y_2s(l_s, l_si, r_s, r_si, Vrh, Vlh) if dynexp: BB12, BB21, dD = self.calc_BB_2s(Y, Vlh, Vrh, l_si, r_si, dD_max=dD_max, sv_tol=sv_tol, D_max=D_max) for n in xrange(1, self.N + 1): if not B[n] is None: self.A[n] += -dtau * B[n] if sp.any(dD > 0): oldA = self.A oldD = self.D.copy() oldeta = self.eta_sq oldetaBB = self.etaBB_sq self.D += dD self._init_arrays() for n in xrange(1, self.N + 1): self.A[n][:, :oldD[n - 1], :oldD[n]] = oldA[n] if not BB12[n] is None: self.A[n][:, :oldD[n - 1], oldD[n]:] = -1.j * sp.sqrt(dtau) * BB12[n] if not BB21[n] is None: self.A[n][:, oldD[n - 1]:, :oldD[n]] = -1.j * sp.sqrt(dtau) * BB21[n] log.info("Dyn. expanded! New D: %s", self.D) self.eta_sq = oldeta self.etaBB_sq = oldetaBB else: for n in xrange(1, self.N + 1): if not B[n] is None: self.A[n] += -dtau * B[n] self.eta = sp.sqrt(self.eta_sq.sum()) self.etaBB = sp.sqrt(self.etaBB_sq.sum())