Source code for qlinks.operators.plaquette

from __future__ import annotations

from dataclasses import dataclass

import numpy as np
import numpy.typing as npt

from qlinks.lattice import LatticeGraph
from qlinks.operators.base import BaseLocalOperator, OperatorAction
from qlinks.operators.diagonal import PatternDiagonalOperator
from qlinks.variables import VariableLayout


[docs] @dataclass(frozen=True, slots=True) class PlaquettePatternTransition: """ One allowed plaquette pattern transition. initial: Values on plaquette links before the operator acts. final: Values on plaquette links after the operator acts. coefficient: Matrix element for this transition. """ initial: npt.NDArray[np.int64] final: npt.NDArray[np.int64] coefficient: complex = 1.0 def __post_init__(self) -> None: initial = np.asarray(self.initial, dtype=np.int64) final = np.asarray(self.final, dtype=np.int64) if initial.ndim != 1: raise ValueError("initial must be one-dimensional.") if final.ndim != 1: raise ValueError("final must be one-dimensional.") if initial.size != final.size: raise ValueError("initial and final must have the same length.") object.__setattr__(self, "initial", initial) object.__setattr__(self, "final", final) object.__setattr__(self, "coefficient", complex(self.coefficient))
[docs] @dataclass(frozen=True, slots=True) class PlaquettePatternOperator(BaseLocalOperator): """ General plaquette transition operator. It reads the plaquette's link variables in the lattice plaquette order. If the current values match one of the allowed transition patterns, it returns the corresponding new configuration. This is suitable for QDM plaquette flips, constrained ring exchanges, and other local loop moves. """ layout: VariableLayout lattice: LatticeGraph plaquette_id: int transitions: tuple[PlaquettePatternTransition, ...] name: str = "plaquette_pattern" def __post_init__(self) -> None: link_ids = self.lattice.plaquette_links(self.plaquette_id) variable_indices = np.asarray( [self.layout.link_variable_index(int(link_id)) for link_id in link_ids], dtype=np.int64, ) if len(self.transitions) == 0: raise ValueError("PlaquettePatternOperator needs at least one transition.") for transition in self.transitions: if transition.initial.size != variable_indices.size: raise ValueError("Transition initial pattern has wrong length.") if transition.final.size != variable_indices.size: raise ValueError("Transition final pattern has wrong length.") for variable_index, initial, final in zip( variable_indices, transition.initial, transition.final, strict=True, ): local_space = self.layout.local_space(int(variable_index)) local_space.validate_value(int(initial)) local_space.validate_value(int(final)) object.__setattr__(self, "_link_ids", link_ids) object.__setattr__(self, "_variable_indices", variable_indices)
[docs] @classmethod def qdm_flip( cls, layout: VariableLayout, lattice: LatticeGraph, plaquette_id: int, coefficient: complex = 1.0, reverse_coefficient: complex | None = None, ) -> PlaquettePatternOperator: """ Standard binary dimer plaquette flip: 1010 <-> 0101 The order is the plaquette link order supplied by the lattice. """ if reverse_coefficient is None: reverse_coefficient = complex(coefficient).conjugate() transitions = ( PlaquettePatternTransition( initial=np.asarray([1, 0, 1, 0], dtype=np.int64), final=np.asarray([0, 1, 0, 1], dtype=np.int64), coefficient=complex(coefficient), ), PlaquettePatternTransition( initial=np.asarray([0, 1, 0, 1], dtype=np.int64), final=np.asarray([1, 0, 1, 0], dtype=np.int64), coefficient=complex(reverse_coefficient), ), ) return cls( layout=layout, lattice=lattice, plaquette_id=plaquette_id, transitions=transitions, name="qdm_plaquette_flip", )
[docs] @classmethod def alternating_binary_flip( cls, layout: VariableLayout, lattice: LatticeGraph, plaquette_id: int, coefficient: complex = 1.0, reverse_coefficient: complex | None = None, ) -> PlaquettePatternOperator: if reverse_coefficient is None: reverse_coefficient = complex(coefficient).conjugate() link_ids = lattice.plaquette_links(plaquette_id) p0, p1 = alternating_binary_patterns(len(link_ids)) transitions = ( PlaquettePatternTransition(initial=p0, final=p1, coefficient=complex(coefficient)), PlaquettePatternTransition( initial=p1, final=p0, coefficient=complex(reverse_coefficient) ), ) return cls( layout=layout, lattice=lattice, plaquette_id=plaquette_id, transitions=transitions, name="alternating_binary_plaquette_flip", )
[docs] @classmethod def alternating_flux_flip( cls, layout: VariableLayout, lattice: LatticeGraph, plaquette_id: int, coefficient: complex = 1.0, ) -> PlaquettePatternOperator: link_ids = lattice.plaquette_links(plaquette_id) p0, p1 = alternating_flux_patterns(len(link_ids)) transitions = ( PlaquettePatternTransition(initial=p0, final=p1, coefficient=coefficient), PlaquettePatternTransition(initial=p1, final=p0, coefficient=coefficient), ) return cls( layout=layout, lattice=lattice, plaquette_id=plaquette_id, transitions=transitions, name="alternating_flux_plaquette_flip", )
@property def link_ids(self) -> npt.NDArray[np.int64]: return self._link_ids.copy() @property def variable_indices(self) -> npt.NDArray[np.int64]: return self._variable_indices.copy()
[docs] def affected_variables(self) -> npt.NDArray[np.int64]: return self._variable_indices.copy()
[docs] def apply(self, config: npt.ArrayLike) -> tuple[OperatorAction, ...]: arr = self._as_config(config) local_values = arr[self._variable_indices] actions: list[OperatorAction] = [] for transition in self.transitions: if np.array_equal(local_values, transition.initial): new = arr.copy() new[self._variable_indices] = transition.final actions.append(OperatorAction(transition.coefficient, new)) return tuple(actions)
[docs] def qdm_flippability_projectors( layout: VariableLayout, lattice: LatticeGraph, plaquette_id: int, coefficient: complex = 1.0, ) -> tuple[PatternDiagonalOperator, PatternDiagonalOperator]: """ Return diagonal projectors onto the two flippable QDM plaquette patterns: 1010 and 0101 The potential term V * P_p^2 in a QDM-like model can be represented using these diagonal projectors. """ link_ids = lattice.plaquette_links(plaquette_id) variable_indices = np.asarray( [layout.link_variable_index(int(link_id)) for link_id in link_ids], dtype=np.int64, ) return ( PatternDiagonalOperator( layout=layout, variable_indices=variable_indices, pattern=np.asarray([1, 0, 1, 0], dtype=np.int64), coefficient=coefficient, name="qdm_flippability_1010", ), PatternDiagonalOperator( layout=layout, variable_indices=variable_indices, pattern=np.asarray([0, 1, 0, 1], dtype=np.int64), coefficient=coefficient, name="qdm_flippability_0101", ), )
[docs] def alternating_binary_patterns(length: int) -> tuple[np.ndarray, np.ndarray]: if length <= 0: raise ValueError("length must be positive.") if length % 2 != 0: raise ValueError("alternating binary plaquette patterns require even length.") p0 = np.asarray([1 if i % 2 == 0 else 0 for i in range(length)], dtype=np.int64) p1 = 1 - p0 return p0, p1
[docs] def alternating_flux_patterns(length: int) -> tuple[np.ndarray, np.ndarray]: if length <= 0: raise ValueError("length must be positive.") if length % 2 != 0: raise ValueError("alternating flux plaquette patterns require even length.") p0 = np.asarray([1 if i % 2 == 0 else -1 for i in range(length)], dtype=np.int64) p1 = -p0 return p0, p1
[docs] def alternating_binary_flippability_projectors( layout: VariableLayout, lattice: LatticeGraph, plaquette_id: int, coefficient: complex = 1.0, ) -> tuple[PatternDiagonalOperator, PatternDiagonalOperator]: link_ids = lattice.plaquette_links(plaquette_id) variable_indices = np.asarray( [layout.link_variable_index(int(link_id)) for link_id in link_ids], dtype=np.int64, ) p0, p1 = alternating_binary_patterns(len(link_ids)) return ( PatternDiagonalOperator( layout=layout, variable_indices=variable_indices, pattern=p0, coefficient=coefficient, name="alternating_binary_flippability_0", ), PatternDiagonalOperator( layout=layout, variable_indices=variable_indices, pattern=p1, coefficient=coefficient, name="alternating_binary_flippability_1", ), )
[docs] def alternating_flux_flippability_projectors( layout: VariableLayout, lattice: LatticeGraph, plaquette_id: int, coefficient: complex = 1.0, ) -> tuple[PatternDiagonalOperator, PatternDiagonalOperator]: link_ids = lattice.plaquette_links(plaquette_id) variable_indices = np.asarray( [layout.link_variable_index(int(link_id)) for link_id in link_ids], dtype=np.int64, ) p0, p1 = alternating_flux_patterns(len(link_ids)) return ( PatternDiagonalOperator( layout=layout, variable_indices=variable_indices, pattern=p0, coefficient=coefficient, name="alternating_flux_flippability_0", ), PatternDiagonalOperator( layout=layout, variable_indices=variable_indices, pattern=p1, coefficient=coefficient, name="alternating_flux_flippability_1", ), )