XY
The XY model of planar spins on a torus, with vortices that bind below the Kosterlitz-Thouless point.
- Name:
xy - Field:
physics/statistical_mechanics - Formalism: Monte Carlo sampling
- References: Kosterlitz and Thouless 1973; Jensen 2023
Factory
@register_model(
field='physics/statistical_mechanics',
tags=('lattice', 'Monte Carlo', 'vortices', 'spins'),
references=('Kosterlitz and Thouless 1973', 'Jensen 2023'),
defaults={'until': 200, 'dt': 1},
)
def xy(
size: int | Sequence[int] = 64,
*,
temperature: float = 0.5,
J: float = 1.0,
width: float = 1.0,
random_state: int | np.random.Generator | None = 0,
) -> System:
"""Return the XY model of planar spins on a torus, with vortices that bind below the Kosterlitz-Thouless point.
Args:
size:
The side of the square lattice, or its shape.
temperature:
The temperature in units of the coupling.
J:
The coupling between neighbouring spins.
width:
The largest change of a proposed rotation.
random_state:
The seed or generator of the random initial angles.
Returns:
system:
The model, with the angles `spins.theta`.
Examples:
>>> from skcomplex.models.physics.statistical_mechanics import xy
>>> xy(8).interactions[0].width
1.0
"""
shape = _square(size)
angles = np.random.default_rng(random_state).uniform(0, 2 * np.pi, size=shape)
space = LatticeSpace(shape)
group = LatticeElement(
'spins',
element=XYSpinElement,
space=space,
values={'theta': angles, 'J': J, 'temperature': temperature},
)
return System(elements=[group], interactions=[XYSamplingInteraction('sampling', target='spins', width=width)])
Components
class XYSpinElement(Element):
"""Planar spin of the XY model, an angle `theta`, with its coupling `J` and its temperature.
An `XYSamplingInteraction` rotates the angles by Monte Carlo sweeps at the temperature of the spins.
Args:
label:
The label of the spin or the lattice group.
visual:
How the spin is drawn, or `None` for `default_visual_`.
theta:
The initial angle.
J:
The coupling between neighbouring spins.
temperature:
The temperature in units of the coupling.
Attributes:
default_visual_:
The visual of the spin when it sets none.
Examples:
>>> from skcomplex.models.physics.statistical_mechanics import XYSpinElement
>>> XYSpinElement('spin').temperature
0.5
"""
def __init__(
self,
label: str,
visual: VisualSpec | None = None,
*,
theta: Annotated[float, StateSpec(period=2 * pi)] = 0.0,
J: Annotated[float, ParameterSpec()] = 1.0,
temperature: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 0.5,
) -> None:
super().__init__(label, visual=visual)
self.theta = theta
self.J = J
self.temperature = temperature
class XYSamplingInteraction(SpinSamplingInteraction):
"""Monte Carlo sweeps of planar spins at angles `theta`, of local energy `-J sum_j cos(theta - theta_j)`.
Every move rotates the angle by a uniform number in `[-width, width]`, accepted by the Metropolis rule. The
coupling `J` and the temperature are parameters of the spins.
Args:
label:
The label of the interaction, unique among the parts of its system.
target:
The path of the group of spins.
width:
The largest change of a proposed rotation.
state:
The name of the state that holds the angle.
neighborhood:
The neighbours of a spin, or `None` for the von Neumann neighbourhood.
Examples:
>>> from skcomplex.models.physics.common.spins import XYSamplingInteraction
>>> XYSamplingInteraction('xy', target='spins', width=0.5).move
'rotate'
"""
def __init__(
self,
label: str,
*,
target: Annotated[str, RoleSpec()],
width: float = 1.0,
state: str = 'theta',
neighborhood: BaseNeighborhood | None = None,
) -> None:
super().__init__(
label,
target=target,
state=state,
energy=self._energy,
move='rotate',
neighborhood=neighborhood,
width=width,
)
def _energy(self, namespace: SimpleNamespace) -> Expression:
"""Return the local energy `-J sum_j cos(theta - theta_j)` of a spin."""
angle, other = getattr(namespace.target, self.state), getattr(namespace.target.other, self.state)
return -namespace.target.J * neighbors(cos(angle - other), over=self.neighborhood)