def renormalization_ddm(cstruct, qnbigl, qnbigr, domain, nexciton, Mmax, percent=0): """ get the new mps, mpsdim, mpdqn, complementary mps to get the next guess with diagonalize reduced density matrix method (> 1 root) """ nroots = len(cstruct) ddm = 0.0 for iroot in range(nroots): if domain == "R": ddm += np.tensordot( cstruct[iroot], cstruct[iroot], axes=(range(qnbigl.ndim), range(qnbigl.ndim)), ) else: ddm += np.tensordot( cstruct[iroot], cstruct[iroot], axes=( range(qnbigl.ndim, cstruct[0].ndim), range(qnbigl.ndim, cstruct[0].ndim), ), ) ddm /= float(nroots) if domain == "L": Uset, Sset, qnnew = svd_qn.Csvd(ddm, qnbigl, qnbigl, nexciton, ddm=True) else: Uset, Sset, qnnew = svd_qn.Csvd(ddm, qnbigr, qnbigr, nexciton, ddm=True) mps, mpsdim, mpsqn, compmps = updatemps( Uset, Sset, qnnew, None, nexciton, Mmax, percent=percent ) if domain == "R": return ( xp.moveaxis(mps.reshape(list(qnbigr.shape) + [mpsdim]), -1, 0), mpsdim, mpsqn, tensordot( asxp(cstruct[0]), mps.reshape(list(qnbigr.shape) + [mpsdim]), axes=(range(qnbigl.ndim, cstruct[0].ndim), range(qnbigr.ndim)), ), ) else: return ( mps.reshape(list(qnbigl.shape) + [mpsdim]), mpsdim, mpsqn, tensordot( mps.reshape(list(qnbigl.shape) + [mpsdim]), asxp(cstruct[0]), axes=(range(qnbigl.ndim), range(qnbigl.ndim)), ), )
def canonicalise(self, stop_idx: int=None): for idx in self.iter_idx_list(full=False): self.qnidx = idx if stop_idx is not None and idx == stop_idx: break mt: Matrix = self[idx] assert mt.any() if self.left: mt = mt.l_combine() else: mt = mt.r_combine() qnbigl, qnbigr = self._get_big_qn(idx) system = "L" if self.left else "R" u, qnlset, v, qnrset = svd_qn.Csvd( mt.asnumpy(), qnbigl, qnbigr, self.qntot, QR=True, system=system, full_matrices=False, ) self._update_ms( idx, Matrix(u), Matrix(v.T), sigma=None, qnlset=qnlset, qnrset=qnrset ) self._switch_direction() return self
def canonicalise(self, stop_idx: int = None, normalize=False): # stop_idx: mix canonical site at `stop_idx` if self.to_right: assert self.qnidx == 0 else: assert self.qnidx == self.site_num - 1 for idx in self.iter_idx_list(full=False, stop_idx=stop_idx): mt: Matrix = self[idx] assert mt.any() qnbigl, qnbigr, _ = self._get_big_qn([idx]) system = "L" if self.to_right else "R" u, qnlset, v, qnrset = svd_qn.Csvd( mt.array, qnbigl, qnbigr, self.qntot, QR=True, system=system, full_matrices=False, ) if normalize: # roughly normalize. Used when the each site of the mps is scaled such as in exact thermal prop v /= np.linalg.norm(v[:, 0]) self._update_ms(idx, u, v.T, sigma=None, qnlset=qnlset, qnrset=qnrset) # can't iter to idx == 0 or idx == self.site_num - 1 if (not self.to_right and idx == 1) or (self.to_right and idx == self.site_num - 2): self._switch_direction() return self
def renormalization_svd(cstruct, qnbigl, qnbigr, domain, nexciton, Mmax, percent=0): """ get the new mps, mpsdim, mpdqn, complementary mps to get the next guess with singular value decomposition method (1 root) """ assert domain in ["R", "L"] Uset, SUset, qnlnew, Vset, SVset, qnrnew = svd_qn.Csvd( cstruct, qnbigl, qnbigr, nexciton, system=domain ) if domain == "R": mps, mpsdim, mpsqn, compmps = updatemps( Vset, SVset, qnrnew, Uset, nexciton, Mmax, percent=percent ) return ( xp.moveaxis(mps.reshape(list(qnbigr.shape) + [mpsdim]), -1, 0), mpsdim, mpsqn, compmps.reshape(list(qnbigl.shape) + [mpsdim]), ) else: mps, mpsdim, mpsqn, compmps = updatemps( Uset, SUset, qnlnew, Vset, nexciton, Mmax, percent=percent ) return ( mps.reshape(list(qnbigl.shape) + [mpsdim]), mpsdim, mpsqn, xp.moveaxis(compmps.reshape(list(qnbigr.shape) + [mpsdim]), -1, 0), )
def compress(self): """ inp: canonicalise MPS (or MPO) side='l': compress LEFT-canonicalised MPS by sweeping from RIGHT to LEFT output MPS is right canonicalised i.e. CRRR side='r': reverse of 'l' returns: truncated MPS """ # used for logging at exit sz_before = self.total_bytes if not self.is_mpo: # ensure mps is canonicalised. This is time consuming. # to disable this, run python as `python -O` if self.is_left_canon: assert self.check_left_canonical() else: assert self.check_right_canonical() system = "L" if self.left else "R" for idx in self.iter_idx_list(full=False): mt: Matrix = self[idx] if self.left: mt = mt.l_combine() else: mt = mt.r_combine() qnbigl, qnbigr = self._get_big_qn(idx) u, sigma, qnlset, v, sigma, qnrset = svd_qn.Csvd( mt.asnumpy(), qnbigl, qnbigr, self.qntot, system=system, full_matrices=False, ) vt = v.T m_trunc = self.compress_config.compute_m_trunc( sigma, idx, self.left ) self._update_ms( idx, Matrix(u), Matrix(vt), Matrix(sigma), qnlset, qnrset, m_trunc ) self._switch_direction() compress_ratio = sz_before / self.total_bytes logger.debug(f"size before/after compress: {sizeof_fmt(sz_before)}/{sizeof_fmt(self.total_bytes)}, ratio: {compress_ratio}") return self
def _evolve_dmrg_tdvp_ps(self, mpo, evolve_dt) -> "Mps": # PhysRevB.94.165116 # TDVP projector splitting imag_time = np.iscomplex(evolve_dt) if imag_time: mps = self.copy() mps_conj = mps else: mps = self.to_complex() mps_conj = mps.conj() # another copy, so 3x memory is used. # construct the environment matrix environ = Environ() # almost half is not used. Not a big deal. environ.construct(mps, mps_conj, mpo, "L") environ.construct(mps, mps_conj, mpo, "R") # a workaround for https://github.com/scipy/scipy/issues/10164 if imag_time: evolve_dt = -evolve_dt.imag # used in calculating derivatives coef = -1 else: coef = 1j # statistics for debug output cmf_rk_steps = [] USE_RK = self.evolve_config.tdvp_ps_rk4 # sweep for 2 rounds for i in range(2): for imps in mps.iter_idx_list(full=True): system = "L" if mps.left else "R" ltensor = environ.read("L", imps - 1) rtensor = environ.read("R", imps + 1) shape = list(mps[imps].shape) l_array = ltensor.array r_array = rtensor.array hop = hop_factory(l_array, r_array, mpo[imps].array, len(shape)) def hop_svt(ms): # S-a l-S # # O-b - b-O # # S-c k-S path = [([0, 1], "abc, ck -> abk"), ([1, 0], "abk, lbk -> al")] HC = multi_tensor_contract(path, l_array, ms, r_array) return HC if USE_RK: def func(t, y): return hop(y.reshape(shape)).ravel() / coef sol = solve_ivp( func, (0, evolve_dt / 2.0), mps[imps].ravel().array, method="RK45" ) cmf_rk_steps.append(len(sol.t)) mps_t = sol.y[:, -1] else: # Can't use the same func because here H should be Hermitian def func(y): return hop(y.reshape(shape)).ravel() mps_t = expm_krylov(func, (evolve_dt / 2) / coef, mps[imps].ravel().array) mps_t = mps_t.reshape(shape) qnbigl, qnbigr = mps._get_big_qn(imps) u, qnlset, v, qnrset = svd_qn.Csvd( asnumpy(mps_t), qnbigl, qnbigr, mps.qntot, QR=True, system=system, full_matrices=False, ) vt = v.T if mps.is_left_canon and imps != 0: mps[imps] = vt.reshape([-1] + shape[1:]) mps_conj[imps] = mps[imps].conj() mps.qn[imps] = qnrset rtensor = environ.GetLR( "R", imps, mps, mps_conj, mpo, itensor=rtensor, method="System" ) r_array = rtensor.array # reverse update u site shape_u = u.shape if USE_RK: def func_u(t, y): return hop_svt(y.reshape(shape_u)).ravel() / coef sol_u = solve_ivp( func_u, (0, -evolve_dt / 2), u.ravel(), method="RK45" ) cmf_rk_steps.append(len(sol_u.t)) mps_t = sol_u.y[:, -1] else: def func_u(y): return hop_svt(y.reshape(shape_u)).ravel() mps_t = expm_krylov(func_u, (-evolve_dt / 2) / coef, u.ravel()) mps_t = mps_t.reshape(shape_u) mps[imps - 1] = tensordot( mps[imps - 1].array, mps_t, axes=(-1, 0), ) mps_conj[imps - 1] = mps[imps - 1].conj() elif mps.is_right_canon and imps != len(mps) - 1: mps[imps] = u.reshape(shape[:-1] + [-1]) mps_conj[imps] = mps[imps].conj() mps.qn[imps + 1] = qnlset ltensor = environ.GetLR( "L", imps, mps, mps_conj, mpo, itensor=ltensor, method="System" ) l_array = ltensor.array # reverse update svt site shape_svt = vt.shape if USE_RK: def func_svt(t, y): return hop_svt(y.reshape(shape_svt)).ravel() / coef sol_svt = solve_ivp( func_svt, (0, -evolve_dt / 2), vt.ravel(), method="RK45" ) cmf_rk_steps.append(len(sol_svt.t)) mps_t = sol_svt.y[:, -1] else: def func_svt(y): return hop_svt(y.reshape(shape_svt)).ravel() mps_t = expm_krylov(func_svt, (-evolve_dt / 2) / coef, vt.ravel()) mps_t = mps_t.reshape(shape_svt) mps[imps + 1] = tensordot( mps_t, mps[imps + 1].array, axes=(1, 0), ) mps_conj[imps + 1] = mps[imps + 1].conj() else: mps[imps] = mps_t mps_conj[imps] = mps[imps].conj() mps._switch_direction() if USE_RK: steps_stat = stats.describe(cmf_rk_steps) logger.debug(f"TDVP-PS CMF steps: {steps_stat}") mps.evolve_config.stat = steps_stat return mps
def _evolve_dmrg_tdvp_mctdhnew(self, mpo, evolve_dt) -> "Mps": # new regularization scheme # JCP 148, 124105 (2018) # JCP 149, 044119 (2018) # a workaround for https://github.com/scipy/scipy/issues/10164 imag_time = np.iscomplex(evolve_dt) if imag_time: evolve_dt = -evolve_dt.imag # used in calculating derivatives coef = -1 else: coef = 1j if self.is_left_canon: assert self.check_left_canonical() self.canonicalise() mps = self.to_complex(inplace=True) # construct the environment matrix environ = Environ() environ.construct(mps, mps.conj(), mpo, "R") # initial matrix ltensor = ones((1, 1, 1)) rtensor = ones((1, 1, 1)) new_mps = mps.metacopy() # statistics for debug output cmf_rk_steps = [] for imps in range(len(mps)): shape = list(mps[imps].shape) system = "L" if mps.left else "R" qnbigl, qnbigr = mps._get_big_qn(imps) u, s, qnlset, v, s, qnrset = svd_qn.Csvd( mps[imps].asnumpy(), qnbigl, qnbigr, mps.qntot, system=system, full_matrices=False, ) vt = v.T mps[imps] = u.reshape(shape[:-1] + [-1]) ltensor = environ.GetLR( "L", imps - 1, mps, mps.conj(), mpo, itensor=ltensor, method="System" ) rtensor = environ.GetLR( "R", imps + 1, mps, mps.conj(), mpo, itensor=rtensor, method="Enviro" ) epsilon = 1e-10 epsilon = np.sqrt(epsilon) s = s + epsilon * np.exp(-s / epsilon) svt = Matrix(np.diag(s).dot(vt)) rtensor = tensordot(rtensor, svt, axes=(2, 1)) rtensor = tensordot(Matrix(vt).conj(), rtensor, axes=(1, 0)) if imps != len(mps) - 1: mps[imps + 1] = tensordot(svt, mps[imps + 1], axes=(-1, 0)) mps.qn[imps + 1] = qnlset new_mps.qn[imps + 1] = qnlset.copy() S_inv = xp.diag(1.0 / s) hop = hop_factory(ltensor, rtensor, mpo[imps], len(shape)) func = integrand_func_factory(shape, hop, imps == len(mps) - 1, S_inv, coef) sol = solve_ivp( func, (0, evolve_dt), mps[imps].ravel().array, method="RK45" ) cmf_rk_steps.append(len(sol.t)) ms = sol.y[:, -1].reshape(shape) if imps == len(mps) - 1: new_mps[imps] = ms * s[0] else: new_mps[imps] = ms mps._switch_direction() new_mps._switch_direction() new_mps.canonicalise() steps_stat = stats.describe(cmf_rk_steps) logger.debug(f"TDVP-MCTDH CMF steps: {steps_stat}") # new_mps.evolve_config.stat = steps_stat return new_mps
def _update_mps(self, cstruct, cidx, qnbigl, qnbigr, Mmax, percent=0): r"""update mps with basis selection algorithm of J. Chem. Phys. 120, 3172 (2004). Parameters --------- cstruct : ndarray, List[ndarray] The active site coefficient. cidx : list The List of active site index. qnbigl : ndarray The super-L-block quantum number. qnbigr : ndarray The super-R-block quantum number. Mmax : int The maximal bond dimension. percent : float, int The percentage of renormalized basis which is equally selected from each quantum number section rather than according to singular values. ``percent`` is defined in ``procedure`` of `renormalizer.utils.configs.OptimizeConfig` and ``vprocedure`` of `renormalizer.utils.configs.CompressConfig`. Returns ------- averaged_ms : if ``cstruct`` is a list, ``averaged_ms`` is a list of rotated ms of each element in ``cstruct`` as a single site calculation. It is used for better initial guess in SA-DMRG algorithm. Otherwise, ``None`` is returned. ``self`` is overwritten inplace. """ system = "L" if self.to_right else "R" # step 1: get the selected U, S, V if type(cstruct) is not list: # SVD method # full_matrices = True here to enable increase the bond dimension Uset, SUset, qnlnew, Vset, SVset, qnrnew = svd_qn.Csvd( asnumpy(cstruct), qnbigl, qnbigr, self.qntot, system=system) if self.to_right: ms, msdim, msqn, compms = select_basis(Uset, SUset, qnlnew, Vset, Mmax, percent=percent) ms = ms.reshape(list(qnbigl.shape) + [msdim]) compms = xp.moveaxis( compms.reshape(list(qnbigr.shape) + [msdim]), -1, 0) else: ms, msdim, msqn, compms = select_basis(Vset, SVset, qnrnew, Uset, Mmax, percent=percent) ms = xp.moveaxis(ms.reshape(list(qnbigr.shape) + [msdim]), -1, 0) compms = compms.reshape(list(qnbigl.shape) + [msdim]) else: # state-averaged method ddm = 0.0 for iroot in range(len(cstruct)): if self.to_right: ddm += tensordot( cstruct[iroot], cstruct[iroot], axes=( range(qnbigl.ndim, cstruct[iroot].ndim), range(qnbigl.ndim, cstruct[iroot].ndim), ), ) else: ddm += tensordot( cstruct[iroot], cstruct[iroot], axes=(range(qnbigl.ndim), range(qnbigl.ndim)), ) ddm /= len(cstruct) Uset, Sset, qnnew = svd_qn.Csvd(asnumpy(ddm), qnbigl, qnbigr, self.qntot, system=system, ddm=True) ms, msdim, msqn, compms = select_basis(Uset, Sset, qnnew, None, Mmax, percent=percent) rotated_c = [] averaged_ms = [] if self.to_right: ms = ms.reshape(list(qnbigl.shape) + [msdim]) for c in cstruct: compms = tensordot( ms, c, axes=(range(qnbigl.ndim), range(qnbigl.ndim)), ) rotated_c.append(compms) compms = rotated_c[0] else: ms = ms.reshape(list(qnbigr.shape) + [msdim]) for c in cstruct: compms = tensordot( c, ms, axes=(range(qnbigl.ndim, cstruct[0].ndim), range(qnbigr.ndim)), ) rotated_c.append(compms) compms = rotated_c[0] ms = xp.moveaxis(ms, -1, 0) # step 2, put updated U, S, V back to self if len(cidx) == 1: # 1site method self[cidx[0]] = ms if self.to_right: if cidx[0] != self.site_num - 1: if type(cstruct) is list: for c in rotated_c: averaged_ms.append( tensordot(c, self[cidx[0] + 1], axes=1)) self[cidx[0] + 1] = tensordot(compms, self[cidx[0] + 1], axes=1) self.qn[cidx[0] + 1] = msqn self.qnidx = cidx[0] + 1 else: if type(cstruct) is list: for c in rotated_c: averaged_ms.append( tensordot(self[cidx[0]], c, axes=1)) self[cidx[0]] = tensordot(self[cidx[0]], compms, axes=1) self.qnidx = self.site_num - 1 else: if cidx[0] != 0: if type(cstruct) is list: for c in rotated_c: averaged_ms.append( tensordot(self[cidx[0] - 1], c, axes=1)) self[cidx[0] - 1] = tensordot(self[cidx[0] - 1], compms, axes=1) self.qn[cidx[0]] = msqn self.qnidx = cidx[0] - 1 else: if type(cstruct) is list: for c in rotated_c: averaged_ms.append( tensordot(c, self[cidx[0]], axes=1)) self[cidx[0]] = tensordot(compms, self[cidx[0]], axes=1) self.qnidx = 0 else: if self.to_right: self[cidx[0]] = ms self[cidx[1]] = compms self.qnidx = cidx[1] else: self[cidx[1]] = ms self[cidx[0]] = compms self.qnidx = cidx[0] if type(cstruct) is list: averaged_ms = rotated_c self.qn[cidx[1]] = msqn if type(cstruct) is list: return averaged_ms else: return None
def compress(self, temp_m_trunc=None, ret_s=False): """ inp: canonicalise MPS (or MPO) side='l': compress LEFT-canonicalised MPS by sweeping from RIGHT to LEFT output MPS is right canonicalised i.e. CRRR side='r': reverse of 'l' Returns ------- truncated MPS """ if self.to_right: assert self.qnidx == 0 else: assert self.qnidx == self.site_num - 1 if self.compress_config.bonddim_should_set: self.compress_config.set_bonddim(len(self) + 1) # used for logging at exit sz_before = self.total_bytes if not self.is_mpo: # ensure mps is canonicalised. This is time consuming. # to disable this, run python as `python -O` if self.is_left_canon: assert self.check_left_canonical() else: assert self.check_right_canonical() system = "L" if self.to_right else "R" s_list = [] for idx in self.iter_idx_list(full=False): mt: Matrix = self[idx] qnbigl, qnbigr, _ = self._get_big_qn([idx]) u, sigma, qnlset, v, sigma, qnrset = svd_qn.Csvd( mt.array, qnbigl, qnbigr, self.qntot, system=system, full_matrices=False, ) vt = v.T s_list.append(sigma) if temp_m_trunc is None: m_trunc = self.compress_config.compute_m_trunc( sigma, idx, self.to_right) else: m_trunc = min(temp_m_trunc, len(sigma)) self._update_ms(idx, u, vt, sigma, qnlset, qnrset, m_trunc) self._switch_direction() compress_ratio = sz_before / self.total_bytes logger.debug( f"size before/after compress: {sizeof_fmt(sz_before)}/{sizeof_fmt(self.total_bytes)}, ratio: {compress_ratio}" ) if not ret_s: # usual exit return self else: # return singular value list return self, s_list