dace_rs/lib.rs
1//! Pure Rust implementation of DACE, the Differential Algebra Computational
2//! Toolbox.
3//!
4//! DACE computes with truncated multivariate Taylor polynomials ("differential
5//! algebra"): it propagates high-order expansions of arbitrary multivariate
6//! functions through arithmetic and elementary-function composition, with a
7//! configurable cut-off epsilon and truncation order. This crate is a full
8//! rewrite of [DACE 2.1](https://github.com/dacelib/dace) in pure Rust.
9//!
10//! # Quickstart
11//!
12//! ```
13//! dace_rs::init(20, 2).unwrap();
14//! let x = dace_rs::Da::variable(1);
15//! let y = dace_rs::Da::variable(2);
16//! let f = (x.clone() * x.clone() + y).sin();
17//! // coefficient of x^2 in sin(x^2+y) at the origin is cos(0) = 1
18//! assert!((f.get_coefficient(&[2, 0]) - 1.0).abs() < 1e-14);
19//! ```
20//!
21//! # Multithreading
22//!
23//! [`Da`] and [`CompiledDa`] are `Send + Sync`: sharing them across threads
24//! (e.g. [Rayon], or PyO3's `allow_threads`) is supported. New threads need
25//! no explicit initialization — computation settings (epsilon cutoff,
26//! truncation order) and scratch buffers are lazily derived from the active
27//! context on each thread's first use.
28//!
29//! After [`init`] is called again, every thread re-derives its settings on
30//! next use, as if the thread had never been used; user-set epsilon and
31//! truncation order therefore do not survive re-initialization on any
32//! thread. Values keep operating on their original context, and mixing
33//! values from different contexts in one operation panics (existing
34//! semantics, enforced by `Da::assert_same_context`).
35//!
36//! [`Da::random`] uses a per-thread LCG seeded with a fixed identical value
37//! on every thread: parallel draws produce the same sequence on each thread,
38//! not distinct streams.
39//!
40//! ```
41//! dace_rs::init(20, 2).unwrap();
42//! let x = dace_rs::Da::variable(1);
43//! let c = std::thread::spawn(move || {
44//! let y = dace_rs::Da::variable(2);
45//! (x * y).get_coefficient(&[1, 1])
46//! })
47//! .join()
48//! .unwrap();
49//! assert_eq!(c, 1.0);
50//! ```
51//!
52//! [Rayon]: https://docs.rs/rayon
53
54pub mod ads;
55pub mod context;
56pub mod error;
57pub mod monomial;
58
59mod da;
60pub mod elementary;
61pub mod eval;
62pub mod io;
63mod kernels;
64pub mod norm;
65pub mod special;
66pub mod vector;
67pub use ads::{AdsConfig, AdsLeaf, AdsResult, ToleranceKind};
68pub use context::{
69 epsilon, init, initialized, machine_epsilon, max_monomials, max_order, max_variables,
70 pop_truncation_order, push_truncation_order, set_epsilon, set_truncation_order,
71 truncation_order, version,
72};
73pub use da::Da;
74pub use elementary::{
75 acos, acosh, asin, asinh, atan, atan2, atanh, cbrt, cos, cosh, erf, erfc, exp, hypot, icrt,
76 isrt, log, log_base, log2, log10, modulo, powf, powi, root, round, sin, sinh, sqrt, tan, tanh,
77 trunc,
78};
79pub use error::DaceError;
80pub use eval::CompiledDa;
81pub use norm::{Interval, NormType};
82pub use special::{bessel_i, bessel_j, bessel_k, bessel_y, gamma, log_gamma, psi};
83
84/// The weighted sum `afac * a + bfac * b` (`daceWeightedSum`).
85///
86/// Unlike the C routine of the same name, this is aliasing-safe.
87///
88/// # Panics
89///
90/// Panics with [`DaceError`] when `a` and `b` belong to different DACE
91/// contexts.
92pub fn fma(a: &Da, afac: f64, b: &Da, bfac: f64) -> Da {
93 Da::assert_same_context(a, b);
94 kernels::weighted_sum(a, afac, b, bfac)
95}
96
97/// Tests mutate the process-global DACE context; serialize context-touching
98/// tests with this lock.
99#[cfg(test)]
100pub(crate) mod test_support {
101 use parking_lot::Mutex;
102
103 pub static CONTEXT_LOCK: Mutex<()> = Mutex::new(());
104}