B-spline finite elements on an interval, built from the Cox-de Boor recursion, with the quadrature and assembly a Galerkin discretisation needs.
The package provides the clamped, periodic and recombined B-spline bases of arbitrary degree on uniform, graded, random or arbitrary meshes; homogeneous Dirichlet, Neumann, Robin, natural and general local boundary conditions per end; tensor products in any number of dimensions, with degree, mesh, domain and boundary condition per axis; and an assembly table from which the mass, stiffness, derivative and variable-coefficient matrices follow as single weighted contractions. Mass solves go through a representation chosen by the basis — an FFT for a periodic uniform basis, a banded Cholesky for a bounded one, and a factored Kronecker product in several dimensions.
It also provides the polar spline space of a mapped disk, where the left radial edge of the parameter rectangle is a pole rather than a boundary. A plain tensor product is discontinuous there; the polar basis replaces the first two radial rows with three functions spanning the constants and linears of a chart at the pole, which makes the space C¹ across it by construction. That space carries its own assembly and a boundary condition on the rim.
It is not a curve- and surface-modelling library: there are no NURBS, no knot insertion, no degree elevation and no least-squares fitting of data. It is for discretising a differential equation and then solving with the result.
The package is registered in the General registry:
using Pkg
Pkg.add("SimpleSplines")Solving -u'' = f with u(0) = u(1) = 0, on a cubic basis over 16 uniform cells:
using SimpleSplines
# the boundary condition is built into the basis, not applied to the matrix afterwards
b = BSplineBasis(UniformMesh(16, 0 .. 1), 3, Dirichlet())
# assembly tabulates the basis and its derivatives at the global Gauß-Legendre points,
# and every matrix is a weighted contraction of that one table
q = SplineQuadrature(b)
M = mass_matrix(q)
K = stiffness_matrix(q)
f(x) = π^2 * sin(π * x)
rhs = basis_values(q, 0) * (quadrature_weights(q) .* f.(quadrature_nodes(q)))
û = Matrix(K) \ rhs
maximum(abs(evaluate(b, û, x) - sin(π * x)) for x in range(0, 1; length = 101))which is 2.08e-6. Fitting a function to the space instead is an L² projection, and the
result is callable:
u = Spline(b, l2_projection(q, x -> sin(π * x)))
u(0.5), u(0.5, 1), u(0.0) # value, first derivative, and the imposed u(0) = 0The manual has a tutorial, the spline theory the package rests on, a usage page per object with the constructors and the traps, a gallery of nine solved problems with their measured errors, and the full API.
Two hooks live in .githooks. They are not active in a fresh clone — core.hooksPath is local
configuration and does not travel with a push — so enable them once per clone:
git config core.hooksPath .githookspre-commit acts on staged .jl files only, and exits immediately when a commit stages
none, so a documentation- or workflow-only commit is not slowed down by it:
- JuliaFormatter
--check, honouring this repository's own.JuliaFormatter.toml— blocks the commit. Formatting is mechanical and always fixable. fatou lint, whenfatouis installed — advisory only, and deliberately so: itsunused-importrule does not followinclude, so it flags the load-bearing imports of every module file.- Unicode NFC — blocks a staged file that is not NFC-normalised. Sources here are NFC, so a
file that is not is a regression: a pattern typed in NFC silently matches nothing in a decomposed
file, which defeats
grep, an editor search and Documenter's doctest comparison alike. Fix withjulia --startup-file=no ~/Research/Environment/Harness/githooks/nfc.jl --apply <files>. The invariant iss == Unicode.normalize(s, :NFC), not the absence of combining marks —q̇,v̄andf̄have no precomposed codepoint and are two codepoints in NFC as well. using <Package>, which catches a syntax error or a brokeninclude— blocks.
pre-push runs the full test suite with --check-bounds=auto, but only when pushing to
main or master; a topic branch is left to CI. It prints nothing for 10–30 minutes, which
looks exactly like a network hang and is not one. If you do interrupt it, check for an orphaned
Julia process that the killed hook left behind.
Either hook can be bypassed for a single command with --no-verify, for a change you know it does
not apply to:
git commit --no-verify
git push --no-verifyThe hooks are generated from one shared copy and are byte-identical across the related repositories, so edit them there rather than here — a local edit is silently undone by the next install.