Nonreciprocal Cahn-Hilliard
The nonreciprocal Cahn-Hilliard equations, a demixing field coupled to a diffusing one.
- Name:
nonreciprocal_cahn_hilliard - Field:
physics/pattern_formation - Formalism: ordinary differential equations
- References: Brauns and Marchetti 2024; Saha, Agudo-Canalejo, and Golestanian 2020; Frohoff-Hülsmann 2021
Factory
@register_model(
field='physics/pattern_formation',
tags=('field', 'conserved', 'nonreciprocal', 'traveling waves', 'phase separation', 'pde'),
references=('Brauns and Marchetti 2024', 'Saha, Agudo-Canalejo, and Golestanian 2020', 'Frohoff-Hülsmann 2021'),
defaults={'until': 400.0, 'dt': 2.0},
)
def nonreciprocal_cahn_hilliard(
shape: Sequence[int] = (128,),
*,
length: float = 40.0,
chi: float = 0.0,
alpha: float = 0.3,
D: float = 0.1,
kappa: float = 1.0,
random_state: int | np.random.Generator | None = 0,
) -> System:
"""Return the nonreciprocal Cahn-Hilliard equations, a demixing field coupled to a diffusing one.
The conserved field `phi` phase separates and the conserved field `psi` diffuses, and they are coupled through
cross-diffusion, `phi_t = laplacian(-phi + phi^3 - kappa laplacian(phi) + (chi + alpha) psi)` and
`psi_t = laplacian(D psi + (chi - alpha) phi)`, the minimal model of Brauns and Marchetti. The coupling `chi` is
reciprocal and `alpha` is not. When `alpha^2 - chi^2` exceeds `D^2`, the unstable band of the dispersion relation
of the mixed state reaches the propagating modes, and the demixed domains travel instead of coarsening.
Args:
shape:
The number of sites along every axis of the periodic lattice.
length:
The side of the domain.
chi:
The reciprocal part of the cross-diffusion.
alpha:
The nonreciprocal part of the cross-diffusion.
D:
The diffusivity of `psi`.
kappa:
The stiffness of the interfaces of `phi`.
random_state:
The seed or generator of the small random initial fields around the mixed state.
Returns:
system:
The model, with the demixing field `mixture.phi` and the diffusing field `mixture.psi`.
Examples:
>>> from skcomplex.models.physics.pattern_formation import nonreciprocal_cahn_hilliard
>>> nonreciprocal_cahn_hilliard((16,), alpha=0.5).elements[0].values['alpha']
0.5
"""
shape = tuple(shape)
rng = np.random.default_rng(random_state)
values = {
'phi': 0.01 * rng.uniform(-1.0, 1.0, shape),
'psi': 0.01 * rng.uniform(-1.0, 1.0, shape),
'chi': chi,
'alpha': alpha,
'D': D,
'kappa': kappa,
}
space = LatticeSpace(shape, spacing=length / shape[0])
return System(elements=[FieldElement('mixture', element=NonreciprocalMixtureElement, space=space, values=values)])
Components
class NonreciprocalMixtureElement(Element):
"""Site of two conserved fields coupled nonreciprocally, a demixing field `phi` and a diffusing field `psi`.
The fields follow `phi_t = laplacian(-phi + phi^3 - kappa laplacian(phi) + (chi + alpha) psi)` and
`psi_t = laplacian(D psi + (chi - alpha) phi)`. The reciprocal coupling `chi` derives from a free energy, and the
nonreciprocal coupling `alpha` does not, so above a threshold the demixed domains travel.
Args:
label:
The label of the site or the field.
visual:
How the site is drawn, or `None` for `default_visual_`.
phi:
The initial demixing field.
psi:
The initial diffusing field.
chi:
The reciprocal coupling.
alpha:
The nonreciprocal coupling.
D:
The diffusion coefficient of `psi`.
kappa:
The stiffness of the interfaces of `phi`.
Attributes:
default_visual_:
The visual of the site when it sets none.
Examples:
>>> from skcomplex.models.physics.pattern_formation import NonreciprocalMixtureElement
>>> NonreciprocalMixtureElement('site').alpha
0.3
"""
def __init__(
self,
label: str,
visual: VisualSpec | None = None,
*,
phi: Annotated[float, StateSpec()] = 0.0,
psi: Annotated[float, StateSpec()] = 0.0,
chi: Annotated[float, ParameterSpec()] = 0.0,
alpha: Annotated[float, ParameterSpec()] = 0.3,
D: Annotated[float, ParameterSpec()] = 0.1,
kappa: Annotated[float, ParameterSpec()] = 1.0,
) -> None:
super().__init__(label, visual=visual)
self.phi = phi
self.psi = psi
self.chi = chi
self.alpha = alpha
self.D = D
self.kappa = kappa
def _get_laws(self, namespace: SimpleNamespace) -> list[BaseLaw]:
"""Return the conserved rates of both fields.
Args:
namespace:
The namespace of the symbols the laws can use.
Returns:
laws:
The rates of `phi` and `psi`.
"""
s = namespace
potential = -s.phi + s.phi**3 - s.kappa * laplacian(s.phi) + (s.chi + s.alpha) * s.psi
return [
RateLaw(s.phi, laplacian(potential)),
RateLaw(s.psi, laplacian(s.D * s.psi + (s.chi - s.alpha) * s.phi)),
]
Equations
mixture.phi[i]: diff(phi_mixture(t), t) == laplacian(phi_mixture(t)^3 - kappa_mixture*laplacian(phi_mixture(t)) + (alpha_mixture + chi_mixture)*psi_mixture(t) - phi_mixture(t))
mixture.psi[i]: diff(psi_mixture(t), t) == laplacian(-(alpha_mixture - chi_mixture)*phi_mixture(t) + D_mixture*psi_mixture(t))