Discretization Workflow
This page describes the complete workflow of symbolic_discretize, the main entry point for converting a PDESystem into a discretized system.
Overview
The symbolic_discretize function orchestrates the entire discretization process through several distinct phases:
- Preprocessing - Handle complex numbers and array variables
- Parsing - Extract and organize variable information
- Validation - Check system compatibility with the discretization
- Transformation (optional) - Modify the system for compatibility
- Discretization Setup - Build discrete space and operators
- Equation Processing - Convert PDEs to discrete equations
- Finalization - Generate the output system
Detailed Workflow
Phase 1: Preprocessing
pdesys, complexmap = handle_complex(pdesys)
cardinalize_eqs!(pdesys)
pdesys, replaced_vars = make_pdesys_compatible(pdesys)handle_complex: Splits complex-valued equations into real and imaginary parts.
Declare complex dependent fields with @variables ψ(..)::Complex before building the PDESystem. Symbolics then keeps conj(ψ(t, x)), real(ψ(t, x)), and imag(ψ(t, x)) symbolic, so handle_complex can split their equations into real and imaginary fields. An untyped field is treated as real while Symbolics constructs the equations, which can simplify those expressions too early. Currently, a PDESystem cannot mix complex-typed fields with real or untyped dependent variables; handle_complex raises an error for that combination.
cardinalize_eqs!: Normalizes equations to the form lhs - rhs ~ 0.
make_pdesys_compatible: Handles array-valued variables, expanding them into scalar equations.
Phase 2: System Parsing
v = VariableMap(pdesys, discretization, replaced_vars = replaced_vars)Creates a VariableMap that stores:
- All dependent and independent variables
- Domain intervals
- Variable argument signatures
- Index mappings
See VariableMap for details.
Phase 3: Validation
interface_errors(pdesys, v, discretization)Checks for fundamental incompatibilities between the PDESystem and the discretization method. Discretization packages should override this to catch unsupported features early.
Phase 4: Boundary Parsing
tspan = t !== nothing ? v.intervals[t] : nothing
bcorders = Dict(map(x -> x => d_orders(x, get_bcs(pdesys)), all_ivs(v)))
boundarymap = parse_bcs(get_bcs(pdesys), v, bcorders)
check_boundarymap(boundarymap, v, discretization)- Extract time span (if time-dependent)
- Find derivative orders in boundary conditions
- Parse boundary conditions into structured
boundarymap - Validate boundaries for the specific discretization
The boundary parsing identifies:
- Which variable each BC applies to
- Which boundary (lower/upper) it is on
- Whether it is a periodic or interface condition
- The derivative order involved
Phase 5: System Transformation (Optional)
if should_transform(pdesys, discretization, boundarymap)
pdesys = transform_pde_system!(v, boundarymap, pdesys, discretization)
endSome discretizations may need to modify the PDESystem. For example:
- Adding auxiliary equations
- Transforming coordinate systems
- Splitting higher-order equations
Phase 6: Discretization Setup
disc_state = construct_disc_state(discretization)
s = construct_discrete_space(v, discretization)
vareqmap = construct_var_equation_mapping(pdeeqs, boundarymap, s, discretization)
orders = Dict(map(x -> x => collect(union(pdeorders[x], bcorders[x])), indvars(v)))
derivweights = construct_differential_discretizer(pdesys, s, discretization, orders)construct_disc_state: Creates a mutable container to accumulate discretized equationsconstruct_discrete_space: Builds the discrete grid/mesh and discretized variablesconstruct_var_equation_mapping: Determines which equation solves for which variableconstruct_differential_discretizer: Pre-computes derivative approximations (e.g., finite difference stencils)
Phase 7: Initial Condition Processing
ics = t === nothing ? [] : mapreduce(u -> boundarymap[u][t], vcat, operation.(depvars(v)))
bcmap = Dict(map(collect(keys(boundarymap))) do u
u => Dict(map(indvars(v)) do x
x => boundarymap[u][x]
end)
end)Separates initial conditions (on time) from spatial boundary conditions.
Phase 8: Equation Discretization Loop
for pde in pdeeqs
# Extract dependent variables from equation
depvars_lhs = get_depvars(pde.lhs, v.depvar_ops)
depvars_rhs = get_depvars(pde.rhs, v.depvar_ops)
depvars = collect(depvars_lhs ∪ depvars_rhs)
depvars = filter(u -> !any(map(x -> x isa Number, arguments(u))), depvars)
# Get the variable this equation solves for
eqvar = get_eqvar(vareqmap, pde)
# Build index map for this equation
args = ivs(eqvar, v)
indexmap = Dict([args[i] => i for i in 1:length(args)])
# Discretize the equation
discretize_equation!(disc_state, pde, vareqmap, eqvar, bcmap,
depvars, s, derivweights, indexmap, discretization)
endFor each PDE:
- Find all dependent variables in the equation
- Determine which variable the equation solves for
- Build an index map for spatial dimensions
- Call
discretize_equation!to add discrete equations todisc_state
The discretize_equation! function is responsible for:
- Converting the PDE to discrete equations (e.g., at interior grid points)
- Handling boundary conditions for this variable
- Adding all generated equations to
disc_state
Phase 9: Finalization
u0 = generate_ic_defaults(ics, s, discretization)
metadata = generate_metadata(s, discretization, pdesys, boundarymap, complexmap)
return generate_system(disc_state, s, u0, tspan, metadata, discretization)generate_ic_defaults: Creates initial condition values for the discrete variablesgenerate_metadata: Builds metadata for solution reconstruction (e.g., grid information)generate_system: Combines everything into the finalODESystem,DAESystem, or other system type
Data Flow Diagram
PDESystem
│
▼
┌─────────────────────────────────────┐
│ Preprocessing │
│ - handle_complex │
│ - cardinalize_eqs! │
│ - make_pdesys_compatible │
└─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Parsing │
│ - VariableMap creation │
│ - Boundary condition parsing │
└─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Setup │
│ - Discrete space construction │
│ - Differential discretizer │
│ - Variable-equation mapping │
└─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Equation Loop │
│ For each PDE: │
│ - discretize_equation! │
│ - Handle BCs │
└─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Finalization │
│ - generate_ic_defaults │
│ - generate_metadata │
│ - generate_system │
└─────────────────────────────────────┘
│
▼
ODESystem / DAESystem / OptimizationSystemKey Extension Points
When implementing a new discretization, the main functions to implement are:
| Function | Purpose | Typical Implementation |
|---|---|---|
construct_discrete_space | Build grid and discrete variables | Create mesh, discretize variables |
construct_differential_discretizer | Prepare derivative approximations | Compute finite difference weights |
discretize_equation! | Convert PDE to discrete equations | Apply stencils, generate equations |
generate_system | Create final symbolic system | Combine equations into ODESystem |
Symbolic Utilities
PDEBase provides several utility functions for working with symbolic expressions during discretization:
Differential Analysis
count_differentials(term, x)- Count derivatives w.r.t. variablexdifferential_order(eq, x)- Get all derivative orders forxin an equationhas_derivatives(term)- Check if term contains any derivativesfind_derivative(term, depvar_op)- Find derivative or dependent variable in expressiond_orders(x, eqs)- Get all derivative orders forxacross equations
Expression Manipulation
split_terms(eq)- Split equation into individual termssplit_additive_terms(eq)- Split only by additionget_depvars(eq, depvar_ops)- Find all dependent variables in expressionex2term(term, v)- Convert expression to a new term variablesubs_alleqs!(eqs, rules)- Substitute rules in all equations
Utility Functions
safe_unwrap(x)- Safely unwrapNumtypesrecursive_unwrap(ex)- Recursively unwrap nested expressionssubsmatch(expr, rule)- Check if a substitution rule matches
Error Handling
The workflow includes several validation points:
interface_errors- Catch unsupported PDESystem featurescheck_boundarymap- Validate boundary conditions- Assertions during parsing - Ensure boundary conditions are on domain boundaries
Implement these in your discretization to provide helpful error messages.
See Also
- Developer Extension API for interface function details
- VariableMap for variable handling
- Boundary Conditions for boundary types