StencilCore.jl
StencilCore is the shared type vocabulary at the root of a small stack for building and assembling sparse operators on structured (Cartesian) meshes. It owns the types and a small scalar CAS; it depends only on StaticArrays.jl and AbstractTrees.jl.
StencilCore stencil types: AccessStyle, AbstractStencil, AbstractPointwise{T},
│ ArrayOrTermLike, StaticShift, LinearStencil,
│ StarStencil, Stencil, as_linear / as_star
│ scalar CAS: AbstractScalar{T}, Var, Constant,
│ Scalar, Null, Unity, @var, @addmethod,
│ simplify, materialize, differentiate
├── StencilAssembly CSC assembly (build / assemble / update!)
└── StencilCalculus term-level CAS (simplify, differentiate, materialize) + bridgeYou probably want one of the leaves: StencilCalculus to build a stencil by differentiating a symbolic grid expression, or StencilAssembly to assemble a stencil into a sparse matrix. This page explains the vocabulary both of them speak.
Why a stencil needs a vocabulary
On a structured mesh, a discrete field is stored as an ordinary N-D array and the connectivity is implicit: the neighbours of node (i, j) are (i±1, j) and (i, j±1), reached by offsetting the index. A great many numerical operations — finite differences, Laplacians — are stencils: the same local formula relating a cell to its neighbours, applied at every point.
A stencil, then, is just a collection of (offset, coefficient) pairs. The two facts about that collection live at very different levels:
- the offsets are structure — a tiny, compile-time-known set that fixes the sparsity pattern and the index arithmetic;
- the coefficients are data — arbitrary numbers (often position-dependent, e.g. a density-weighted gradient
(ϕ[i] − ϕ[i−1]) / ρ[i]).
StencilCore encodes that split directly: offsets are type-level (StaticShift), coefficients are ordinary (or lazy) arrays, and a stencil's element type is an SVector of all the per-offset coefficients on a single column.
Two parallel algebras
The CAS half of StencilCore is built from two sibling type hierarchies that mirror each other:
| role | AbstractScalar (Core) | AbstractPointwise (StencilCalculus) |
|---|---|---|
| named substitution leaf | Var{S, T} | Slot{S, T} |
| literal carrier | Constant{T} with val::T | — (literals enter terms via Fill(Constant(…))) |
| interior tree node | Scalar{F, A<:Tuple{Vararg{AbstractScalar}}} | Pointwise{F, A<:Tuple{Vararg{AbstractPointwise}}} |
| additive identity | Null{T} (structural) | Zero{T} |
| multiplicative identity | Unity{T} (structural; requires one(T)) | One{T} |
| broadcast bridge | — | Fill{T} (lives in StencilCalculus, wraps an AbstractScalar or a literal) |
An AbstractPointwise{T} is array-like — a dimension-/size-less collection that materializes to a per-cell array. An AbstractScalar{T} is not array-like — it materializes to a single value. The two are bridged in StencilCalculus by the Fill term, which broadcasts a scalar across the grid. Scalars never appear inside a Pointwise directly — they enter via Fill, so Pointwise.args stays Tuple{Vararg{AbstractPointwise}}.
The pieces
AccessStyle— a trait (ColumnAccess/RowAccess) recording whether a stencil's coefficients are anchored at the column or the row index;ColumnAccessis what a compressed-sparse-column matrix wants.AbstractPointwise{T}— "a dimension-/size-less array-like object whose element type isT", andArrayOrTermLike{T} = Union{AbstractArray{T}, AbstractPointwise{T}}, the coefficient type of every stencil. A stencil is assemblable with a concrete-array coefficient and symbolic with a pointwise coefficient.AbstractScalar{T}— the cell-level scalar algebra. Concrete leaves (Var,Constant,Null,Unity) plus the interiorScalarnode have their own operator overloads, a structuralsimplifyrewriter (identities by dispatch onNull/Unity; folding combines values but does not match on them), amaterializereduction, and a Jacobian-awaredifferentiatechain rule — all parallel to the term side, all living entirely in scalar-land. The@varmacro declares a namedVar;@addmethodinstalls a typed Julia method from a scalar expression.StaticPair{D,O}/StaticShift— type-level lattice offsets with a+/-/*algebra, the zero shiftô, and basis shiftsê₁ … ê₉. They read like lattice vectors:using StencilCore 3ê₁ + ê₂ # StaticShift{Tuple{StaticPair{1,3}, StaticPair{2,1}}}LinearStencil(contiguous offsets along one axis), the interlacedStarStencil(the whole star per cell, with the diagonal as an explicit slot), and the generalStencil(an arbitrary reverse-lex offset list — the form differentiation produces).as_linear/as_star— narrow a generalStencilto an assemblableLinearStencil/StarStencilby a shift-pattern match and a verbatim coefficient copy.
using StencilCore, StaticArrays
# A 1-D forward-difference stencil: offsets 0 and 1, one SVector of
# coefficients per column.
st = LinearStencil{1}(SUnitRange(0, 1), fill(SVector(-1.0, 1.0), 5))
# The scalar CAS, on its own:
@var τ Float64
α = Constant(2)
differentiate(τ * α + α, τ) # === Constant{Float64}(2.0)
# eltype promotes via Scalar(*); broadcasts to `2.0` per cell once wrapped in FillSee the Guide for worked examples and the API reference for the full surface.