The shared type vocabulary at the root of a three-package stack for variable-coefficient Cartesian stencils and the symbolic algebra that builds them. StencilCore owns the types; it has no assembly and depends only on StaticArrays.jl.
StencilCore stencil types: AccessStyle, AbstractStencil, AbstractPointwise,
│ ArrayOrTermLike, StaticShift, LinearStencil,
│ StarStencil, Stencil, as_linear / as_star
│ scalar CAS: AbstractScalar{T}, Var, Constant, Null, Unity,
│ Scalar, @var, @addmethod,
│ simplify, materialize, differentiate
├── StencilAssembly CSC assembly (build / assemble / update!)
└── StencilCalculus term-level CAS (simplify, differentiate, materialize) + bridge
AccessStyletrait (ColumnAccess/RowAccess) and theAbstractStencil{T}/NeighborhoodStencil{T, S<:AccessStyle}hierarchy —Tis the coefficient eltype (the linear-map space, mirroringUnity{T}in scalar-land), accessed viaBase.eltype(::AbstractStencil). The two abstract children ofAbstractStencilareAbstractPointwise{T}(diagonal stencils, no offsets) andNeighborhoodStencil{T, S}(offset-bearing, carries anAccessStyle).AbstractTerm{T}— a dimension-/size-less array-like with element typeT— andArrayOrTermLike{T} = Union{AbstractArray{T}, AbstractTerm{T}}, the coefficient type of every stencil (a stencil is assemblable with a concrete array coefficient, symbolic with a term coefficient).StaticPair{D,O}/StaticShift— type-level lattice offsets with a+/-/*algebra, the zero shiftô, and basis shiftsê₁ … ê₉.LinearStencil(contiguous offsets along one axis), the interlacedStarStencil(whole star per cell, explicit diagonal), and the generalStencil(arbitrary reverse-lex offset list).as_linear/as_star— narrow a generalStencilto an assemblable type by a shift-pattern match and a verbatim coefficient copy.AbstractScalar{T}— the cell-level scalar algebra. Concrete leavesVar,Constant,Null,Unityplus the interiorScalarnode, with operator overloads, a structuralsimplify,materialize, and a Jacobian-awaredifferentiate(including theSVector → SMatrixself-derivative case). The@varmacro declares a namedVar;@addmethodinstalls a typed Julia method from a scalar expression.
The three packages are unregistered and resolve each other through relative
[sources] paths, so clone them side by side:
git clone …/StencilCore
git clone …/StencilAssembly
git clone …/StencilCalculus
Then ]dev /path/to/StencilCore (or add the others, whose [sources] point at
../StencilCore).
using StencilCore, StaticArrays
# A 1-D forward difference stencil (offsets 0,1; one SVector of coeffs per cell).
st = LinearStencil{1}(SUnitRange(0, 1), fill(SVector(-1.0, 1.0), 5))
# Type-level offsets read like lattice vectors:
3ê₁ + ê₂ # StaticShift{Tuple{StaticPair{1,3}, StaticPair{2,1}}}
# The scalar CAS, on its own:
@var τ Float64
differentiate(2τ + τ * τ, τ) # 2 + 2τ (simplified)
differentiate(Constant(2.0), τ) # Null{Float64}() — no dependence
# Vector-valued symbols: the Jacobian lands in the matching square SMatrix.
@var x SVector{2, Float64}
differentiate(2x, x) # Scalar tree, eltype SMatrix{2,2,Float64,4}
# Turn a scalar expression into a callable Julia method:
@var y Int
@addmethod myfun (τ ^ y) (y, τ) # function myfun(y::Int, τ::Float64); τ ^ y; end
myfun(2, 3.0) # 9.0
# ...or widen the emitted signature with a NamedTuple of type overrides
# (the tree stays concretely typed; only the method signature widens):
@addmethod myfun (τ ^ y) (y = Integer, τ = AbstractFloat)
myfun(big(2), 3.0f0) # accepts any Integer / AbstractFloatSee AGENTS.md for the canonical design decisions.
StencilCore encodes expression structure in the type system rather than runtime tags. The full AST of a scalar expression lives in type parameters:
Scalar{typeof(+), Tuple{Var{:τ,Float64}, Constant{Float64}}, Float64}This pays dividends throughout the stack:
materialize/differentiateare type-stable. Return types are computed at compile time from type parameters;@code_warntypeshows a concreteBody::T, neverAny.simplifyis type-stable. A@generatedrewriter resolves Null/Unity identities, double-negation, and constant fold entirely from types — no runtime value inspection.- Zero-overhead narrowing.
as_linear/as_starmatch the offset pattern at compile time; the coefficient is reused verbatim with no runtime loop. - Materialized values are plain Julia arrays, with no symbolic wrapper to strip before calling BLAS or StaticArrays kernels.
The trade-off vs. Symbolics.jl:
Symbolics uses a uniform Num type (type erasure) for generality;
StencilCore uses concrete per-shape types (type explosion) for compiler
transparency. For the bounded, shape-homogeneous expression vocabulary of
stencil coefficients the explosion is acceptable and the transparency matters.
See the Design rationale
page in the docs for the full analysis and comparison table.