Skip to content

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)