# Copyright 2024 The YASTN Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
import numpy as np
from typing import NamedTuple
from yastn.tensor._algebra import exp
from yastn.tensor.linalg import eigh
from yastn.tensor._contractions import ncon
from ._gates_auxiliary import fkron
[docs]
class Gate_nn(NamedTuple):
"""
G0 should be before G1 in the fermionic and lattice orders.
The third legs of G0 and G1 are auxiliary legs connecting them into a two-site operator.
If a bond is None, this is a general operator.
Otherwise, the bond can carry information where it should be applied
(potentially, after fixing the order mismatches).
"""
G0 : tuple = None
G1 : tuple = None
bond : tuple = None
[docs]
class Gate_local(NamedTuple):
"""
G is a local operator with ndim==2.
If site is None, this is a general operator.
Otherwise, the site can carry information where it should be applied.
"""
G : tuple = None
site : tuple = None
[docs]
class Gates(NamedTuple):
"""
List of nearest-neighbor and local operators to be applied to PEPS during evolution_step.
"""
nn : list = () # list of NN gates
local : list = () # list of local gates
def decompose_nn_gate(Gnn, bond=None) -> Gate_nn:
"""
Auxiliary function cutting a two-site gate with SVD
into two local operators with the connecting legs.
"""
U, S, V = Gnn.svd_with_truncation(axes=((0, 2), (1, 3)), sU=-1, tol=1e-14, Vaxis=2)
S = S.sqrt()
return Gate_nn(S.broadcast(U, axes=2), S.broadcast(V, axes=2), bond=bond)
[docs]
def gate_nn_hopping(t, step, I, c, cdag, bond=None) -> Gate_nn:
"""
Nearest-neighbor gate G = exp(-step * H)
for H = -t * (cdag_1 c_2 + cdag_2 c_1)
G = I + (cosh(x) - 1) * (n_1 h_2 + h_1 n_2) + sinh(x) * (cdag_1 c_2 + cdag_2 c_1)
"""
n = cdag @ c
h = c @ cdag
II = fkron(I, I, sites=(0, 1))
nh = fkron(n, h, sites=(0, 1))
hn = fkron(h, n, sites=(0, 1))
cc = fkron(cdag, c, sites=(0, 1)) \
+ fkron(cdag, c, sites=(1, 0))
G = II + (np.cosh(t * step) - 1) * (nh + hn) + np.sinh(t * step) * cc
return decompose_nn_gate(G, bond)
[docs]
def gate_nn_Ising(J, step, I, X, bond=None) -> Gate_nn:
"""
Nearest-neighbor gate G = exp(-step * H)
for H = J X_1 X_2, where X is Pauli matrix
G = cosh(x) I - sinh(x) X_1 X_2; x = step * J
"""
II = fkron(I, I, sites=(0, 1))
XX = fkron(X, X, sites=(0, 1))
G = np.cosh(J * step) * II - np.sinh(J * step) * XX
return decompose_nn_gate(G, bond)
def gate_nn_tJ(J, tu, td, muu0, muu1, mud0, mud1, step, I, cu, cpu, cd, cpd, bond=None) -> Gate_nn:
"""
Gate exp(-step * H_tj)
"""
nu = cpu @ cu
nd = cpd @ cd
Sp = cpu @ cd
Sm = cpd @ cu
H = 0 * fkron(I, I, sites=(0, 1))
H = H + 0.5 * J * fkron(Sp, Sm, sites=(0, 1))
H = H + 0.5 * J * fkron(Sm, Sp, sites=(0, 1))
H = H - 0.5 * J * fkron(nu, nd, sites=(0, 1))
H = H - 0.5 * J * fkron(nd, nu, sites=(0, 1))
H = H - tu * fkron(cpu, cu, sites=(0, 1))
H = H - tu * fkron(cpu, cu, sites=(1, 0))
H = H - td * fkron(cpd, cd, sites=(0, 1))
H = H - td * fkron(cpd, cd, sites=(1, 0))
H = H - muu0 * fkron(cpu @ cu, I, sites=(0, 1))
H = H - muu1 * fkron(I, cpu @ cu, sites=(0, 1))
H = H - mud0 * fkron(cpd @ cd, I, sites=(0, 1))
H = H - mud1 * fkron(I, cpd @ cd, sites=(0, 1))
H = H.fuse_legs(axes = ((0, 1), (2, 3)))
D, S = eigh(H, axes = (0, 1))
D = exp(D, step=-step)
G = ncon((S, D, S), ([-1, 1], [1, 2], [-3, 2]), conjs=(0, 0, 1))
G = G.unfuse_legs(axes=(0, 1))
return decompose_nn_gate(G, bond)
[docs]
def gate_local_Coulomb(mu_up, mu_dn, U, step, I, n_up, n_dn, site=None) -> Gate_local:
"""
Local gate exp(-step * H)
for H = U * (n_up - I / 2) * (n_dn - I / 2) - mu_up * n_up - mu_dn * n_dn
We ignore a constant U / 4 in the above Hamiltonian.
"""
nn = n_up @ n_dn
G_loc = I
G_loc = G_loc + (n_dn - nn) * (np.exp(step * (mu_dn + U / 2)) - 1)
G_loc = G_loc + (n_up - nn) * (np.exp(step * (mu_up + U / 2)) - 1)
G_loc = G_loc + nn * (np.exp(step * (mu_up + mu_dn)) - 1)
return Gate_local(G_loc, site)
[docs]
def gate_local_occupation(mu, step, I, n, site=None) -> Gate_local:
"""
Local gate G = exp(-step * H) for H = -mu * n
G = I + n * (exp(mu * step) - 1)
"""
G_loc = I + n * (np.exp(mu * step) - 1)
return Gate_local(G_loc, site)
[docs]
def gate_local_field(h, step, I, X, site=None) -> Gate_local:
"""
Local gate G = exp(-step * H) for H = -h * X
G = cosh(h * step) * I + np.sinh(h * step) * X
"""
G_loc = np.cosh(h * step) * I + np.sinh(h * step) * X
return Gate_local(G_loc, site)
[docs]
def distribute(geometry, gates_nn=None, gates_local=None) -> Gates:
"""
Distributes gates homogeneous over the lattice.
Parameters
----------
geometry : yastn.tn.fpeps.SquareLattice | yastn.tn.fpeps.CheckerboardLattice | yast.tn.fpeps.Peps
Geometry of PEPS lattice.
Can be any structure that includes geometric information about the lattice, like the Peps class.
nn : Gate_nn | Sequence[Gate_nn]
Nearest-neighbor gate, or a list of gates, to be distributed over all unique lattice bonds.
local : Gate_local | Sequence[Gate_local]
Local gate, or a list of local gates, to be distributed over all unique lattice sites.
"""
if isinstance(gates_nn, Gate_nn):
gates_nn = [gates_nn]
nn = []
if gates_nn is not None:
for bond in geometry.bonds():
for Gnn in gates_nn:
nn.append(Gnn._replace(bond=bond))
if isinstance(gates_local, Gate_local):
gates_local = [gates_local]
local = []
if gates_local is not None:
for site in geometry.sites():
for Gloc in gates_local:
local.append(Gloc._replace(site=site))
return Gates(nn=nn, local=local)