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 update_cv(vset, sset, qnset, compset, nexciton, Mmax, spectratype, percent=0): sidx = select_Xbasis(qnset, sset, range(nexciton + 1), Mmax, spectratype, percent=percent) xdim = len(sidx) x = np.zeros((vset.shape[0], xdim), dtype=vset.dtype) xqn = [] if compset is not None: compx = np.zeros((compset.shape[0], xdim), dtype=compset.dtype) else: compx = None for idim in range(xdim): x[:, idim] = vset[:, sidx[idim]].copy() if (compset is not None) and (sidx[idim] < compset.shape[1]): compx[:, idim] = compset[:, sidx[idim]].copy() * sset[sidx[idim]] xqn.append(qnset[sidx[idim]]) if compx is not None: compx = Matrix(compx) return Matrix(x), xdim, xqn, compx
def updatemps(vset, sset, qnset, compset, nexciton, Mmax, percent=0): """ select basis to construct new mps, and complementary mps vset, compset is the column vector """ sidx = select_basis(qnset, sset, range(nexciton + 1), Mmax, percent=percent) mpsdim = len(sidx) # need to set value column by column. better in CPU ms = np.zeros((vset.shape[0], mpsdim), dtype=vset.dtype) if compset is not None: compmps = np.zeros((compset.shape[0], mpsdim), dtype=compset.dtype) else: compmps = None mpsqn = [] stot = 0.0 for idim in range(mpsdim): ms[:, idim] = vset[:, sidx[idim]].copy() if (compset is not None) and sidx[idim] < compset.shape[1]: compmps[:, idim] = compset[:, sidx[idim]].copy() * sset[sidx[idim]] mpsqn.append(qnset[sidx[idim]]) stot += sset[sidx[idim]]**2 # print("discard:", 1.0 - stot) if compmps is not None: compmps = Matrix(compmps) return Matrix(ms), mpsdim, mpsqn, compmps
def _array2mt(self, array, idx): if isinstance(array, Matrix): mt = array.astype(self.dtype) else: mt = Matrix(array, dtype=self.dtype) if self.use_dummy_qn: mt.sigmaqn = np.zeros(mt.pdim_prod, dtype=np.int) else: mt.sigmaqn = self._get_sigmaqn(idx) return mt
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 hop(c): # convert c to initial structure according to qn pattern cstruct = cvec2cmat(cshape, c, qnmat, nexciton) if method == "1site": # S-a l-S # d # O-b-O-f-O # e # S-c k-S path = [ ([0, 1], "abc, adl -> bcdl"), ([2, 0], "bcdl, bdef -> clef"), ([1, 0], "clef, lfk -> cek"), ] cout = multi_tensor_contract(path, ltensor, Matrix(cstruct), mpo[imps], rtensor) # for small matrices, check hermite: # a=tensordot(ltensor, mpo[imps], ((1), (0))) # b=tensordot(a, rtensor, ((4), (1))) # c=b.transpose((0, 2, 4, 1, 3, 5)) # d=c.reshape(16, 16) else: # S-a l-S # d g # O-b-O-f-O-j-O # e h # S-c k-S path = [ ([0, 1], "abc, adgl -> bcdgl"), ([3, 0], "bcdgl, bdef -> cglef"), ([2, 0], "cglef, fghj -> clehj"), ([1, 0], "clehj, ljk -> cehk"), ] cout = multi_tensor_contract( path, ltensor, Matrix(cstruct), mpo[imps - 1], mpo[imps], rtensor, ) # convert structure c to 1d according to qn return inverse * cout.asnumpy()[qnmat == nexciton]
def _array2mt(self, array, idx, allow_dump=True): # convert dtype if isinstance(array, Matrix): mt = array.astype(self.dtype) else: mt = Matrix(array, dtype=self.dtype) if mt.pdim[0] != self.pbond_list[idx]: raise ValueError( "Matrix physical bond dimension does not match system information" ) # setup the matrix mt.sigmaqn = self._get_sigmaqn(idx) # array too large. Should be stored in disk # use ``while`` to handle the multiple-exit logic while allow_dump and self.compress_config.dump_matrix_size < mt.array.nbytes: dir_with_id = os.path.join(self.compress_config.dump_matrix_dir, str(id(self))) if not os.path.exists(dir_with_id): try: os.mkdir(dir_with_id) except: logger.exception( "Creating dump dir failed. Working with the matrix in memory." ) break dump_name = os.path.join(dir_with_id, f"{idx}.npy") try: array = mt.array if not array.flags.c_contiguous and not array.flags.f_contiguous: # for faster dump (3x). Costs more memory. array = np.ascontiguousarray(array) np.save(dump_name, array) except: logger.exception( "Save matrix to disk failed. Working with the matrix in memory." ) break return dump_name return mt
def __getitem__(self, item): mt_or_str_or_list = self._mp[item] if isinstance(mt_or_str_or_list, list): assert isinstance(item, slice) for elem in mt_or_str_or_list: if isinstance(elem, str): # load all matrices to memory will make # the dump mechanism pointless raise IndexError("Can't slice on dump matrices.") if isinstance(mt_or_str_or_list, str): try: mt = Matrix(np.load(mt_or_str_or_list), dtype=self.dtype) mt.sigmaqn = self._get_sigmaqn(item) except: logger.exception(f"Can't load matrix from {mt_or_str_or_list}") raise RuntimeError("MPS internal structure corrupted.") else: if not isinstance(mt_or_str_or_list, (Matrix, type(None))): raise RuntimeError( f"Unknown matrix type: {type(mt_or_str_or_list)}") mt = mt_or_str_or_list return mt
def _update_ms( self, idx: int, u: Matrix, vt: Matrix, sigma=None, qnlset=None, qnrset=None, m_trunc=None ): if m_trunc is None: m_trunc = u.shape[1] u = u[:, :m_trunc] vt = vt[:m_trunc, :] if sigma is None: # canonicalise, vt is not unitary if self.is_mpo: if self.left: norm = vt.norm() u = Matrix(u.array * norm) vt = Matrix(vt.array / norm) else: norm = u.norm() u = Matrix(u.array / norm) vt = Matrix(vt.array * norm) else: sigma = sigma[:m_trunc] if (not self.is_mpo and self.left) or (self.is_mpo and not self.left): vt = einsum("i, ij -> ij", sigma, vt) else: u = einsum("ji, i -> ji", u, sigma) if self.left: self[idx + 1] = tensordot(vt, self[idx + 1], axes=1) ret_mpsi = u.reshape( [u.shape[0] // self[idx].pdim_prod] + list(self[idx].pdim) + [m_trunc] ) if qnlset is not None: self.qn[idx + 1] = qnlset[:m_trunc] else: self[idx - 1] = tensordot(self[idx - 1], u, axes=1) ret_mpsi = vt.reshape( [m_trunc] + list(self[idx].pdim) + [vt.shape[1] // self[idx].pdim_prod] ) if qnrset is not None: self.qn[idx] = qnrset[:m_trunc] if ret_mpsi.nbytes < ret_mpsi.base.nbytes * 0.8: # do copy here to discard unnecessary data. Note that in NumPy common slicing returns # a `view` containing the original data. If `ret_mpsi` is used directly the original # `u` or `vt` is not garbage collected. ret_mpsi = ret_mpsi.copy() assert ret_mpsi.any() self[idx] = ret_mpsi
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 ( moveaxis(mps.reshape(list(qnbigr.shape) + [mpsdim]), -1, 0), mpsdim, mpsqn, tensordot( Matrix(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]), Matrix(cstruct[0]), axes=(range(qnbigl.ndim), range(qnbigl.ndim)), ), )
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