Skip to content

Lattice Yang-Mills and pure lattice QCD

SU(2) or SU(3) matrices on the links of a periodic lattice, sampled by the Wilson action.

  • Name: lattice_yang_mills
  • Field: physics/waves
  • Formalism: lattice dynamics
  • References: Satz 2022; Schwichtenberg 2020; Wilson 1974; Creutz 1980

Select this system

from skcomplex.systems import create_simulation, create_system, describe

system = create_system('lattice_yang_mills')
entry = describe('lattice_yang_mills')
trajectory = create_simulation('lattice_yang_mills').prepare().run().trajectory_

Factory

@register_system(
    field='physics/waves',
    tags=('gauge', 'lattice', 'monte carlo', 'yang-mills', 'qcd', 'cell complex'),
    references=('Satz 2022', 'Schwichtenberg 2020', 'Wilson 1974', 'Creutz 1980'),
    defaults={'until': 100, 'dt': 1},
    observables=_yang_mills_observables,
)
def lattice_yang_mills(
    size: int = 6,
    *,
    dimension: int = 4,
    extent: int | None = None,
    colors: int = 2,
    beta: float = 2.3,
    width: float = 0.5,
    hits: int = 4,
    links: npt.ArrayLike | None = None,
    seed: int = 0,
) -> System:
    """Return SU(2) or SU(3) matrices on the links of a periodic lattice, sampled by the Wilson action.

    The link matrices sample the Wilson action `-(beta / N) sum_p Re tr U_p` under discrete time, where `U_p` is the
    ordered product of the link matrices around the plaquette `p`, with the inverse of a link traversed against its
    orientation. Every sweep proposes `hits` Metropolis moves per link, each a product with a random matrix of the group
    near the identity, and projects the links back onto the group. SU(2) is the isospin connection of the weak force,
    and SU(3) the color connection of the strong force, so that SU(3) is pure lattice QCD. The last axis has `extent`
    sites, the inverse temperature of the lattice, along which the Polyakov loop winds. In two dimensions the plaquettes
    are independent, and the SU(2) mean plaquette at large size is `I_2(beta) / I_1(beta)`.

    Args:
        size:
            The number of sites along every axis but the last.

        dimension:
            The number of axes of the lattice.

        extent:
            The number of sites along the last axis, or `None` for `size`.

        colors:
            The degree `N` of the group SU(N), 2 or 3.

        beta:
            The inverse coupling of the Wilson action, `2 N / g^2`.

        width:
            The spread around the identity of the matrices that move a link.

        hits:
            The number of moves proposed per link and sweep.

        links:
            The initial link matrices, one `N` by `N` complex matrix per link, or `None` for matrices drawn from the
            Haar measure of the group.

        seed:
            The seed of the initial link matrices.

    Returns:
        system:
            The system.

    Raises:
        ValidationError:
            The number of colors is not 2 or 3, or the initial links do not hold one matrix of the group per link.

    Examples:
        >>> from skcomplex.systems.physics.waves import lattice_yang_mills
        >>> [element.label for element in lattice_yang_mills(2, dimension=2).elements]
        ['links']
        >>> lattice_yang_mills(2, dimension=2, colors=3).elements[0].element.__name__
        'ColorLinkElement'
    """
    if colors not in LINKS:
        error_msg = f'The lattice gauge group is SU(2) or SU(3), so colors is 2 or 3, but got {colors!r}.'
        raise ValidationError(error_msg)
    element, group = LINKS[colors]
    shape = (size,) * (dimension - 1) + (size if extent is None else extent,)
    space = CellComplexSpace(LatticeSpace(shape)).prepare()
    count = space.incidences_[0].shape[0]
    if links is None:
        matrices = group.uniform_of(np.random.default_rng(seed), count)
    else:
        matrices = np.asarray(links, dtype=complex)
    if matrices.shape != (count, colors, colors) or not np.allclose(group.projection_of(matrices), matrices):
        error_msg = (
            f'The initial links are {matrices.shape[0]} matrices of the shape {matrices.shape[1:]}, but the lattice '
            f'takes {count} matrices of SU({colors}), one per link.'
        )
        raise ValidationError(error_msg)
    group_links = GroupElement(
        'links',
        element=element,
        space=space,
        on='edges',
        values={'U': group.components_of(matrices)},
    )
    sampling = WilsonSamplingInteraction(
        'wilson',
        target='links',
        beta=beta,
        state='U',
        width=width,
        group=group,
        hits=hits,
    )
    return System(elements=[group_links], interactions=[sampling], time=DiscreteTime())

Components

Elements:

  • IsospinLinkElement, from Element

Interactions:

  • WilsonSamplingInteraction, from SamplingInteraction
class IsospinLinkElement(Element):
    """Link of a lattice with an SU(2) matrix `U`, the parallel transport of isospin along the link.

    Args:
        label:
            The label of the link or the group on the links.

        visual:
            How the link is drawn, or `None` for `default_visual_`.

        U:
            The real components of the matrix of the link, the identity by default.

    Attributes:
        default_visual_:
            The visual of the link when it sets none.

    Examples:
        >>> from skcomplex.systems.physics.waves import IsospinLinkElement
        >>> len(IsospinLinkElement('link').U)
        8
    """

    def __init__(
        self,
        label: str,
        visual: VisualSpec | None = None,
        *,
        U: Annotated[object, StateSpec(group=ISOSPIN)] = tuple(ISOSPIN.identity),
    ) -> None:
        super().__init__(label, visual=visual)
        self.U = U

class WilsonSamplingInteraction(SamplingInteraction):
    """Monte Carlo sampling of link variables with the local Wilson action of the plaquettes of a link.

    For link angles the action is `-beta sum_p cos(theta_p)`, where `theta_p` is the oriented sum of the link angles
    around the plaquette `p`. For the matrices of a group `SU(N)` it is `-(beta / N) sum_p Re tr U_p`, where `U_p` is
    the path-ordered product of the link matrices around `p`, and the move `'rotate'` multiplies a link by a random
    matrix of the group near the identity of spread `width`.

    Args:
        label:
            The label of the interaction, unique among the parts of its system.

        target:
            The path of the group on the edges of a cell complex.

        beta:
            The inverse coupling of the Wilson action.

        state:
            The name of the state that holds the link angles.

        move:
            `'rotate'` for U(1) or a matrix group, or `'random-value'` for Z_N.

        values:
            The angles a random-value move draws from.

        width:
            The largest change of a rotation.

        rule:
            The acceptance, `'metropolis'`, `'glauber'`, or `'heat-bath'`.

        schedule:
            `'colored'` or `'random-sequential'`.

        group:
            The matrix group of the links, or `None` for link angles.

        hits:
            The number of moves proposed per link and sweep of matrix links.

    Examples:
        >>> from skcomplex.systems.physics.common.fields import WilsonSamplingInteraction
        >>> WilsonSamplingInteraction('wilson', target='links', beta=2.0).beta
        2.0
        >>> from skcomplex.core.specs import MatrixGroupSpec
        >>> color = WilsonSamplingInteraction('wilson', target='links', state='U', group=MatrixGroupSpec('SU', 3))
        >>> color.objective.group.degree
        3
    """

    def __init__(
        self,
        label: str,
        *,
        target: Annotated[str, RoleSpec()],
        beta: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 2.0,
        state: str = 'theta',
        move: str = 'rotate',
        values: Sequence[float] = (),
        width: float = 1.5,
        rule: str = 'metropolis',
        schedule: str = 'colored',
        group: MatrixGroupSpec | None = None,
        hits: int = 1,
    ) -> None:
        super().__init__(
            label,
            target=target,
            state=state,
            objective=self._get_action if group is None else HolonomyScoreSpec(self._get_trace_action, group, hits),
            move=move,
            rule=rule,
            schedule=schedule,
            values=values,
            width=width,
        )
        self.beta = beta
        self.group = group
        self.hits = hits

    def _get_action(self, namespace: SimpleNamespace) -> Expression:
        """Return the terms of the Wilson action that hold the angle of a link."""
        theta = getattr(namespace.target, self.state)
        return -namespace.beta * neighbors(cos(exterior_derivative(theta)), over=CofaceNeighborhood())

    def _get_trace_action(self, holonomies: ComplexArray, beta: float) -> Array:
        """Return the Wilson action `-(beta / N) Re tr U_p` of the holonomies of the plaquettes."""
        degree = holonomies.shape[-1]
        return -beta / degree * np.trace(holonomies, axis1=-2, axis2=-1).real