def energy_2x1_1x2(self, state, env): r""" :param state: wavefunction :param env: CTM environment :type state: IPEPS :type env: ENV :return: energy per site :rtype: float We assume iPEPS with 1x2 unit cell containing two tensors A, B simple PBC tiling:: A Au A Au Bu B Bu B A Au A Au Bu B Bu B where unitary "u" operates on every site of "odd" sublattice to realize AFM correlations. Taking the reduced density matrix :math:`\rho_{2x1}` (:math:`\rho_{1x2}`) of 2x1 (1x2) cluster given by :py:func:`rdm.rdm2x1` (:py:func:`rdm.rdm1x2`) with indexing of sites as follows :math:`s_0,s_1;s'_0,s'_1` for both types of density matrices:: rdm2x1 rdm1x2 s0--s1 s0 | s1 and without assuming any symmetry on the indices of individual tensors a following set of terms has to be evaluated in order to compute energy-per-site:: 0 0 0 1--A--3 1--A--3 1--A--3 2 2 2 0 0 0 1--B--3 1--B--3 1--B--3 2 2 2 A B 0 0 2 2 1--A--3 1--A--3 A--3 1--A, 0 0 2 2 , terms B--3 1--B, and B, A, """ energy = 0. for coord, site in state.sites.items(): # for coord in [(0,0),(0,1),(1,0),(1,1)]: rdm2x1 = rdm.rdm2x1(coord, state, env) rdm1x2 = rdm.rdm1x2(coord, state, env) ss = torch.einsum('ijab,ijab', rdm2x1, self.h2_rot) energy += ss if coord[1] % 2 == 0: ss = torch.einsum('ijab,ijab', rdm1x2, self.h2_rot) else: ss = torch.einsum('ijab,jiba', rdm1x2, self.alpha * self.h2_rot) energy += ss # return energy-per-site energy_per_site = energy / len(state.sites.items()) return energy_per_site
def eval_obs(self,state,env): obs= dict() with torch.no_grad(): for coord,site in state.sites.items(): rdm1x1 = rdm.rdm1x1(coord,state,env) for label,op in self.obs_ops.items(): obs[str(coord)+"|"+label] = torch.trace(rdm1x1@op) obs= dict({"avg_m": 0.}) with torch.no_grad(): for coord,site in state.sites.items(): rdm1x1 = rdm.rdm1x1(coord,state,env) for label,op in self.obs_ops.items(): obs[f"{label}{coord}"]= torch.trace(rdm1x1@op) obs[f"m{coord}"]= sqrt(abs(obs[f"sz{coord}"]**2 + obs[f"sp{coord}"]*obs[f"sm{coord}"])) obs["avg_m"] += obs[f"m{coord}"] obs["avg_m"]= obs["avg_m"]/len(state.sites.keys()) for coord,site in state.sites.items(): rdm2x1 = rdm.rdm2x1(coord,state,env) rdm1x2 = rdm.rdm1x2(coord,state,env) obs[f"SS2x1{coord}"]= torch.einsum('ijab,ijab',rdm2x1,self.SS) obs[f"SS1x2{coord}"]= torch.einsum('ijab,ijab',rdm1x2,self.SS) # prepare list with labels and values obs_labels=["avg_m"]+[f"m{coord}" for coord in state.sites.keys()]\ +[f"{lc[1]}{lc[0]}" for lc in list(itertools.product(state.sites.keys(), self.obs_ops.keys()))] obs_labels += [f"SS2x1{coord}" for coord in state.sites.keys()] obs_labels += [f"SS1x2{coord}" for coord in state.sites.keys()] obs_values=[obs[label] for label in obs_labels] return obs_values, obs_labels
def ctmrg_conv_f(state, env, history, ctm_args=cfg.ctm_args): with torch.no_grad(): if not history: history = dict({"log": []}) dist = float('inf') list_rdm = [] for coord, site in state.sites.items(): rdm2x1 = rdm.rdm2x1(coord, state, env) rdm1x2 = rdm.rdm1x2(coord, state, env) list_rdm.extend([rdm2x1, rdm1x2]) # compute observables e_curr = model.energy_2x1_1x2(state, env) obs_values, obs_labels = model.eval_obs(state, env) print(", ".join([f"{len(history['log'])}", f"{e_curr}"] + [f"{v}" for v in obs_values])) if len(history["log"]) > 1: dist = 0. for i in range(len(list_rdm)): dist += torch.dist(list_rdm[i], history["rdm"][i], p=2).item() history["rdm"] = list_rdm history["log"].append(dist) if dist < ctm_args.ctm_conv_tol: log.info({ "history_length": len(history['log']), "history": history['log'] }) return True, history return False, history
def ctmrg_conv_f(state, env, history, ctm_args=cfg.ctm_args): with torch.no_grad(): if not history: history = dict({"log": []}) dist = float('inf') list_rdm = [] for coord, site in state.sites.items(): rdm2x1 = rdm.rdm2x1(coord, state, env) rdm1x2 = rdm.rdm1x2(coord, state, env) list_rdm.extend([rdm2x1, rdm1x2]) if len(history["log"]) > 1: dist = 0. for i in range(len(list_rdm)): dist += torch.dist(list_rdm[i], history["rdm"][i], p=2).item() history["rdm"] = list_rdm history["log"].append(dist) if dist < ctm_args.ctm_conv_tol: log.info({ "history_length": len(history['log']), "history": history['log'] }) return True, history return False, history
def energy_2x1_1x2(self, state, env): r""" :param state: wavefunction :param env: CTM environment :type state: IPEPS :type env: ENV :return: energy per site :rtype: float We assume iPEPS with 2x2 unit cell containing four tensors A, B, C, and D with simple PBC tiling:: A B A B C D C D A B A B C D C D Taking the reduced density matrix :math:`\rho_{2x1}` (:math:`\rho_{1x2}`) of 2x1 (1x2) cluster given by :py:func:`rdm.rdm2x1` (:py:func:`rdm.rdm1x2`) with indexing of sites as follows :math:`s_0,s_1;s'_0,s'_1` for both types of density matrices:: rdm2x1 rdm1x2 s0--s1 s0 | s1 and without assuming any symmetry on the indices of individual tensors a following set of terms has to be evaluated in order to compute energy-per-site:: 0 0 0 1--A--3 1--B--3 1--A--3 2 2 2 0 0 0 1--C--3 1--D--3 1--C--3 2 2 2 A--3 1--B, A B C D 0 0 B--3 1--A, 2 2 2 2 1--A--3 1--B--3 C--3 1--D, 0 0 0 0 2 2 , terms D--3 1--C, and C, D, A, B """ energy = 0. for coord, site in state.sites.items(): rdm2x1 = rdm.rdm2x1(coord, state, env) rdm1x2 = rdm.rdm1x2(coord, state, env) ss = einsum('ijab,ijab', rdm2x1, self.h2) energy += ss if coord[1] % 2 == 0: ss = einsum('ijab,ijab', rdm1x2, self.h2) else: ss = einsum('ijab,ijab', rdm1x2, self.alpha * self.h2) energy += ss # return energy-per-site energy_per_site = energy / len(state.sites.items()) energy_per_site = _cast_to_real(energy_per_site) return energy_per_site
def energy_2x1_1x2(self, state, env): energy = 0. for coord, site in state.sites.items(): rdm2x1 = rdm.rdm2x1(coord, state, env) rdm1x2 = rdm.rdm1x2(coord, state, env) energy += torch.einsum('ijab,ijab', rdm2x1, self.h2) energy += torch.einsum('ijab,ijab', rdm1x2, self.h2) energy_per_site = energy / len(state.sites.items()) return energy_per_site
def eval_obs(self, state, env): r""" :param state: wavefunction :param env: CTM environment :type state: IPEPS :type env: ENV :return: expectation values of observables, labels of observables :rtype: list[float], list[str] Computes the following observables in order 1. :math:`\langle 2S^z \rangle,\ \langle 2S^x \rangle` for each site in the unit cell """ obs = dict() with torch.no_grad(): for coord, site in state.sites.items(): rdm1x1 = rdm.rdm1x1(coord, state, env) for label, op in self.obs_ops.items(): obs[f"{label}{coord}"] = torch.trace(rdm1x1 @ op) obs[f"sx{coord}"] = 0.5 * (obs[f"sp{coord}"] + obs[f"sm{coord}"]) for coord, site in state.sites.items(): rdm2x1 = rdm.rdm2x1(coord, state, env) rdm1x2 = rdm.rdm1x2(coord, state, env) rdm2x2 = rdm.rdm2x2(coord, state, env) obs[f"SzSz2x1{coord}"] = torch.einsum('ijab,ijab', rdm2x1, self.h2) obs[f"SzSz1x2{coord}"] = torch.einsum('ijab,ijab', rdm1x2, self.h2) obs[f"SzSzSzSz{coord}"] = torch.einsum('ijklabcd,ijklabcd', rdm2x2, self.h4) # prepare list with labels and values obs_labels = [ f"{lc[1]}{lc[0]}" for lc in list(itertools.product(state.sites.keys(), ["sz", "sx"])) ] obs_labels += [f"SzSz2x1{coord}" for coord in state.sites.keys()] obs_labels += [f"SzSz1x2{coord}" for coord in state.sites.keys()] obs_labels += [f"SzSzSzSz{coord}" for coord in state.sites.keys()] obs_values = [obs[label] for label in obs_labels] return obs_values, obs_labels
def eval_obs_1site_BP(self, state, env): r""" :param state: wavefunction :param env: CTM environment :type state: IPEPS :type env: ENV :return: expectation values of observables, labels of observables :rtype: list[float], list[str] Evaluates observables for single-site ansatz by including the sublattice rotation in the physical space. """ # TODO optimize/unify ? # expect "list" of (observable label, value) pairs ? obs = dict({"avg_m": 0.}) with torch.no_grad(): for coord, site in state.sites.items(): rdm1x1 = rdm.rdm1x1(coord, state, env) for label, op in self.obs_ops.items(): obs[f"{label}{coord}"] = torch.trace(rdm1x1 @ op) obs[f"m{coord}"] = sqrt( abs(obs[f"sz{coord}"]**2 + obs[f"sp{coord}"] * obs[f"sm{coord}"])) obs["avg_m"] += obs[f"m{coord}"] obs["avg_m"] = obs["avg_m"] / len(state.sites.keys()) for coord, site in state.sites.items(): rdm2x1 = rdm.rdm2x1(coord, state, env) rdm1x2 = rdm.rdm1x2(coord, state, env) SS2x1 = torch.einsum('ijab,ijab', rdm2x1, self.SS_rot) SS1x2 = torch.einsum('ijab,ijab', rdm1x2, self.SS_rot) obs[f"SS2x1{coord}"] = _cast_to_real(SS2x1) obs[f"SS1x2{coord}"] = _cast_to_real(SS1x2) # prepare list with labels and values obs_labels=["avg_m"]+[f"m{coord}" for coord in state.sites.keys()]\ +[f"{lc[1]}{lc[0]}" for lc in list(itertools.product(state.sites.keys(), self.obs_ops.keys()))] obs_labels += [f"SS2x1{coord}" for coord in state.sites.keys()] obs_labels += [f"SS1x2{coord}" for coord in state.sites.keys()] obs_values = [obs[label] for label in obs_labels] return obs_values, obs_labels
def eval_obs(self, state, env): r""" :param state: wavefunction :param env: CTM environment :type state: IPEPS :type env: ENV :return: expectation values of observables, labels of observables :rtype: list[float], list[str] Computes the following observables in order 1. average magnetization over the unit cell, 2. magnetization for each site in the unit cell 3. :math:`\langle S^z \rangle,\ \langle S^+ \rangle,\ \langle S^- \rangle` for each site in the unit cell 4. :math:`\mathbf{S}_i.\mathbf{S}_j` for all non-equivalent nearest neighbour bonds where the on-site magnetization is defined as .. math:: \begin{align*} m &= \sqrt{ \langle S^z \rangle^2+\langle S^x \rangle^2+\langle S^y \rangle^2 } =\sqrt{\langle S^z \rangle^2+1/4(\langle S^+ \rangle+\langle S^- \rangle)^2 -1/4(\langle S^+\rangle-\langle S^-\rangle)^2} \\ &=\sqrt{\langle S^z \rangle^2 + 1/2\langle S^+ \rangle \langle S^- \rangle)} \end{align*} Usual spin components can be obtained through the following relations .. math:: \begin{align*} S^+ &=S^x+iS^y & S^x &= 1/2(S^+ + S^-)\\ S^- &=S^x-iS^y\ \Rightarrow\ & S^y &=-i/2(S^+ - S^-) \end{align*} """ obs = dict({"avg_m": 0.}) with torch.no_grad(): rot_op = su2.get_rot_op(self.phys_dim, dtype=self.dtype, device=self.device) for coord, site in state.sites.items(): rdm1x1 = rdm.rdm1x1(coord, state, env) if coord[1] % 2 == 0: rdm1x1 = rot_op @ rdm1x1 @ rot_op.t() for label, op in self.obs_ops.items(): obs[f"{label}{coord}"] = torch.trace(rdm1x1 @ op) obs[f"m{coord}"] = sqrt( abs(obs[f"sz{coord}"]**2 + obs[f"sp{coord}"] * obs[f"sm{coord}"])) obs["avg_m"] += obs[f"m{coord}"] obs["avg_m"] = obs["avg_m"] / len(state.sites.keys()) # for coord,site in state.sites.items(): for coord in [(0, 0), (0, 1), (1, 0), (1, 1)]: rdm2x1 = rdm.rdm2x1(coord, state, env) rdm1x2 = rdm.rdm1x2(coord, state, env) if (coord[1] % 2 == 0) ^ (coord[0] % 2 == 0): SS1x2 = torch.einsum('ijab,ijab', rdm1x2, self.h2_rot) else: SS1x2 = torch.einsum('ijab,jiba', rdm1x2, self.h2_rot) obs[f"SS1x2{coord}"] = SS1x2.real if SS1x2.is_complex( ) else SS1x2 if (coord[0] % 2 == 0) ^ (coord[0] % 2 == 0): SS2x1 = torch.einsum('ijab,ijab', rdm2x1, self.h2_rot) else: SS2x1 = torch.einsum('ijab,jiba', rdm2x1, self.h2_rot) obs[f"SS2x1{coord}"] = SS2x1.real if SS2x1.is_complex( ) else SS2x1 # prepare list with labels and values obs_labels=["avg_m"]+[f"m{coord}" for coord in state.sites.keys()]\ +[f"{lc[1]}{lc[0]}" for lc in list(itertools.product(state.sites.keys(), self.obs_ops.keys()))] obs_labels += [ f"SS2x1{coord}" for coord in [(0, 0), (0, 1), (1, 0), (1, 1)] ] obs_labels += [ f"SS1x2{coord}" for coord in [(0, 0), (0, 1), (1, 0), (1, 1)] ] obs_values = [obs[label] for label in obs_labels] return obs_values, obs_labels
def eval_obs(self, state, env): r""" :param state: wavefunction :param env: CTM environment :type state: IPEPS :type env: ENV :return: expectation values of observables, labels of observables :rtype: list[float], list[str] Computes the following observables in order 1. average magnetization over the unit cell, 2. magnetization for each site in the unit cell 3. :math:`\langle S^z \rangle,\ \langle S^+ \rangle,\ \langle S^- \rangle` for each site in the unit cell where the on-site magnetization is defined as .. math:: \begin{align*} m &= \sqrt{ \langle S^z \rangle^2+\langle S^x \rangle^2+\langle S^y \rangle^2 } =\sqrt{\langle S^z \rangle^2+1/4(\langle S^+ \rangle+\langle S^- \rangle)^2 -1/4(\langle S^+\rangle-\langle S^-\rangle)^2} \\ &=\sqrt{\langle S^z \rangle^2 + 1/2\langle S^+ \rangle \langle S^- \rangle)} \end{align*} Usual spin components can be obtained through the following relations .. math:: \begin{align*} S^+ &=S^x+iS^y & S^x &= 1/2(S^+ + S^-)\\ S^- &=S^x-iS^y\ \Rightarrow\ & S^y &=-i/2(S^+ - S^-) \end{align*} """ # TODO optimize/unify ? # expect "list" of (observable label, value) pairs ? obs = dict({"avg_m": 0.}) with torch.no_grad(): for coord, site in state.sites.items(): rdm1x1 = rdm.rdm1x1(coord, state, env) for label, op in self.obs_ops.items(): obs[f"{label}{coord}"] = torch.trace(rdm1x1 @ op) obs[f"m{coord}"] = sqrt( abs(obs[f"sz{coord}"]**2 + obs[f"sp{coord}"] * obs[f"sm{coord}"])) obs["avg_m"] += obs[f"m{coord}"] obs["avg_m"] = obs["avg_m"] / len(state.sites.keys()) for coord, site in state.sites.items(): rdm2x1 = rdm.rdm2x1(coord, state, env) rdm1x2 = rdm.rdm1x2(coord, state, env) SS2x1 = torch.einsum('ijab,ijab', rdm2x1, self.h2) SS1x2 = torch.einsum('ijab,ijab', rdm1x2, self.h2) obs[f"SS2x1{coord}"] = _cast_to_real(SS2x1) obs[f"SS1x2{coord}"] = _cast_to_real(SS1x2) # prepare list with labels and values obs_labels=["avg_m"]+[f"m{coord}" for coord in state.sites.keys()]\ +[f"{lc[1]}{lc[0]}" for lc in list(itertools.product(state.sites.keys(), self.obs_ops.keys()))] obs_labels += [f"SS2x1{coord}" for coord in state.sites.keys()] obs_labels += [f"SS1x2{coord}" for coord in state.sites.keys()] obs_values = [obs[label] for label in obs_labels] return obs_values, obs_labels