Skip to content

Swift-Hohenberg

The Swift-Hohenberg equation u_t = r u - (1 + laplacian)^2 u - u^3, which forms rolls of length 2 pi.

  • Name: swift_hohenberg
  • Field: physics/pattern_formation
  • Formalism: ordinary differential equations
  • References: Swift and Hohenberg 1977; Cross and Hohenberg 1993; Datseris and Parlitz 2022

Factory

@register_model(
    field='physics/pattern_formation',
    tags=('field', 'rolls', 'convection', 'spectral', 'pde'),
    references=('Swift and Hohenberg 1977', 'Cross and Hohenberg 1993', 'Datseris and Parlitz 2022'),
    defaults={'until': 200.0, 'dt': 5.0, 'solver': ETDRK4Solver(dt=0.5)},
)
def swift_hohenberg(
    shape: Sequence[int] = (64, 64),
    *,
    length: float = 16 * math.pi,
    r: float = 0.3,
    random_state: int | np.random.Generator | None = 0,
) -> System:
    """Return the Swift-Hohenberg equation `u_t = r u - (1 + laplacian)^2 u - u^3`, which forms rolls of length 2 pi.

    Above onset, `r > 0`, small noise grows into rolls whose wavenumber is near 1, the fastest growing one.

    Args:
        shape:
            The number of sites along every axis of the periodic lattice.

        length:
            The side of the domain.

        r:
            The distance above onset.

        random_state:
            The seed or generator of the noise.

    Returns:
        system:
            The model, with the field `cells.u`.

    Examples:
        >>> from skcomplex.models.physics.pattern_formation import swift_hohenberg
        >>> swift_hohenberg((8, 8), r=0.1).interactions[0].r
        0.1
    """
    shape = tuple(shape)
    values = {'u': _noise(shape, 0.01, random_state)}
    space = LatticeSpace(shape, spacing=length / shape[0])
    return System(
        elements=[
            FieldElement('cells', element=PatternFieldElement, space=space, values=values, discretization='spectral'),
        ],
        interactions=[SwiftHohenbergEnergyInteraction('energy', target='cells', r=r)],
    )

Components

class PatternFieldElement(Element):
    """Site of a real field `u` whose patterns a free energy selects.

    Args:
        label:
            The label of the site or the field.

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

        u:
            The initial value of the field.

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

    Examples:
        >>> from skcomplex.models.physics.pattern_formation import PatternFieldElement
        >>> PatternFieldElement('site', u=0.1).u
        0.1
    """

    def __init__(self, label: str, visual: VisualSpec | None = None, *, u: Annotated[float, StateSpec()] = 0.0) -> None:
        super().__init__(label, visual=visual)
        self.u = u

class SwiftHohenbergEnergyInteraction(GradientInteraction):
    """Gradient flow `u_t = r u - (1 + laplacian)^2 u + g u^2 - u^3` of a field in the Swift-Hohenberg free energy.

    The free energy is `F = integral -r u^2 / 2 + ((1 + laplacian) u)^2 / 2 - g u^3 / 3 + u^4 / 4`. Above onset,
    `r > 0`, the modes of wavenumber near 1 grow into rolls, or into hexagons for a large enough quadratic term `g`.

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

        target:
            The path of the field.

        state:
            The name of the state of the field.

        r:
            The distance above onset.

        g:
            The strength of the quadratic term, which breaks the symmetry `u -> -u`.

    Examples:
        >>> from types import SimpleNamespace
        >>> from sage.all__sagemath_symbolics import var
        >>> from skcomplex.models.physics.common.fields import SwiftHohenbergEnergyInteraction
        >>> energy = SwiftHohenbergEnergyInteraction('energy', target='cells').check().prepare()
        >>> namespace = SimpleNamespace(target=SimpleNamespace(u=var('u')), r=var('r'), g=0)
        >>> [law.value for law in energy.run(namespace).laws_]
        [-u^3 + r*u - u - 2*laplacian(u) - laplacian(laplacian(u))]
    """

    def __init__(
        self,
        label: str,
        *,
        target: Annotated[str, RoleSpec()],
        state: str = 'u',
        r: Annotated[float, ParameterSpec()] = 0.3,
        g: Annotated[float, ParameterSpec()] = 0.0,
    ) -> None:
        super().__init__(label, target=target, states=state, variational=True)
        self.state = state
        self.r = r
        self.g = g

    def _get_potential(self, namespace: SimpleNamespace) -> Expression:
        """Return the density `-r u^2 / 2 + (u + laplacian(u))^2 / 2 - g u^3 / 3 + u^4 / 4`.

        Args:
            namespace:
                The namespace of the symbols the density can use.

        Returns:
            density:
                The density of the free energy.
        """
        u = getattr(namespace.target, self.state)
        return -namespace.r * u**2 / 2 + (u + laplacian(u)) ** 2 / 2 - namespace.g * u**3 / 3 + u**4 / 4

Equations

cells.u[i]: diff(u_cells(t), t) == g_energy*u_cells(t)^2 - u_cells(t)^3 + r_energy*u_cells(t) - laplacian(laplacian(u_cells(t))) - 2*laplacian(u_cells(t)) - u_cells(t)