Source code for pde.pdes.kuramoto_sivashinsky

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

.. codeauthor:: David Zwicker <david.zwicker@ds.mpg.de>
"""

from __future__ import annotations

from typing import TYPE_CHECKING

from ..fields import ScalarField
from ..grids.boundaries import set_default_bc
from ..tools.docstrings import fill_in_docstring
from .base import SDEBase, expr_prod

if TYPE_CHECKING:
    from collections.abc import Callable

    import numpy as np

    from ..grids.boundaries.axes import BoundariesData
    from ..tools.typing import NumericArray


[docs] class KuramotoSivashinskyPDE(SDEBase): r"""The Kuramoto-Sivashinsky equation. The mathematical definition is .. math:: \partial_t u = -\nu \nabla^4 u - \nabla^2 u - \frac{1}{2} \left(\nabla h\right)^2 + \eta(\boldsymbol r, t) where :math:`u` is the height of the interface in Monge parameterization. The dynamics are governed by the parameters :math:`\nu` , while :math:`\eta` is Gaussian white noise, whose strength is controlled by the `noise` argument. """ explicit_time_dependence = False default_bc = "auto_periodic_neumann" """Default boundary condition used when no specific conditions are chosen.""" @fill_in_docstring def __init__( self, nu: float = 1, *, bc: BoundariesData | None = None, bc_lap: BoundariesData | None = None, noise: float = 0, rng: np.random.Generator | None = None, ): r""" Args: nu (float): Parameter :math:`\nu` for the strength of the fourth-order term bc: The boundary conditions applied to the field. {ARG_BOUNDARIES} bc_lap: The boundary conditions applied to the second derivative of the scalar field :math:`c`. If `None`, the same boundary condition as `bc` is chosen. Otherwise, this supports the same options as `bc`. 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) self.nu = nu self.bc = set_default_bc(bc, self.default_bc) self.bc_lap = self.bc if bc_lap is None else bc_lap @property def expression(self) -> str: """str: the expression of the right hand side of this PDE""" expr = f"c + {expr_prod(self.nu, '∇²c')}" return f"-∇²({expr}) - 0.5 * |∇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 """ if not isinstance(state, ScalarField): msg = "`state` must be ScalarField" raise TypeError(msg) state_lap = state.laplace(bc=self.bc, args={"t": t}) result = ( -self.nu * state_lap.laplace(bc=self.bc_lap, args={"t": t}) - state_lap - 0.5 * state.gradient_squared(bc=self.bc, args={"t": t}) ) result.label = "evolution rate" return result # type: ignore
[docs] def make_pde_rhs_numba( # type: ignore self, state: ScalarField ) -> Callable[[NumericArray, float], NumericArray]: """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. """ import numba as nb arr_type = nb.typeof(state.data) signature = arr_type(arr_type, nb.double) nu_value = self.nu laplace = state.grid.make_operator("laplace", bc=self.bc) laplace2 = state.grid.make_operator("laplace", bc=self.bc_lap) gradient_sq = state.grid.make_operator("gradient_squared", bc=self.bc) @nb.jit(signature) def pde_rhs(state_data: NumericArray, t: float): """Compiled helper function evaluating right hand side.""" result = -laplace(state_data, args={"t": t}) result += nu_value * laplace2(result, args={"t": t}) result -= 0.5 * gradient_sq(state_data, args={"t": t}) return result return pde_rhs # type: ignore