def calc_Vsh(A, r_s, sanity_checks=False): D = A.shape[2] Dm1 = A.shape[1] q = A.shape[0] if q * D - Dm1 <= 0: return None R = sp.zeros((D, q, Dm1), dtype=A.dtype, order='C') for s in xrange(q): R[:,s,:] = r_s.dot(A[s].conj().T) R = R.reshape((q * D, Dm1)) Vconj = ns.nullspace_qr(R.conj().T).T if sanity_checks: if not sp.allclose(mm.mmul(Vconj.conj(), R), 0): log.warning("Sanity Fail in calc_Vsh!: VR != 0") if not sp.allclose(mm.mmul(Vconj, Vconj.conj().T), sp.eye(Vconj.shape[0])): log.warning("Sanity Fail in calc_Vsh!: V H(V) != eye") Vconj = Vconj.reshape((q * D - Dm1, D, q)) Vsh = Vconj.T Vsh = sp.asarray(Vsh, order='C') if sanity_checks: Vs = sp.transpose(Vsh, axes=(0, 2, 1)).conj() M = eps_r_noop(r_s, Vs, A) if not sp.allclose(M, 0): log.warning("Sanity Fail in calc_Vsh!: Bad Vsh") return Vsh
def density_2s(self, n1, n2): """Returns a reduced density matrix for a pair of sites. Currently only supports sites in the nonuniform window. Parameters ---------- n1 : int The site number of the first site. n2 : int The site number of the second site (must be > n1). """ rho = sp.empty((self.q[n1] * self.q[n2], self.q[n1] * self.q[n2]), dtype=sp.complex128) r_n2 = sp.empty_like(self.r[n2 - 1]) r_n1 = sp.empty_like(self.r[n1 - 1]) ln1m1 = self.get_l(n1 - 1) for s2 in xrange(self.q[n2]): for t2 in xrange(self.q[n2]): r_n2 = mm.mmul(self.A[n2][t2], self.r[n2], mm.H(self.A[n2][s2])) r_n = r_n2 for n in reversed(xrange(n1 + 1, n2)): r_n = tm.eps_r_noop(r_n, self.A[n], self.A[n]) for s1 in xrange(self.q[n1]): for t1 in xrange(self.q[n1]): r_n1 = mm.mmul(self.A[n1][t1], r_n, mm.H(self.A[n1][s1])) tmp = mm.adot(ln1m1, r_n1) rho[s1 * self.q[n1] + s2, t1 * self.q[n1] + t2] = tmp return rho
def density_2s(self, d): """Returns a reduced density matrix for a pair of (seperated) sites. The site number basis is used: rho[s * q + u, t * q + v] with 0 <= s, t < q and 0 <= u, v < q. The state must be up-to-date -- see self.update()! Parameters ---------- d : int The distance between the first and the second sites considered (d = n2 - n1). Returns ------- rho : ndarray Reduced density matrix in the number basis. """ rho = sp.empty((self.q * self.q, self.q * self.q), dtype=sp.complex128) for s2 in xrange(self.q): for t2 in xrange(self.q): r_n2 = m.mmul(self.A[t2], self.r, m.H(self.A[s2])) r_n = r_n2 for n in xrange(d - 1): r_n = tm.eps_r_noop(r_n, self.A, self.A) for s1 in xrange(self.q): for t1 in xrange(self.q): r_n1 = m.mmul(self.A[t1], r_n, m.H(self.A[s1])) tmp = m.adot(self.l, r_n1) rho[s1 * self.q + s2, t1 * self.q + t2] = tmp return rho
def density_2s(self, n1, n2): """Returns a reduced density matrix for a pair of sites. Parameters ---------- n1 : int The site number of the first site. n2 : int The site number of the second site (must be > n1). """ rho = sp.empty((self.q[n1] * self.q[n2], self.q[n1] * self.q[n2]), dtype=sp.complex128) r_n2 = sp.empty_like(self.r[n2 - 1]) r_n1 = sp.empty_like(self.r[n1 - 1]) for s2 in xrange(self.q[n2]): for t2 in xrange(self.q[n2]): r_n2 = m.mmul(self.A[n2][t2], self.r[n2], m.H(self.A[n2][s2])) r_n = r_n2 for n in reversed(xrange(n1 + 1, n2)): r_n = self.eps_r(n, r_n) for s1 in xrange(self.q[n1]): for t1 in xrange(self.q[n1]): r_n1 = m.mmul(self.A[n1][t1], r_n, m.H(self.A[n1][s1])) tmp = m.mmul(self.l[n1 - 1], r_n1) rho[s1 * self.q[n1] + s2, t1 * self.q[n1] + t2] = tmp.trace() return rho
def calc_Vsh(A, r_s, sanity_checks=False): D = A.shape[2] Dm1 = A.shape[1] q = A.shape[0] if q * D - Dm1 <= 0: return None R = sp.zeros((D, q, Dm1), dtype=A.dtype, order='C') for s in xrange(q): R[:, s, :] = r_s.dot(A[s].conj().T) R = R.reshape((q * D, Dm1)) Vconj = ns.nullspace_qr(R.conj().T).T if sanity_checks: if not sp.allclose(mm.mmul(Vconj.conj(), R), 0): log.warning("Sanity Fail in calc_Vsh!: VR != 0") if not sp.allclose(mm.mmul(Vconj, Vconj.conj().T), sp.eye(Vconj.shape[0])): log.warning("Sanity Fail in calc_Vsh!: V H(V) != eye") Vconj = Vconj.reshape((q * D - Dm1, D, q)) Vsh = Vconj.T Vsh = sp.asarray(Vsh, order='C') if sanity_checks: Vs = sp.transpose(Vsh, axes=(0, 2, 1)).conj() M = eps_r_noop(r_s, Vs, A) if not sp.allclose(M, 0): log.warning("Sanity Fail in calc_Vsh!: Bad Vsh") return Vsh
def restore_ONR_n(self, n, G_n_i): """Transforms a single A[n] to obtain right orthonormalization. Implements the condition for right-orthonormalization from sub-section 3.1, theorem 1 of arXiv:quant-ph/0608197v2. This function must be called for each n in turn, starting at N + 1, passing the gauge transformation matrix from the previous step as an argument. Finds a G[n-1] such that ON_R is fulfilled for n. Eigenvalues = 0 are a problem here... IOW rank-deficient matrices. Apparently, they can turn up during a run, but if they do we're screwed. The fact that M should be positive definite is used to optimize this. Parameters ---------- n : int The site number. G_n_i : ndarray The inverse gauge transform matrix for site n obtained in the previous step (for n + 1). Returns ------- G_n_m1_i : ndarray The inverse gauge transformation matrix for the site n - 1. """ if G_n_i is None: GGh_n_i = self.r[n] else: GGh_n_i = mm.mmul(G_n_i, self.r[n], mm.H(G_n_i)) M = self.eps_r(n, GGh_n_i) try: tu = la.cholesky(M) #Assumes M is pos. def.. It should raise LinAlgError if not. G_nm1 = mm.H(mm.invtr(tu)) #G is now lower-triangular G_nm1_i = mm.H(tu) except sp.linalg.LinAlgError: print "Restore_ON_R_%u: Falling back to eigh()!" % n e,Gh = la.eigh(M) G_nm1 = mm.H(mm.mmul(Gh, sp.diag(1/sp.sqrt(e) + 0.j))) G_nm1_i = la.inv(G_nm1) if G_n_i is None: G_n_i = G_nm1_i if self.sanity_checks: if not sp.allclose(sp.dot(G_nm1, G_nm1_i), sp.eye(G_nm1.shape[0]), atol=1E-13, rtol=1E-13): print "Sanity Fail in restore_ONR_%u!: Bad GT at n=%u" % (n, n) for s in xrange(self.q[n]): self.A[n][s] = mm.mmul(G_nm1, self.A[n][s], G_n_i) return G_nm1_i, G_nm1
def restore_ONR_n(self, n, G_n_i): """Transforms a single A[n] to obtain right orthonormalization. Implements the condition for right-orthonormalization from sub-section 3.1, theorem 1 of arXiv:quant-ph/0608197v2. This function must be called for each n in turn, starting at N + 1, passing the gauge transformation matrix from the previous step as an argument. Finds a G[n-1] such that ON_R is fulfilled for n. Eigenvalues = 0 are a problem here... IOW rank-deficient matrices. Apparently, they can turn up during a run, but if they do we're screwed. The fact that M should be positive definite is used to optimize this. Parameters ---------- n : int The site number. G_n_i : ndarray The inverse gauge transform matrix for site n obtained in the previous step (for n + 1). Returns ------- G_n_m1_i : ndarray The inverse gauge transformation matrix for the site n - 1. """ GGh_n_i = m.mmul( G_n_i, m.H(G_n_i) ) #r[n] does not belong here. The condition is for sum(AA). r[n] = 1 is a consequence. M = self.eps_r(n, GGh_n_i) #The following should be more efficient than eigh(): try: tu = la.cholesky( M ) #Assumes M is pos. def.. It should raise LinAlgError if not. G_nm1 = m.H(m.invtr(tu)) #G is now lower-triangular G_nm1_i = m.H(tu) except sp.linalg.LinAlgError: print "restore_ONR_n: Falling back to eigh()!" e, Gh = la.eigh(M) G_nm1 = m.H(m.mmul(Gh, sp.diag(1 / sp.sqrt(e) + 0.j))) G_nm1_i = la.inv(G_nm1) for s in xrange(self.q[n]): self.A[n][s] = m.mmul(G_nm1, self.A[n][s], G_n_i) #It's ok to use the same matrix as out and as an operand here #since there are > 2 matrices in the chain and it is not the last argument. return G_nm1_i
def calc_x(self, n, Vsh, sqrt_l, sqrt_r, sqrt_l_inv, sqrt_r_inv): """Calculate the parameter matrix x* giving the desired B. This is equivalent to eqn. (49) of arXiv:1103.0936v2 [cond-mat.str-el] except that, here, norm-preservation is not enforced, such that the optimal parameter matrices x*_n (for the parametrization of B) are given by the derivative w.r.t. x_n of <Phi[B, A]|Ĥ|Psi[A]>, rather than <Phi[B, A]|Ĥ - H|Psi[A]> (with H = <Psi|Ĥ|Psi>). An additional sum was added for the single-site hamiltonian. Some multiplications have been pulled outside of the sums for efficiency. Direct dependencies: - A[n - 1], A[n], A[n + 1] - r[n], r[n + 1], l[n - 2], l[n - 1] - C[n], C[n - 1] - K[n + 1] - V[n] """ x = sp.zeros((self.D[n - 1], self.q[n] * self.D[n] - self.D[n - 1]), dtype=self.typ, order=self.odr) x_part = sp.empty_like(x) x_subpart = sp.empty_like(self.A[n][0]) x_subsubpart = sp.empty_like(self.A[n][0]) x_part.fill(0) for s in xrange(self.q[n]): x_subpart.fill(0) if n < self.N: x_subsubpart.fill(0) for t in xrange(self.q[n + 1]): x_subsubpart += m.mmul(self.C[n][s,t], self.r[n + 1], m.H(self.A[n + 1][t])) #~1st line x_subsubpart += m.mmul(self.A[n][s], self.K[n + 1]) #~3rd line x_subpart += m.mmul(x_subsubpart, sqrt_r_inv) if not self.h_ext is None: x_subsubpart.fill(0) for t in xrange(self.q[n]): #Extra term to take care of h_ext.. x_subsubpart += self.h_ext(n, s, t) * self.A[n][t] #it may be more effecient to squeeze this into the nn term... x_subpart += m.mmul(x_subsubpart, sqrt_r) x_part += m.mmul(x_subpart, Vsh[s]) x += m.mmul(sqrt_l, x_part) if n > 1: x_part.fill(0) for s in xrange(self.q[n]): #~2nd line x_subsubpart.fill(0) for t in xrange(self.q[n + 1]): x_subsubpart += m.mmul(m.H(self.A[n - 1][t]), self.l[n - 2], self.C[n - 1][t, s]) x_part += m.mmul(x_subsubpart, sqrt_r, Vsh[s]) x += m.mmul(sqrt_l_inv, x_part) return x
def restore_RCF_l(self): G_nm1 = None l_nm1 = self.l[0] for n in xrange(self.N + 1): if n == 0: x = l_nm1 else: x = mm.mmul(mm.H(G_nm1), l_nm1, G_nm1) M = self.eps_l(n, x) ev, EV = la.eigh(M) self.l[n] = mm.simple_diag_matrix(ev, dtype=self.typ) G_n_i = EV if n == 0: G_nm1 = mm.H(EV) #for left uniform case l_nm1 = self.l[n] #for sanity check self.u_gnd_l.r = mm.mmul(G_nm1, self.u_gnd_l.r, G_n_i) #since r is not eye for s in xrange(self.q[n]): self.A[n][s] = mm.mmul(G_nm1, self.A[n][s], G_n_i) if self.sanity_checks: l = self.eps_l(n, l_nm1) if not sp.allclose(l, self.l[n], atol=1E-12, rtol=1E-12): print "Sanity Fail in restore_RCF_l!: l_%u is bad" % n print la.norm(l - self.l[n]) G_nm1 = mm.H(EV) l_nm1 = self.l[n] if self.sanity_checks: if not sp.allclose(sp.dot(G_nm1, G_n_i), sp.eye(G_n_i.shape[0]), atol=1E-12, rtol=1E-12): print "Sanity Fail in restore_RCF_l!: Bad GT for l_%u" % n #Now G_nm1 = G_N G_nm1_i = mm.H(G_nm1) for s in xrange(self.q[self.N + 1]): self.A[self.N + 1][s] = mm.mmul(G_nm1, self.A[self.N + 1][s], G_nm1_i) ##This should not be necessary if G_N is really unitary #self.r[self.N] = mm.mmul(G_nm1, self.r[self.N], mm.H(G_nm1)) #self.r[self.N + 1] = self.r[self.N] self.u_gnd_r.l[:] = mm.mmul(mm.H(G_nm1_i), self.u_gnd_r.l, G_nm1_i) self.S_hc = sp.zeros((self.N), dtype=sp.complex128) for n in xrange(1, self.N + 1): self.S_hc[n-1] = -sp.sum(self.l[n].diag * sp.log2(self.l[n].diag))
def restore_SCF(self): X = la.cholesky(self.r, lower=True) Y = la.cholesky(self.l, lower=False) U, sv, Vh = la.svd(Y.dot(X)) #s contains the Schmidt coefficients, lam = sv**2 self.S_hc = - np.sum(lam * sp.log2(lam)) S = m.simple_diag_matrix(sv, dtype=self.typ) Srt = S.sqrt() g = m.mmul(Srt, Vh, m.invtr(X, lower=True)) g_i = m.mmul(m.invtr(Y, lower=False), U, Srt) for s in xrange(self.q): self.A[s] = m.mmul(g, self.A[s], g_i) if self.sanity_checks: Sfull = np.asarray(S) if not np.allclose(g.dot(g_i), np.eye(self.D)): print "Sanity check failed! Restore_SCF, bad GT!" l = m.mmul(m.H(g_i), self.l, g_i) r = m.mmul(g, self.r, m.H(g)) if not np.allclose(Sfull, l): print "Sanity check failed: Restorce_SCF, left failed!" if not np.allclose(Sfull, r): print "Sanity check failed: Restorce_SCF, right failed!" l = self.eps_l(Sfull) r = self.eps_r(Sfull) if not np.allclose(Sfull, l, rtol=self.itr_rtol*self.check_fac, atol=self.itr_atol*self.check_fac): print "Sanity check failed: Restorce_SCF, left bad!" if not np.allclose(Sfull, r, rtol=self.itr_rtol*self.check_fac, atol=self.itr_atol*self.check_fac): print "Sanity check failed: Restorce_SCF, right bad!" self.l = S self.r = S
def density_1s(self, n): """Returns a reduced density matrix for a single site. The site number basis is used: rho[s, t] with 0 <= s, t < q[n]. The state must be up-to-date -- see self.update()! Parameters ---------- n1 : int The site number. Returns ------- rho : ndarray Reduced density matrix in the number basis. """ rho = sp.empty((self.q[n], self.q[n]), dtype=sp.complex128) r_n = self.r[n] r_nm1 = sp.empty_like(self.r[n - 1]) for s in xrange(self.q[n]): for t in xrange(self.q[n]): r_nm1 = m.mmul(self.A[n][t], r_n, m.H(self.A[n][s])) rho[s, t] = m.adot(self.l[n - 1], r_nm1) return rho
def eps_l(self, n, x): """Implements the left epsilon map Parameters ---------- res : ndarray A matrix to hold the result (with the same dimensions as l[n]). May be None. n : int The site number. x : ndarray The argument matrix. For example, using l[n - 1] gives a result l[n] Returns ------- res : ndarray The resulting matrix. """ if n > self.N + 1: n = self.N + 1 elif n < 0: n = 0 res = sp.zeros_like(self.l[n]) for s in xrange(self.q[n]): res += mm.mmul(mm.H(self.A[n][s]), x, self.A[n][s]) return res
def calc_C(self, n_low=-1, n_high=-1): """Generates the C matrices used to calculate the K's and ultimately the B's These are to be used on one side of the super-operator when applying the nearest-neighbour Hamiltonian, similarly to C in eqn. (44) of arXiv:1103.0936v2 [cond-mat.str-el], except being for the non-norm-preserving case. Makes use only of the nearest-neighbour hamiltonian, and of the A's. C[n] depends on A[n] and A[n + 1]. """ if self.h_nn is None: return 0 if n_low < 1: n_low = 1 if n_high < 1: n_high = self.N for n in xrange(n_low, n_high): self.C[n].fill(0) for u in xrange(self.q[n]): for v in xrange(self.q[n + 1]): AA = m.mmul(self.A[n][u], self.A[n + 1][v]) #only do this once for each for s in xrange(self.q[n]): for t in xrange(self.q[n + 1]): h_nn_stuv = self.h_nn(n, s, t, u, v) if h_nn_stuv != 0: self.C[n][s, t] += h_nn_stuv * AA
def calc_Vsh(self, n, sqrt_r): """Generates m.H(V[n][s]) for a given n, used for generating B[n][s] This is described on p. 14 of arXiv:1103.0936v2 [cond-mat.str-el] for left gauge fixing. Here, we are using right gauge fixing. Array slicing and reshaping is used to manipulate the indices as necessary. Each V[n] directly depends only on A[n] and r[n]. We return the conjugate m.H(V) because we use it in more places than V. """ R = sp.zeros((self.D[n], self.q[n], self.D[n-1]), dtype=self.typ, order='C') for s in xrange(self.q[n]): R[:,s,:] = m.mmul(sqrt_r, m.H(self.A[n][s])) R = R.reshape((self.q[n] * self.D[n], self.D[n-1])) V = m.H(ns.nullspace_qr(m.H(R))) #print (q[n]*D[n] - D[n-1], q[n]*D[n]) #print V.shape #print sp.allclose(mat(V) * mat(V).H, sp.eye(q[n]*D[n] - D[n-1])) #print sp.allclose(mat(V) * mat(Rh).H, 0) V = V.reshape((self.q[n] * self.D[n] - self.D[n - 1], self.D[n], self.q[n])) #this works with the above form for R #prepare for using V[s] and already take the adjoint, since we use it more often Vsh = sp.empty((self.q[n], self.D[n], self.q[n] * self.D[n] - self.D[n - 1]), dtype=self.typ, order=self.odr) for s in xrange(self.q[n]): Vsh[s] = m.H(V[:,:,s]) return Vsh
def eps_l(self, n, x, out=None): """Implements the left epsilon map FIXME: Ref. Parameters ---------- n : int The site number. x : ndarray The argument matrix. For example, using l[n - 1] gives a result l[n] out : ndarray A matrix to hold the result (with the same dimensions as l[n]). May be None. Returns ------- res : ndarray The resulting matrix. """ if out is None: out = sp.zeros_like(self.l[n]) else: out.fill(0.) for s in xrange(self.q[n]): out += m.mmul(m.H(self.A[n][s]), x, self.A[n][s]) return out
def expect_1s_cor(self, o1, o2, n1, n2): """Computes the correlation of two single site operators acting on two different sites. See expect_1s(). n1 must be smaller than n2. Assumes that the state is normalized. Parameters ---------- o1 : function The first operator, acting on the first site. o2 : function The second operator, acting on the second site. n1 : int The site number of the first site. n2 : int The site number of the second site (must be > n1). """ r_n = self.eps_r(n2, self.r[n2], o2) for n in reversed(xrange(n1 + 1, n2)): r_n = self.eps_r(n, r_n) r_n = self.eps_r(n1, r_n, o1) res = m.mmul(self.l[n1 - 1], r_n) return res.trace()
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 density_1s(self, n): """Returns a reduced density matrix for a single site. Parameters ---------- n1 : int The site number. """ rho = sp.empty((self.q[n], self.q[n]), dtype=sp.complex128) r_n = self.r[n] r_nm1 = sp.empty_like(self.r[n - 1]) for s in xrange(self.q[n]): for t in xrange(self.q[n]): r_nm1 = m.mmul(self.A[n][t], r_n, m.H(self.A[n][s])) rho[s, t] = m.mmul(self.l[n - 1], r_nm1).trace() return rho
def get_B_from_x(self, x, Vsh, l_sqrt_i, r_sqrt_i, out=None): if out is None: out = np.zeros_like(self.A) for s in xrange(self.q): out[s] = m.mmul(l_sqrt_i, x, m.H(Vsh[s]), r_sqrt_i) return out
def calc_BHB_prereq(self, tdvp, tdvp2): """Calculates prerequisites for the application of the effective Hamiltonian in terms of tangent vectors. This is called (indirectly) by the self.excite.. functions. Parameters ---------- tdvp2: EvoMPS_TDVP_Uniform Second state (may be the same, or another ground state). Returns ------- A lot of stuff. """ l = tdvp.l[0] r_ = tdvp2.r[0] r__sqrt = tdvp2.r_sqrt[0] r__sqrt_i = tdvp2.r_sqrt_i[0] A = tdvp.A[0] A_ = tdvp2.A[0] #Note: V has ~ D**2 * q**2 elements. We avoid making any copies of it except this one. # This one is only needed because low-level routines force V_[s] to be contiguous. # TODO: Store V instead of Vsh in tdvp_uniform too... V_ = sp.transpose(tdvp2.Vsh[0], axes=(0, 2, 1)).conj().copy(order='C') if self.ham_sites == 2: #eyeham = m.eyemat(self.q, dtype=sp.complex128) eyeham = sp.eye(self.q, dtype=sp.complex128) #diham = m.simple_diag_matrix(sp.repeat([-tdvp.h_expect.real], self.q)) diham = -tdvp.h_expect.real * sp.eye(self.q, dtype=sp.complex128) _ham_tp = self.ham_tp + [[diham, eyeham]] #subtract norm dof Ao1 = get_Aop(A, _ham_tp, 2, conj=False) AhlAo1 = [tm.eps_l_op_1s(l, A, A, o1.conj().T) for o1, o2 in _ham_tp] A_o2c = get_Aop(A_, _ham_tp, 1, conj=True) Ao1c = get_Aop(A, _ham_tp, 0, conj=True) A_Vr_ho2 = [tm.eps_r_op_1s(r__sqrt, A_, V_, o2) for o1, o2 in _ham_tp] A_A_o12c = get_A_ops(A_, A_, _ham_tp, conj=True) A_o1 = get_Aop(A_, _ham_tp, 2, conj=False) tmp = sp.empty((A_.shape[1], V_.shape[1]), dtype=A.dtype, order='C') tmp2 = sp.empty((A_.shape[1], A_o2c[0].shape[1]), dtype=A.dtype, order='C') rhs10 = 0 for al in xrange(len(A_o1)): tmp2 = tm.eps_r_noop_inplace(r_, A_, A_o2c[al], tmp2) tmp3 = m.mmul(tmp2, r__sqrt_i) rhs10 += tm.eps_r_noop_inplace(tmp3, A_o1[al], V_, tmp) return V_, AhlAo1, A_o2c, Ao1, Ao1c, A_Vr_ho2, A_A_o12c, rhs10, _ham_tp elif self.ham_sites == 3: return
def eps_l(self, x, out=None): if out is None: out = np.zeros_like(self.A[0]) else: out.fill(0.) for s in xrange(self.q): out += m.mmul(m.H(self.A[s]), x, self.A[s]) return out
def calc_C(self, n_low=-1, n_high=-1): """Generates the C matrices used to calculate the K's and ultimately the B's These are to be used on one side of the super-operator when applying the nearest-neighbour Hamiltonian, similarly to C in eqn. (44) of arXiv:1103.0936v2 [cond-mat.str-el], except being for the non-norm-preserving case. Makes use only of the nearest-neighbour hamiltonian, and of the A's. C[n] depends on A[n] and A[n + 1]. This calculation can be significantly faster if a matrix form for h_nn is available. See gen_h_matrix(). """ if self.h_nn is None: return 0 if n_low < 1: n_low = 0 if n_high < 1: n_high = self.N + 1 if self.h_nn_mat is None: for n in xrange(n_low, n_high): self.C[n].fill(0) for u in xrange(self.q[n]): for v in xrange(self.q[n + 1]): AA = mm.mmul(self.A[n][u], self.A[n + 1][v]) #only do this once for each for s in xrange(self.q[n]): for t in xrange(self.q[n + 1]): h_nn_stuv = self.h_nn(n, s, t, u, v) if h_nn_stuv != 0: self.C[n][s, t] += h_nn_stuv * AA else: dot = sp.dot for n in xrange(n_low, n_high): An = self.A[n] Anp1 = self.A[n + 1] AA = sp.empty_like(self.C[n]) for u in xrange(self.q[n]): for v in xrange(self.q[n + 1]): AA[u, v] = dot(An[u], Anp1[v]) if n == 0: #FIXME: Temp. hack self.AA0 = AA elif n == 1: self.AA1 = AA res = sp.tensordot(AA, self.h_nn_mat[n], ((0, 1), (2, 3))) res = sp.rollaxis(res, 3) res = sp.rollaxis(res, 3) self.C[n][:] = res
def calc_l_r_roots(self, n): """Returns the matrix square roots (and inverses) needed to calculate B. Hermiticity of l[n] and r[n] is used to speed this up. If an exception occurs here, it is probably because these matrices are no longer Hermitian (enough). If l[n] or r[n] are diagonal or the identity, further optimizations are used. """ try: l_sqrt = self.l[n - 1].sqrt() except AttributeError: l_sqrt, evd = mm.sqrtmh(self.l[n - 1], ret_evd=True) try: l_sqrt_inv = l_sqrt.inv() except AttributeError: l_sqrt_inv = mm.invmh(l_sqrt, evd=evd) try: r_sqrt = self.r[n].sqrt() except AttributeError: r_sqrt, evd = mm.sqrtmh(self.r[n], ret_evd=True) try: r_sqrt_inv = r_sqrt.inv() except AttributeError: r_sqrt_inv = mm.invmh(r_sqrt, evd=evd) if self.sanity_checks: if not sp.allclose(mm.mmul(l_sqrt, l_sqrt), self.l[n - 1]): print "Sanity Fail in calc_l_r_roots: Bad l_sqrt_%u" % (n - 1) if not sp.allclose(mm.mmul(r_sqrt, r_sqrt), self.r[n]): print "Sanity Fail in calc_l_r_roots: Bad r_sqrt_%u" % (n) if not sp.allclose(mm.mmul(l_sqrt, l_sqrt_inv), sp.eye(l_sqrt.shape[0])): print "Sanity Fail in calc_l_r_roots: Bad l_sqrt_inv_%u" % (n - 1) if not sp.allclose(mm.mmul(r_sqrt, r_sqrt_inv), sp.eye(r_sqrt.shape[0])): print "Sanity Fail in calc_l_r_roots: Bad r_sqrt_inv_%u" % (n) return l_sqrt, r_sqrt, l_sqrt_inv, r_sqrt_inv
def calc_K(self, n_low=-1, n_high=-1): """Generates the K matrices used to calculate the B's K[n] is recursively defined. It depends on C[m] and A[m] for all m >= n. It directly depends on A[n], A[n + 1], r[n], r[n + 1], C[n] and K[n + 1]. This is equivalent to K on p. 14 of arXiv:1103.0936v2 [cond-mat.str-el], except that it is for the non-gauge-preserving case, and includes a single-site Hamiltonian term. K[1] is, assuming a normalized state, the expectation value H of Ĥ. Instead of an explicit single-site term here, one could also include the single-site Hamiltonian in the nearest-neighbour term, which may be more efficient. """ if n_low < 1: n_low = 1 if n_high < 1: n_high = self.N + 1 for n in reversed(xrange(n_low, n_high)): self.K[n].fill(0) if n < self.N: for s in xrange(self.q[n]): for t in xrange(self.q[n+1]): self.K[n] += m.mmul(self.C[n][s, t], self.r[n + 1], m.H(self.A[n+1][t]), m.H(self.A[n][s])) self.K[n] += m.mmul(self.A[n][s], self.K[n + 1], m.H(self.A[n][s])) if not self.h_ext is None: for s in xrange(self.q[n]): for t in xrange(self.q[n]): h_ext_st = self.h_ext(n, s, t) if h_ext_st != 0: self.K[n] += h_ext_st * m.mmul(self.A[n][t], self.r[n], m.H(self.A[n][s]))
def eps_r(self, x, A1=None, A2=None, op=None, out=None): """Implements the right epsilon map FIXME: Ref. Parameters ---------- op : function The single-site operator to use. out : ndarray A matrix to hold the result (with the same dimensions as r). x : ndarray The argument matrix. Returns ------- res : ndarray The resulting matrix. """ if out is None: out = np.zeros_like(self.A[0]) else: out.fill(0.) if A1 is None: A1 = self.A if A2 is None: A2 = self.A if op is None: for s in xrange(self.q): out += m.mmul(A1[s], x, m.H(A2[s])) else: for s in xrange(self.q): for t in xrange(self.q): o_st = op(s, t) if o_st != 0.: tmp = m.mmul(A1[t], x, m.H(A2[s])) tmp *= o_st out += tmp return out
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 B1, use the general B1 generated in calc_B1(). """ if self.q[n] * self.D[n] - self.D[n - 1] > 0: if n == 1: B, eta1 = self.calc_B1() if set_eta: self.eta[1] = eta1 else: l_sqrt, r_sqrt, l_sqrt_inv, r_sqrt_inv = self.calc_l_r_roots(n) Vsh = self.calc_Vsh(n, r_sqrt) x = self.calc_opt_x(n, Vsh, l_sqrt, r_sqrt, l_sqrt_inv, r_sqrt_inv) if set_eta: self.eta[n] = sp.sqrt(mm.adot(x, x)) 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 = sp.zeros_like(self.r[n - 1]) for s in xrange(self.q[n]): M += mm.mmul(B[s], self.r[n], mm.H(self.A[n][s])) if not sp.allclose(M, 0): print "Sanity Fail in calc_B!: B_%u does not satisfy GFC!" % n return B else: return None, 0
def calc_Vsh(self, n, sqrt_r): """Generates mm.H(V[n][s]) for a given n, used for generating B[n][s] This is described on p. 14 of arXiv:1103.0936v2 [cond-mat.str-el] for left gauge fixing. Here, we are using right gauge fixing. Array slicing and reshaping is used to manipulate the indices as necessary. Each V[n] directly depends only on A[n] and r[n]. We return the conjugate mm.H(V) because we use it in more places than V. """ R = sp.zeros((self.D[n], self.q[n], self.D[n-1]), dtype=self.typ, order='C') for s in xrange(self.q[n]): R[:,s,:] = mm.mmul(sqrt_r, mm.H(self.A[n][s])) R = R.reshape((self.q[n] * self.D[n], self.D[n-1])) Vconj = ns.nullspace_qr(mm.H(R)).T if self.sanity_checks: if not sp.allclose(mm.mmul(Vconj.conj(), R), 0): print "Sanity Fail in calc_Vsh!: VR_%u != 0" % (n) if not sp.allclose(mm.mmul(Vconj, mm.H(Vconj)), sp.eye(Vconj.shape[0])): print "Sanity Fail in calc_Vsh!: V H(V)_%u != eye" % (n) Vconj = Vconj.reshape((self.q[n] * self.D[n] - self.D[n - 1], self.D[n], self.q[n])) Vsh = Vconj.T Vsh = sp.asarray(Vsh, order='C') if self.sanity_checks: M = sp.zeros((self.q[n] * self.D[n] - self.D[n - 1], self.D[n]), dtype=self.typ) for s in xrange(self.q[n]): M += mm.mmul(mm.H(Vsh[s]), sqrt_r, mm.H(self.A[n][s])) if not sp.allclose(M, 0): print "Sanity Fail in calc_Vsh!: Bad Vsh_%u" % (n) return Vsh
def calc_x(self, l_sqrt, l_sqrt_i, r_sqrt, r_sqrt_i, Vsh, out=None): if out is None: out = np.zeros((self.D, (self.q - 1) * self.D), dtype=self.typ, order=self.odr) tmp = np.zeros_like(out) for s in xrange(self.q): tmp2 = m.mmul(self.A[s], self.K) for t in xrange(self.q): tmp2 += m.mmul(self.C[s, t], self.r, m.H(self.A[t])) tmp += m.mmul(tmp2, r_sqrt_i, Vsh[s]) out += l_sqrt.dot(tmp) tmp.fill(0) for s in xrange(self.q): tmp2.fill(0) for t in xrange(self.q): tmp2 += m.mmul(m.H(self.A[t]), self.l, self.C[t, s]) tmp += m.mmul(tmp2, r_sqrt, Vsh[s]) out += l_sqrt_i.dot(tmp) return out
def eps_r(self, n, x, o=None, out=None): """Implements the right epsilon map FIXME: Ref. Parameters ---------- n : int The site number. x : ndarray The argument matrix. For example, using r[n] (and o=None) gives a result r[n - 1] o : function The single-site operator to use. May be None. out : ndarray A matrix to hold the result (with the same dimensions as r[n - 1]). May be None. Returns ------- res : ndarray The resulting matrix. """ if out is None: out = sp.zeros((self.D[n - 1], self.D[n - 1]), dtype=self.typ) else: out.fill(0) if o is None: for s in xrange(self.q[n]): out += m.mmul(self.A[n][s], x, m.H(self.A[n][s])) else: for s in xrange(self.q[n]): for t in xrange(self.q[n]): o_st = o(n, s, t) if o_st != 0.: tmp = m.mmul(self.A[n][t], x, m.H(self.A[n][s])) tmp *= o_st out += tmp return out
def restore_RCF_r(self): G_n_i = None for n in reversed(xrange(1, self.N + 2)): G_n_i, G_n = self.restore_ONR_n(n, G_n_i) self.r[n - 1] = mm.eyemat(self.D[n - 1], self.typ) if self.sanity_checks: r_n = mm.eyemat(self.D[n], self.typ) r_nm1 = self.eps_r(n, r_n) if not sp.allclose(r_nm1, self.r[n - 1].A, atol=1E-13, rtol=1E-13): print "Sanity Fail in restore_RCF_r!: r_%u is bad" % (n - 1) print la.norm(r_nm1 - self.r[n - 1]) #self.r[self.N + 1] = self.r[self.N] #Now G_n_i contains g_0_i for s in xrange(self.q[0]): #Note: This does not change the scale of A[0] self.A[0][s] = mm.mmul(G_n, self.A[0][s], G_n_i) self.u_gnd_l.r = mm.mmul(G_n, self.u_gnd_l.r, mm.H(G_n)) self.l[0] = mm.mmul(mm.H(G_n_i), self.l[0], G_n_i)
def calc_l(self, start=-1, finish=-1): """Updates the l matrices using the current state. Implements step 5 of the TDVP algorithm or, equivalently, eqn. (41). (arXiv:1103.0936v2 [cond-mat.str-el]) """ if start < 0: start = 1 if finish < 0: finish = self.N for n in xrange(start, finish + 1): self.l[n].fill(0) for s in xrange(self.q[n]): self.l[n] += m.mmul(m.H(self.A[n][s]), self.l[n - 1], self.A[n][s])
def density_2s(self, n1, n2): """Returns a reduced density matrix for a pair of (seperated) sites. The site number basis is used: rho[s * q[n1] + u, t * q[n1] + v] with 0 <= s, t < q[n1] and 0 <= u, v < q[n2]. The state must be up-to-date -- see self.update()! Parameters ---------- n1 : int The site number of the first site. n2 : int The site number of the second site (must be > n1). Returns ------- rho : ndarray Reduced density matrix in the number basis. """ rho = sp.empty((self.q[n1] * self.q[n2], self.q[n1] * self.q[n2]), dtype=sp.complex128) for s2 in xrange(self.q[n2]): for t2 in xrange(self.q[n2]): r_n2 = m.mmul(self.A[n2][t2], self.r[n2], m.H(self.A[n2][s2])) r_n = r_n2 for n in reversed(xrange(n1 + 1, n2)): r_n = tm.eps_r_noop(r_n, self.A[n], self.A[n]) for s1 in xrange(self.q[n1]): for t1 in xrange(self.q[n1]): r_n1 = m.mmul(self.A[n1][t1], r_n, m.H(self.A[n1][s1])) tmp = m.adot(self.l[n1 - 1], r_n1) rho[s1 * self.q[n1] + s2, t1 * self.q[n1] + t2] = tmp return rho
def density_1s(self): """Returns a reduced density matrix for a single site. The site number basis is used: rho[s, t] with 0 <= s, t < q. The state must be up-to-date -- see self.update()! Returns ------- rho : ndarray Reduced density matrix in the number basis. """ rho = np.empty((self.q, self.q), dtype=self.typ) for s in xrange(self.q): for t in xrange(self.q): rho[s, t] = m.adot(self.l, m.mmul(self.A[t], self.r, m.H(self.A[s]))) return rho
def _calc_B_r_diss(self, op, K, C, n, set_eta=True): if self.q[n] * self.D[n] - self.D[n - 1] > 0: l_sqrt, l_sqrt_inv, r_sqrt, r_sqrt_inv = tm.calc_l_r_roots( self.l[n - 1], self.r[n], sanity_checks=self.sanity_checks, sc_data=("site", n)) 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) if set_eta: self.eta[n] = sp.sqrt(mm.adot(x, x)) 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) return B else: return None
def expect_2s_diss(self, op, LC, LK, n, AA=None): """Applies a two-site operator to two sites and returns the value after the change. In contrast to mps_gen.apply_op_2s, this routine does not change the state itself. Also, this does not perform self.update(). Parameters ---------- op : ndarray or callable The two-site operator. See self.expect_2s(). n: int The site to apply the operator to. (It's also applied to n-1.) """ #No neighbors, no fun. if n is 1: return 0 if n is N: return 0 A = self.A[n - 1] Ap1 = self.A[n] if AA is None: AA = tm.calc_AA(A, Ap1) if callable(op): op = sp.vectorize(op, otypes=[sp.complex128]) op = sp.fromfunction( op, (A.shape[0], Ap1.shape[0], A.shape[0], Ap1.shape[0])) op = op.reshape(4, 4, 4, 4) C = tm.calc_C_mat_op_AA(op, AA) res = tm.eps_r_op_2s_C12_AA34(self.r[n + 1], LC, AA) operand = self.l[n - 1] operand = sp.reshape(operand, (1, 16)) operand = sp.reshape(operand, (2, 8)) return mm.mmul(operand, res) return mm.adot(self.l[n - 1], res)
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_BHB(self, x, p, tdvp, tdvp2, prereq, M_prev=None, y_pi_prev=None, pinv_solver=None): if pinv_solver is None: pinv_solver = las.gmres if self.ham_sites == 3: return else: V_, AhlAo1, A_o2c, Ao1, Ao1c, A_Vr_ho2, A_A_o12c, rhs10, _ham_tp = prereq A = tdvp.A[0] A_ = tdvp2.A[0] l = tdvp.l[0] r_ = tdvp2.r[0] l_sqrt = tdvp.l_sqrt[0] l_sqrt_i = tdvp.l_sqrt_i[0] r__sqrt = tdvp2.r_sqrt[0] r__sqrt_i = tdvp2.r_sqrt_i[0] K__r = tdvp2.K[0] K_l = tdvp.K_left[0] pseudo = tdvp2 is tdvp B = tdvp2.get_B_from_x(x, tdvp2.Vsh[0], l_sqrt_i, r__sqrt_i) #Skip zeros due to rank-deficiency if la.norm(B) == 0: return sp.zeros_like(x), M_prev, y_pi_prev if self.sanity_checks: tst = tm.eps_r_noop(r_, B, A_) if not la.norm(tst) > self.sanity_tol: log.warning("Sanity check failed: Gauge-fixing violation! " + str(la.norm(tst))) if self.sanity_checks: B2 = np.zeros_like(B) for s in xrange(self.q): B2[s] = l_sqrt_i.dot(x.dot(m.mmul(V_[s], r__sqrt_i))) if la.norm(B - B2) / la.norm(B) > self.sanity_tol: log.warning("Sanity Fail in calc_BHB! Bad Vri!") y = tm.eps_l_noop(l, B, A) # if pseudo: # y = y - m.adot(r_, y) * l #should just = y due to gauge-fixing M = pinv_1mE(y, [A_], [A], l, r_, p=-p, left=True, pseudo=pseudo, out=M_prev, tol=self.pinv_tol, solver=pinv_solver, use_CUDA=self.pinv_CUDA, CUDA_use_batch=self.pinv_CUDA_batch, sanity_checks=self.sanity_checks, sc_data='M') #print m.adot(r, M) if self.sanity_checks: y2 = M - sp.exp(+1.j * p) * tm.eps_l_noop(M, A_, A) norm = la.norm(y.ravel()) if norm == 0: norm = 1 tst = la.norm(y - y2) / norm if tst > self.sanity_tol: log.warning("Sanity Fail in calc_BHB! Bad M. Off by: %g", tst) # if pseudo: # M = M - l * m.adot(r_, M) Mh = M.conj().T.copy(order='C') res_ls = 0 res_lsi = 0 exp = sp.exp if self.ham_sites == 3: pass else: Bo1 = get_Aop(B, _ham_tp, 2, conj=False) tmp = sp.empty((B.shape[1], V_.shape[1]), dtype=A.dtype, order='C') tmp2 = sp.empty((A_.shape[1], A_o2c[0].shape[1]), dtype=A.dtype, order='C') tmp3 = sp.empty_like(tmp2, order='C') for al in xrange(len(Bo1)): tmp3 = m.dot_inplace( tm.eps_r_noop_inplace(r_, A_, A_o2c[al], tmp2), r__sqrt_i, tmp3) res_ls += tm.eps_r_noop_inplace(tmp3, Bo1[al], V_, tmp) #1 tmp3 = m.dot_inplace( tm.eps_r_noop_inplace(r_, B, A_o2c[al], tmp2), r__sqrt_i, tmp3) tmp = tm.eps_r_noop_inplace(tmp3, Ao1[al], V_, tmp) #3 tmp *= exp(+1.j * p) res_ls += tmp del (tmp) del (tmp2) del (tmp3) res_lsi += sp.exp(-1.j * p) * Mh.dot(rhs10) #10 if self.ham_sites == 3: pass else: Bo2 = get_Aop(B, _ham_tp, 3, conj=False) for al in xrange(len(AhlAo1)): res_lsi += AhlAo1[al].dot(tm.eps_r_noop(r__sqrt, Bo2[al], V_)) #2 res_lsi += exp(-1.j * p) * tm.eps_l_noop(l, Ao1c[al], B).dot( A_Vr_ho2[al]) #4 res_lsi += exp(-2.j * p) * tm.eps_l_noop(Mh, Ao1c[al], A_).dot( A_Vr_ho2[al]) #12 K__rri = m.mmul(K__r, r__sqrt_i) res_ls += tm.eps_r_noop(K__rri, B, V_) #5 res_lsi += K_l.dot(tm.eps_r_noop(r__sqrt, B, V_)) #6 res_lsi += sp.exp(-1.j * p) * Mh.dot(tm.eps_r_noop(K__rri, A_, V_)) #8 y1 = sp.exp(+1.j * p) * tm.eps_r_noop(K__r, B, A_) #7 if self.ham_sites == 3: pass elif self.ham_sites == 2: tmp = 0 for al in xrange(len(A_A_o12c)): tmp += sp.exp(+1.j * p) * tm.eps_r_noop( tm.eps_r_noop(r_, A_, A_A_o12c[al][1]), B, A_A_o12c[al][0]) #9 tmp += sp.exp(+2.j * p) * tm.eps_r_noop( tm.eps_r_noop(r_, B, A_A_o12c[al][1]), A, A_A_o12c[al][0]) #11 y = y1 + tmp #7, 9, 11 del (tmp) if pseudo: y = y - m.adot(l, y) * r_ y_pi = pinv_1mE(y, [A], [A_], l, r_, p=p, left=False, pseudo=pseudo, out=y_pi_prev, tol=self.pinv_tol, solver=pinv_solver, use_CUDA=self.pinv_CUDA, CUDA_use_batch=self.pinv_CUDA_batch, sanity_checks=self.sanity_checks, sc_data='y_pi') #print m.adot(l, y_pi) if self.sanity_checks: z = y_pi - sp.exp(+1.j * p) * tm.eps_r_noop(y_pi, A, A_) tst = la.norm((y - z).ravel()) / la.norm(y.ravel()) if tst > self.sanity_tol: log.warning("Sanity Fail in calc_BHB! Bad x_pi. Off by: %g", tst) res_ls += tm.eps_r_noop(m.mmul(y_pi, r__sqrt_i), A, V_) res = l_sqrt.dot(res_ls) res += l_sqrt_i.dot(res_lsi) if self.sanity_checks: expval = m.adot(x, res) / m.adot(x, x) #print "expval = " + str(expval) if expval < -self.sanity_tol: log.warning( "Sanity Fail in calc_BHB! H is not pos. semi-definite (%s)", expval) if abs(expval.imag) > self.sanity_tol: log.warning("Sanity Fail in calc_BHB! H is not Hermitian (%s)", expval) return res, M, y_pi
def restore_RCF(self, update_l=True, normalize=True, diag_l=True): """Use a gauge-transformation to restore right canonical form. Implements the conditions for right canonical form from sub-section 3.1, theorem 1 of arXiv:quant-ph/0608197v2. This performs two 'almost' gauge transformations, where the 'almost' means we allow the norm to vary (if "normalize" = True). The last step (A[1]) is done diffently to the others since G[0], the gauge-transf. matrix, is just a number, which can be found more efficiently and accurately without using matrix methods. The last step (A[1]) is important because, if we have successfully made r[1] = 1 in the previous steps, it fully determines the normalization of the state via r[0] ( = l[N]). Optionally (normalize=False), the function will not attempt to make A[1] satisfy the orthonorm. condition, and will take G[0] = 1 = G[N], thus performing a pure gauge-transformation, but not ensuring complete canonical form. It is also possible to begin the process from a site n other than N, in case the sites > n are known to be in the desired form already. It is also possible to skip the diagonalization of the l's, such that only the right orthonormalization condition (r_n = eye) is met. By default, the l's are updated even if diag_l=False. Parameters ---------- update_l : bool Whether to call calc_l() after completion (defaults to True) normalize : bool Whether to also attempt to normalize the state. diag_l : bool Whether to put l in diagonal form (defaults to True) """ start = self.N G_n_i = sp.eye(self.D[start], dtype=self.typ) #This is actually just the number 1 for n in xrange(start, 1, -1): self.r[n - 1], G_n, G_n_i = tm.restore_RCF_r( self.A[n], self.r[n], G_n_i, sc_data=('site', n), zero_tol=self.zero_tol, sanity_checks=self.sanity_checks) #Now do A[1]... #Apply the remaining G[1]^-1 from the previous step. for s in xrange(self.q[1]): self.A[1][s] = m.mmul(self.A[1][s], G_n_i) #Now finish off tm.eps_r_noop_inplace(self.r[1], self.A[1], self.A[1], out=self.r[0]) if normalize: G0 = 1. / sp.sqrt(self.r[0].squeeze().real) self.A[1] *= G0 self.r[0][:] = 1 if self.sanity_checks: r0 = tm.eps_r_noop(self.r[1], self.A[1], self.A[1]) if not sp.allclose(r0, 1, atol=1E-12, rtol=1E-12): log.warning( "Sanity Fail in restore_RCF!: r_0 is bad / norm failure" ) if diag_l: G_nm1 = sp.eye(self.D[0], dtype=self.typ) for n in xrange(1, self.N): self.l[n], G_nm1, G_nm1_i = tm.restore_RCF_l( self.A[n], self.l[n - 1], G_nm1, self.sanity_checks) #Apply remaining G_Nm1 to A[N] n = self.N for s in xrange(self.q[n]): self.A[n][s] = m.mmul(G_nm1, self.A[n][s]) #Deal with final, scalar l[N] tm.eps_l_noop_inplace(self.l[n - 1], self.A[n], self.A[n], out=self.l[n]) if self.sanity_checks: if not sp.allclose( self.l[self.N].real, 1, atol=1E-12, rtol=1E-12): log.warning( "Sanity Fail in restore_RCF!: l_N is bad / norm failure" ) log.warning("l_N = %s", self.l[self.N].squeeze().real) for n in xrange(1, self.N + 1): r_nm1 = tm.eps_r_noop(self.r[n], self.A[n], self.A[n]) #r_nm1 = tm.eps_r_noop(m.eyemat(self.D[n], self.typ), self.A[n], self.A[n]) if not sp.allclose( r_nm1, self.r[n - 1], atol=1E-11, rtol=1E-11): log.warning( "Sanity Fail in restore_RCF!: r_%u is bad (off by %g)", n, la.norm(r_nm1 - self.r[n - 1])) elif update_l: self.calc_l()
def restore_SCF(self, ret_g=False, zero_tol=None): """Restores symmetric canonical form. In this canonical form, self.l == self.r and are diagonal matrices with the Schmidt coefficients corresponding to the half-chain decomposition form the diagonal entries. Parameters ---------- ret_g : bool Whether to return the gauge-transformation matrices used. Returns ------- g, g_i : ndarray Gauge transformation matrix g and its inverse g_i. """ if zero_tol is None: zero_tol = self.zero_tol X, Xi = tm.herm_fac_with_inv(self.r, lower=True, zero_tol=zero_tol) Y, Yi = tm.herm_fac_with_inv(self.l, lower=False, zero_tol=zero_tol) U, sv, Vh = la.svd(Y.dot(X)) #s contains the Schmidt coefficients, lam = sv**2 self.S_hc = - np.sum(lam * sp.log2(lam)) S = m.simple_diag_matrix(sv, dtype=self.typ) Srt = S.sqrt() g = m.mmul(Srt, Vh, Xi) g_i = m.mmul(Yi, U, Srt) for s in xrange(self.q): self.A[s] = m.mmul(g, self.A[s], g_i) if self.sanity_checks: Sfull = np.asarray(S) if not np.allclose(g.dot(g_i), np.eye(self.D)): log.warning("Sanity check failed! Restore_SCF, bad GT!") l = m.mmul(m.H(g_i), self.l, g_i) r = m.mmul(g, self.r, m.H(g)) if not np.allclose(Sfull, l): log.warning("Sanity check failed: Restorce_SCF, left failed!") if not np.allclose(Sfull, r): log.warning("Sanity check failed: Restorce_SCF, right failed!") l = tm.eps_l_noop(Sfull, self.A, self.A) r = tm.eps_r_noop(Sfull, self.A, self.A) if not np.allclose(Sfull, l, rtol=self.itr_rtol*self.check_fac, atol=self.itr_atol*self.check_fac): log.warning("Sanity check failed: Restorce_SCF, left bad!") if not np.allclose(Sfull, r, rtol=self.itr_rtol*self.check_fac, atol=self.itr_atol*self.check_fac): log.warning("Sanity check failed: Restorce_SCF, right bad!") self.l = S self.r = S if ret_g: return g, g_i else: return
def calc_BHB_prereq(self, tdvp, tdvp2): """Calculates prerequisites for the application of the effective Hamiltonian in terms of tangent vectors. This is called (indirectly) by the self.excite.. functions. Parameters ---------- tdvp2: EvoMPS_TDVP_Uniform Second state (may be the same, or another ground state). Returns ------- A lot of stuff. """ l = tdvp.l[0] r_ = tdvp2.r[0] r__sqrt = tdvp2.r_sqrt[0] r__sqrt_i = tdvp2.r_sqrt_i[0] A = tdvp.A[0] A_ = tdvp2.A[0] #Note: V has ~ D**2 * q**2 elements. We avoid making any copies of it except this one. # This one is only needed because low-level routines force V_[s] to be contiguous. # TODO: Store V instead of Vsh in tdvp_uniform too... V_ = sp.transpose(tdvp2.Vsh[0], axes=(0, 2, 1)).conj().copy(order='C') if self.ham_sites == 2: #eyeham = m.eyemat(self.q, dtype=sp.complex128) eyeham = sp.eye(self.q, dtype=sp.complex128) #diham = m.simple_diag_matrix(sp.repeat([-tdvp.h_expect.real], self.q)) diham = -tdvp.h_expect.real * sp.eye(self.q, dtype=sp.complex128) _ham_tp = self.ham_tp + [[diham, eyeham]] #subtract norm dof Ao1 = get_Aop(A, _ham_tp, 2, conj=False) AhlAo1 = [ tm.eps_l_op_1s(l, A, A, o1.conj().T) for o1, o2 in _ham_tp ] A_o2c = get_Aop(A_, _ham_tp, 1, conj=True) Ao1c = get_Aop(A, _ham_tp, 0, conj=True) A_Vr_ho2 = [ tm.eps_r_op_1s(r__sqrt, A_, V_, o2) for o1, o2 in _ham_tp ] A_A_o12c = get_A_ops(A_, A_, _ham_tp, conj=True) A_o1 = get_Aop(A_, _ham_tp, 2, conj=False) tmp = sp.empty((A_.shape[1], V_.shape[1]), dtype=A.dtype, order='C') tmp2 = sp.empty((A_.shape[1], A_o2c[0].shape[1]), dtype=A.dtype, order='C') rhs10 = 0 for al in xrange(len(A_o1)): tmp2 = tm.eps_r_noop_inplace(r_, A_, A_o2c[al], tmp2) tmp3 = m.mmul(tmp2, r__sqrt_i) rhs10 += tm.eps_r_noop_inplace(tmp3, A_o1[al], V_, tmp) return V_, AhlAo1, A_o2c, Ao1, Ao1c, A_Vr_ho2, A_A_o12c, rhs10, _ham_tp elif self.ham_sites == 3: return
def restore_RCF(self, ret_g=False, zero_tol=None): """Restores right canonical form. In this form, self.r = sp.eye(self.D) and self.l is diagonal, with the squared Schmidt coefficients corresponding to the half-chain decomposition as eigenvalues. Parameters ---------- ret_g : bool Whether to return the gauge-transformation matrices used. Returns ------- g, g_i : ndarray Gauge transformation matrix g and its inverse g_i. """ if zero_tol is None: zero_tol = self.zero_tol #First get G such that r = eye G, G_i, rank = tm.herm_fac_with_inv(self.r, lower=True, zero_tol=zero_tol, return_rank=True) self.l = m.mmul(m.H(G), self.l, G) #Now bring l into diagonal form, trace = 1 (guaranteed by r = eye..?) ev, EV = la.eigh(self.l) G = G.dot(EV) G_i = m.H(EV).dot(G_i) for s in xrange(self.q): self.A[s] = m.mmul(G_i, self.A[s], G) #ev contains the squares of the Schmidt coefficients, self.S_hc = - np.sum(ev * sp.log2(ev)) self.l = m.simple_diag_matrix(ev, dtype=self.typ) r_old = self.r if rank == self.D: self.r = m.eyemat(self.D, self.typ) else: self.r = sp.zeros((self.D), dtype=self.typ) self.r[-rank:] = 1 self.r = m.simple_diag_matrix(self.r, dtype=self.typ) if self.sanity_checks: r_ = m.mmul(G_i, r_old, m.H(G_i)) if not np.allclose(self.r, r_, rtol=self.itr_rtol*self.check_fac, atol=self.itr_atol*self.check_fac): log.warning("Sanity check failed: RestoreRCF, bad r (bad GT).") l = tm.eps_l_noop(self.l, self.A, self.A) r = tm.eps_r_noop(self.r, self.A, self.A) if not np.allclose(r, self.r, rtol=self.itr_rtol*self.check_fac, atol=self.itr_atol*self.check_fac): log.warning("Sanity check failed: Restore_RCF, r not eigenvector! %s", la.norm(r - self.r)) if not np.allclose(l, self.l, rtol=self.itr_rtol*self.check_fac, atol=self.itr_atol*self.check_fac): log.warning("Sanity check failed: Restore_RCF, l not eigenvector! %s", la.norm(l - self.l)) if ret_g: return G, G_i else: return