Skip to content

Complex Ginzburg-Landau

The complex Ginzburg-Landau equation A_t = A + (1 + i b) laplacian(A) - (1 + i c) |A|^2 A.

  • Name: complex_ginzburg_landau
  • Field: physics/pattern_formation
  • Formalism: ordinary differential equations
  • References: Aranson and Kramer 2002; Gros 2015

Factory

@register_model(
    field='physics/pattern_formation',
    tags=('field', 'chaos', 'spirals', 'spectral', 'pde'),
    references=('Aranson and Kramer 2002', 'Gros 2015'),
    defaults={'until': 50.0, 'dt': 1.0, 'solver': ETDRK4Solver(dt=0.1)},
)
def complex_ginzburg_landau(
    shape: Sequence[int] = (64, 64),
    *,
    length: float = 64.0,
    b: float = 1.0,
    c: float = -1.5,
    random_state: int | np.random.Generator | None = 0,
) -> System:
    """Return the complex Ginzburg-Landau equation `A_t = A + (1 + i b) laplacian(A) - (1 + i c) |A|^2 A`.

    The amplitude `A = u + i v` is held as its real and imaginary parts, and the linear part couples them per Fourier
    mode. With `1 + b c < 0` the uniform oscillation is unstable and spiral defects turn into turbulence.

    Args:
        shape:
            The number of sites along both axes of the periodic lattice.

        length:
            The side of the square.

        b:
            The linear dispersion.

        c:
            The nonlinear dispersion.

        random_state:
            The seed or generator of the small random initial amplitude.

    Returns:
        system:
            The model, with the real part `medium.u` and the imaginary part `medium.v`.

    Examples:
        >>> from skcomplex.models.physics.pattern_formation import complex_ginzburg_landau
        >>> complex_ginzburg_landau((8, 8)).elements[0].values['c']
        -1.5
    """
    shape = tuple(shape)
    rng = np.random.default_rng(random_state)
    values = {'u': _noise(shape, 0.01, rng), 'v': _noise(shape, 0.01, rng), 'b': b, 'c': c}
    space = LatticeSpace(shape, spacing=length / shape[0])
    return System(
        elements=[
            FieldElement(
                'medium',
                element=GinzburgLandauElement,
                space=space,
                values=values,
                discretization='spectral',
            ),
        ],
    )

Components

class GinzburgLandauElement(Element):
    """Site of the complex Ginzburg-Landau equation `A_t = A + (1 + i b) laplacian(A) - (1 - i c) |A|^2 A`.

    The amplitude `A = u + i v` is held as its real and imaginary parts. Without the Laplacian every site is a
    Stuart-Landau oscillator, and the dispersion `b` and the nonlinear frequency shift `c` set whether plane waves are
    stable or break into spiral defect chaos.

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

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

        u:
            The initial real part of the amplitude.

        v:
            The initial imaginary part of the amplitude.

        b:
            The linear dispersion.

        c:
            The nonlinear frequency shift.

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

    Examples:
        >>> from skcomplex.models.physics.pattern_formation import GinzburgLandauElement
        >>> GinzburgLandauElement('site').c
        -1.5
    """

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

    def _get_laws(self, namespace: SimpleNamespace) -> list[BaseLaw]:
        """Return the rates of the real and imaginary parts of the amplitude.

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

        Returns:
            laws:
                The rates of `u` and `v`.
        """
        s = namespace
        square = s.u**2 + s.v**2
        return [
            RateLaw(s.u, s.u + laplacian(s.u) - s.b * laplacian(s.v) - square * (s.u - s.c * s.v)),
            RateLaw(s.v, s.v + s.b * laplacian(s.u) + laplacian(s.v) - square * (s.c * s.u + s.v)),
        ]

Equations

medium.u[i]: diff(u_medium(t), t) == (u_medium(t)^2 + v_medium(t)^2)*(c_medium*v_medium(t) - u_medium(t)) - b_medium*laplacian(v_medium(t)) + laplacian(u_medium(t)) + u_medium(t)
medium.v[i]: diff(v_medium(t), t) == -(c_medium*u_medium(t) + v_medium(t))*(u_medium(t)^2 + v_medium(t)^2) + b_medium*laplacian(u_medium(t)) + laplacian(v_medium(t)) + v_medium(t)