Python package for solving partial differential equations with a focus on ease of use and performance
—
PDE classes provide mathematical descriptions of partial differential equations with automatic operator compilation and built-in support for common equations in physics and dynamical systems.
General PDE class for defining custom differential equations using mathematical expressions.
class PDE:
def __init__(self, rhs, *, noise=None, is_sde_value=None):
"""
Initialize generic PDE.
Parameters:
- rhs: callable or dict, right-hand side of PDE
- noise: float or FieldBase, noise strength for stochastic PDEs
- is_sde_value: bool, whether noise affects the value directly
"""
def solve(self, initial_state, t_range, *, dt=None, solver="auto",
tracker=None, bc=None, **kwargs):
"""
Solve the PDE with given initial conditions.
Parameters:
- initial_state: FieldBase, initial field configuration
- t_range: float or array-like, time range for integration
- dt: float, time step (auto-determined if None)
- solver: str or SolverBase, time integration method
- tracker: TrackerBase or list, simulation tracking
- bc: boundary conditions
- kwargs: additional solver parameters
Returns:
FieldBase: Final field state
"""
def evolution_rate(self, state, t=0):
"""
Calculate time derivative of state.
Parameters:
- state: FieldBase, current field state
- t: float, current time
Returns:
FieldBase: Time derivative
"""
@property
def expressions(self):
"""dict: Mathematical expressions defining the PDE"""
def make_post_step_hook(self, state):
"""
Create hook function called after each time step.
Parameters:
- state: FieldBase, current field state
Returns:
tuple: (hook_function, hook_data)
"""Standard diffusion equation with optional convection and reaction terms.
class DiffusionPDE:
def __init__(self, diffusivity=1, *, bc=None, noise=0, rng=None):
"""
Initialize diffusion PDE: ∂c/∂t = D∇²c + η
Parameters:
- diffusivity: float, diffusion coefficient
- bc: boundary conditions
- noise: float, noise strength (default: 0)
- rng: numpy random generator for noise (optional)
"""
@property
def diffusivity(self):
"""Diffusion coefficient"""
def get_initial_condition(self, grid, **kwargs):
"""
Get suitable initial condition for the grid.
Parameters:
- grid: GridBase, spatial grid
- kwargs: additional parameters
Returns:
ScalarField: Initial condition
"""Allen-Cahn equation for phase separation dynamics.
class AllenCahnPDE:
def __init__(self, a=1, b=1, *, noise=None, bc=None):
"""
Initialize Allen-Cahn PDE: ∂φ/∂t = a∇²φ + bφ - bφ³ + η
Parameters:
- a: float, interface energy parameter
- b: float, bulk energy parameter
- noise: float or FieldBase, noise strength
- bc: boundary conditions
"""
def get_initial_condition(self, grid, **kwargs):
"""
Get random initial condition with appropriate statistics.
Parameters:
- grid: GridBase, spatial grid
- kwargs: parameters like 'amplitude' for noise level
Returns:
ScalarField: Random initial condition
"""Cahn-Hilliard equation for conserved phase separation.
class CahnHilliardPDE:
def __init__(self, a=1, b=1, *, noise=None, bc=None):
"""
Initialize Cahn-Hilliard PDE: ∂φ/∂t = ∇²[aφ - bφ³ - ∇²φ] + η
Parameters:
- a: float, linear coefficient
- b: float, nonlinear coefficient
- noise: float or FieldBase, noise strength
- bc: boundary conditions
"""Wave equation for oscillatory dynamics.
class WavePDE:
def __init__(self, speed=1, *, noise=None, bc=None):
"""
Initialize wave PDE: ∂²u/∂t² = c²∇²u + η
Parameters:
- speed: float, wave propagation speed
- noise: float or FieldBase, noise strength
- bc: boundary conditions
"""
def get_initial_condition(self, u, v=None):
"""
Create suitable initial condition with position and velocity.
Parameters:
- u: ScalarField, initial position field
- v: ScalarField, optional initial velocity field (zeros if None)
Returns:
FieldCollection: Initial [position, velocity] fields
"""Kuramoto-Sivashinsky equation exhibiting spatiotemporal chaos.
class KuramotoSivashinskyPDE:
def __init__(self, *, noise=None, bc=None):
"""
Initialize Kuramoto-Sivashinsky PDE: ∂u/∂t = -∇²u - ∇⁴u - ½|∇u|² + η
Parameters:
- noise: float or FieldBase, noise strength
- bc: boundary conditions (typically periodic)
"""Swift-Hohenberg equation for pattern formation.
class SwiftHohenbergPDE:
def __init__(self, a=1, b=1, *, noise=None, bc=None):
"""
Initialize Swift-Hohenberg PDE: ∂u/∂t = au - (1+∇²)²u - bu³ + η
Parameters:
- a: float, linear instability parameter
- b: float, nonlinear saturation parameter
- noise: float or FieldBase, noise strength
- bc: boundary conditions
"""Kardar-Parisi-Zhang equation for interface growth.
class KPZInterfacePDE:
def __init__(self, diffusivity=1, lambda_=1, *, noise=1, bc=None):
"""
Initialize KPZ interface PDE: ∂h/∂t = D∇²h + λ|∇h|² + η
Parameters:
- diffusivity: float, surface tension coefficient
- lambda_: float, nonlinear growth coefficient
- noise: float, noise strength
- bc: boundary conditions
"""Direct solvers for elliptic PDEs like Laplace and Poisson equations.
def solve_laplace_equation(grid, bc, *, solver="auto"):
"""
Solve Laplace equation ∇²u = 0.
Parameters:
- grid: GridBase, spatial grid
- bc: boundary conditions
- solver: str, solution method ("scipy" or "numba")
Returns:
ScalarField: Solution field
"""
def solve_poisson_equation(grid, rhs, bc, *, solver="auto"):
"""
Solve Poisson equation ∇²u = f.
Parameters:
- grid: GridBase, spatial grid
- rhs: ScalarField or callable, right-hand side
- bc: boundary conditions
- solver: str, solution method
Returns:
ScalarField: Solution field
"""Base class providing common PDE functionality.
class PDEBase:
@property
def complex_valued(self):
"""bool: Whether PDE involves complex values"""
@property
def is_sde(self):
"""bool: Whether this is a stochastic differential equation"""
def make_post_step_hook(self, state):
"""
Create hook function called after each time step.
Parameters:
- state: FieldBase, current field state
Returns:
tuple: (hook_function, hook_data)
"""
def evolution_rate(self, state, t=0):
"""
Calculate time derivative of state (abstract method).
Parameters:
- state: FieldBase, current field state
- t: float, current time
Returns:
FieldBase: Time derivative
"""
def check_rhs_consistency(self, state, t=0):
"""
Check consistency of right-hand side calculation.
Parameters:
- state: FieldBase, current field state
- t: float, current time
Returns:
bool: True if consistent
"""
def make_pde_rhs(self, state, backend='auto'):
"""
Create compiled right-hand side function.
Parameters:
- state: FieldBase, field state for compilation
- backend: str, computational backend
Returns:
Callable: Compiled RHS function
"""
def noise_realization(self, state, t=0, label=None):
"""
Generate noise realization for stochastic PDEs.
Parameters:
- state: FieldBase, current field state
- t: float, current time
- label: str, optional label for noise field
Returns:
FieldBase: Noise field
"""
def make_sde_rhs(self, state, backend='auto'):
"""
Create compiled SDE right-hand side function.
Parameters:
- state: FieldBase, field state for compilation
- backend: str, computational backend
Returns:
Callable: Compiled SDE RHS function
"""
def get_state_serializer(self, **kwargs):
"""
Get serializer for PDE state data.
Parameters:
- kwargs: serialization options
Returns:
Callable: State serialization function
"""
def check_initial_condition_compatibility(self, initial_state):
"""
Check compatibility of initial condition.
Parameters:
- initial_state: FieldBase, initial field state
Raises:
ValueError: If incompatible
"""import pde
import numpy as np
# Create grid and initial condition
grid = pde.UnitGrid([64, 64], periodic=False)
state = pde.ScalarField.random_uniform(grid, 0.2, 0.3)
# Solve diffusion equation
eq = pde.DiffusionPDE(diffusivity=0.1)
result = eq.solve(state, t_range=10.0, dt=0.01)
print(f"Initial average: {state.average:.3f}")
print(f"Final average: {result.average:.3f}")import pde
# Define custom reaction-diffusion PDE
def my_pde_rhs(state, t):
c = state
return 0.1 * c.laplace("auto_periodic_neumann") + c - c**3
# Create PDE
grid = pde.UnitGrid([32], periodic=True)
initial = pde.ScalarField.random_uniform(grid, -0.1, 0.1)
eq = pde.PDE(my_pde_rhs)
result = eq.solve(initial, t_range=50)import pde
import matplotlib.pyplot as plt
# Set up 1D wave equation
grid = pde.UnitGrid([128], periodic=False)
eq = pde.WavePDE(speed=1.0)
# Gaussian initial condition
initial = eq.get_initial_condition(grid)
initial[0] = pde.ScalarField.from_expression(grid, "exp(-(x-0.3)**2/0.01)")
# Solve with progress tracking
tracker = pde.PlotTracker(interrupts=0.1, filename="wave_evolution.mp4")
result = eq.solve(initial, t_range=2.0, tracker=tracker)
print("Wave simulation complete - check wave_evolution.mp4")import pde
# Solve Poisson equation with source term
grid = pde.CartesianGrid([[-1, 1], [-1, 1]], [64, 64])
# Source term: Gaussian
source = pde.ScalarField.from_expression(grid, "exp(-(x**2 + y**2)/0.1)")
# Dirichlet boundary conditions
bc = {"value": 0}
# Solve Poisson equation
solution = pde.solve_poisson_equation(grid, source, bc)
print(f"Source integral: {source.integral():.3f}")
print(f"Solution range: [{solution.data.min():.3f}, {solution.data.max():.3f}]")Install with Tessl CLI
npx tessl i tessl/pypi-py-pde