def hop(c): nonlocal count count += 1 xstruct = asxp(svd_qn.cvec2cmat(xshape, c, qnmat, constrain_qn)) if self.method == "1site": path_a = [([0, 1], "abcd, aef->bcdef"), ([3, 0], "bcdef, begh->cdfgh"), ([2, 0], "cdfgh, cgij->dfhij"), ([1, 0], "dfhij, fhjk->dik")] ax1 = multi_tensor_contract(path_a, first_L, xstruct, a_oper_isite1, a_oper_isite1, first_R) ax2 = xstruct ax = ax1 + ax2 * self.eta**2 else: path_a = [([0, 1], "abcd, aefg->bcdefg"), ([5, 0], "bcdefg, behi->cdfghi"), ([4, 0], "cdfghi, ifjk->cdghjk"), ([3, 0], "cdghjk, chlm->dgjklm"), ([2, 0], "dgjklm, mjno->dgklno"), ([1, 0], "dgklno, gkop->dlnp")] ax1 = multi_tensor_contract(path_a, first_L, xstruct, a_oper_isite2, a_oper_isite1, a_oper_isite2, a_oper_isite1, first_R) ax2 = xstruct ax = ax1 + ax2 * self.eta**2 cout = ax[qnmat == constrain_qn].reshape(nonzeros, 1) return asnumpy(cout)
def hop(x): nonlocal count count += 1 clist = [] if x.ndim == 1: clist.append(x) else: for icol in range(x.shape[1]): clist.append(x[:, icol]) res = [] for c in clist: # convert c to initial structure according to qn pattern cstruct = asxp(cvec2cmat(cshape, c, qnmat, mps.qntot)) if omega is None: 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, cstruct, cmo[0], rtensor) 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, cstruct, cmo[0], cmo[1], rtensor, ) else: cout = expr(cstruct, backend=oe_backend) # convert structure c to 1d according to qn res.append(asnumpy(cout)[qnmat == mps.qntot]) if len(res) == 1: return inverse * res[0] else: return inverse * np.stack(res, axis=1)
def update_LR(self, lr_group, direction, isite): first_LR = lr_group[0] second_LR = lr_group[1] # use the updated local site of cv_mps to update LR if self.method == "1site": if direction == "left": path1 = [([0, 1], "abcd, efa->bcdef"), ([3, 0], "bcdef, gfhb->cdegh"), ([2, 0], "cdegh, ihjc->degij"), ([1, 0], "degij, kjd->egik")] path2 = [([0, 1], "ab, cda->bcd"), ([1, 0], "bcd, edb->ce")] first_LR[isite - 1] = multi_tensor_contract( path1, first_LR[isite], self.cv_mps[isite - 1], self.a_oper[isite - 1], self.a_oper[isite - 1], self.cv_mps[isite - 1]) second_LR[isite - 1] = multi_tensor_contract( path2, second_LR[isite], self.b_oper[isite - 1], self.cv_mps[isite - 1]) else: path1 = [([0, 1], "abcd, aef->bcdef"), ([3, 0], "bcdef, begh->cdfgh"), ([2, 0], "cdfgh, cgij->dfhij"), ([1, 0], "dfhij, dik->fhjk")] path2 = [([0, 1], "ab, acd->bcd"), ([1, 0], "bcd, bce->de")] first_LR[isite] = multi_tensor_contract( path1, first_LR[isite - 1], self.cv_mps[isite - 1], self.a_oper[isite - 1], self.a_oper[isite - 1], self.cv_mps[isite - 1]) second_LR[isite] = multi_tensor_contract( path2, second_LR[isite - 1], self.b_oper[isite - 1], self.cv_mps[isite - 1]) else: if direction == "left": path1 = [([0, 1], "abc, efa->bcdef"), ([3, 0], "bcdef, gfhb->cdegh"), ([2, 0], "cdegh, ihgc->degij"), ([1, 0], "degij, kjd->egik")] path2 = [([0, 1], "ab, cda->bcd"), ([1, 0], "bcd, edb->ce")] first_LR[isite - 1] = multi_tensor_contract( path1, first_LR[isite], self.cv_mps[isite - 1], self.a_oper[isite - 1], self.a_oper[isite - 1], self.cv_mps[isite - 1]) second_LR[isite - 1] = multi_tensor_contract( path2, second_LR[isite], self.b_oper[isite - 1], self.cv_mps[isite - 1]) else: path1 = [([0, 1], "abc, aef->bcdef"), ([3, 0], "bcdef, begh->cdfgh"), ([2, 0], "cdfgh, cgij->dfhij"), ([1, 0], "dfhij, dik->fhjk")] path2 = [([0, 1], "ab, acd->bcd"), ([1, 0], "bcd, bce->de")] first_LR[isite - 1] = multi_tensor_contract( path1, first_LR[isite - 2], self.cv_mps[isite - 2], self.a_oper[isite - 2], self.a_oper[isite - 2], self.cv_mps[isite - 2]) second_LR[isite - 1] = multi_tensor_contract( path2, second_LR[isite - 2], self.b_oper[isite - 2], self.cv_mps[isite - 2]) return [first_LR, second_LR]
def expectations(self, mpos) -> np.ndarray: if len(mpos) < 3: return np.array([self.expectation(mpo) for mpo in mpos]) assert 2 < len(mpos) # id can be used as efficient hash because of `Matrix` implementation mpo_ids = np.array([[id(m) for m in mpo] for mpo in mpos]) common_mpo_ids = mpo_ids[0].copy() mpo0_unique_idx = np.where(np.sum(mpo_ids == common_mpo_ids, axis=0) == 1)[0][0] common_mpo_ids[mpo0_unique_idx] = mpo_ids[1][mpo0_unique_idx] x, unique_idx = np.where(mpo_ids != common_mpo_ids) # should find one at each line assert np.allclose(x, np.arange(len(mpos))) common_mpo = list(mpos[0]) common_mpo[mpo0_unique_idx] = mpos[1][mpo0_unique_idx] self_conj = self._expectation_conj() environ = Environ() environ.construct(self, self_conj, common_mpo, "l") environ.construct(self, self_conj, common_mpo, "r") res_list = [] for idx, mpo in zip(unique_idx, mpos): l = environ.read("l", idx - 1) r = environ.read("r", idx + 1) path = self._expectation_path() res = multi_tensor_contract(path, l, self[idx], mpo[idx], self_conj[idx], r) res_list.append(float(res.real)) return np.array(res_list)
def hop(x): # H*X nonlocal count count += 1 assert len(x) == xsize tda_coeff = reshape_x(x) res = [np.zeros_like(coeff) if coeff is not None else None for coeff in tda_coeff] # fix ket and sweep bra and accumulate into res for ims in range(site_num): if tda_coeff[ims] is None: assert tangent_u[ims] is None continue # mix-canonical mps mps_tangent = merge(mps_l_cano, mps_r_cano, ims+1) mps_tangent[ims] = tensordot(tangent_u[ims], tda_coeff[ims], (-1, 0)) mps_tangent_conj = mps_r_cano.copy() environ = Environ(mps_tangent, mpo, "R", mps_conj=mps_tangent_conj) for ims_conj in range(site_num): ltensor = environ.GetLR( "L", ims_conj-1, mps_tangent, mpo, itensor=None, mps_conj=mps_tangent_conj, method="System" ) rtensor = environ.GetLR( "R", ims_conj+1, mps_tangent, mpo, itensor=None, mps_conj=mps_tangent_conj, method="Enviro" ) if tda_coeff[ims_conj] is not None: # S-a l-S # d # O-b-O-f-O # e # S-c k-S path = [ ([0, 1], "abc, cek -> abek"), ([2, 0], "abek, bdef -> akdf"), ([1, 0], "akdf, lfk -> adl"), ] out = multi_tensor_contract( path, ltensor, asxp(mps_tangent[ims_conj]), asxp(mpo[ims_conj]), rtensor ) res[ims_conj] += asnumpy(tensordot(tangent_u[ims_conj], out, ([0,1], [0,1]))) # mps_conj combine mps_tangent_conj[ims_conj] = mps_l_cano[ims_conj] res = [mat for mat in res if mat is not None] return np.concatenate(res, axis=None)
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 hop(x): nonlocal count count += 1 dag_struct = asxp(self.dag2mat(xshape, x, dag_qnmat)) if self.method == "1site": M1 = multi_tensor_contract(path_1, first_L, dag_struct, a_oper_isite, a_oper_isite, first_R) M2 = multi_tensor_contract(path_2, second_L, dag_struct, a_oper_isite, h_mpo_isite, second_R) M2 = xp.moveaxis(M2, (1, 2), (2, 1)) M3 = multi_tensor_contract(path_2, third_L, h_mpo_isite, dag_struct, h_mpo_isite, third_R) M3 = xp.moveaxis(M3, (1, 2), (2, 1)) cout = M1 + 2 * M2 + M3 + dag_struct * self.eta**2 cout = cout[self.condition(dag_qnmat, [down_exciton, up_exciton])] return asnumpy(cout)
def expectation(self, mpo, self_conj=None) -> float: if self_conj is None: self_conj = self._expectation_conj() environ = Environ() environ.construct(self, self_conj, mpo, "r") l = ones((1, 1, 1)) r = environ.read("r", 1) path = self._expectation_path() return float(multi_tensor_contract(path, l, self[0], mpo[0], self_conj[0], r).real)
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
def initialize_LR(self): # initialize the Lpart and Rpart first_LR = [] first_LR.append(np.ones((1, 1, 1, 1))) second_LR = [] second_LR.append(np.ones((1, 1))) for isite in range(1, len(self.cv_mps)): first_LR.append(None) second_LR.append(None) first_LR.append(np.ones((1, 1, 1, 1))) second_LR.append(np.ones((1, 1))) if self.cv_mps.to_right: path1 = [([0, 1], "abcd, efa->bcdef"), ([3, 0], "bcdef, gfhb->cdegh"), ([2, 0], "cdegh, ihjc->degij"), ([1, 0], "degij, kjd->egik")] path2 = [([0, 1], "ab, cda->bcd"), ([1, 0], "bcd, edb->ce")] for isite in range(len(self.cv_mps), 1, -1): first_LR[isite - 1] = asnumpy(multi_tensor_contract( path1, first_LR[isite], self.cv_mps[isite - 1], self.a_oper[isite - 1], self.a_oper[isite - 1], self.cv_mps[isite - 1])) second_LR[isite - 1] = asnumpy(multi_tensor_contract( path2, second_LR[isite], self.b_mps[isite - 1], self.cv_mps[isite - 1])) else: path1 = [([0, 1], "abcd, aef->bcdef"), ([3, 0], "bcdef, begh->cdfgh"), ([2, 0], "cdfgh, cgij->dfhij"), ([1, 0], "dfhij, dik->fhjk")] path2 = [([0, 1], "ab, acd->bcd"), ([1, 0], "bcd, bce->de")] for isite in range(1, len(self.cv_mps)): mps_isite = asxp(self.cv_mps[isite - 1]) first_LR[isite] = asnumpy(multi_tensor_contract( path1, first_LR[isite - 1], mps_isite, self.a_oper[isite - 1], self.a_oper[isite - 1], self.cv_mps[isite - 1])) second_LR[isite] = asnumpy(multi_tensor_contract( path2, second_LR[isite - 1], self.b_mps[isite - 1], mps_isite)) return [first_LR, second_LR]
def update_LR(self, lr_group, isite): first_LR, second_LR, third_LR, forth_LR = lr_group cv_isite = self.cv_mpo[isite - 1] dag_cv_isite = moveaxis(cv_isite, (1, 2), (2, 1)) if self.method == "1site": if not self.cv_mpo.to_right: path1 = [([0, 1], "abcd, efga -> bcdefg"), ([3, 0], "bcdefg, hgib -> cdefhi"), ([2, 0], "cdefhi, jikc -> defhjk"), ([1, 0], "defhjk, lkfd -> ehjl")] path2 = [([0, 1], "ab, cdea->bcde"), ([1, 0], "bcde, fedb->cf")] first_LR[isite - 1] = multi_tensor_contract( path1, first_LR[isite], dag_cv_isite, self.a_oper[isite - 1], self.a_oper[isite - 1], cv_isite) second_LR[isite - 1] = multi_tensor_contract( path1, second_LR[isite], dag_cv_isite, self.a_oper[isite - 1], cv_isite, self.h_mpo[isite - 1]) third_LR[isite - 1] = multi_tensor_contract( path1, third_LR[isite], self.h_mpo[isite - 1], dag_cv_isite, cv_isite, self.h_mpo[isite - 1]) forth_LR[isite - 1] = multi_tensor_contract( path2, forth_LR[isite], moveaxis(self.b_mpo[isite - 1], (1, 2), (2, 1)), cv_isite) else: path1 = [([0, 1], "abcd, aefg -> bcdefg"), ([3, 0], "bcdefg, bfhi -> cdeghi"), ([2, 0], "cdeghi, chjk -> degijk"), ([1, 0], "degijk, djel -> gikl")] path2 = [([0, 1], "ab, acde->bcde"), ([1, 0], "bcde, bdcf->ef")] first_LR[isite] = multi_tensor_contract( path1, first_LR[isite - 1], dag_cv_isite, self.a_oper[isite - 1], self.a_oper[isite - 1], cv_isite) second_LR[isite] = multi_tensor_contract( path1, second_LR[isite - 1], dag_cv_isite, self.a_oper[isite - 1], cv_isite, self.h_mpo[isite - 1]) third_LR[isite] = multi_tensor_contract( path1, third_LR[isite - 1], self.h_mpo[isite - 1], dag_cv_isite, cv_isite, self.h_mpo[isite - 1]) forth_LR[isite] = multi_tensor_contract( path2, forth_LR[isite - 1], moveaxis(self.b_mpo[isite - 1], (1, 2), (2, 1)), cv_isite) else: # 2site for finite temperature is too expensive, so I drop it # (at least for now) raise NotImplementedError return first_LR, second_LR, third_LR, forth_LR
def hop(c): nonlocal count count += 1 xstruct = asxp(cvec2cmat(xshape, c, qnmat, constrain_qn)) if self.method == "1site": path_a = [([0, 1], "abcd, aef->bcdef"), ([3, 0], "bcdef, begh->cdfgh"), ([2, 0], "cdfgh, cgij->dfhij"), ([1, 0], "dfhij, fhjk->dik")] ax1 = multi_tensor_contract(path_a, first_L, xstruct, a_oper_isite1, a_oper_isite1, first_R) else: # opt_einsum v3.2.1 is not bad, ~10% faster than the hand-design # contraction path for this complicated cases and consumes a little bit less memory # this is the only place in renormalizer we use opt_einsum now. # we keep it here just for a demo. # ax1 = oe.contract("abcd, befh, cfgi, hjkn, iklo, mnop, dglp -> aejm", # first_L, a_oper_isite2, a_oper_isite2, a_oper_isite1, # a_oper_isite1, first_R, xstruct) if USE_GPU: oe_backend = "cupy" else: oe_backend = "numpy" ax1 = expr(xstruct, backend=oe_backend) #print(oe.contract_path("abcd, befh, cfgi, hjkn, iklo, mnop, dglp -> aejm", # first_L, a_oper_isite2, a_oper_isite2, a_oper_isite1, # a_oper_isite1, first_R, xstruct)) #path_a = [([0, 1], "abcd, aefg->bcdefg"), # ([5, 0], "bcdefg, behi->cdfghi"), # ([4, 0], "cdfghi, ifjk->cdghjk"), # ([3, 0], "cdghjk, chlm->dgjklm"), # ([2, 0], "dgjklm, mjno->dgklno"), # ([1, 0], "dgklno, gkop->dlnp")] #ax1 = multi_tensor_contract(path_a, first_L, xstruct, # a_oper_isite2, a_oper_isite1, # a_oper_isite2, a_oper_isite1, # first_R) ax = ax1 + xstruct * self.eta**2 cout = ax[qnmat == constrain_qn] return asnumpy(cout)
def optimize_mps_dmrg(mps, mpo): """ 1 or 2 site optimization procedure """ method = mps.optimize_config.method procedure = mps.optimize_config.procedure inverse = mps.optimize_config.inverse nroots = mps.optimize_config.nroots assert method in ["2site", "1site"] # print("optimization method", method) nexciton = mps.nexciton # construct the environment matrix environ = Environ() environ.construct(mps, mps, mpo, "L") nMPS = len(mps) # construct each sweep cycle scheme if method == "1site": loop = [["R", i] for i in range(nMPS - 1, -1, -1)] + [["L", i] for i in range(0, nMPS)] else: loop = [["R", i] for i in range(nMPS - 1, 0, -1)] + [["L", i] for i in range(1, nMPS)] # initial matrix ltensor = ones((1, 1, 1)) rtensor = ones((1, 1, 1)) energies = [] for isweep, (mmax, percent) in enumerate(procedure): logger.debug(f"mmax, percent: {mmax}, {percent}") logger.debug(f"energy: {mps.expectation(mpo)}") logger.debug(f"{mps}") for system, imps in loop: if system == "R": lmethod, rmethod = "Enviro", "System" else: lmethod, rmethod = "System", "Enviro" if method == "1site": lsite = imps - 1 addlist = [imps] else: lsite = imps - 2 addlist = [imps - 1, imps] ltensor = environ.GetLR("L", lsite, mps, mps, mpo, itensor=ltensor, method=lmethod) rtensor = environ.GetLR("R", imps + 1, mps, mps, mpo, itensor=rtensor, method=rmethod) # get the quantum number pattern qnmat, qnbigl, qnbigr = construct_qnmat(mps, mpo.ephtable, mpo.pbond_list, addlist, method, system) cshape = qnmat.shape # hdiag tmp_ltensor = einsum("aba -> ba", ltensor) tmp_MPOimps = einsum("abbc -> abc", mpo[imps]) tmp_rtensor = einsum("aba -> ba", rtensor) if method == "1site": # S-a c f-S # O-b-O-g-O # S-a c f-S path = [([0, 1], "ba, bcg -> acg"), ([1, 0], "acg, gf -> acf")] hdiag = multi_tensor_contract(path, tmp_ltensor, tmp_MPOimps, tmp_rtensor)[(qnmat == nexciton)] # initial guess b-S-c # a cguess = mps[imps][qnmat == nexciton] else: # S-a c d f-S # O-b-O-e-O-g-O # S-a c d f-S tmp_MPOimpsm1 = einsum("abbc -> abc", mpo[imps - 1]) path = [ ([0, 1], "ba, bce -> ace"), ([0, 1], "edg, gf -> edf"), ([0, 1], "ace, edf -> acdf"), ] hdiag = multi_tensor_contract(path, tmp_ltensor, tmp_MPOimpsm1, tmp_MPOimps, tmp_rtensor)[(qnmat == nexciton)] # initial guess b-S-c-S-e # a d cguess = tensordot(mps[imps - 1], mps[imps], axes=1)[qnmat == nexciton] cguess = cguess.asnumpy() hdiag *= inverse nonzeros = np.sum(qnmat == nexciton) # print("Hmat dim", nonzeros) 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] if nroots != 1: cguess = [cguess] for iroot in range(nroots - 1): cguess.append(np.random.random([nonzeros]) - 0.5) precond = lambda x, e, *args: x / (hdiag.asnumpy() - e + 1e-4) e, c = davidson(hop, cguess, precond, max_cycle=100, nroots=nroots, max_memory=64000) # scipy arpack solver : much slower than davidson # A = spslinalg.LinearOperator((nonzeros,nonzeros), matvec=hop) # e, c = spslinalg.eigsh(A,k=1, which="SA",v0=cguess) # print("HC loops:", count[0]) # logger.debug(f"isweep: {isweep}, e: {e}") energies.append(e) cstruct = cvec2cmat(cshape, c, qnmat, nexciton, nroots=nroots) if nroots == 1: # direct svd the coefficient matrix mt, mpsdim, mpsqn, compmps = renormalization_svd( cstruct, qnbigl, qnbigr, system, nexciton, Mmax=mmax, percent=percent, ) else: # diagonalize the reduced density matrix mt, mpsdim, mpsqn, compmps = renormalization_ddm( cstruct, qnbigl, qnbigr, system, nexciton, Mmax=mmax, percent=percent, ) if method == "1site": mps[imps] = mt if system == "L": if imps != len(mps) - 1: mps[imps + 1] = tensordot(compmps, mps[imps + 1], axes=1) mps.qn[imps + 1] = mpsqn else: mps[imps] = tensordot(mps[imps], compmps, axes=1) mps.qn[imps + 1] = [0] else: if imps != 0: mps[imps - 1] = tensordot(mps[imps - 1], compmps, axes=1) mps.qn[imps] = mpsqn else: mps[imps] = tensordot(compmps, mps[imps], axes=1) mps.qn[imps] = [0] else: if system == "L": mps[imps - 1] = mt mps[imps] = compmps else: mps[imps] = mt mps[imps - 1] = compmps # mps.dim_list[imps] = mpsdim mps.qn[imps] = mpsqn energies = np.array(energies) if nroots == 1: logger.debug("Optimization complete, lowest energy = %g", energies.min()) return energies
def optimize_cv(self, lr_group, isite, percent=0): if self.spectratype == "abs": # quantum number restriction, |1><0| up_exciton, down_exciton = 1, 0 elif self.spectratype == "emi": # quantum number restriction, |0><1| up_exciton, down_exciton = 0, 1 nexciton = 1 first_LR, second_LR, third_LR, forth_LR = lr_group if self.method == "1site": add_list = [isite - 1] first_L = asxp(first_LR[isite - 1]) first_R = asxp(first_LR[isite]) second_L = asxp(second_LR[isite - 1]) second_R = asxp(second_LR[isite]) third_L = asxp(third_LR[isite - 1]) third_R = asxp(third_LR[isite]) forth_L = asxp(forth_LR[isite - 1]) forth_R = asxp(forth_LR[isite]) else: add_list = [isite - 2, isite - 1] first_L = asxp(first_LR[isite - 2]) first_R = asxp(first_LR[isite]) second_L = asxp(second_LR[isite - 2]) second_R = asxp(second_LR[isite]) third_L = asxp(third_LR[isite - 2]) third_R = asxp(third_LR[isite]) forth_L = asxp(forth_LR[isite - 2]) forth_R = asxp(forth_LR[isite]) xqnmat, xqnbigl, xqnbigr, xshape = \ self.construct_X_qnmat(add_list) dag_qnmat, dag_qnbigl, dag_qnbigr = self.swap(xqnmat, xqnbigl, xqnbigr) nonzeros = int( np.sum(self.condition(dag_qnmat, [down_exciton, up_exciton]))) if self.method == "1site": guess = moveaxis(self.cv_mpo[isite - 1], (1, 2), (2, 1)) else: guess = tensordot(moveaxis(self.cv_mpo[isite - 2], (1, 2), (2, 1)), moveaxis(self.cv_mpo[isite - 1]), axes=(-1, 0)) guess = guess[self.condition(dag_qnmat, [down_exciton, up_exciton])] if self.method == "1site": # define dot path path_1 = [([0, 1], "abcd, aefg -> bcdefg"), ([3, 0], "bcdefg, bfhi -> cdeghi"), ([2, 0], "cdeghi, chjk -> degijk"), ([1, 0], "degijk, gikl -> dejl")] path_2 = [([0, 1], "abcd, aefg -> bcdefg"), ([3, 0], "bcdefg, bfhi -> cdeghi"), ([2, 0], "cdeghi, djek -> cghijk"), ([1, 0], "cghijk, gilk -> chjl")] path_3 = [([0, 1], "ab, acde -> bcde"), ([1, 0], "bcde, ef -> bcdf")] vecb = multi_tensor_contract( path_3, forth_L, moveaxis(self.b_mpo[isite - 1], (1, 2), (2, 1)), forth_R)[self.condition(dag_qnmat, [down_exciton, up_exciton])] a_oper_isite = asxp(self.a_oper[isite - 1]) h_mpo_isite = asxp(self.h_mpo[isite - 1]) # construct preconditioner Idt = xp.identity(h_mpo_isite.shape[1]) M1_1 = xp.einsum('abca->abc', first_L) path_m1 = [([0, 1], "abc, bdef->acdef"), ([1, 0], "acdef, cegh->adfgh")] M1_2 = multi_tensor_contract(path_m1, M1_1, a_oper_isite, a_oper_isite) M1_2 = xp.einsum("abcbd->abcd", M1_2) M1_3 = xp.einsum('ecde->ecd', first_R) M1_4 = xp.einsum('ff->f', Idt) path_m1 = [([0, 1], "abcd,ecd->abe"), ([1, 0], "abe,f->abef")] pre_M1 = multi_tensor_contract(path_m1, M1_2, M1_3, M1_4) pre_M1 = xp.moveaxis(pre_M1, [-2, -1], [-1, -2])[self.condition( dag_qnmat, [down_exciton, up_exciton])] M2_1 = xp.einsum('aeag->aeg', second_L) M2_2 = xp.einsum('eccf->ecf', a_oper_isite) M2_3 = xp.einsum('gbbh->gbh', h_mpo_isite) M2_4 = xp.einsum('dfdh->dfh', second_R) path_m2 = [([0, 1], "aeg,gbh->aebh"), ([2, 0], "aebh,ecf->abchf"), ([1, 0], "abhcf,dfh->abcd")] pre_M2 = multi_tensor_contract(path_m2, M2_1, M2_3, M2_2, M2_4) pre_M2 = pre_M2[self.condition(dag_qnmat, [down_exciton, up_exciton])] M4_1 = xp.einsum('faah->fah', third_L) M4_4 = xp.einsum('gddi->gdi', third_R) M4_5 = xp.einsum('cc->c', Idt) M4_path = [([0, 1], "fah,febg->ahebg"), ([2, 0], "ahebg,hjei->abgji"), ([1, 0], "abgji,gdi->abjd")] pre_M4 = multi_tensor_contract(M4_path, M4_1, h_mpo_isite, h_mpo_isite, M4_4) pre_M4 = xp.einsum('abbd->abd', pre_M4) pre_M4 = xp.tensordot(pre_M4, M4_5, axes=0) pre_M4 = xp.moveaxis(pre_M4, [2, 3], [3, 2])[self.condition( dag_qnmat, [down_exciton, up_exciton])] M_x = lambda x: asnumpy( asxp(x) / (pre_M1 + 2 * pre_M2 + pre_M4 + xp.ones(nonzeros) * self.eta**2)) pre_M = scipy.sparse.linalg.LinearOperator((nonzeros, nonzeros), M_x) count = 0 def hop(x): nonlocal count count += 1 dag_struct = asxp(self.dag2mat(xshape, x, dag_qnmat)) if self.method == "1site": M1 = multi_tensor_contract(path_1, first_L, dag_struct, a_oper_isite, a_oper_isite, first_R) M2 = multi_tensor_contract(path_2, second_L, dag_struct, a_oper_isite, h_mpo_isite, second_R) M2 = xp.moveaxis(M2, (1, 2), (2, 1)) M3 = multi_tensor_contract(path_2, third_L, h_mpo_isite, dag_struct, h_mpo_isite, third_R) M3 = xp.moveaxis(M3, (1, 2), (2, 1)) cout = M1 + 2 * M2 + M3 + dag_struct * self.eta**2 cout = cout[self.condition(dag_qnmat, [down_exciton, up_exciton])] return asnumpy(cout) # Matrix A mat_a = scipy.sparse.linalg.LinearOperator((nonzeros, nonzeros), matvec=hop) x, info = scipy.sparse.linalg.cg(mat_a, asnumpy(vecb), tol=1.e-5, x0=asnumpy(guess), maxiter=500, M=pre_M, atol=0) # logger.info(f"linear eq dim: {nonzeros}") # logger.info(f'times for hop:{count}') self.hop_time.append(count) if info != 0: logger.warning( f"cg not converged, vecb.norm:{xp.linalg.norm(vecb)}") l_value = xp.dot(asxp(hop(x)), asxp(x)) - 2 * xp.dot(vecb, asxp(x)) x = self.dag2mat(xshape, x, dag_qnmat) if self.method == "1site": x = np.moveaxis(x, [1, 2], [2, 1]) x, xdim, xqn, compx = self.x_svd(x, xqnbigl, xqnbigr, nexciton, percent=percent) if self.method == "1site": self.cv_mpo[isite - 1] = x if not self.cv_mpo.to_right: if isite != 1: self.cv_mpo[isite - 2] = \ tensordot(self.cv_mpo[isite - 2], compx, axes=(-1, 0)) self.cv_mpo.qn[isite - 1] = xqn self.cv_mpo.qnidx = isite - 2 else: self.cv_mpo[isite - 1] = \ tensordot(compx, self.cv_mpo[isite - 1], axes=(-1, 0)) self.cv_mpo.qnidx = 0 else: if isite != len(self.cv_mpo): self.cv_mpo[isite] = \ tensordot(compx, self.cv_mpo[isite], axes=(-1, 0)) self.cv_mpo.qn[isite] = xqn self.cv_mpo.qnidx = isite else: self.cv_mpo[isite - 1] = \ tensordot(self.cv_mpo[isite - 1], compx, axes=(-1, 0)) self.cv_mpo.qnidx = self.cv_mpo.site_num - 1 else: if not self.cv_mpo.to_right: self.cv_mpo[isite - 2] = compx self.cv_mpo[isite - 1] = x self.cv_mpo.qnidx = isite - 2 else: self.cv_mpo[isite - 2] = x self.cv_mpo[isite - 1] = compx self.cv_mpo.qnidx = isite - 1 self.cv_mpo.qn[isite - 1] = xqn return float(l_value)
def initialize_LR(self): first_LR = [np.ones((1, 1, 1, 1))] forth_LR = [np.ones((1, 1))] for isite in range(1, len(self.cv_mpo)): first_LR.append(None) forth_LR.append(None) first_LR.append(np.ones((1, 1, 1, 1))) second_LR = copy.deepcopy(first_LR) third_LR = copy.deepcopy(first_LR) forth_LR.append(np.ones((1, 1))) if self.cv_mpo.to_right: for isite in range(len(self.cv_mpo), 1, -1): cv_isite = self.cv_mpo[isite - 1] dag_cv_isite = moveaxis(cv_isite, (1, 2), (2, 1)) path1 = [([0, 1], "abcd, efga -> bcdefg"), ([3, 0], "bcdefg, hgib -> cdefhi"), ([2, 0], "cdefhi, jikc -> defhjk"), ([1, 0], "defhjk, lkfd -> ehjl")] path2 = [([0, 1], "ab, cdea->bcde"), ([1, 0], "bcde, fedb->cf")] first_LR[isite - 1] = asnumpy( multi_tensor_contract(path1, first_LR[isite], dag_cv_isite, self.a_oper[isite - 1], self.a_oper[isite - 1], cv_isite)) second_LR[isite - 1] = asnumpy( multi_tensor_contract(path1, second_LR[isite], dag_cv_isite, self.a_oper[isite - 1], cv_isite, self.h_mpo[isite - 1])) third_LR[isite - 1] = asnumpy( multi_tensor_contract(path1, third_LR[isite], self.h_mpo[isite - 1], dag_cv_isite, cv_isite, self.h_mpo[isite - 1])) forth_LR[isite - 1] = asnumpy( multi_tensor_contract( path2, forth_LR[isite], moveaxis(self.b_mpo[isite - 1], (1, 2), (2, 1)), cv_isite)) else: for isite in range(1, len(self.cv_mpo)): cv_isite = self.cv_mpo[isite - 1] dag_cv_isite = moveaxis(cv_isite, (1, 2), (2, 1)) path1 = [([0, 1], "abcd, aefg -> bcdefg"), ([3, 0], "bcdefg, bfhi -> cdeghi"), ([2, 0], "cdeghi, chjk -> degijk"), ([1, 0], "degijk, djel -> gikl")] path2 = [([0, 1], "ab, acde->bcde"), ([1, 0], "bcde, bdcf->ef")] first_LR[isite] = asnumpy( multi_tensor_contract(path1, first_LR[isite - 1], dag_cv_isite, self.a_oper[isite - 1], self.a_oper[isite - 1], cv_isite)) second_LR[isite] = asnumpy( multi_tensor_contract(path1, second_LR[isite - 1], dag_cv_isite, self.a_oper[isite - 1], cv_isite, self.h_mpo[isite - 1])) third_LR[isite] = asnumpy( multi_tensor_contract(path1, third_LR[isite - 1], self.h_mpo[isite - 1], dag_cv_isite, cv_isite, self.h_mpo[isite - 1])) forth_LR[isite] = asnumpy( multi_tensor_contract( path2, forth_LR[isite - 1], moveaxis(self.b_mpo[isite - 1], (1, 2), (2, 1)), cv_isite)) return [first_LR, second_LR, third_LR, forth_LR]
def optimize_cv(self, lr_group, direction, isite, num, percent=0.0): # depending on the spectratype, to restrict the exction first_LR = lr_group[0] second_LR = lr_group[1] if self.spectratype == "abs": constrain_qn = 1 else: constrain_qn = 0 # this function aims at solving the work equation of ZT CV-DMRG # L = <CV|op_a|CV>+2\eta<op_b|CV>, take a derivative to local CV # S-a-S-e-S S-a-S-d-S # | d | | | | # O-b-O-g-O * CV[isite-1] = -\eta | c | # | f | | | | # S-c- -h-S S-b- -e-S # note to be a_mat * x = vec_b # the environment matrix if self.method == "1site": addlist = [isite - 1] first_L = asxp(first_LR[isite - 1]) first_R = asxp(first_LR[isite]) second_L = asxp(second_LR[isite - 1]) second_R = asxp(second_LR[isite]) else: addlist = [isite - 2, isite - 1] first_L = asxp(first_LR[isite - 2]) first_R = asxp(first_LR[isite]) second_L = asxp(second_LR[isite - 2]) second_R = asxp(second_LR[isite]) if direction == 'left': system = 'R' else: system = 'L' # this part just be similar with ground state calculation qnmat, qnbigl, qnbigr = svd_qn.construct_qnmat( self.cv_mps, self.mpo.pbond_list, addlist, self.method, system) xshape = qnmat.shape nonzeros = np.sum(qnmat == constrain_qn) if self.method == '1site': guess = self.cv_mps[isite - 1][qnmat == constrain_qn].reshape(nonzeros, 1) path_b = [([0, 1], "ab, acd->bcd"), ([1, 0], "bcd, de->bce")] vec_b = multi_tensor_contract( path_b, second_L, self.b_oper[isite - 1], second_R )[qnmat == constrain_qn].reshape(nonzeros, 1) else: guess = tensordot( self.cv_mps[isite - 2], self.cv_mps[isite - 1], axes=(-1, 0) ) guess = guess[qnmat == constrain_qn].reshape(nonzeros, 1) path_b = [([0, 1], "ab, acd->bcd"), ([2, 0], "bcd, def->bcef"), ([1, 0], "bcef, fg->bceg")] vec_b = multi_tensor_contract( path_b, second_L, self.b_oper[isite - 2], self.b_oper[isite - 1], second_R )[qnmat == constrain_qn].reshape(nonzeros, 1) if self.method == "2site": a_oper_isite2 = asxp(self.a_oper[isite - 2]) else: a_oper_isite2 = None a_oper_isite1 = asxp(self.a_oper[isite - 1]) # use the diagonal part of mat_a to construct the preconditinoner for linear solver if self.method == "1site": part_l = xp.einsum('abca->abc', first_L) part_r = xp.einsum('hfgh->hfg', first_R) path_pre = [([0, 1], "abc, bdef->acdef"), ([1, 0], "acdef, hfg->acdehg")] pre_a_mat1 = multi_tensor_contract(path_pre, part_l, a_oper_isite1, part_r) path_pre2 = [([0, 1], "acdehg, ceig->adhi")] pre_a_mat1 = multi_tensor_contract(path_pre2, pre_a_mat1, a_oper_isite1) pre_a_mat1 = xp.einsum('adhd->adh', pre_a_mat1)[qnmat == constrain_qn] # pre_a_mat1 = xp.einsum('abca, bdef, cedg, hfgh->adh', first_L, a_oper_isite1, # a_oper_isite1, first_R)[qnmat == constrain_qn] cv_shape = self.cv_mps[isite - 1].shape pre_a_mat2 = xp.ones(cv_shape)[qnmat == constrain_qn] pre_a_mat = pre_a_mat1 + pre_a_mat2 * self.eta**2 else: pre_a_mat1 = xp.einsum( 'abca, bdef, cedg, fhij, gihk, ljkl->adhl', first_L, a_oper_isite2, a_oper_isite2, a_oper_isite1, a_oper_isite1, first_R)[qnmat == constrain_qn] cv_shape1 = self.cv_mps[isite - 2].shape cv_shape2 = self.cv_mps[isite - 1].shape new_shape = [cv_shape1[0], cv_shape1[1], cv_shape2[1], cv_shape2[2]] pre_a_mat2 = xp.ones(new_shape)[qnmat == constrain_qn] pre_a_mat = pre_a_mat1 + pre_a_mat2 * self.eta**2 pre_a_mat = np.diag(1./asnumpy(pre_a_mat)) count = 0 def hop(c): nonlocal count count += 1 xstruct = asxp(svd_qn.cvec2cmat(xshape, c, qnmat, constrain_qn)) if self.method == "1site": path_a = [([0, 1], "abcd, aef->bcdef"), ([3, 0], "bcdef, begh->cdfgh"), ([2, 0], "cdfgh, cgij->dfhij"), ([1, 0], "dfhij, fhjk->dik")] ax1 = multi_tensor_contract(path_a, first_L, xstruct, a_oper_isite1, a_oper_isite1, first_R) ax2 = xstruct ax = ax1 + ax2 * self.eta**2 else: path_a = [([0, 1], "abcd, aefg->bcdefg"), ([5, 0], "bcdefg, behi->cdfghi"), ([4, 0], "cdfghi, ifjk->cdghjk"), ([3, 0], "cdghjk, chlm->dgjklm"), ([2, 0], "dgjklm, mjno->dgklno"), ([1, 0], "dgklno, gkop->dlnp")] ax1 = multi_tensor_contract(path_a, first_L, xstruct, a_oper_isite2, a_oper_isite1, a_oper_isite2, a_oper_isite1, first_R) ax2 = xstruct ax = ax1 + ax2 * self.eta**2 cout = ax[qnmat == constrain_qn].reshape(nonzeros, 1) return asnumpy(cout) mat_a = scipy.sparse.linalg.LinearOperator((nonzeros, nonzeros), matvec=hop) # for the first two sweep, not use the previous matrix as initial guess # at the inital stage, they are far from from the optimized one if num in [1, 2]: x, info = scipy.sparse.linalg.cg(mat_a, asnumpy(vec_b), atol=0) else: x, info = scipy.sparse.linalg.cg(mat_a, asnumpy(vec_b), tol=1.e-5, x0=guess, M=pre_a_mat, atol=0) # logger.info(f'hop times:{count}') self.hop_time.append(count) if info != 0: logger.info(f"iteration solver not converged") # the value of the functional L l_value = np.inner(hop(x).reshape(1, nonzeros), x.reshape(1, nonzeros) ) - 2 * np.inner( asnumpy(vec_b).reshape(1, nonzeros), x.reshape(1, nonzeros)) xstruct = svd_qn.cvec2cmat(xshape, x, qnmat, constrain_qn) x, xdim, xqn, compx = \ solver.renormalization_svd(xstruct, qnbigl, qnbigr, system, constrain_qn, self.m_max, percent) if self.method == "1site": self.cv_mps[isite - 1] = x if direction == "left": if isite != 1: self.cv_mps[isite - 2] = tensordot( self.cv_mps[isite - 2], compx, axes=(-1, 0)) self.cv_mps.qn[isite - 1] = xqn else: self.cv_mps[isite - 1] = tensordot( compx, self.cv_mps[isite - 1], axes=(-1, 0)) self.cv_mps.qn[isite - 1] = [0] elif direction == "right": if isite != len(self.cv_mps): self.cv_mps[isite] = tensordot( compx, self.cv_mps[isite], axes=(-1, 0)) self.cv_mps.qn[isite] = xqn else: self.cv_mps[isite - 1] = tensordot( self.cv_mps[isite - 1], compx, axes=(-1, 0)) self.cv_mps.qn[isite] = [0] else: if direction == "left": self.cv_mps[isite - 1] = x self.cv_mps[isite - 2] = compx else: self.cv_mps[isite - 2] = x self.cv_mps[isite - 1] = compx self.cv_mps.qn[isite - 1] = xqn return l_value[0][0]
def update_LR(self, lr_group, direction, isite): first_LR, second_LR, third_LR, forth_LR = lr_group assert direction in ["left", "right"] if self.method == "1site": if direction == "left": path1 = [([0, 1], "abc, defa -> bcdef"), ([2, 0], "bcdef, gfhb -> cdegh"), ([1, 0], "cdegh, ihec -> dgi")] first_LR[isite - 1] = multi_tensor_contract( path1, first_LR[isite], moveaxis(self.cv_mpo[isite - 1], (1, 2), (2, 1)), self.a_oper[isite - 1], self.cv_mpo[isite - 1]) path2 = [([0, 1], "abcd, efga -> bcdefg"), ([3, 0], "bcdefg, hgib -> cdefhi"), ([2, 0], "cdefhi, jikc -> defhjk"), ([1, 0], "defhjk, lkfd -> ehjl")] path4 = [([0, 1], "ab, cdea->bcde"), ([1, 0], "bcde, fedb->cf")] second_LR[isite - 1] = multi_tensor_contract( path2, second_LR[isite], moveaxis(self.cv_mpo[isite - 1], (1, 2), (2, 1)), self.b_oper[isite - 1], self.cv_mpo[isite - 1], self.h_mpo[isite - 1]) third_LR[isite - 1] = multi_tensor_contract( path2, third_LR[isite], self.h_mpo[isite - 1], moveaxis(self.cv_mpo[isite - 1], (1, 2), (2, 1)), self.cv_mpo[isite - 1], self.h_mpo[isite - 1]) forth_LR[isite - 1] = multi_tensor_contract( path4, forth_LR[isite], moveaxis(self.a_ket_mpo[isite - 1], (1, 2), (2, 1)), self.cv_mpo[isite - 1]) elif direction == "right": path1 = [([0, 1], "abc, adef -> bcdef"), ([2, 0], "bcdef, begh -> cdfgh"), ([1, 0], "cdfgh, cgdi -> fhi")] first_LR[isite] = multi_tensor_contract( path1, first_LR[isite - 1], moveaxis(self.cv_mpo[isite - 1], (1, 2), (2, 1)), self.a_oper[isite - 1], self.cv_mpo[isite - 1]) path2 = [([0, 1], "abcd, aefg -> bcdefg"), ([3, 0], "bcdefg, bfhi -> cdeghi"), ([2, 0], "cdeghi, chjk -> degijk"), ([1, 0], "degijk, djel -> gikl")] path4 = [([0, 1], "ab, acde->bcde"), ([1, 0], "bcde, bdcf->ef")] second_LR[isite] = multi_tensor_contract( path2, second_LR[isite - 1], moveaxis(self.cv_mpo[isite - 1], (1, 2), (2, 1)), self.b_oper[isite - 1], self.cv_mpo[isite - 1], self.h_mpo[isite - 1]) third_LR[isite] = multi_tensor_contract( path2, third_LR[isite - 1], self.h_mpo[isite - 1], moveaxis(self.cv_mpo[isite - 1], (1, 2), (2, 1)), self.cv_mpo[isite - 1], self.h_mpo[isite - 1]) forth_LR[isite] = multi_tensor_contract( path4, forth_LR[isite - 1], moveaxis(self.a_ket_mpo[isite - 1], (1, 2), (2, 1)), self.cv_mpo[isite - 1]) else: # 2site for finite temperature is too expensive, so I drop it # (at least for now) raise NotImplementedError return first_LR, second_LR, third_LR, forth_LR
def initialize_LR(self, direction): first_LR = [np.ones((1, 1, 1))] second_LR = [np.ones((1, 1, 1, 1))] forth_LR = [np.ones((1, 1))] for isite in range(1, len(self.cv_mpo)): first_LR.append(None) second_LR.append(None) forth_LR.append(None) first_LR.append(np.ones((1, 1, 1))) second_LR.append(np.ones((1, 1, 1, 1))) third_LR = copy.deepcopy(second_LR) forth_LR.append(np.ones((1, 1))) if direction == "right": for isite in range(len(self.cv_mpo), 1, -1): path1 = [([0, 1], "abc, defa -> bcdef"), ([2, 0], "bcdef, gfhb -> cdegh"), ([1, 0], "cdegh, ihec -> dgi")] first_LR[isite - 1] = asnumpy( multi_tensor_contract( path1, first_LR[isite], moveaxis(self.cv_mpo[isite - 1], (1, 2), (2, 1)), self.a_oper[isite - 1], self.cv_mpo[isite - 1])) path2 = [([0, 1], "abcd, efga -> bcdefg"), ([3, 0], "bcdefg, hgib -> cdefhi"), ([2, 0], "cdefhi, jikc -> defhjk"), ([1, 0], "defhjk, lkfd -> ehjl")] path4 = [([0, 1], "ab, cdea->bcde"), ([1, 0], "bcde, fedb->cf")] second_LR[isite - 1] = asnumpy( multi_tensor_contract( path2, second_LR[isite], moveaxis(self.cv_mpo[isite - 1], (1, 2), (2, 1)), self.b_oper[isite - 1], self.cv_mpo[isite - 1], self.h_mpo[isite - 1])) third_LR[isite - 1] = asnumpy( multi_tensor_contract( path2, third_LR[isite], self.h_mpo[isite - 1], moveaxis(self.cv_mpo[isite - 1], (1, 2), (2, 1)), self.cv_mpo[isite - 1], self.h_mpo[isite - 1])) forth_LR[isite - 1] = asnumpy( multi_tensor_contract( path4, forth_LR[isite], moveaxis(self.a_ket_mpo[isite - 1], (1, 2), (2, 1)), self.cv_mpo[isite - 1])) if direction == "left": for isite in range(1, len(self.cv_mpo)): path1 = [([0, 1], "abc, adef -> bcdef"), ([2, 0], "bcdef, begh -> cdfgh"), ([1, 0], "cdfgh, cgdi -> fhi")] first_LR[isite] = asnumpy( multi_tensor_contract( path1, first_LR[isite - 1], moveaxis(self.cv_mpo[isite - 1], (1, 2), (2, 1)), self.a_oper[isite - 1], self.cv_mpo[isite - 1])) path2 = [([0, 1], "abcd, aefg -> bcdefg"), ([3, 0], "bcdefg, bfhi -> cdeghi"), ([2, 0], "cdeghi, chjk -> degijk"), ([1, 0], "degijk, djel -> gikl")] path4 = [([0, 1], "ab, acde->bcde"), ([1, 0], "bcde, bdcf->ef")] second_LR[isite] = asnumpy( multi_tensor_contract( path2, second_LR[isite - 1], moveaxis(self.cv_mpo[isite - 1], (1, 2), (2, 1)), self.b_oper[isite - 1], self.cv_mpo[isite - 1], self.h_mpo[isite - 1])) third_LR[isite] = asnumpy( multi_tensor_contract( path2, third_LR[isite - 1], self.h_mpo[isite - 1], moveaxis(self.cv_mpo[isite - 1], (1, 2), (2, 1)), self.cv_mpo[isite - 1], self.h_mpo[isite - 1])) forth_LR[isite] = asnumpy( multi_tensor_contract( path4, forth_LR[isite - 1], moveaxis(self.a_ket_mpo[isite - 1], (1, 2), (2, 1)), self.cv_mpo[isite - 1])) return [first_LR, second_LR, third_LR, forth_LR]
def optimize_cv(self, lr_group, direction, isite, num, percent=0): if self.spectratype == "abs": # quantum number restriction, |1><0| up_exciton, down_exciton = 1, 0 elif self.spectratype == "emi": # quantum number restriction, |0><1| up_exciton, down_exciton = 0, 1 nexciton = 1 first_LR, second_LR, third_LR, forth_LR = lr_group if self.method == "1site": add_list = [isite - 1] first_L = asxp(first_LR[isite - 1]) first_R = asxp(first_LR[isite]) second_L = asxp(second_LR[isite - 1]) second_R = asxp(second_LR[isite]) third_L = asxp(third_LR[isite - 1]) third_R = asxp(third_LR[isite]) forth_L = asxp(forth_LR[isite - 1]) forth_R = asxp(forth_LR[isite]) else: add_list = [isite - 2, isite - 1] first_L = asxp(first_LR[isite - 2]) first_R = asxp(first_LR[isite]) second_L = asxp(second_LR[isite - 2]) second_R = asxp(second_LR[isite]) third_L = asxp(third_LR[isite - 2]) third_R = asxp(third_LR[isite]) forth_L = asxp(forth_LR[isite - 2]) forth_R = asxp(forth_LR[isite]) xqnmat, xqnbigl, xqnbigr, xshape = \ self.construct_X_qnmat(add_list, direction) dag_qnmat, dag_qnbigl, dag_qnbigr = self.swap(xqnmat, xqnbigl, xqnbigr, direction) nonzeros = np.sum(self.condition(dag_qnmat, [down_exciton, up_exciton])) if self.method == "1site": guess = moveaxis(self.cv_mpo[isite - 1], (1, 2), (2, 1)) else: guess = tensordot(moveaxis(self.cv_mpo[isite - 2], (1, 2), (2, 1)), moveaxis(self.cv_mpo[isite - 1]), axes=(-1, 0)) guess = guess[self.condition(dag_qnmat, [down_exciton, up_exciton])].reshape( nonzeros, 1) if self.method == "1site": # define dot path path_1 = [([0, 1], "abc, adef -> bcdef"), ([2, 0], "bcdef, begh -> cdfgh"), ([1, 0], "cdfgh, fhi -> cdgi")] path_2 = [([0, 1], "abcd, aefg -> bcdefg"), ([3, 0], "bcdefg, bfhi -> cdeghi"), ([2, 0], "cdeghi, djek -> cghijk"), ([1, 0], "cghijk, gilk -> chjl")] path_4 = [([0, 1], "ab, acde -> bcde"), ([1, 0], "bcde, ef -> bcdf")] vecb = multi_tensor_contract( path_4, forth_L, moveaxis(self.a_ket_mpo[isite - 1], (1, 2), (2, 1)), forth_R) vecb = -self.eta * vecb a_oper_isite = asxp(self.a_oper[isite - 1]) b_oper_isite = asxp(self.b_oper[isite - 1]) h_mpo_isite = asxp(self.h_mpo[isite - 1]) # construct preconditioner Idt = xp.identity(h_mpo_isite.shape[1]) M1_1 = xp.einsum('aea->ae', first_L) M1_2 = xp.einsum('eccf->ecf', a_oper_isite) M1_3 = xp.einsum('dfd->df', first_R) M1_4 = xp.einsum('bb->b', Idt) path_m1 = [([0, 1], "ae,b->aeb"), ([2, 0], "aeb,ecf->abcf"), ([1, 0], "abcf, df->abcd")] pre_M1 = multi_tensor_contract(path_m1, M1_1, M1_4, M1_2, M1_3) pre_M1 = pre_M1[self.condition(dag_qnmat, [down_exciton, up_exciton])] M2_1 = xp.einsum('aeag->aeg', second_L) M2_2 = xp.einsum('eccf->ecf', b_oper_isite) M2_3 = xp.einsum('gbbh->gbh', h_mpo_isite) M2_4 = xp.einsum('dfdh->dfh', second_R) path_m2 = [([0, 1], "aeg,gbh->aebh"), ([2, 0], "aebh,ecf->abchf"), ([1, 0], "abhcf,dfh->abcd")] pre_M2 = multi_tensor_contract(path_m2, M2_1, M2_3, M2_2, M2_4) pre_M2 = pre_M2[self.condition(dag_qnmat, [down_exciton, up_exciton])] M4_1 = xp.einsum('faah->fah', third_L) M4_4 = xp.einsum('gddi->gdi', third_R) M4_5 = xp.einsum('cc->c', Idt) M4_path = [([0, 1], "fah,febg->ahebg"), ([2, 0], "ahebg,hjei->abgji"), ([1, 0], "abgji,gdi->abjd")] pre_M4 = multi_tensor_contract(M4_path, M4_1, h_mpo_isite, h_mpo_isite, M4_4) pre_M4 = xp.einsum('abbd->abd', pre_M4) pre_M4 = xp.tensordot(pre_M4, M4_5, axes=0) pre_M4 = xp.moveaxis(pre_M4, [2, 3], [3, 2])[self.condition( dag_qnmat, [down_exciton, up_exciton])] pre_M = (pre_M1 + 2 * pre_M2 + pre_M4) indices = np.array(range(nonzeros)) indptr = np.array(range(nonzeros + 1)) pre_M = scipy.sparse.csc_matrix((asnumpy(pre_M), indices, indptr), shape=(nonzeros, nonzeros)) M_x = lambda x: scipy.sparse.linalg.spsolve(pre_M, x) M = scipy.sparse.linalg.LinearOperator((nonzeros, nonzeros), M_x) count = 0 def hop(x): nonlocal count count += 1 dag_struct = asxp(self.dag2mat(xshape, x, dag_qnmat, direction)) if self.method == "1site": M1 = multi_tensor_contract(path_1, first_L, dag_struct, a_oper_isite, first_R) M2 = multi_tensor_contract(path_2, second_L, dag_struct, b_oper_isite, h_mpo_isite, second_R) M2 = xp.moveaxis(M2, (1, 2), (2, 1)) M3 = multi_tensor_contract(path_2, third_L, h_mpo_isite, dag_struct, h_mpo_isite, third_R) M3 = xp.moveaxis(M3, (1, 2), (2, 1)) cout = M1 + 2 * M2 + M3 cout = cout[self.condition(dag_qnmat, [down_exciton, up_exciton])].reshape( nonzeros, 1) return asnumpy(cout) # Matrix A and Vector b vecb = asnumpy(vecb)[self.condition( dag_qnmat, [down_exciton, up_exciton])].reshape(nonzeros, 1) mata = scipy.sparse.linalg.LinearOperator((nonzeros, nonzeros), matvec=hop) # conjugate gradient method # x, info = scipy.sparse.linalg.cg(MatA, VecB, atol=0) if num == 1: x, info = scipy.sparse.linalg.cg(mata, vecb, tol=1.e-5, maxiter=500, M=M, atol=0) else: x, info = scipy.sparse.linalg.cg(mata, vecb, tol=1.e-5, x0=guess, maxiter=500, M=M, atol=0) # logger.info(f"linear eq dim: {nonzeros}") # logger.info(f'times for hop:{count}') self.hop_time.append(count) if info != 0: logger.warning( f"cg not converged, vecb.norm:{np.linalg.norm(vecb)}") l_value = np.inner( hop(x).reshape(1, nonzeros), x.reshape(1, nonzeros)) - \ 2 * np.inner(vecb.reshape(1, nonzeros), x.reshape(1, nonzeros)) x = self.dag2mat(xshape, x, dag_qnmat, direction) if self.method == "1site": x = np.moveaxis(x, [1, 2], [2, 1]) x, xdim, xqn, compx = self.x_svd(x, xqnbigl, xqnbigr, nexciton, direction, percent=percent) if self.method == "1site": self.cv_mpo[isite - 1] = x if direction == "left": if isite != 1: self.cv_mpo[isite - 2] = \ tensordot(self.cv_mpo[isite - 2], compx, axes=(-1, 0)) self.cv_mpo.qn[isite - 1] = xqn else: self.cv_mpo[isite - 1] = \ tensordot(compx, self.cv_mpo[isite - 1], axes=(-1, 0)) elif direction == "right": if isite != len(self.cv_mpo): self.cv_mpo[isite] = \ tensordot(compx, self.cv_mpo[isite], axes=(-1, 0)) self.cv_mpo.qn[isite] = xqn else: self.cv_mpo[isite - 1] = \ tensordot(self.cv_mpo[isite - 1], compx, axes=(-1, 0)) else: if direction == "left": self.cv_mpo[isite - 2] = compx self.cv_mpo[isite - 1] = x else: self.cv_mpo[isite - 2] = x self.cv_mpo[isite - 1] = compx self.cv_mpo.qn[isite - 1] = xqn return l_value[0][0]
def addone(self, intensor, MPS, MPSconj, MPO, isite, domain): """ add one MPO/MPS(MPO) site _ _ | | | | S-S- | S-|-S- O-O- or | O-|-O- (the ancillary bond is traced) S-S- | S-|-S- |_| |_| """ assert domain in ["L", "R", "l", "r"] if domain == "L" or domain == "l": assert intensor.shape[0] == MPSconj[isite].shape[0] assert intensor.shape[1] == MPO[isite].shape[0] assert intensor.shape[2] == MPS[isite].shape[0] """ l S-a-S-f O-a-O-f d d O-b-O-g or O-b-O-g e e S-c-S-h O-c-O-h l """ if MPS[isite].ndim == 3: path = [ ([0, 1], "abc, adf -> bcdf"), ([2, 0], "bcdf, bdeg -> cfeg"), ([1, 0], "cfeg, ceh -> fgh"), ] elif MPS[isite].ndim == 4: path = [ ([0, 1], "abc, adlf -> bcdlf"), ([2, 0], "bcdlf, bdeg -> clfeg"), ([1, 0], "clfeg, celh -> fgh"), ] else: raise ValueError("MPS ndim at %d is not 3 or 4, got %s" % (isite, MPS[isite].ndim)) outtensor = multi_tensor_contract(path, intensor, MPSconj[isite], MPO[isite], MPS[isite]) else: assert intensor.shape[0] == MPSconj[isite].shape[-1] assert intensor.shape[1] == MPO[isite].shape[-1] assert intensor.shape[2] == MPS[isite].shape[-1] """ l -f-S-a-S -f-S-a-S d d -g-O-b-O or -g-O-b-O e e -h-S-c-S -h-S-c-S l """ if MPS[isite].ndim == 3: path = [ ([0, 1], "fda, abc -> fdbc"), ([2, 0], "fdbc, gdeb -> fcge"), ([1, 0], "fcge, hec -> fgh"), ] elif MPS[isite].ndim == 4: path = [ ([0, 1], "fdla, abc -> fdlbc"), ([2, 0], "fdlbc, gdeb -> flcge"), ([1, 0], "flcge, helc -> fgh"), ] else: raise ValueError("MPS ndim at %d is not 3 or 4, got %s" % (isite, MPS[isite].ndim)) outtensor = multi_tensor_contract(path, MPSconj[isite], intensor, MPO[isite], MPS[isite]) return outtensor
def optimize_mps(mps: Mps, mpo: Mpo, omega: float = None) -> Tuple[List, Mps]: r""" DMRG ground state algorithm and state-averaged excited states algorithm Parameters ---------- mps : renormalizer.mps.Mps initial guess of mps mpo : renormalizer.mps.Mpo mpo of Hamiltonian omega: float, optional target the eigenpair near omega with special variational function :math:(\hat{H}-\omega)^2. Default is `None`. Returns ------- energy : list list of energy of each marco sweep. :math:`[e_0, e_0, \cdots, e_0]` if ``nroots=1``. :math:`[[e_0, \cdots, e_n], \dots, [e_0, \cdots, e_n]]` if ``nroots=n``. mps : renormalizer.mps.Mps optimized ground state mps. The input mps is overwritten and could not be used anymore. See Also -------- renormalizer.utils.configs.OptimizeConfig : The optimization configuration. """ algo = mps.optimize_config.algo method = mps.optimize_config.method procedure = mps.optimize_config.procedure inverse = mps.optimize_config.inverse nroots = mps.optimize_config.nroots assert method in ["2site", "1site"] logger.info(f"optimization method: {method}") logger.info(f"e_rtol: {mps.optimize_config.e_rtol}") logger.info(f"e_atol: {mps.optimize_config.e_atol}") if USE_GPU: oe_backend = "cupy" else: oe_backend = "numpy" # ensure that mps is left or right-canonical # TODO: start from a mix-canonical MPS if mps.is_left_canon: mps.ensure_right_canon() env = "R" else: mps.ensure_left_canon() env = "L" # in state-averged calculation, contains C of each state for better initial # guess averaged_ms = None # the index of active site of the returned mps res_mps_idx = None # target eigenstate close to omega with (H-omega)^2 # construct the environment matrix if omega is not None: identity = Mpo.identity(mpo.model) mpo = mpo.add(identity.scale(-omega)) environ = Environ(mps, [mpo, mpo], env) else: environ = Environ(mps, mpo, env) macro_iteration_result = [] converged = False for isweep, (mmax, percent) in enumerate(procedure): logger.debug(f"isweep: {isweep}") logger.debug(f"mmax, percent: {mmax}, {percent}") logger.debug(f"{mps}") micro_iteration_result = [] for imps in mps.iter_idx_list(full=True): if method == "2site" and \ ((mps.to_right and imps == mps.site_num-1) or ((not mps.to_right) and imps == 0)): break if mps.to_right: lmethod, rmethod = "System", "Enviro" else: lmethod, rmethod = "Enviro", "System" if method == "1site": lidx = imps - 1 cidx = [imps] ridx = imps + 1 elif method == "2site": if mps.to_right: lidx = imps - 1 cidx = [imps, imps + 1] ridx = imps + 2 else: lidx = imps - 2 cidx = [imps - 1, imps] # center site ridx = imps + 1 else: assert False logger.debug(f"optimize site: {cidx}") if omega is None: operator = mpo else: operator = [mpo, mpo] ltensor = environ.GetLR("L", lidx, mps, operator, itensor=None, method=lmethod) rtensor = environ.GetLR("R", ridx, mps, operator, itensor=None, method=rmethod) # get the quantum number pattern qnbigl, qnbigr, qnmat = mps._get_big_qn(cidx) cshape = qnmat.shape nonzeros = np.sum(qnmat == mps.qntot) logger.debug(f"Hmat dim: {nonzeros}") # center mo cmo = [asxp(mpo[idx]) for idx in cidx] if qnmat.size > 1000 and algo != "direct": # iterative algorithm # diagonal elements of H if omega is None: tmp_ltensor = xp.einsum("aba -> ba", ltensor) tmp_cmo0 = xp.einsum("abbc -> abc", cmo[0]) tmp_rtensor = xp.einsum("aba -> ba", rtensor) if method == "1site": # S-a c f-S # O-b-O-g-O # S-a c f-S path = [([0, 1], "ba, bcg -> acg"), ([1, 0], "acg, gf -> acf")] hdiag = multi_tensor_contract( path, tmp_ltensor, tmp_cmo0, tmp_rtensor)[(qnmat == mps.qntot)] else: # S-a c d f-S # O-b-O-e-O-g-O # S-a c d f-S tmp_cmo1 = xp.einsum("abbc -> abc", cmo[1]) path = [ ([0, 1], "ba, bce -> ace"), ([0, 1], "edg, gf -> edf"), ([0, 1], "ace, edf -> acdf"), ] hdiag = multi_tensor_contract( path, tmp_ltensor, tmp_cmo0, tmp_cmo1, tmp_rtensor)[(qnmat == mps.qntot)] else: if method == "1site": # S-a d h-S # O-b-O-f-O # | e | # O-c-O-g-O # S-a d h-S hdiag = oe.contract( "abca, bdef, cedg, hfgh -> adh", ltensor, cmo[0], cmo[0], rtensor, backend=oe_backend)[(qnmat == mps.qntot)] else: # S-a d h l-S # O-b-O-f-O-j-O # | e i | # O-c-O-g-O-k-O # S-a d h l-S hdiag = oe.contract( "abca, bdef, cedg, fhij, gihk, ljkl -> adhl", ltensor, cmo[0], cmo[0], cmo[1], cmo[1], rtensor, backend=oe_backend)[(qnmat == mps.qntot)] hdiag = asnumpy(hdiag * inverse) # initial guess if method == "1site": # initial guess b-S-c # a if nroots == 1: cguess = [asnumpy(mps[cidx[0]])[qnmat == mps.qntot]] else: cguess = [] if averaged_ms is not None: for ms in averaged_ms: cguess.append(asnumpy(ms)[qnmat == mps.qntot]) else: # initial guess b-S-c-S-e # a d if nroots == 1: cguess = [ asnumpy( tensordot(mps[cidx[0]], mps[cidx[1]], axes=1)[qnmat == mps.qntot]) ] else: cguess = [] if averaged_ms is not None: for ms in averaged_ms: if mps.to_right: cguess.append( asnumpy( tensordot( ms, mps[cidx[1]], axes=1)[qnmat == mps.qntot])) else: cguess.append( asnumpy( tensordot( mps[cidx[0]], ms, axes=1)[qnmat == mps.qntot])) if omega is not None: if method == "1site": # S-a e j-S # O-b-O-g-O # | f | # O-c-O-i-O # S-d h k-S expr = oe.contract_expression( "abcd, befg, cfhi, jgik, aej -> dhk", ltensor, cmo[0], cmo[0], rtensor, cshape, constants=[0, 1, 2, 3]) else: # S-a e j o-S # O-b-O-g-O-l-O # | f k | # O-c-O-i-O-n-O # S-d h m p-S expr = oe.contract_expression( "abcd, befg, cfhi, gjkl, ikmn, olnp, aejo -> dhmp", ltensor, cmo[0], cmo[0], cmo[1], cmo[1], rtensor, cshape, constants=[0, 1, 2, 3, 4, 5]) count = 0 def hop(x): nonlocal count count += 1 clist = [] if x.ndim == 1: clist.append(x) else: for icol in range(x.shape[1]): clist.append(x[:, icol]) res = [] for c in clist: # convert c to initial structure according to qn pattern cstruct = asxp(cvec2cmat(cshape, c, qnmat, mps.qntot)) if omega is None: 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, cstruct, cmo[0], rtensor) 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, cstruct, cmo[0], cmo[1], rtensor, ) else: cout = expr(cstruct, backend=oe_backend) # convert structure c to 1d according to qn res.append(asnumpy(cout)[qnmat == mps.qntot]) if len(res) == 1: return inverse * res[0] else: return inverse * np.stack(res, axis=1) if len(cguess) < nroots: cguess.extend([ np.random.random([nonzeros]) - 0.5 for i in range(len(cguess), nroots) ]) if algo == "davidson": precond = lambda x, e, *args: x / (hdiag - e + 1e-4) e, c = davidson(hop, cguess, precond, max_cycle=100, nroots=nroots, max_memory=64000) # if one root, davidson return e as np.float #elif algo == "arpack": # # scipy arpack solver : much slower than pyscf/davidson # A = scipy.sparse.linalg.LinearOperator((nonzeros,nonzeros), matvec=hop) # e, c = scipy.sparse.linalg.eigsh(A, k=nroots, which="SA", v0=cguess) # # scipy return numpy.array # if nroots == 1: # e = e[0] #elif algo == "lobpcg": # precond = lambda x: scipy.sparse.diags(1/(hdiag+1e-4)) @ x # A = scipy.sparse.linalg.LinearOperator((nonzeros,nonzeros), # matvec=hop, matmat=hop) # M = scipy.sparse.linalg.LinearOperator((nonzeros,nonzeros), # matvec=precond, matmat=hop) # e, c = scipy.sparse.linalg.lobpcg(A, np.array(cguess).T, # M=M, largest=False) elif algo == "primme": precond = lambda x: scipy.sparse.diags(1 / (hdiag + 1e-4)) @ x A = scipy.sparse.linalg.LinearOperator( (nonzeros, nonzeros), matvec=hop, matmat=hop) M = scipy.sparse.linalg.LinearOperator( (nonzeros, nonzeros), matvec=precond, matmat=hop) e, c = primme.eigsh(A, k=min(nroots, nonzeros), which="SA", v0=np.array(cguess).T, OPinv=M, method="PRIMME_DYNAMIC", tol=1e-6) else: assert False logger.debug(f"use {algo}, HC hops: {count}") else: logger.debug(f"use direct eigensolver") # direct algorithm if omega is None: if method == "1site": # S-a l-S # d # O-b-O-f-O # e # S-c k-S ham = oe.contract("abc,bdef,lfk->adlcek", ltensor, cmo[0], rtensor, backend=oe_backend) ham = ham[:, :, :, qnmat == mps.qntot][ qnmat == mps.qntot, :] * inverse else: # S-a l-S # d g # O-b-O-f-O-j-O # e h # S-c k-S ham = oe.contract("abc,bdef,fghj,ljk->adglcehk", ltensor, cmo[0], cmo[1], rtensor) ham = ham[:, :, :, :, qnmat == mps.qntot][ qnmat == mps.qntot, :] * inverse else: if method == "1site": # S-a e j-S # O-b-O-g-O # | f | # O-c-O-i-O # S-d h k-S ham = oe.contract("abcd, befg, cfhi, jgik -> aejdhk", ltensor, cmo[0], cmo[0], rtensor) ham = ham[:, :, :, qnmat == mps.qntot][ qnmat == mps.qntot, :] * inverse else: # S-a e j o-S # O-b-O-g-O-l-O # | f k | # O-c-O-i-O-n-O # S-d h m p-S ham = oe.contract( "abcd, befg, cfhi, gjkl, ikmn, olnp -> aejodhmp", ltensor, cmo[0], cmo[0], cmo[1], cmo[1], rtensor) ham = ham[:, :, :, :, qnmat == mps.qntot][ qnmat == mps.qntot, :] * inverse w, v = scipy.linalg.eigh(asnumpy(ham)) if nroots == 1: e = w[0] c = v[:, 0] else: e = w[:nroots] c = [ v[:, iroot] for iroot in range(min(nroots, v.shape[1])) ] # if multi roots, both davidson and primme return np.ndarray if nroots > 1: e = e.tolist() logger.debug(f"energy: {e}") micro_iteration_result.append(e) cstruct = cvec2cmat(cshape, c, qnmat, mps.qntot, nroots=nroots) # store the "optimal" mps (usually in the middle of each sweep) if res_mps_idx is not None and res_mps_idx == imps: if nroots == 1: res_mps = mps.copy() res_mps._update_mps(cstruct, cidx, qnbigl, qnbigr, mmax, percent) else: res_mps = [mps.copy() for i in range(len(cstruct))] for iroot in range(len(cstruct)): res_mps[iroot]._update_mps(cstruct[iroot], cidx, qnbigl, qnbigr, mmax, percent) averaged_ms = mps._update_mps(cstruct, cidx, qnbigl, qnbigr, mmax, percent) mps._switch_direction() res_mps_idx = micro_iteration_result.index(min(micro_iteration_result)) macro_iteration_result.append(micro_iteration_result[res_mps_idx]) # check if convergence if isweep > 0 and percent == 0: v1, v2 = sorted(macro_iteration_result)[:2] if np.allclose(v1, v2, rtol=mps.optimize_config.e_rtol, atol=mps.optimize_config.e_atol): converged = True break logger.debug( f"{isweep+1} sweeps are finished, lowest energy = {sorted(macro_iteration_result)[0]}" ) if converged: logger.info("DMRG is converged!") else: logger.warning("DMRG is not converged! Please increase the procedure!") logger.info( f"The lowest two energies: {sorted(macro_iteration_result)[:2]}.") # remove the redundant basis near the edge if nroots == 1: res_mps = res_mps.normalize().ensure_left_canon().canonicalise() logger.info(f"{res_mps}") else: res_mps = [ mp.normalize().ensure_left_canon().canonicalise() for mp in res_mps ] logger.info(f"{res_mps[0]}") return macro_iteration_result, res_mps
def variational_compress(self, mpo=None, guess=None): r"""Variational compress an mps/mpdm/mpo Parameters ---------- mpo : renormalizer.mps.Mpo, optional Default is ``None``. if mpo is not ``None``, the returned mps is an approximation of ``mpo @ self`` guess : renormalizer.mps.MatrixProduct, optional Initial guess of compressed mps/mpdm/mpo. Default is ``None``. Note ---- the variational compress related configurations is defined in ``self`` if ``guess=None``, otherwise is defined in ``guess`` Returns ------- mp : renormalizer.mps.MatrixProduct a new compressed mps/mpdm/mpo, ``self`` is not overwritten. ``guess`` is overwritten. """ if mpo is None: logger.info( "Recommend to use svd to compress a single mps/mpo/mpdm.") raise NotImplementedError if guess is None: # a minimal representation of self and mpo compressed_mpo = mpo.copy().canonicalise().compress( temp_m_trunc=self.compress_config.vguess_m[0]) compressed_mps = self.copy().canonicalise().compress( temp_m_trunc=self.compress_config.vguess_m[1]) # the attributes of guess would be the same as self guess = compressed_mpo.apply(compressed_mps) mps = guess mps.ensure_left_canon() logger.info(f"initial guess bond dims: {mps.bond_dims}") procedure = mps.compress_config.vprocedure method = mps.compress_config.vmethod environ = Environ(self, mpo, "L", mps_conj=mps.conj()) converged = False for isweep, (mmax, percent) in enumerate(procedure): logger.debug(f"isweep: {isweep}") logger.debug(f"mmax, percent: {mmax}, {percent}") logger.debug(f"mps bond dims: {mps.bond_dims}") for imps in mps.iter_idx_list(full=True): if method == "2site" and \ ((mps.to_right and imps == mps.site_num-1) or ((not mps.to_right) and imps == 0)): break if mps.to_right: lmethod, rmethod = "System", "Enviro" else: lmethod, rmethod = "Enviro", "System" if method == "1site": lidx = imps - 1 cidx = [imps] ridx = imps + 1 elif method == "2site": if mps.to_right: lidx = imps - 1 cidx = [imps, imps + 1] ridx = imps + 2 else: lidx = imps - 2 cidx = [imps - 1, imps] # center site ridx = imps + 1 else: assert False logger.debug(f"optimize site: {cidx}") ltensor = environ.GetLR("L", lidx, self, mpo, itensor=None, method=lmethod, mps_conj=mps.conj()) rtensor = environ.GetLR("R", ridx, self, mpo, itensor=None, method=rmethod, mps_conj=mps.conj()) # get the quantum number pattern qnbigl, qnbigr, qnmat = mps._get_big_qn(cidx) # center mo cmo = [asxp(mpo[idx]) for idx in cidx] cms = [asxp(self[idx]) for idx in cidx] if method == "1site": if cms[0].ndim == 3: # S-a l-S # d # O-b-O-f-O # e # S-c k-S path = [ ([0, 1], "abc, cek -> abek"), ([2, 0], "abek, bdef -> akdf"), ([1, 0], "akdf, lfk -> adl"), ] elif cms[0].ndim == 4: # S-a l-S # d # O-b-O-f-O # e # S-c k-S # g path = [ ([0, 2], "abc, bdef -> acdef"), ([2, 0], "acdef, cegk -> adfgk"), ([1, 0], "adfgk, lfk -> adgl"), ] cout = multi_tensor_contract(path, ltensor, cms[0], cmo[0], rtensor) else: if USE_GPU: oe_backend = "cupy" else: oe_backend = "numpy" if cms[0].ndim == 3: # S-a l-S # d g # O-b-O-f-O-j-O # e h # S-c m k-S cout = oe.contract( "abc, bdef, fghj, ljk, cem, mhk -> adgl", ltensor, cmo[0], cmo[1], rtensor, cms[0], cms[1], backend=oe_backend) elif cms[0].ndim == 4: # S-a l-S # d g # O-b-O-f-O-j-O # e h # S-c m k-S # n p cout = oe.contract( "abc, bdef, fghj, ljk, cenm, mhpk -> adngpl", ltensor, cmo[0], cmo[1], rtensor, cms[0], cms[1], backend=oe_backend) # clean up the elements which do not meet the qn requirements cout[qnmat != mps.qntot] = 0 mps._update_mps(cout, cidx, qnbigl, qnbigr, mmax, percent) mps._switch_direction() # check if convergence if isweep > 0 and percent == 0 and \ mps.distance(mps_old) / np.sqrt(mps.dot(mps.conj()).real) < mps.compress_config.vrtol: converged = True break mps_old = mps.copy() if converged: logger.info("Variational compress is converged!") else: logger.warning( "Variational compress is not converged! Please increase the procedure!" ) # remove the redundant bond dimension near the boundary of the MPS mps.canonicalise() logger.info(f"{mps}") return mps
def hop(ms: xp.ndarray): return multi_tensor_contract(path, ltensor, mo, ms, rtensor)
def contract_one_site(environ, ms, mo, domain, ms_conj=None): """ contract one mpo/mps(mpdm) site _ _ | | | | S-S- | S-|-S- O-O- or | O-|-O- (the ancillary bond is traced) S-S- | S-|-S- |_| |_| """ assert domain in ["L", "R"] if isinstance(ms, Matrix): ms = ms.array if isinstance(mo, Matrix): mo = mo.array if ms_conj is None: ms_conj = ms.conj() if domain == "L": assert environ.shape[0] == ms_conj.shape[0] assert environ.shape[1] == mo.shape[0] assert environ.shape[2] == ms.shape[0] """ l S-a-S-f O-a-O-f d d O-b-O-g or O-b-O-g e e S-c-S-h O-c-O-h l """ if ms.ndim == 3: path = [ ([0, 1], "abc, adf -> bcdf"), ([2, 0], "bcdf, bdeg -> cfeg"), ([1, 0], "cfeg, ceh -> fgh"), ] elif ms.ndim == 4: path = [ ([0, 1], "abc, adlf -> bcdlf"), ([2, 0], "bcdlf, bdeg -> clfeg"), ([1, 0], "clfeg, celh -> fgh"), ] else: raise ValueError(f"MPS ndim is not 3 or 4, got {ms.ndim}") outtensor = multi_tensor_contract(path, environ, ms_conj, mo, ms) else: assert environ.shape[0] == ms_conj.shape[-1] assert environ.shape[1] == mo.shape[-1] assert environ.shape[2] == ms.shape[-1] """ l -f-S-a-S -f-S-a-S d d -g-O-b-O or -g-O-b-O e e -h-S-c-S -h-S-c-S l """ if ms.ndim == 3: path = [ ([0, 1], "fda, abc -> fdbc"), ([2, 0], "fdbc, gdeb -> fcge"), ([1, 0], "fcge, hec -> fgh"), ] elif ms.ndim == 4: path = [ ([0, 1], "fdla, abc -> fdlbc"), ([2, 0], "fdlbc, gdeb -> flcge"), ([1, 0], "flcge, helc -> fgh"), ] else: raise ValueError(f"MPS ndim is not 3 or 4, got {ms.ndim}") outtensor = multi_tensor_contract(path, ms_conj, environ, mo, ms) return outtensor
def optimize_cv(self, lr_group, isite, percent=0.0): # depending on the spectratype, to restrict the exction first_LR = lr_group[0] second_LR = lr_group[1] constrain_qn = self.cv_mps.qntot # this function aims at solving the work equation of ZT CV-DMRG # L = <CV|op_a|CV>+2\eta<op_b|CV>, take a derivative to local CV # S-a-S-e-S S-a-S-d-S # | d | | | | # O-b-O-g-O * CV[isite-1] = -\eta | c | # | f | | | | # S-c- -h-S S-b- -e-S # note to be a_mat * x = vec_b # the environment matrix if self.method == "1site": cidx = [isite - 1] first_L = asxp(first_LR[isite - 1]) first_R = asxp(first_LR[isite]) second_L = asxp(second_LR[isite - 1]) second_R = asxp(second_LR[isite]) else: cidx = [isite - 2, isite - 1] first_L = asxp(first_LR[isite - 2]) first_R = asxp(first_LR[isite]) second_L = asxp(second_LR[isite - 2]) second_R = asxp(second_LR[isite]) # this part just be similar with ground state calculation qnbigl, qnbigr, qnmat = self.cv_mps._get_big_qn(cidx) xshape = qnmat.shape nonzeros = int(np.sum(qnmat == constrain_qn)) if self.method == '1site': guess = self.cv_mps[isite - 1][qnmat == constrain_qn] path_b = [([0, 1], "ab, acd->bcd"), ([1, 0], "bcd, de->bce")] vec_b = multi_tensor_contract( path_b, second_L, self.b_mps[isite - 1], second_R )[qnmat == constrain_qn] else: guess = tensordot( self.cv_mps[isite - 2], self.cv_mps[isite - 1], axes=(-1, 0) )[qnmat == constrain_qn] path_b = [([0, 1], "ab, acd->bcd"), ([2, 0], "bcd, def->bcef"), ([1, 0], "bcef, fg->bceg")] vec_b = multi_tensor_contract( path_b, second_L, self.b_mps[isite - 2], self.b_mps[isite - 1], second_R )[qnmat == constrain_qn] if self.method == "2site": a_oper_isite2 = asxp(self.a_oper[isite - 2]) else: a_oper_isite2 = None a_oper_isite1 = asxp(self.a_oper[isite - 1]) # use the diagonal part of mat_a to construct the preconditinoner # for linear solver part_l = xp.einsum('abca->abc', first_L) part_r = xp.einsum('hfgh->hfg', first_R) if self.method == "1site": # S-a d h-S # O-b -O- f-O # | e | # O-c -O- g-O # S-a i h-S path_pre = [([0, 1], "abc, bdef -> acdef"), ([1, 0], "acdef, ceig -> adfig")] a_diag = multi_tensor_contract(path_pre, part_l, a_oper_isite1, a_oper_isite1) a_diag = xp.einsum("adfdg -> adfg", a_diag) a_diag = xp.tensordot(a_diag, part_r, axes=([2, 3], [1, 2]))[qnmat == constrain_qn] else: # S-a d k h-S # O-b -O- j -O- f-O # | e l | # O-c -O- m -O- g-O # S-a i n h-S # first left half, second right half, last contraction path_pre = [([0, 1], "abc, bdej -> acdej"), ([1, 0], "acdej, ceim -> adjim")] a_diagl = multi_tensor_contract(path_pre, part_l, a_oper_isite2, a_oper_isite2) a_diagl = xp.einsum("adjdm -> adjm", a_diagl) path_pre = [([0, 1], "hfg, jklf -> hgjkl"), ([1, 0], "hgjkl, mlng -> hjkmn")] a_diagr = multi_tensor_contract(path_pre, part_r, a_oper_isite1, a_oper_isite1) a_diagr = xp.einsum("hjkmk -> khjm", a_diagr) a_diag = xp.tensordot( a_diagl, a_diagr, axes=([2, 3], [2, 3]))[qnmat == constrain_qn] a_diag = asnumpy(a_diag + xp.ones(nonzeros) * self.eta**2) M_x = lambda x: x / a_diag pre_M = scipy.sparse.linalg.LinearOperator((nonzeros, nonzeros), M_x) count = 0 # cache oe path if self.method == "2site": expr = oe.contract_expression( "abcd, befh, cfgi, hjkn, iklo, mnop, dglp -> aejm", first_L, a_oper_isite2, a_oper_isite2, a_oper_isite1, a_oper_isite1, first_R, xshape, constants=[0, 1, 2, 3, 4, 5]) def hop(c): nonlocal count count += 1 xstruct = asxp(cvec2cmat(xshape, c, qnmat, constrain_qn)) if self.method == "1site": path_a = [([0, 1], "abcd, aef->bcdef"), ([3, 0], "bcdef, begh->cdfgh"), ([2, 0], "cdfgh, cgij->dfhij"), ([1, 0], "dfhij, fhjk->dik")] ax1 = multi_tensor_contract(path_a, first_L, xstruct, a_oper_isite1, a_oper_isite1, first_R) else: # opt_einsum v3.2.1 is not bad, ~10% faster than the hand-design # contraction path for this complicated cases and consumes a little bit less memory # this is the only place in renormalizer we use opt_einsum now. # we keep it here just for a demo. # ax1 = oe.contract("abcd, befh, cfgi, hjkn, iklo, mnop, dglp -> aejm", # first_L, a_oper_isite2, a_oper_isite2, a_oper_isite1, # a_oper_isite1, first_R, xstruct) if USE_GPU: oe_backend = "cupy" else: oe_backend = "numpy" ax1 = expr(xstruct, backend=oe_backend) #print(oe.contract_path("abcd, befh, cfgi, hjkn, iklo, mnop, dglp -> aejm", # first_L, a_oper_isite2, a_oper_isite2, a_oper_isite1, # a_oper_isite1, first_R, xstruct)) #path_a = [([0, 1], "abcd, aefg->bcdefg"), # ([5, 0], "bcdefg, behi->cdfghi"), # ([4, 0], "cdfghi, ifjk->cdghjk"), # ([3, 0], "cdghjk, chlm->dgjklm"), # ([2, 0], "dgjklm, mjno->dgklno"), # ([1, 0], "dgklno, gkop->dlnp")] #ax1 = multi_tensor_contract(path_a, first_L, xstruct, # a_oper_isite2, a_oper_isite1, # a_oper_isite2, a_oper_isite1, # first_R) ax = ax1 + xstruct * self.eta**2 cout = ax[qnmat == constrain_qn] return asnumpy(cout) mat_a = scipy.sparse.linalg.LinearOperator((nonzeros, nonzeros), matvec=hop) x, info = scipy.sparse.linalg.cg(mat_a, asnumpy(vec_b), tol=1.e-5, x0=asnumpy(guess), M=pre_M, atol=0) self.hop_time.append(count) if info != 0: logger.info(f"iteration solver not converged") # the value of the functional L l_value = xp.dot(asxp(hop(x)), asxp(x)) - 2 * xp.dot(vec_b, asxp(x)) xstruct = cvec2cmat(xshape, x, qnmat, constrain_qn) self.cv_mps._update_mps(xstruct, cidx, qnbigl, qnbigr, self.m_max, percent) return float(l_value)