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) + bridge

You 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]).
Motto

Type parameters are structure; values are data.

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:

roleAbstractScalar (Core)AbstractPointwise (StencilCalculus)
named substitution leafVar{S, T}Slot{S, T}
literal carrierConstant{T} with val::T— (literals enter terms via Fill(Constant(…)))
interior tree nodeScalar{F, A<:Tuple{Vararg{AbstractScalar}}}Pointwise{F, A<:Tuple{Vararg{AbstractPointwise}}}
additive identityNull{T} (structural)Zero{T}
multiplicative identityUnity{T} (structural; requires one(T))One{T}
broadcast bridgeFill{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; ColumnAccess is what a compressed-sparse-column matrix wants.

  • AbstractPointwise{T} — "a dimension-/size-less array-like object whose element type is T", and ArrayOrTermLike{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 interior Scalar node have their own operator overloads, a structural simplify rewriter (identities by dispatch on Null/Unity; folding combines values but does not match on them), a materialize reduction, and a Jacobian-aware differentiate chain rule — all parallel to the term side, all living entirely in scalar-land. The @var macro declares a named Var; @addmethod installs 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 interlaced StarStencil (the whole star per cell, with the diagonal as an explicit slot), and the general Stencil (an arbitrary reverse-lex offset list — the form differentiation produces).

  • as_linear / as_star — narrow a general Stencil to an assemblable LinearStencil / StarStencil by 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 Fill

See the Guide for worked examples and the API reference for the full surface.