Pure Rust implementation of DACE, the Differential Algebra Computational Toolbox.
dace-rs computes with truncated multivariate Taylor polynomials ("differential algebra"): it propagates high-order expansions of arbitrary multivariate functions through arithmetic, elementary-function composition, and full polynomial map inversion, with a configurable cut-off epsilon and truncation order. It is a full rewrite of DACE 2.1 in safe Rust — no C toolchain at build time, no unsafe code in the computation kernels.
English | 简体中文
The existing dace crate is a C-binding wrapper around the upstream library: it requires a C toolchain and links the C core. dace-rs is an independent pure-Rust implementation of the same algorithms and numerics, verified coefficient-for-coefficient against the C reference (see Parity Methodology). It adds Rust-native ergonomics:
- operator traits (
Add/Sub/Mul/Div/Neg+ assignments) forDa × Da,Da × f64,f64 × Da; - values survive re-initialization (each
Dakeeps its context; C purges everything); - recoverable failures return
Result, domain misuse panics with the C error code, informational degradation goes through thelogcrate — no sticky error state, noexit(1); - automatic domain splitting (
ads::split, a Rust-only extension with no C counterpart) keeps polynomial enclosures rigorous over large uncertainty boxes via recursive bisection and re-expansion.
use dace_rs::Da;
fn main() {
dace_rs::init(20, 2).unwrap(); // order 20, 2 variables
let x = Da::variable(1);
let y = Da::variable(2);
let f = (1.0 + x.clone() * y.clone()).sin(); // Taylor expansion of sin(1+xy)
println!("{f}"); // daceWrite-style listing
// Coefficients: coefficient of x*y in sin(1+xy) is cos(1)
assert!((f.get_coefficient(&[1, 1]) - 1.0f64.cos()).abs() < 1e-14);
}See examples/quickstart.rs for a map-iteration example, and the crate documentation for the full API.
All dependencies are pure Rust; there is no build.rs and no C toolchain requirement.
| Target | CI |
|---|---|
| Linux x86-64 | ubuntu-latest |
| Linux aarch64 | ubuntu-24.04-arm |
| Windows x86-64 | windows-latest |
| Windows aarch64 | windows-11-arm |
Arithmetic kernels use a fixed accumulation order and default floating-point settings (no fast-math, no FMA contraction), so results are bit-identical across the four platforms; only scalar transcendentals (f64::sin, ...) may differ by a few ulps between libm implementations.
| DACE C++ | dace-rs |
|---|---|
DA::DA(var), DA::identity |
Da::variable / [Da::identity] |
DA::cons, DA::linear, DA::gradient |
Da::cons / [Da::linear] / [Da::gradient] |
DA::getCoefficient, setCoefficient |
[Da::get_coefficient] / [Da::set_coefficient] |
DA::deriv, DA::integ, DA::trim |
[Da::deriv] / [Da::integ] / [Da::trim] |
exp, log, sin, tan, asin, ... |
[dace_rs::exp] etc. (free fns) and Da methods |
BesselJFunction etc. |
[dace_rs::bessel_j] etc. |
GammaFunction, PsiFunction |
[dace_rs::gamma] / [dace_rs::psi] |
DA::norm, orderNorm, estimNorm, bound, convRadius |
[Da::norm] etc. |
DA::compile, compiledDA::eval |
Da::compile / CompiledDa::eval / [CompiledDa::eval_da] |
DA::plug, DA::eval |
[Da::plug] / [Da::eval] / [Da::eval_da] |
DA::replaceVariable, scaleVariable, translateVariable |
[Da::replace_variable] etc. |
DA::read/write (blob), operator>>/<< |
[Da::to_blob]/[Da::from_blob], [Display]/[FromStr] |
DASimpleFormatter |
SimpleFormat presets and format_da |
AlgebraicVector<DA>::invert |
DaVector::invert |
| — (no C counterpart; Rust-only extension) | ads::split with AdsConfig/AdsResult |
Not ported (upstream experimental/off by default): AlgebraicMatrix, dacecompat aliases, the MATLAB interface.
The C library has no test suite, so dace-rs is verified against the C reference directly:
- Goldens:
dev/golden/gen.shbuilds the C library and runs a fixed case table (dev/golden/main.c) covering arithmetic, all elementary and special functions, calculus, norms, evaluation, and blob export at(6,3),(20,6), and(6,2)contexts with several constant-term choices. The committedtests/golden/cases.txtis replayed bytests/parity.rsin CI (no C toolchain needed); coefficients agree within rtol1e-10(arithmetic within1e-13, verified bit-identical in many cases). - Bit-level checks during development: the monomial encoding tables, the multiply/inverse kernels, the text format, and the elementary-function layer were compared byte-for-byte against the C library on this machine.
Two upstream C bugs are deliberately not reproduced; both divergences are documented in the rustdoc of the affected functions:
daceReplaceVariableindexes its exponent array with 1-based variable numbers (documented semantics say 1-based replacement; implemented is effectivelyfrom+1 → val·(to+1), a silent no-op forfrom == nvmax). dace-rs implements the documented semantics.dacePowerwith negative powers calls the multiplicative inverse on an aliased result, and the Newton iteration is not aliasing safe; C returns wrong coefficients (C'spow(A,-2)disagrees with C's ownminv(sqr(A))). dace-rs returns the correct value.
Criterion benchmarks for the core kernels live in benches/kernels.rs. Run the full suite with cargo bench (or cargo bench --bench kernels for this target only); filter a single benchmark with e.g. cargo bench mul/20x6.
Multiplication, the elementary functions (sin, sqrt, exp), compilation, and compiled evaluation are each measured in two configurations: order 6 with 2 variables (28 monomial slots) and order 20 with 6 variables (230,230 slots). Map inversion is measured at 6/2 and 10/4 instead: a single near-identity map inversion at order 20 with 6 variables takes ≈ 29 s per call (measured on the baseline machine below, release build), so that magnitude is recorded here and kept out of the regular bench loop.
Baseline medians (default criterion settings, Linux x86-64, AMD Ryzen Threadripper 9960X, bench/release profile):
| Benchmark | Config | Median |
|---|---|---|
| mul/6x2 | order 6, 2 vars | 124 ns |
| mul/20x6 | order 20, 6 vars | 137 µs |
| sin/6x2 | order 6, 2 vars | 969 ns |
| sin/20x6 | order 20, 6 vars | 2.68 ms |
| sqrt/6x2 | order 6, 2 vars | 973 ns |
| sqrt/20x6 | order 20, 6 vars | 2.61 ms |
| exp/6x2 | order 6, 2 vars | 969 ns |
| exp/20x6 | order 20, 6 vars | 2.64 ms |
| compile/6x2 | order 6, 2 vars | 198 ns |
| compile/20x6 | order 20, 6 vars | 97.4 µs |
| eval/6x2 | order 6, 2 vars | 23.5 ns |
| eval/20x6 | order 20, 6 vars | 33.4 ns |
| invert/6x2 | order 6, 2 vars | 6.64 µs |
| invert/10x4 | order 10, 4 vars | 979 µs |
Criterion persists results under target/criterion/; re-running cargo bench reports the change against the previous run (change: [...]), which is the intended before/after workflow for kernel changes.
The usual caveat applies (see Platform Support): arithmetic kernels use a fixed accumulation order and default floating-point settings, and these microbenchmarks do not generalize to arbitrary workloads.
Apache-2.0. dace-rs is a derivative work of DACE (Copyright 2016 Politecnico di Milano and contributors; Copyright 2014 Dinamica Srl). See LICENSE, NOTICE, and THIRD_PARTY_NOTICES.md.