Expand description
§legendre — a PDE simulation framework
legendre solves systems of time-dependent partial differential
equations — deterministic or stochastic, in any spatial dimension — on
block-decomposed structured grids. Heat transport, reaction–diffusion,
and phase-field solidification are models here, not the framework:
they are all expressed against the same small trait surface.
The architecture follows four principles:
- Mathematical objects own no execution. A
physics::model::Modelcannot spawn threads, ageometry::grid::Gridcannot write files, adiscretization::stencil::Stencilcannot allocate. - Execution is scheduler-driven. Everything runs because the
core::scheduler::Schedulerrequests it. No trait in this crate mentions Rayon; only concrete schedulers do. - Storage is separate from views. Fields are typed views into a
core::storage::StorageBackend; allocation happens once, up front, through ancore::storage::Allocator. - Numerical methods are policies. A grid plus a discretization
policy yields operator realizations
(
discretization::stencil::Stencils); models state what operators they need and never learn how they were realized.
The fundamental unit of computation is the block, not the grid. Even a uniform Cartesian grid is a collection of fixed-size blocks: this gives cache locality, natural parallel work units, localized halo exchange, and an execution model that is unchanged when adaptive refinement arrives.
§Ownership graph
Simulation
├── Scheduler (how blocks are dispatched)
├── State (Storage + field views, ghost-inclusive slabs)
├── Grid (topology: blocks, indices, views)
├── Discretization (policy: builds Stencils for Operators)
├── Model (mathematics: rhs, never mutates state)
├── Integrator (advances State using Model::rhs)
└── Observers (async output; never block the solver)§A complete model
The dimension-generic heat equation ∂u/∂t = κ∇²u with no-flux
boundaries, wired to a grid, integrator, and scheduler:
use legendre::core::{
scheduler::SerialScheduler,
scratch::Scratch,
simulation::Simulation,
state::{BlockStateMut, FieldHandle, State, StateBuilder},
storage::{StorageBackend, SystemAllocator},
};
use legendre::discretization::{
finite_difference::FiniteDifference,
operators::{Discretizes, Laplacian},
stencil::Stencil,
};
use legendre::geometry::cartesian::{CartesianGrid, fill_ghosts_mirror, for_each_interior};
use legendre::integrators::ForwardEuler;
use legendre::physics::model::{Model, RhsContext};
struct Heat<const D: usize> {
kappa: f64,
u: Option<FieldHandle<f64>>,
}
impl<const D: usize, P> Model<CartesianGrid<D>, P> for Heat<D>
where
P: Discretizes<CartesianGrid<D>, Laplacian>,
{
type Scalar = f64;
fn register_fields(&mut self, builder: &mut StateBuilder<f64>) {
self.u = Some(builder.register("u", 1)); // name + ghost width
}
fn fill_ghosts<S: StorageBackend<f64>>(
&self,
grid: &CartesianGrid<D>,
state: &mut State<f64, S>,
_t: f64,
) {
fill_ghosts_mirror(grid, state, self.u.unwrap());
}
fn rhs_block<S: StorageBackend<f64>>(
&self,
ctx: &RhsContext<'_, CartesianGrid<D>, P>,
state: &State<f64, S>,
out: &mut BlockStateMut<'_, f64, S>,
_scratch: &mut Scratch<f64, S>,
) {
let u = self.u.unwrap();
let lap = ctx.disc.build(ctx.grid, Laplacian);
let input = state.view(ctx.grid, ctx.block, u);
let mut output = out.view_mut(ctx.grid, ctx.block, u);
lap.apply(ctx.grid, ctx.block, input, &mut output);
for_each_interior(input.interior(), |i| {
output.set(i, output.get(i) * self.kappa);
});
}
}
let grid = CartesianGrid::new([32; 2], [16; 2], [0.0; 2], [0.1; 2])?;
let heat = Heat::<2> { kappa: 0.7, u: None };
let mut sim = Simulation::new(
grid,
FiniteDifference,
heat,
ForwardEuler,
SerialScheduler,
SystemAllocator,
);
for _ in 0..10 {
sim.step(1e-3);
}Swap core::scheduler::SerialScheduler for
core::scheduler::RayonScheduler and the run parallelizes over blocks
with bitwise-identical results; the examples/ directory adds the
async observation pipeline (Parquet snapshots, live statistics) on top.
Modules§
- core
- Framework core: state, storage, scheduling, scratch, observation, and the simulation owner. Everything here is grid- and physics-agnostic.
- discretization
- Numerical schemes as policies.
- geometry
- Grids and topology: the
Gridtrait and concrete grid families. Quadtree/octree AMR grids land here later behind the same trait. - integrators
- Time integration.
- io
- Output backends.
- physics
- Models: pure mathematics over grids and discretization policies. Phase-fields, diffusion, elasticity etc. are peer modules here.
- util
- Small shared utilities. Nothing here may depend on other framework modules.