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 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.
No version is registered yet, so install from the repository:
using Pkg
Pkg.add(url = "https://github.com/JuliaDEC/SimpleSplines.jl")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 eight 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.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.