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))