Source code for pde.pdes.kpz_interface

The Kardar–Parisi–Zhang (KPZ) equation describing the evolution of an interface

.. codeauthor:: David Zwicker <> 

from typing import Callable, Optional

import numba as nb
import numpy as np

from ..fields import ScalarField
from ..grids.boundaries.axes import BoundariesData
from import fill_in_docstring
from import jit
from .base import PDEBase, expr_prod

[docs]class KPZInterfacePDE(PDEBase): r"""The Kardar–Parisi–Zhang (KPZ) equation The mathematical definition is .. math:: \partial_t h = \nu \nabla^2 h + \frac{\lambda}{2} \left(\nabla h\right)^2 + \eta(\boldsymbol r, t) where :math:`h` is the height of the interface in Monge parameterization. The dynamics are governed by the two parameters :math:`\nu` and :math:`\lambda`, while :math:`\eta` is Gaussian white noise, whose strength is controlled by the `noise` argument. """ explicit_time_dependence = False @fill_in_docstring def __init__( self, nu: float = 0.5, lmbda: float = 1, *, bc: BoundariesData = "auto_periodic_neumann", noise: float = 0, rng: Optional[np.random.Generator] = None, ): r""" Args: nu (float): Parameter :math:`\nu` for the strength of the diffusive term lmbda (float): Parameter :math:`\lambda` for the strenth of the gradient term bc: The boundary conditions applied to the field. {ARG_BOUNDARIES} noise (float): Variance of the (additive) noise term rng (:class:`~numpy.random.Generator`): Random number generator (default: :func:`~numpy.random.default_rng()`) used for stochastic simulations. Note that this random number generator is only used for numpy function, while compiled numba code uses the random number generator of numba. Moreover, in simulations using multiprocessing, setting the same generator in all processes might yield unintended correlations in the simulation results. """ super().__init__(noise=noise, rng=rng) = nu self.lmbda = lmbda self.bc = bc @property def expression(self) -> str: """str: the expression of the right hand side of this PDE""" return expr_prod(, "∇²c") + " + " + expr_prod(self.lmbda, "|∇c|²")
[docs] def evolution_rate( # type: ignore self, state: ScalarField, t: float = 0, ) -> ScalarField: """evaluate the right hand side of the PDE Args: state (:class:`~pde.fields.ScalarField`): The scalar field describing the concentration distribution t (float): The current time point Returns: :class:`~pde.fields.ScalarField`: Scalar field describing the evolution rate of the PDE """ assert isinstance(state, ScalarField), "`state` must be ScalarField" result = * state.laplace(bc=self.bc, args={"t": t}) result += self.lmbda * state.gradient_squared(bc=self.bc, args={"t": t}) result.label = "evolution rate" return result # type: ignore
def _make_pde_rhs_numba( # type: ignore self, state: ScalarField ) -> Callable[[np.ndarray, float], np.ndarray]: """create a compiled function evaluating the right hand side of the PDE Args: state (:class:`~pde.fields.ScalarField`): An example for the state defining the grid and data types Returns: A function with signature `(state_data, t)`, which can be called with an instance of :class:`~numpy.ndarray` of the state data and the time to obtained an instance of :class:`~numpy.ndarray` giving the evolution rate. """ arr_type = nb.typeof( signature = arr_type(arr_type, nb.double) nu_value, lambda_value =, self.lmbda laplace = state.grid.make_operator("laplace", bc=self.bc) gradient_squared = state.grid.make_operator("gradient_squared", bc=self.bc) @jit(signature) def pde_rhs(state_data: np.ndarray, t: float): """compiled helper function evaluating right hand side""" result = nu_value * laplace(state_data, args={"t": t}) result += lambda_value * gradient_squared(state_data, args={"t": t}) return result return pde_rhs # type: ignore