"""SU(2) rishon-mode operator builders.
The module provides two implementations of the local SU(2) rishon algebra:
one specialized for the currently supported ``spin=1/2`` case and one more
general constructor used for extended truncations.
"""
import logging
import numpy as np
from scipy.sparse import csr_matrix, diags, identity
from scipy.sparse.linalg import norm
from edlgt.tools import anti_commutator as anti_comm
from edlgt.tools import check_matrix as check_m
from edlgt.tools import commutator as comm
from edlgt.tools import validate_parameters
from .spin_operators import SU2_generators, m_values, spin_space
logger = logging.getLogger(__name__)
__all__ = ["SU2_Rishon", "SU2_Rishon_gen"]
[docs]
class SU2_Rishon:
"""Construct the hardcoded SU(2) rishon algebra for ``spin=1/2``.
Parameters
----------
spin : scalar
Rishon spin truncation. The current implementation supports only
``spin = 1/2``.
Attributes
----------
ops : dict
Dictionary of sparse rishon operators and SU(2) generators.
"""
def __init__(self, spin):
if not np.isscalar(spin):
raise TypeError(f"s must be SCALAR & (semi)INTEGER, not {type(spin)}")
# Maximal spin rep and color
self.s = spin
# Compute the dimension of the rishon mode
self.largest_s_size = spin_space(spin)
self.size = np.sum([s_size for s_size in range(1, self.largest_s_size + 1)])
self.shape = (self.size, self.size)
self.construct_rishons()
self.SU2_check_rishon_algebra()
[docs]
def construct_rishons(self):
"""Populate :attr:`ops` with rishon, parity, and SU(2) generator operators."""
# Define dictionary for operators
self.ops = {}
# ---------------------------------------------------------------------
# Define SU2 Parity and Identity
P_diag = []
for s_size in range(self.largest_s_size):
P_diag += [(-1) ** s_size] * (s_size + 1)
self.ops["P"] = diags(P_diag, 0, self.shape, dtype=np.float64, format="csr")
self.ops["Iz"] = identity(self.size, dtype=float)
# In the special case of s=1/2, the shape of rishon is simpler
cf = 1 / (2 ** (0.25))
if self.s == 1 / 2:
self.ops["Zr"] = cf * csr_matrix(([1, 1], ([0, 2], [1, 0])), shape=(3, 3))
self.ops["Zg"] = cf * csr_matrix(([1, -1], ([0, 1], [2, 0])), shape=(3, 3))
for color in "rg":
self.ops[f"Z{color}_dag"] = self.ops[f"Z{color}"].transpose()
# Useful operators for corner operators
self.ops[f"Z{color}_P"] = self.ops[f"Z{color}"] * self.ops["P"]
self.ops[f"P_Z{color}_dag"] = self.ops["P"] * self.ops[f"Z{color}_dag"]
self.ops[f"Z{color}_dag_P"] = self.ops[f"Z{color}_dag"] * self.ops["P"]
# ---------------------------------------------------------------------
else:
raise ValueError("For the moment it works only with j=1/2")
# Add SU2 generators
self.ops |= SU2_generators(self.s)
[docs]
def SU2_check_rishon_algebra(self):
"""Validate commutation/anticommutation relations of the rishon algebra."""
check_m(2 * comm(self.ops["Zr"], self.ops["Tx"]), self.ops["Zg"])
check_m(2 * comm(self.ops["Zg"], self.ops["Tx"]), self.ops["Zr"])
check_m(comm(self.ops["Zr"], self.ops["Ty"]), -complex(0, 0.5) * self.ops["Zg"])
check_m(comm(self.ops["Zg"], self.ops["Ty"]), complex(0, 0.5) * self.ops["Zr"])
check_m(2 * comm(self.ops["Zr"], self.ops["Tz"]), self.ops["Zr"])
check_m(2 * comm(self.ops["Zg"], self.ops["Tz"]), -self.ops["Zg"])
for color in ["r", "g"]:
# CHECK RISHON MODES TO BEHAVE LIKE FERMIONS (anticommute with parity)
if norm(anti_comm(self.ops[f"Z{color}"], self.ops["P"])) > 1e-15:
raise ValueError(f"Z{color} is a Fermion and must anticommute with P")
"""
# CHECK THE ACTION OF THE RISHONS ON THE CASIMIR OPERATOR
if check_m(
2 * comm(self.ops["T2_root"], self.ops[f"Z{color}"]),
self.ops[f"Z{color}"],
):
raise ValueError(
f"Z{color} has a wrong action onto the Casimir operator"
)
"""
logger.info("SU2 RISHON ALGEBRA SATISFIED")
[docs]
class SU2_Rishon_gen:
"""Construct SU(2) rishon operators for a generic spin truncation.
Parameters
----------
spin : scalar
Maximum SU(2) irrep used in the rishon construction.
Attributes
----------
ops : dict
Dictionary of sparse rishon operators and SU(2) generators.
"""
def __init__(self, spin):
# Check spin
validate_parameters(spin_list=[spin])
# Maximal spin rep and color
self.s = spin
# Compute the dimension of the rishon mode
self.largest_s_size = spin_space(spin)
self.size = np.sum([s_size for s_size in range(1, self.largest_s_size + 1)])
self.shape = (self.size, self.size)
self.construct_rishons()
self.SU2_check_rishon_algebra()
[docs]
def construct_rishons(self):
"""Populate :attr:`ops` with the generic rishon operator set."""
# Define dictionary for operators
self.ops = {}
# ---------------------------------------------------------------------
# Define SU2 Parity and Identity
P_diag = []
for s_size in range(self.largest_s_size):
P_diag += [(-1) ** s_size] * (s_size + 1)
self.ops["P"] = diags(P_diag, 0, self.shape, dtype=np.float64, format="csr")
self.ops["Iz"] = identity(self.size, dtype=float)
# ---------------------------------------------------------------------
# Starting diagonals of the s=0 case for the red and green rishon
initial_diags = [1, 2]
# Define the Rishons Z_red and Z_green
for ii, color in enumerate(["r", "g"]):
# List of diagonal entries
entries = []
# List of diagonals
diagonals = []
# Number of zeros at the beginning of the diagonals.
# It increases with the spin representation
in_zeros = 0
# Select the initial diag
diag = initial_diags[ii]
# Run over the sizes of the sectors with increasing spin
for s_size in range(self.largest_s_size - 1):
# Obtain spin
spin = s_size / 2
# Compute chi & P coefficients
sz_diag = m_values(spin)
chi_diags = (
np.vectorize(self.chi_function)(spin, color, sz_diag)
).tolist()
# Fill the diags with zeros according to the len of the diag
out_zeros = self.size - len(chi_diags) - diag - in_zeros
chi_diags = [0] * in_zeros + chi_diags + [0] * out_zeros
# Append the diags
entries.append(chi_diags)
diagonals.append(diag)
# Update the diagonals and the number of initial zeros
diag += 1
in_zeros += s_size + 1
# Compose the Rishon operators
self.ops[f"Z{color}"] = diags(entries, diagonals, self.shape)
self.ops[f"Z{color}_dag"] = self.ops[f"Z{color}"].transpose()
self.ops[f"ZB_{color}"] = self.ops[f"Z{color}"]
self.ops[f"ZB_{color}_dag"] = self.ops[f"Z{color}_dag"]
# ---------------------------------------------------------------------
# Useful operators for corner operators
self.ops[f"ZB_{color}_P"] = self.ops[f"ZB_{color}"] * self.ops["P"]
self.ops[f"P_ZB_{color}_dag"] = self.ops["P"] * self.ops[f"ZB_{color}_dag"]
# Define eventually the two species of rishons
self.ops["ZA_r"] = self.ops["ZB_g_dag"]
self.ops["ZA_g"] = -self.ops["ZB_r_dag"]
for ii, color in enumerate(["r", "g"]):
self.ops[f"ZA_{color}_dag"] = self.ops[f"ZA_{color}"].transpose()
# Useful operators for corner operators
self.ops[f"ZA_{color}_P"] = self.ops[f"ZA_{color}"] * self.ops["P"]
self.ops[f"P_ZA_{color}_dag"] = self.ops["P"] * self.ops[f"ZA_{color}_dag"]
self.ops[f"ZA_{color}_dag_P"] = self.ops[f"ZA_{color}_dag"] * self.ops["P"]
self.ops[f"ZB_{color}_dag_P"] = self.ops[f"ZB_{color}_dag"] * self.ops["P"]
# Add SU2 generators
self.ops |= SU2_generators(self.s)
[docs]
def SU2_check_rishon_algebra(self):
"""Validate the generic SU(2) rishon algebra relations."""
logger.info("CHECK SU2 RISHON ALGEBRA")
check_m(2 * comm(self.ops["Zr"], self.ops["Tx"]), self.ops["Zg"])
check_m(2 * comm(self.ops["Zg"], self.ops["Tx"]), self.ops["Zr"])
check_m(comm(self.ops["Zr"], self.ops["Ty"]), -complex(0, 0.5) * self.ops["Zg"])
check_m(comm(self.ops["Zg"], self.ops["Ty"]), complex(0, 0.5) * self.ops["Zr"])
check_m(2 * comm(self.ops["Zr"], self.ops["Tz"]), self.ops["Zr"])
check_m(2 * comm(self.ops["Zg"], self.ops["Tz"]), -self.ops["Zg"])
for color in ["r", "g"]:
# CHECK RISHON MODES TO BEHAVE LIKE FERMIONS (anticommute with parity)
if norm(anti_comm(self.ops[f"Z{color}"], self.ops["P"])) > 1e-15:
raise ValueError(f"Z{color} is a Fermion and must anticommute with P")
logger.info("SU2 RISHON ALGEBRA SATISFIED")
[docs]
@staticmethod
def chi_function(s, color, m):
"""Return the coefficient used in the generic SU(2) rishon matrices.
Parameters
----------
s : scalar
Intermediate spin irrep.
color : str
Rishon color label, ``"r"`` or ``"g"``.
m : scalar
Magnetic quantum number.
Returns
-------
float
Coefficient entering the diagonal construction of the rishon
operators.
"""
validate_parameters(spin_list=[s], sz_list=[m])
if color == "r":
return np.sqrt((s + m + 1) / np.sqrt((2 * s + 1) * (2 * s + 2)))
elif color == "g":
return np.sqrt((s - m + 1) / np.sqrt((2 * s + 1) * (2 * s + 2)))
else:
raise ValueError(f"color can be only 'r' (red) or 'g'(green): got {color}")