Mattis spin glass
The Mattis spin glass on a periodic square lattice, with couplings J_ij = J xi_i xi_j of a pattern xi.
- Name:
mattis_spin_glass - Field:
physics/statistical_mechanics - Formalism: Monte Carlo sampling
- References: Mattis 1976; Bertin 2021
Factory
@register_model(
field='physics/statistical_mechanics',
tags=('spin glass', 'disorder', 'lattice', 'Monte Carlo', 'spins'),
references=('Mattis 1976', 'Bertin 2021'),
defaults={'until': 300, 'dt': 2},
observables=_pattern_observables,
)
def mattis_spin_glass(
size: int | Sequence[int] = 48,
*,
temperature: float = 1.5,
J: float = 1.0,
h: float = 0.0,
random_state: int | np.random.Generator | None = 0,
) -> System:
"""Return the Mattis spin glass on a periodic square lattice, with couplings `J_ij = J xi_i xi_j` of a pattern `xi`.
The gauge change `sigma_i = xi_i s_i` maps the model at `h = 0` to the Ising model, so the overlap
`m_xi = sum_i xi_i s_i / N` orders below the Ising critical temperature `2 J / ln(1 + sqrt 2)` while the
magnetization averages to zero over the disorder. One unit of time is one sweep of Metropolis updates.
Args:
size:
The side of the square lattice, or its two sides.
temperature:
The temperature, which can change during a run.
J:
The coupling.
h:
The external field.
random_state:
The seed or generator of the pattern and of the random initial spins.
Returns:
system:
The model, with the spins `spins.spin`, the pattern `spins.pattern`, and the observables `magnetization`
and `overlap`.
Examples:
>>> import numpy as np
>>> from skcomplex.models.physics.statistical_mechanics import mattis_spin_glass
>>> values = mattis_spin_glass(8).elements[0].values
>>> pattern = values['pattern'].reshape(8, 8)
>>> bool(np.array_equal(values['J_right'].reshape(8, 8), pattern * np.roll(pattern, -1, axis=1)))
True
"""
shape = _square(size)
rng = np.random.default_rng(random_state)
pattern = rng.choice([-1.0, 1.0], size=shape)
right = J * pattern * np.roll(pattern, -1, axis=1)
down = J * pattern * np.roll(pattern, -1, axis=0)
spins = rng.choice([-1.0, 1.0], size=shape)
group = LatticeElement(
'spins',
element=MattisSpinElement,
space=LatticeSpace(shape),
values={'spin': spins, 'pattern': pattern, 'h': h, 'temperature': temperature, **_bond_states(right, down)},
visual=VisualSpec('image', {'states': ()}),
)
return System(elements=[group], interactions=[BondSamplingInteraction('sampling', target='spins')])
Components
class MattisSpinElement(BondSpinElement):
"""Spin of the Mattis model, a bond spin that also holds its quenched pattern `xi`, with couplings `J xi_i xi_j`.
Args:
label:
The label of the spin or the lattice group.
visual:
How the element is drawn, or `None` for `default_visual_`.
spin:
The initial spin, 1 or -1.
J_up:
The coupling to the upper neighbour.
J_left:
The coupling to the left neighbour.
J_right:
The coupling to the right neighbour.
J_down:
The coupling to the lower neighbour.
h:
The external field.
temperature:
The temperature.
pattern:
The quenched pattern `xi`, 1 or -1.
Examples:
>>> from skcomplex.models.physics.statistical_mechanics import MattisSpinElement
>>> MattisSpinElement('spin', pattern=-1.0).pattern
-1.0
"""
def __init__(
self,
label: str,
visual: VisualSpec | None = None,
*,
spin: float = 1.0,
J_up: float = 1.0,
J_left: float = 1.0,
J_right: float = 1.0,
J_down: float = 1.0,
h: float = 0.0,
temperature: float = 1.0,
pattern: Annotated[float, StateSpec(domain=ZZ)] = 1.0,
) -> None:
super().__init__(
label,
visual=visual,
spin=spin,
J_up=J_up,
J_left=J_left,
J_right=J_right,
J_down=J_down,
h=h,
temperature=temperature,
)
self.pattern = pattern
class BondSamplingInteraction(SpinSamplingInteraction):
"""Metropolis sweeps of Ising spins with quenched couplings to their neighbours, of energy `-s (sum J_k s_k + h)`.
The couplings `J_up`, `J_left`, `J_right`, and `J_down` are constant states of every spin, in the order of the
offsets of the von Neumann neighbourhood, and the field `h` and the temperature are its parameters.
Args:
label:
The label of the interaction, unique among the parts of its system.
target:
The path of the lattice group of spins.
Examples:
>>> from skcomplex.models.physics.statistical_mechanics import BondSamplingInteraction
>>> BondSamplingInteraction('sampling', target='spins').neighborhood
VonNeumannNeighborhood()
"""
def __init__(self, label: str, *, target: Annotated[str, RoleSpec()]) -> None:
super().__init__(label, target=target, state='spin', energy=self._energy, neighborhood=NEAREST)
def _energy(self, namespace: SimpleNamespace) -> Expression:
"""Return the local energy `-s (sum_k J_k s_k + h)` of a spin."""
s = namespace.target
couplings = (s.J_up, s.J_left, s.J_right, s.J_down)
neighbours = neighbors(s.spin, NEAREST, reduce='each')
return -s.spin * (sum(coupling * other for coupling, other in zip(couplings, neighbours, strict=True)) + s.h)