multicalc 0.6.0

Rust scientific computing for single and multi-variable calculus
Documentation

multicalc

On crates.io Downloads CI Docs License: MIT

multicalc is a Rust library for single- and multi-variable calculus: derivatives, integrals, Jacobians and Hessians, vector-field operators, and Taylor approximation in a no-std environment.

Why use it

  • Pure, safe Rust.
  • no_std, with no heap allocation and no panics — it runs on bare-metal and embedded targets.
  • Transcendental functions come from libm, so the math works without std.
  • Every fallible call returns a typed CalcError; convenience wrappers fill in sensible defaults.
  • A runnable example for every module, and a test suite covering each error path.

What it does

  • Differentiation of any order (finite differences), total and partial.
  • Integration of any order:
    • Iterative rules — Boole, Simpson, Trapezoidal — over finite, semi-infinite, and infinite limits.
    • Gaussian quadrature — Gauss-Legendre, Gauss-Hermite, Gauss-Laguerre.
  • Jacobian and Hessian matrices.
  • Vector calculus — line and flux integrals, curl, and divergence.
  • Approximation — linear and quadratic (Taylor) models, with goodness-of-fit metrics.

Install

cargo add multicalc

Enable the alloc feature if you want the heap-based methods (see Heap allocation).

A note on no_std

Methods like f64::sin are only available with std. In a no_std crate, call the libm versions instead — for example libm::sin(x) in place of x.sin(). multicalc re-exports libm, so you can reach it as multicalc::libm. The examples below use x.sin() because they assume std.

At a glance

Derivatives

use multicalc::numerical_derivative::derivator::DerivatorSingleVariable;
use multicalc::numerical_derivative::finite_difference::FiniteDifferenceSingle;

let f = |x: f64| x * x * x;                  // f(x) = x^3
let d = FiniteDifferenceSingle::default();   // central difference, default step size

let first = d.get(1, &f, 2.0).unwrap();      // 12.0
let third = d.get(3, &f, 2.0).unwrap();      //  6.0
// get_single / get_double are wrappers for orders 1 and 2

For several variables, the derivative order is just the number of indices you pass:

use multicalc::numerical_derivative::derivator::DerivatorMultiVariable;
use multicalc::numerical_derivative::finite_difference::FiniteDifferenceMulti;

// g(x, y, z) = y*sin(x) + x*cos(y) + x*y*e^z
let g = |v: &[f64; 3]| v[1] * v[0].sin() + v[0] * v[1].cos() + v[0] * v[1] * v[2].exp();
let d = FiniteDifferenceMulti::default();
let point = [1.0, 2.0, 3.0];

let dx    = d.get_single_partial(&g, 0, &point).unwrap();  // dg/dx
let mixed = d.get(&g, &[0, 1], &point).unwrap();           // d(dg/dx)/dy

Integration

use multicalc::numerical_integration::integrator::IntegratorSingleVariable;
use multicalc::numerical_integration::iterative_integration::IterativeSingle;

let f = |x: f64| 2.0 * x;
let integrator = IterativeSingle::default();   // Boole's rule, 120 intervals

let area = integrator.get_single(&f, &[0.0, 2.0]).unwrap();   // 4.0

// infinite and semi-infinite limits are supported for decaying integrands
let bell = integrator
    .get_single(&|x| (-x * x).exp(), &[f64::NEG_INFINITY, f64::INFINITY])
    .unwrap();                                                // sqrt(pi)

Choose the rule and interval count with from_parameters:

use multicalc::numerical_integration::mode::IterativeMethod;
let integrator = IterativeSingle::from_parameters(120, IterativeMethod::Simpsons);

Gaussian quadrature

Each Gaussian rule integrates over a fixed domain. Pass the bare integrand f(x) — the weights already carry the weighting factor.

use multicalc::numerical_integration::integrator::IntegratorSingleVariable;
use multicalc::numerical_integration::gaussian_integration::GaussianSingle;
use multicalc::numerical_integration::mode::GaussianQuadratureMethod;

// Gauss-Hermite integrates f(x) * e^(-x^2) over the whole real line.
let hermite = GaussianSingle::from_parameters(5, GaussianQuadratureMethod::GaussHermite);
let val = hermite
    .get_single(&|x| x * x, &[f64::NEG_INFINITY, f64::INFINITY])
    .unwrap();                                                // sqrt(pi)/2
Rule Computes
Gauss-Legendre $\int_a^b f(x), \mathrm{d}x$
Gauss-Laguerre $\int_0\infty f(x), e{-x}, \mathrm{d}x$
Gauss-Hermite $\int_{-\infty}\infty f(x), e{-x^2}, \mathrm{d}x$

Jacobian and Hessian

use multicalc::numerical_derivative::finite_difference::FiniteDifferenceMulti;
use multicalc::numerical_derivative::jacobian::Jacobian;
use multicalc::numerical_derivative::hessian::Hessian;

let f1 = |v: &[f64; 3]| v[0] * v[1] * v[2];
let f2 = |v: &[f64; 3]| v[0] * v[0] + v[1] * v[1];
let functions: [&dyn Fn(&[f64; 3]) -> f64; 2] = [&f1, &f2];

let jacobian = Jacobian::<FiniteDifferenceMulti>::default();
let j = jacobian.get(&functions, &[1.0, 2.0, 3.0]).unwrap();   // [[6, 3, 2], [2, 4, 0]]

let g = |v: &[f64; 2]| v[1] * v[0].sin() + 2.0 * v[0] * v[1].exp();
let hessian = Hessian::<FiniteDifferenceMulti>::default();
let h = hessian.get(&g, &[1.0, 2.0]).unwrap();

Vector calculus

use multicalc::numerical_derivative::finite_difference::FiniteDifferenceMulti;
use multicalc::vector_field::{curl, divergence, line_integral, flux_integral};

// field (2xy, 3cos y)
let field: [&dyn Fn(&[f64; 2]) -> f64; 2] =
    [&(|v: &[f64; 2]| 2.0 * v[0] * v[1]), &(|v: &[f64; 2]| 3.0 * v[1].cos())];
let derivator = FiniteDifferenceMulti::default();
let c = curl::get_2d(derivator, &field, &[1.0, 3.14]).unwrap();
let d = divergence::get_2d(derivator, &field, &[1.0, 3.14]).unwrap();

// field (y, -x) along the unit circle (cos t, sin t)
let g: [&dyn Fn(&[f64; 2]) -> f64; 2] = [&(|v: &[f64; 2]| v[1]), &(|v: &[f64; 2]| -v[0])];
let curve: [&dyn Fn(f64) -> f64; 2] = [&(|t: f64| t.cos()), &(|t: f64| t.sin())];
let limit = [0.0, 2.0 * std::f64::consts::PI];
let line = line_integral::get_2d(&g, &curve, &limit).unwrap();   // -2*pi
let flux = flux_integral::get_2d(&g, &curve, &limit).unwrap();   //  0

Approximation

use multicalc::approximation::linear_approximation::LinearApproximator;
use multicalc::numerical_derivative::finite_difference::FiniteDifferenceMulti;

let f = |v: &[f64; 3]| v[0] + v[1] * v[1] + v[2] * v[2] * v[2];
let model = LinearApproximator::<FiniteDifferenceMulti>::default()
    .get(&f, &[1.0, 2.0, 3.0])
    .unwrap();

let y = model.predict(&[1.1, 2.1, 3.1]);
// model.get_prediction_metrics(&samples, &f) returns RMSE, R^2, and more

QuadraticApproximator works the same way and captures curvature as well.

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 Result<f64, CalcError>, so you decide how to handle bad input. All variants are listed in error_codes.rs.

Heap allocation

By default everything uses fixed-size stack arrays. Enable the alloc feature for Vec-based methods that handle inputs too large for the stack. This currently covers the Jacobian's get_on_heap, which returns a Vec<Vec<f64>>.

Examples

Runnable, self-contained programs for each module live in examples/ — see examples/README.md. Run one with:

cargo run --example <name>

Benchmarks

See BENCHMARKS.md for accuracy figures and measured latency.

Contributing

See CONTRIBUTIONS.md.

License

multicalc is licensed under the MIT license.

Contact

anmolkathail@gmail.com