The PDE Definition Interface

PDE discretization packages bridge high-level equation descriptions to the solver-ready problems used throughout the SciML ecosystem. The shared interface is based on dispatch: a package extends discretize for each supported pair of PDE representation and discretizer, and may also extend symbolic_discretize to expose the intermediate representation.

SciMLBase does not prescribe one PDE equation language, domain type, boundary condition syntax, or spatial discretization. Those semantics belong to the packages that own the high-level representation and discretizer. This keeps the result of discretization compatible with ordinary ODE, nonlinear, optimization, linear, and other SciML solvers without requiring those solvers to understand PDE-specific syntax.

Core Extension Contract

A discretizer package implements:

SciMLBase.discretize(sys, discretizer, args...; kwargs...)

for each supported sys and discretizer pair. The method must return a solver-ready SciML problem, such as an ODEProblem, NonlinearProblem, or OptimizationProblem, or another documented problem type accepted by the intended solver. It should forward relevant problem-construction keyword arguments, validate unsupported equations, domains, and boundary conditions, and preserve enough metadata to reconstruct the PDE solution.

AbstractDiscretization is an optional common marker for discretizer algorithms. Implementing the interface does not require subtyping it: a package may own a more specific public algorithm hierarchy and still participate by extending SciMLBase.discretize.

A package may additionally implement:

SciMLBase.symbolic_discretize(sys, discretizer, args...; kwargs...)

The return type is part of the discretizer's public contract. It may be a lowered symbolic system, a tuple containing a system and time span, a collection of generated operators, or a diagnostic representation such as loss functions. It need not be a ModelingToolkit AbstractSystem, but it should describe the same discretization used by discretize and retain the information needed for inspection or downstream transformations.

PDE Representations

SciMLBase defines AbstractPDEProblem and the lightweight PDEProblem wrapper. PDEProblem stores a package-specific PDE representation together with extrapolation and spatial metadata; SciMLBase does not interpret those fields.

High-level packages may instead define their own representation and dispatch directly on it. For example, ModelingToolkit owns PDESystem, including its equations, independent and dependent variables, parameters, domains, and boundary or initial conditions. A downstream discretizer should document which representation types it accepts rather than assuming every PDE enters through PDEProblem.

SciMLBase.PDEProblem — Type
struct PDEProblem{P, E, S} <: SciMLBase.AbstractPDEProblem

Concrete wrapper for PDE problems before discretization.

PDEProblem stores a symbolic or package-specific PDE problem object together with extrapolation and spatial-domain metadata used by discretization backends. SciMLBase owns only this lightweight wrapper; concrete PDE semantics and the conversion to ODE, nonlinear, optimization, or linear problems are supplied by downstream discretization packages through discretize and symbolic_discretize.

Fields

  • prob::Any

  • extrapolation::Any

  • space::Any

source

Domains and Boundary Conditions

Domain and boundary-condition syntax is owned by the high-level PDE and discretizer packages. SciMLBase does not require a universal interval, mesh, geometry, or boundary-condition type. A discretizer's public interface should document:

  • supported domain dimensions, geometries, coordinate systems, and grid or sampling specifications;
  • supported initial, Dirichlet, Neumann, Robin, periodic, interface, and other boundary-condition classes;
  • how conditions are validated, eliminated, embedded, or transformed; and
  • restrictions imposed by the selected discretization, including required regularity, compatible derivative orders, and unsupported combinations.

Invalid or unsupported input should produce an actionable error before the generated numerical problem is passed to a solver.

Discretization Functions

SciMLBase.discretize — Function
discretize(sys, discretizer, args...; kwargs...)

Transform a symbolic or high-level problem description into a solver-ready problem.

Discretizer packages implement SciMLBase.discretize for the system/problem types and discretization algorithms they own. A method should return an AbstractSciMLProblem or another documented problem type accepted by the intended solver. Implementations should document the accepted input system type, the meaning of discretizer, the generated problem family, forwarded problem-construction keywords, and any metadata needed to map numerical solutions back to the original variables and domains.

Use symbolic_discretize when the caller needs a diagnostic or symbolic view of the discretized system rather than the solver-ready problem.

source
SciMLBase.symbolic_discretize — Function
symbolic_discretize(sys, discretizer, args...; kwargs...)

Return a symbolic or diagnostic representation of a discretization.

Discretizer packages implement SciMLBase.symbolic_discretize when they can expose lowered equations, operators, grids, boundary-condition handling, or other intermediate artifacts without constructing only the final solver-ready problem. The result should correspond to the same mathematical discretization used by discretize, but may preserve additional information useful for inspection, debugging, code generation, or downstream transformations. Its type is package-defined and need not subtype a symbolic-system abstraction.

source

Discretization Metadata and Solution Wrapping

Discretizers that return PDE-aware solutions can subtype AbstractDiscretizationMetadata. Use AbstractDiscretizationMetadata{Val(true)} when the generated solution has a saved time axis and AbstractDiscretizationMetadata{Val(false)} when it does not. wrap_sol(sol, metadata) routes these to PDETimeSeriesSolution(sol, metadata) and PDENoTimeSolution(sol, metadata), respectively.

The package that owns the metadata must define the corresponding outer solution constructor. Its metadata should retain the original variables and domains, the layout of generated state variables, and any grids, transformations, or interpolants needed to map the solver output back to the PDE representation. If the wrapper is callable, metadata-specific methods should define the supported evaluation coordinates, interpolation behavior, and extrapolation behavior.

SciMLBase.PDETimeSeriesSolution — Type
struct PDETimeSeriesSolution{T, N, uType, Disc, Sol, DType, tType, domType, ivType, dvType, P, A, IType, S} <: SciMLBase.AbstractPDETimeSeriesSolution{T, N, uType, Disc}

Solution to a PDE, solved from an ODEProblem generated by a discretizer.

PDETimeSeriesSolution is the concrete wrapper for time-dependent PDE discretizations whose metadata subtypes AbstractDiscretizationMetadata{Val(true)}. Discretizer packages usually define an outer constructor SciMLBase.PDETimeSeriesSolution(sol, metadata::D) for their metadata type D; the generic wrap_sol dispatch calls that constructor after the underlying ODE or time-dependent solve has finished.

Fields

  • u: the solution to the PDE, as a dictionary of symbols to Arrays of values. The Arrays are of the same shape as the domain of the PDE. Time is always the first axis.
  • original_sol: The original ODESolution that was used to generate this solution.
  • t: the time points corresponding to the saved values of the ODE solution.
  • ivdomain: The full list of domains for the independent variables. May be a grid for a discrete solution, or a vector/tuple of tuples for a continuous solution.
  • ivs: The list of independent variables for the solution.
  • dvs: The list of dependent variables for the solution.
  • disc_data: Metadata about the discretization process and type.
  • prob: The ODEProblem that was used to generate this solution.
  • alg: The algorithm used to solve the ODEProblem.
  • interp: Interpolations for the solution.
  • retcode: the return code from the solver. Used to determine whether the solver solved successfully, whether it terminated early due to a user-defined callback, or whether it exited due to an error. For more details, see the return code documentation.
  • stats: statistics of the solver, such as the number of function evaluations required.
source
SciMLBase.PDENoTimeSolution — Type
struct PDENoTimeSolution{T, N, uType, Disc, Sol, domType, ivType, dvType, P, A, IType, S} <: SciMLBase.AbstractPDENoTimeSolution{T, N, uType, Disc}

Solution to a PDE, solved from a NonlinearProblem generated by a discretizer.

PDENoTimeSolution is the concrete wrapper for time-independent PDE discretizations whose metadata subtypes AbstractDiscretizationMetadata{Val(false)}. Discretizer packages usually define an outer constructor SciMLBase.PDENoTimeSolution(sol, metadata::D) for their metadata type D; the generic wrap_sol dispatch calls that constructor after the underlying nonlinear, linear, optimization, or stationary solve has finished.

Fields

  • u: the solution to the PDE, as a dictionary of symbols to Arrays of values. The Arrays are of the same shape as the domain of the PDE. Time is always the first axis.
  • original_sol: The original NonlinearSolution that was used to generate this solution.
  • ivdomain: The full list of domains for the independent variables. May be a grid for a discrete solution, or a vector/tuple of tuples for a continuous solution.
  • ivs: The list of independent variables for the solution.
  • dvs: The list of dependent variables for the solution.
  • disc_data: Metadata about the discretization process and type.
  • prob: The NonlinearProblem that was used to generate this solution.
  • alg: The algorithm used to solve the NonlinearProblem.
  • interp: Interpolations for the solution.
  • retcode: The return code from the solver. Used to determine whether the solver solved successfully (sol.retcode === ReturnCode.Success), whether it terminated due to a user-defined callback (sol.retcode === ReturnCode.Terminated), or whether it exited due to an error. For more details, see the return code section of the ODEProblem.jl documentation.
source
SciMLBase.wrap_sol — Function
wrap_sol(sol)
wrap_sol(sol, problem_type_or_metadata)

Return sol or wrap it in a higher-level SciML solution container.

Solvers call wrap_sol(sol) after constructing a low-level solution. When sol.prob is an AbstractSciMLProblem, the default implementation queries problem_type and dispatches to wrap_sol(sol, problem_type_or_metadata) when the result is not nothing. Problem-family packages extend the two-argument form when a generated solver solution should be returned as a more specific public solution type.

The fallback two-argument method returns sol unchanged. PDE discretizer packages extend the metadata path by defining constructors such as PDETimeSeriesSolution(sol, metadata::D) or PDENoTimeSolution(sol, metadata::D) for their concrete discretization metadata type.

source
wrap_sol(sol, metadata::AbstractDiscretizationMetadata)

Wrap a solver solution produced by a PDE discretizer in the appropriate PDE solution type.

Metadata with parameter Val(true) is routed to PDETimeSeriesSolution(sol, metadata). Metadata with parameter Val(false) is routed to PDENoTimeSolution(sol, metadata). Discretizer packages are responsible for implementing those outer constructors for their concrete metadata types and for filling the wrapper fields from the discretized solve.

source

Reverse-mode differentiation

A discretizer whose metadata subtypes AbstractDifferentiableDiscretizationMetadata gets reverse-mode rules for sol[sym], sol[sym, inds...] (sol[sym][inds...] in the interface) and sol(args...; dv), for ChainRulesCore (Zygote), Mooncake and Enzyme. They route the cotangent back to sol.original_sol, whose rules the solver packages provide, and ask the discretizer for the map from a field or an evaluation back to the solver's solution through two hooks, which are plain code with no AD types. The package that owns the metadata implements them; a subtype without them errors, so a solution never returns a wrong gradient silently. Only original_sol carries a gradient back: a cotangent that reaches the wrapper through sol.u or sol.interp is an error, and so is one through sol.prob with ChainRulesCore and Mooncake, while the other fields of the wrapper are constants of the wrapping. Enzyme does not cover observed values yet, since the solver's Enzyme rule has no way to take the parameter part of their cotangent. The metadata is a constant of the solve, and metadata that subtypes AbstractDiscretizationMetadata directly gets none of these rules.

SciMLBase.AbstractDifferentiableDiscretizationMetadata — Type
abstract type AbstractDifferentiableDiscretizationMetadata{hasTime} <: SciMLBase.AbstractDiscretizationMetadata{hasTime}

Metadata of a discretizer whose wrapped solutions differentiate in reverse mode: sol[sym], sol[sym, inds...] and sol(args...; dv) get rules in the ChainRulesCore, Mooncake and Enzyme extensions that route the cotangent to sol.original_sol, whose rules the solver packages provide, through pde_index_cotangent and pde_call_cotangent, which the discretizer implements for its metadata type. The solutions of metadata that subtypes AbstractDiscretizationMetadata directly get none of these rules.

source
SciMLBase.pde_index_cotangent — Function
pde_index_cotangent(sol::AbstractPDESolution, sym, Δ)

Reverse-mode hook behind sol[sym] and sol[sym, inds...] (sol[sym][inds...] in the interface) for a solution whose metadata subtypes AbstractDifferentiableDiscretizationMetadata: how a cotangent Δ of the array sol[sym] goes back to the solver's solution sol.original_sol. It returns nothing when sym does not depend on the solve (a grid, for instance), and otherwise a named tuple (; u, observed):

  • u: the cotangent of sol.original_sol.u, of the same shape, or nothing;
  • observed: a collection of terms (f, k, δ) for the values the solution observes rather than stores, each the cotangent δ of f(sol.original_sol.u[k], p, sol.original_sol.t[k]) with p = parameter_values(sol.original_sol), or terms (f, δ) of f(sol.original_sol.u, p) when sol.original_sol is not a time series (an optimization solution, for instance).

The hook is plain code, with no AD types: the AD extensions pull the observed terms back with the AD system in use and hand the cotangent to the solver's rules for original_sol. Only the map from a field back to the solver's solution is the discretizer's, implemented here for its metadata type D. sym is whatever the solution was indexed with, and the metadata and the functions f are constants of the solve. The fallback errors, and a hook that cannot provide a cotangent throws as well, so that a solution never returns a wrong gradient silently.

source
SciMLBase.pde_call_cotangent — Function
pde_call_cotangent(sol::AbstractPDESolution, args, dv, Δ)

Reverse-mode hook behind sol(args...; dv): a tuple of the cotangent of sol.original_sol, in the form pde_index_cotangent returns it, and the cotangents of args, for a cotangent Δ of the evaluation at args. dv is the keyword as it was passed to the call, or nothing when it was not, and Δ then has the shape of what the call returns in that case (a collection over sol.dvs in the documented interface). The cotangents of args are one per argument: that of a coordinate, of the shape of the argument when it is an array, or an error when the discretizer cannot provide it, and nothing for an index or a colon. The fallback errors.

source

Transformations and Analysis

Equation simplification, index reduction, domain decomposition, coordinate transformations, sparsity analysis, and operator construction belong to the packages that own the PDE representation or discretizer. When such a transformation changes variables or state layout, the discretizer must preserve the map needed for solution reconstruction. symbolic_discretize is the common inspection hook for exposing the lowered equations, operators, grids, loss functions, or other package-defined intermediate data.

Downstream Patterns

MethodOfLines.jl extends the interface for MOLFiniteDifference. Its symbolic path returns a semi-discrete system and time span; its solver-ready path constructs an ODEProblem for time-dependent systems or a NonlinearProblem for stationary systems. Its metadata records the discrete space and maps the numerical solution back to the original PDE variables and grids.

NeuralPDE.jl extends the interface for PhysicsInformedNN. Its symbolic path returns a representation containing the generated loss functions and parameters, while discretize constructs an OptimizationProblem. This demonstrates why symbolic_discretize has a package-defined return type and why the shared contract is dispatch-based rather than tied to a single discretizer hierarchy.