multicalc
multicalc is a pure no_std Rust library for numerical calculus and the linear algebra
around it: exact derivatives via automatic differentiation, integration, Jacobians and Hessians,
nonlinear least-squares curve fitting, root finding, ODE integration, and 2D/3D rigid-body
math.
Why use it
- Runs the same math from a server to a microcontroller. Every commit is built and tested on six targets:
on
x86_64andaarch64Linux and on four bare-metal ABIs (thumbv7emsoft-float,thumbv7emhardware-FPU,thumbv6m, andriscv32imc-unknown-none-elf). The bare-metal builds run the real math under QEMU and check the answers, sono_std, no-heap, and no-panic rules hold from a 64-bit CPU down to a microcontroller with no operating system. - Every module checked against the reference libraries. Each module's results are verified
against
mpmath,numpy, andscipyfixtures within ~1 ulp, thus validating the rust implementation. - Fast, and measured. On an i7-12650H: a third derivative in 26.7 ns, a small Jacobian in 9.3 ns, a 10×10 LU solve in 239 ns, and a full Levenberg-Marquardt curve fit in 2.0 µs. See the benchmarks.
- Exact derivatives, not estimates. Differentiation, Jacobians, Hessians, Newton steps, and
least-squares fits use forward-mode automatic differentiation (
Dual,HyperDual,Jet), so derivatives are exact to machine precision rather than finite-difference approximations. - Pure safe and panic-free.
forbid(unsafe_code)across the workspace;unwrap/expect/panicare denied on library paths. Every fallible call returns a typed error. - One dependency. Transcendental functions come from
libm(re-exported), so the math works withoutstd.
What it does
- Automatic differentiation:
Dual,HyperDual, andJetscalars for exact first, second, and nth-order derivatives. - Differentiation: derivatives of any order (total and partial), plus Jacobian and Hessian matrices; autodiff by default, finite differences for black-box functions.
- Integration: iterative Newton-Cotes rules (Boole, Simpson, Trapezoidal) and Gaussian quadrature (Legendre, Hermite, Laguerre) over finite, semi-infinite, and infinite limits.
- Linear algebra: fixed-size, stack-allocated
MatrixandVectorwith LU, Cholesky, column-pivoted QR, and SVD: solves, determinant, inverse, pseudo-inverse, and condition number. - Least-squares optimization:
LevenbergMarquardtandGaussNewtonsolvers for nonlinear curve fitting. - Root finding: bracketed bisection and Newton solvers for scalar equations and square systems, with an optional damped line search.
- Vector calculus: curl, divergence, and line and flux integrals.
- Approximation: linear and quadratic Taylor models with goodness-of-fit metrics.
- ODE integrators: fixed-step
Rk4and adaptiveRk45(Dormand-Prince 5(4)) with PI step control and dense output. - Discretization: zero-order hold, Van Loan, and discrete white-noise models for continuous-time linear systems.
- Spatial math:
Quaternionand theSO2/SE2/SO3/SE3Lie groups for 2D and 3D rotations and rigid-body transforms.
Install
Tutorial
The guide is a comprehesive tutorial for each module. It shows the full imports, expected outputs in comments, error-path notes, and pointers to runnable demos. Start there when you need the complete picture of a feature.
Example snippets
Exact derivatives
One formula, differentiated to any order by forward-mode autodiff:
use ;
use ;
use scalar_fn;
let f = scalar_fn!; // f(x) = x^3
let d = default; // forward-mode autodiff, exact
let first = d.get.unwrap; // 12.0
let third = d.get.unwrap; // 6.0
// Partial derivatives of a multivariable function; order = number of indices.
let g = scalar_fn!; // g(x, y) = x^2 * y
let dm = default;
let dx = dm.get_single_partial.unwrap; // dg/dx = 2xy = 24.0
let mixed = dm.get.unwrap; // d^2g/dxdy = 2x = 6.0
Integration
Newton-Cotes and Gaussian rules over finite, semi-infinite, and infinite limits:
use IntegratorSingleVariable;
use IterativeSingle;
use GaussianSingle;
use GaussianQuadratureMethod;
let integrator = default; // Boole's rule, 120 intervals
let area = integrator.get_single.unwrap; // 2x on [0, 2] -> 4.0
// decaying integrands may run to a semi-infinite or infinite limit
let tail = integrator.get_single.unwrap; // e^-x on [0, inf) -> 1.0
let bell = integrator
.get_single
.unwrap; // e^(-x^2) on R -> sqrt(pi)
// Gauss-Hermite already carries the e^(-x^2) weight, so pass the bare integrand.
let gh = from_parameters;
let moment = gh.get_single.unwrap; // x^2, weight e^(-x^2) -> sqrt(pi)/2
Nonlinear curve fitting
Author the residuals model - data with scalar_fn_vec!; LevenbergMarquardt differentiates
them under autodiff and drives the fit:
use LevenbergMarquardt;
use AutoDiffMulti;
use c;
use scalar_fn_vec;
// Fit a*e^(b*t) to (0, 100), (1, 50), (2, 25): the minimum is a = 100, b = -ln 2.
let residuals = scalar_fn_vec!;
let report = default
.minimize
.unwrap;
// report.solution ~ [100.0, -0.693]; report.termination says which test converged
Linear solves
Fixed-size Matrix and Vector with dimensions as const generics (shape mismatches are
compile errors):
use ;
// Solve A·x = b.
let a = new;
let b = new;
let x = a.solve.unwrap; // [1, 2, 3]
// A symmetric positive-definite matrix has a faster Cholesky path.
let s = new;
let s_inv = s.cholesky.unwrap.inverse;
Rotations and rigid-body transforms
Quaternion plus the SO2/SE2/SO3/SE3 Lie groups, generic over the scalar so autodiff
flows straight through:
use ;
use Vector;
let r = SO3::exp; // 90° about z
let g = SE3from_parts;
let p = g.act; // rotate then translate → (1, 3, 3)
let xi = g.log; // 6-vector twist [v; ω]
Root finding
Scalar equations and square systems F(x) = 0, with exact autodiff derivatives:
use ;
use ;
use c;
use ;
let f = scalar_fn!; // f(x) = x^2 - 2, root at sqrt(2)
let bracketed = default.solve.unwrap; // ~ 1.41421356
let newton = default.solve.unwrap; // ~ 1.41421356
// Square system: x^2 + y^2 = 4 and x*y = 1.
let system = scalar_fn_vec!;
let solved = default.solve.unwrap;
// solved.root ~ [1.9319, 0.5176]
ODE integration
Initial-value solvers for y' = f(t, y) systems, generic over the state dimension:
use ;
use Vector;
// Harmonic oscillator y'' = -y as the first-order system [position, velocity].
let f = ;
let y0 = new;
let step = step; // one fixed RK4 step of size 0.1
// adaptive Dormand-Prince 5(4) over one full period returns to the start [1, 0]
let yf = default.solve.unwrap;
Refer to the guide for a comprehensive tutorial.
Error handling
Where a sensible default exists, a "safe" wrapper (such as get_single or get_double) returns
the answer directly. Otherwise the call returns a Result whose error is the module family's own
enum (LinalgError, DiffError, IntegrateError, or SolveError), each convertible into the
CalcError umbrella. All variants are listed in
error.rs.
Accuracy
Accuracy is verified against external-library fixtures (mpmath, numpy, scipy) in the
multicalc-qa crate, with per-module tables generated from those fixtures. See
benchmarks/README.md
for the index, or go straight to
calculus,
linear_algebra,
optimization,
or root_finding.
Runnable demos
Runnable, self-contained programs for each module live in the
demos/ crate. See
demos/README.md. Run one
with:
Contributing
See CONTRIBUTING.md.
Acknowledgements
The least-squares solvers and QR factorization port the public-domain MINPACK routines lmder,
lmpar, qrfac, and qrsolv (Moré, Garbow, Hillstrom; netlib), following Moré (1978), "The
Levenberg-Marquardt algorithm: Implementation and theory", and Nocedal & Wright, Numerical
Optimization (chapters 4 and 10).
License
multicalc is licensed under the MIT license.
Feature flags
alloc(off by default): enables the heap-based methods for inputs too large for the stack. See Heap allocation.
MSRV and edition
Edition 2024, minimum supported Rust version 1.85.