Skip to content

Langevin field theory of absorbing-state transitions with a conserved energy field

The Langevin field theory of an absorbing-state transition, an active density fed by an energy field.

  • Name: directed_percolation_langevin
  • Field: physics/criticality
  • Formalism: stochastic differential equations
  • References: Tian et al. 2022; Henkel, Hinrichsen, and Lübeck 2008; Bonachela and Muñoz 2009

Factory

@register_model(
    field='physics/criticality',
    tags=('field', 'absorbing state', 'directed percolation', 'multiplicative noise', 'spde'),
    references=('Tian et al. 2022', 'Henkel, Hinrichsen, and Lübeck 2008', 'Bonachela and Muñoz 2009'),
    defaults={'until': 100.0, 'dt': 1.0, 'solver': EulerMaruyamaSolver(dt=0.01)},
)
def directed_percolation_langevin(
    shape: Sequence[int] = (64, 64),
    *,
    a: float = -1.0,
    b: float = 1.0,
    c: float = 1.0,
    d: float = 1.0,
    e: float = 0.2,
    f: float = 0.0,
    g: float = 0.0,
    energy: float = 1.2,
    density: float = 0.5,
    random_state: int | np.random.Generator | None = 0,
) -> System:
    """Return the Langevin field theory of an absorbing-state transition, an active density fed by an energy field.

    The active density `rho` grows at the local rate `a + b nu`, saturates through `-c rho^2`, spreads with the
    diffusivity `d`, and fluctuates with the multiplicative noise `e sqrt(rho)`, which vanishes in the absorbing state
    `rho = 0`. The energy field `nu` diffuses, gains the drive `f`, and loses the dissipation `g rho`, so
    `rho_t = (a + b nu) rho - c rho^2 + d laplacian(rho) + e sqrt(rho) xi` and `nu_t = laplacian(nu) + f - g rho`
    (Tian et al., Eqs. 3-4). Both fields stay nonnegative. Without drive and dissipation the energy is a fixed control
    parameter: below the threshold `nu_c = -a / b` activity dies out, and above it the density settles near
    `(a + b nu) / c`.

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

        a:
            The bare growth rate of the activity.

        b:
            The gain of the growth rate per unit of energy.

        c:
            The saturation of the activity.

        d:
            The diffusivity of the activity.

        e:
            The amplitude of the multiplicative noise.

        f:
            The drive of the energy.

        g:
            The dissipation of the energy per unit of activity.

        energy:
            The initial energy at every site.

        density:
            The mean initial activity, drawn uniformly between 0 and twice this value.

        random_state:
            The seed or generator of the initial activity.

    Returns:
        system:
            The model, with the activity `neurons.rho` and the energy `neurons.nu`.

    Examples:
        >>> from skcomplex.models.physics.criticality import directed_percolation_langevin
        >>> float(directed_percolation_langevin((8, 8), energy=0.5).elements[0].values['nu'].mean())
        0.5
    """
    shape = tuple(shape)
    rng = np.random.default_rng(random_state)
    values = {
        'rho': rng.uniform(0.0, 2 * density, shape),
        'nu': np.full(shape, energy),
        'a': a,
        'b': b,
        'c': c,
        'd': d,
        'e': e,
        'f': f,
        'g': g,
    }
    return System(
        elements=[FieldElement('neurons', element=ActiveFieldElement, space=LatticeSpace(shape), values=values)],
    )

Components

class ActiveFieldElement(Element):
    """Site of the Langevin equation of directed percolation with a conserved field, an activity `rho` and a field `nu`.

    The activity grows at the rate `a + b nu`, saturates, diffuses, and fluctuates with the multiplicative noise
    `e sqrt(rho)` of an absorbing state, `rho_t = (a + b nu) rho - c rho^2 + d laplacian(rho) + e sqrt(rho) eta`. The
    field diffuses, gains `f`, and is consumed by the activity, `nu_t = laplacian(nu) + f - g rho`. Both stay
    nonnegative.

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

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

        rho:
            The initial activity.

        nu:
            The initial field.

        a:
            The linear growth rate of the activity.

        b:
            The growth of the activity per unit of the field.

        c:
            The saturation of the activity.

        d:
            The diffusion coefficient of the activity.

        e:
            The amplitude of the noise.

        f:
            The input of the field.

        g:
            The consumption of the field by the activity.

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

    Examples:
        >>> from skcomplex.models.physics.criticality import ActiveFieldElement
        >>> ActiveFieldElement('site').e
        0.2
    """

    def __init__(
        self,
        label: str,
        visual: VisualSpec | None = None,
        *,
        rho: Annotated[float, StateSpec()] = 0.5,
        nu: Annotated[float, StateSpec()] = 1.0,
        a: Annotated[float, ParameterSpec()] = -1.0,
        b: Annotated[float, ParameterSpec()] = 1.0,
        c: Annotated[float, ParameterSpec()] = 1.0,
        d: Annotated[float, ParameterSpec()] = 1.0,
        e: Annotated[float, ParameterSpec()] = 0.2,
        f: Annotated[float, ParameterSpec()] = 0.0,
        g: Annotated[float, ParameterSpec()] = 0.0,
    ) -> None:
        super().__init__(label, visual=visual)
        self.rho = rho
        self.nu = nu
        self.a = a
        self.b = b
        self.c = c
        self.d = d
        self.e = e
        self.f = f
        self.g = g

    def _get_laws(self, namespace: SimpleNamespace) -> list[BaseLaw]:
        """Return the rates, the noise, and the nonnegative bounds of the activity and the field.

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

        Returns:
            laws:
                The laws of `rho` and `nu`.
        """
        s = namespace
        return [
            RateLaw(s.rho, (s.a + s.b * s.nu) * s.rho - s.c * s.rho**2 + s.d * laplacian(s.rho)),
            NoiseLaw(s.rho, s.e * sqrt(s.rho)),
            RateLaw(s.nu, laplacian(s.nu) + s.f - s.g * s.rho),
            BoundaryLaw(s.rho, (s.rho + abs(s.rho)) / 2),
            BoundaryLaw(s.nu, (s.nu + abs(s.nu)) / 2),
        ]

Equations

neurons.rho[i]: diff(rho_neurons(t), t) == -c_neurons*rho_neurons(t)^2 + d_neurons*laplacian(rho_neurons(t)) + (b_neurons*nu_neurons(t) + a_neurons)*rho_neurons(t)
neurons.nu[i]: diff(nu_neurons(t), t) == -g_neurons*rho_neurons(t) + f_neurons + laplacian(nu_neurons(t))