Skip to main content

numdiff/
lib.rs

1//! [![github]](https://github.com/tamaskis/numdiff) [![crates-io]](https://crates.io/crates/numdiff) [![docs-rs]](https://docs.rs/numdiff)
2//!
3//! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github
4//! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust
5//! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs
6//!
7//! Automatic and numerical differentiation.
8//!
9//! # Overview
10//!
11//! This crate implements two different methods for evaluating derivatives in Rust:
12//!
13//! 1. Automatic differentiation (forward-mode using first-order dual numbers).
14//! 2. Numerical differentiation (using forward difference and central difference approximations).
15//!
16//! This crate provides generic functions (for numerical differentiation) and macros (for automatic
17//! differentiation) to evaluate various types of derivatives of the following types of functions:
18//!
19//! * Univariate, scalar-valued functions ($f:\mathbb{R}\to\mathbb{R}$)
20//! * Univariate, vector-valued functions ($\mathbf{f}:\mathbb{R}\to\mathbb{R}^{m}$)
21//! * Multivariate, scalar-valued functions ($f:\mathbb{R}^{n}\to\mathbb{R}$)
22//! * Multivariate, vector-valued functions ($\mathbf{f}:\mathbb{R}^{n}\to\mathbb{R}^{m}$)
23//!
24//! These functions and macros are made generic over the choice of vector representation, as long as
25//! the vector type implements the `linalg_traits::Vector` trait. See the
26//! [`linalg_traits` documentation](https://docs.rs/linalg-traits/latest/linalg_traits/) for more
27//! information.
28//!
29//! # Automatic Differentiation (Forward-Mode)
30//!
31//! This crate provides forward-mode automatic differentiation of functions that are generic over
32//! their vector and scalar types.
33//!
34//! Typing `R: linalg_traits::RealField` allows the automatic differentiation macros to substitute
35//! this crate's [`Dual`] and [`HyperDual`] types, which allow propagating 1st and 2nd-order
36//! derivative information through the computation. The resulting way functions have to be written
37//! differs mainly from a more "standard" numerical computing by requiring concrete types such as
38//! `f64` and `SVector<f64>` to be written in terms of generic types such as
39//! `R: linalg_traits::RealField` and `V: linalg_traits::Vector<R>`. It is, however, a close
40//! approximation to the standard Rust numeric style: the
41//! [`linalg_traits::RealField`](https://docs.rs/linalg-traits/latest/linalg_traits/trait.RealField.html)
42//! requires a superset of the functionality provided by common traits such as `num_traits::Float`
43//! and `nalgebra::RealField`, while additionally requiring a high degree of interoperability with
44//! `f64`. In that sense, this crate aims to stay near the ergonomics of normal Rust code without
45//! forcing a custom AD type at every call site.
46//!
47//! A major benefit of this approach is that the same function can be evaluated with an `f64`,
48//! allowing it to be used both in ordinary numerical computations and in automatic differentiation
49//! contexts. As an example, consider a function operating with conrete `f64`'s:
50//!
51//! ```rust
52//! fn f_f64(x: f64) -> f64 {
53//!     x * x.sin() + 1.0
54//! }
55//! ```
56//!
57//! To write this function in a generic way suitable for automatic differentiation:
58//!
59//! ```
60//! use linalg_traits::RealField;
61//!
62//! fn f_generic<R: RealField>(x: R) -> R {
63//!     x * x.sin() + 1.0
64//! }
65//! ```
66//!
67//! `f_generic` can be called with an `f64` just like `f_f64`, but it can also be called with a
68//! [`Dual`] or [`HyperDual`] type, which is the special sauce that allows this function to be
69//! automatically differentiated with one of the derivative macros provided by this crate.
70//!
71//! ## 1st-Order Derivatives
72//!
73//! | Derivative Type | Function Type | Macro to Generate Derivative Function |
74//! | --------------- | ------------- | ------------------------------------- |
75//! | derivative | $f:\mathbb{R}\to\mathbb{R}$ | [`get_sderivative!`] |
76//! | derivative | $\mathbf{f}:\mathbb{R}\to\mathbb{R}^{m}$ | [`get_vderivative!`] |
77//! | partial derivative | $f:\mathbb{R}^{n}\to\mathbb{R}$ | [`get_spartial_derivative!`] |
78//! | partial derivative | $\mathbf{f}:\mathbb{R}^{n}\to\mathbb{R}^{m}$ | [`get_vpartial_derivative!`] |
79//! | gradient | $f:\mathbb{R}^{n}\to\mathbb{R}$ | [`get_gradient!`] |
80//! | directional derivative | $f:\mathbb{R}^{n}\to\mathbb{R}$ | [`get_directional_derivative!`] |
81//! | Jacobian| $\mathbf{f}:\mathbb{R}^{n}\to\mathbb{R}^{m}$ | [`get_jacobian!`] |
82//!
83//! ## 2nd-Order Derivatives
84//!
85//! | Derivative Type | Function Type | Macro to Generate Derivative Function |
86//! | --------------- | ------------- | ------------------------------------- |
87//! | 2nd derivative | $f:\mathbb{R}\to\mathbb{R}$ | [`get_sderivative2!`] |
88//! | 2nd derivative | $\mathbf{f}:\mathbb{R}\to\mathbb{R}^{m}$ | [`get_vderivative2!`] |
89//! | 2nd partial derivative | $f:\mathbb{R}^{n}\to\mathbb{R}$ | [`get_spartial_derivative2!`] |
90//! | 2nd partial derivative | $\mathbf{f}:\mathbb{R}^{n}\to\mathbb{R}^{m}$ | [`get_vpartial_derivative2!`] |
91//! | mixed 2nd partial derivative | $f:\mathbb{R}^{n}\to\mathbb{R}$ | [`get_mixed_spartial_derivative2!`] |
92//! | mixed 2nd partial derivative | $\mathbf{f}:\mathbb{R}^{n}\to\mathbb{R}^{m}$ | [`get_mixed_vpartial_derivative2!`] |
93//! | Hessian | $f:\mathbb{R}^{n}\to\mathbb{R}$ | [`get_shessian!`] |
94//! | Hessian | $\mathbf{f}:\mathbb{R}^{n}\to\mathbb{R}^{m}$ | [`get_vhessian!`] |
95//!
96//! ## Passing Runtime Parameters
97//!
98//! Many times, we want to automatically differentiate functions that can also depend on parameters
99//! defined at runtime. However, automatic differentiation is performed at compile time, so we
100//! cannot simply "capture" these parameters using closures. To solve this problem, all automatic
101//! differentiation macros expect the function being differentiated to not only accept the point at
102//! which it is differentiated, but also a runtime parameter of an arbitrary type (could be a
103//! `&[f64]`, could be a `&T` where `T` is some custom struct, etc.).
104//!
105//! Examples are included for each macro (for example, here is the example for the
106//! [`get_jacobian!`] macro):
107//! [`get_jacobian!` - Example Passing Custom Parameter Types](macro@get_jacobian#example-passing-custom-parameter-types)
108//!
109//! ## Limitations
110//!
111//! * These macros only work on functions that are generic both over the type of scalar and the type
112//!   of vector.
113//!     - Consequently, these macros do _not_ work on closures.
114//! * Occasionally, constants (e.g. `5.0_f64`) need to be defined using `R::from` (e.g. if a
115//!   function has the generic parameter `R: RealField`, then instead of defining a constant number
116//!   such as `5.0_f64`, we need to do `5.0`).
117//!     - However, this is not always required, for example if you are doing something like
118//!       `5.0 * x` where `x: R` and `R: RealField`, then you don't have to do `5.0 * x`
119//!       because `RealField` requires that a type is interoperable with `f64`.
120//! * We cannot do assignment operations on `f64` where right hand side is an `x: R` where
121//!   `R: RealField` (e.g. `let y = 1.0; y += x;`).
122//!     - In cases like this you should do `let y = 1.0; y += x;`.
123//!
124//! ## Alternatives
125//!
126//! The Rust ecosystem has many different crates and approaches for automatic differentiation. Here
127//! we summarize some of the alternatives.
128//!
129//! ### Standard Library
130//!
131//! The most prominent alternative nowadays is the standard library's
132//! [`std::autodiff`](https://doc.rust-lang.org/std/autodiff/index.html) module which is in
133//! `nightly` Rust.
134//!
135//! This module provides low-level AD capabilities, but is not yet available in stable Rust. If it
136//! were, this crate perhaps would have been written as a convenience wrapper around that
137//! lower-level API.
138//!
139//! ### Forward-mode implementations
140//!
141//! These crates are broadly similar in computational model to this one, but they tend to require a
142//! custom numeric type or library-specific expression style rather than a more generic,
143//! backend-agnostic (i.e. not tied to a specific linear algebra library such as `nalgebra`).
144//!
145//! * [`num-dual`](https://docs.rs/num-dual/latest/num_dual/) — similar to this approach, but uses a
146//!   `DualNum` trait instead of a more generic-sounding `RealField`, and is only compatible with
147//!   `nalgebra` types.
148//! * [`autodiff`](https://github.com/elrnv/autodiff) — a solid forward-mode AD crate, but centered
149//!   on custom types such as
150//!   [`FT<T>`](https://docs.rs/autodiff/latest/autodiff/forward_autodiff/type.FT.html) instead of a
151//!   generic scalar interface.
152//! * [`autodj`](https://docs.rs/autodj/latest/autodj/) — works through custom AD types, but writing
153//!   autodifferentiable mathematical functions is more cumbersome, especially for multivariate
154//!   cases.
155//! * [`fwd_ad`](https://crates.io/crates/fwd_ad) — another crate using forward-mode AD, but
156//!   requires significant changes to make functions autodifferentiable.
157//! * [`kophy/autodiff`](https://github.com/kophy/autodiff) — only provides a very minimal `Dual`
158//!   number implementation, and is unpublished.
159//!
160//! ### Reverse-mode implementations
161//!
162//! All three of these libraries implement reverse-mode AD, but require users to use a significantly
163//! different syntax when writing autodifferentiable functions:
164//!
165//! * [`gad`](https://github.com/facebookresearch/gad)
166//! * [`reverse`](https://github.com/al-jshen/reverse)
167//! * [`revad`](https://github.com/Rufflewind/revad)
168//! * [`rustydiff`](https://github.com/Janko-dev/rustydiff)
169//! * [`rust-autograd`](https://github.com/raskr/rust-autograd)
170//!
171//! Note that the first four crates listed here also have not been maintained for 3+ years (some
172//! much longer), with at least one of them ([`gad`](https://github.com/facebookresearch/gad))
173//! having already been archived.
174//!
175//! # Finite Difference Methods
176//!
177//! ## Central Difference Approximations
178//!
179//! ### First-Order Derivatives
180//!
181//! | Derivative Type | Function Type | Function to Approximate Derivative |
182//! | --------------- | ------------- | ---------------------------------- |
183//! | derivative | $f:\mathbb{R}\to\mathbb{R}$ | [`central_difference::sderivative()`] |
184//! | derivative | $\mathbf{f}:\mathbb{R}\to\mathbb{R}^{m}$ | [`central_difference::vderivative()`] |
185//! | partial derivative | $f:\mathbb{R}^{n}\to\mathbb{R}$ | [`central_difference::spartial_derivative()`] |
186//! | partial derivative | $\mathbf{f}:\mathbb{R}^{n}\to\mathbb{R}^{m}$ | [`central_difference::vpartial_derivative()`] |
187//! | gradient | $f:\mathbb{R}^{n}\to\mathbb{R}$ | [`central_difference::gradient()`] |
188//! | directional derivative | $f:\mathbb{R}^{n}\to\mathbb{R}$ | [`central_difference::directional_derivative()`] |
189//! | Jacobian | $\mathbf{f}:\mathbb{R}^{n}\to\mathbb{R}^{m}$ | [`central_difference::jacobian()`] |
190//!
191//! ### Second-Order Derivatives
192//!
193//! | Derivative Type | Function Type | Function to Approximate Derivative |
194//! | --------------- | ------------- | ---------------------------------- |
195//! | Hessian | $f:\mathbb{R}^{n}\to\mathbb{R}$ | [`central_difference::shessian()`] |
196//! | Hessian | $\mathbf{f}:\mathbb{R}^{n}\to\mathbb{R}^{m}$ | [`central_difference::vhessian()`] |
197//! | 2nd derivative | $f:\mathbb{R}\to\mathbb{R}$ | [`central_difference::sderivative2()`] |
198//! | 2nd derivative | $\mathbf{f}:\mathbb{R}\to\mathbb{R}^{m}$ | [`central_difference::vderivative2()`] |
199//! | 2nd partial derivative | $f:\mathbb{R}^{n}\to\mathbb{R}$ | [`central_difference::spartial_derivative2()`] |
200//! | 2nd partial derivative | $\mathbf{f}:\mathbb{R}^{n}\to\mathbb{R}^{m}$ | [`central_difference::vpartial_derivative2()`] |
201//! | mixed 2nd partial derivative | $f:\mathbb{R}^{n}\to\mathbb{R}$ | [`central_difference::mixed_spartial_derivative2()`] |
202//! | mixed 2nd partial derivative | $\mathbf{f}:\mathbb{R}^{n}\to\mathbb{R}^{m}$ | [`central_difference::mixed_vpartial_derivative2()`] |
203//!
204//! ## Forward Difference Approximations
205//!
206//! ### First-Order Derivatives
207//!
208//! | Derivative Type | Function Type | Function to Approximate Derivative |
209//! | --------------- | ------------- | ---------------------------------- |
210//! | derivative | $f:\mathbb{R}\to\mathbb{R}$ | [`forward_difference::sderivative()`] |
211//! | derivative | $\mathbf{f}:\mathbb{R}\to\mathbb{R}^{m}$ | [`forward_difference::vderivative()`] |
212//! | partial derivative | $f:\mathbb{R}^{n}\to\mathbb{R}$ | [`forward_difference::spartial_derivative()`] |
213//! | partial derivative | $\mathbf{f}:\mathbb{R}^{n}\to\mathbb{R}^{m}$ | [`forward_difference::vpartial_derivative()`] |
214//! | gradient | $f:\mathbb{R}^{n}\to\mathbb{R}$ | [`forward_difference::gradient()`] |
215//! | directional derivative | $f:\mathbb{R}^{n}\to\mathbb{R}$ | [`forward_difference::directional_derivative()`] |
216//! | Jacobian | $\mathbf{f}:\mathbb{R}^{n}\to\mathbb{R}^{m}$ | [`forward_difference::jacobian()`] |
217//!
218//! ### Second-Order Derivatives
219//!
220//! | Derivative Type | Function Type | Function to Approximate Derivative |
221//! | --------------- | ------------- | ---------------------------------- |
222//! | Hessian | $f:\mathbb{R}^{n}\to\mathbb{R}$ | [`forward_difference::shessian()`] |
223//! | Hessian | $\mathbf{f}:\mathbb{R}^{n}\to\mathbb{R}^{m}$ | [`forward_difference::vhessian()`] |
224//! | 2nd derivative | $f:\mathbb{R}\to\mathbb{R}$ | [`forward_difference::sderivative2()`] |
225//! | 2nd derivative | $\mathbf{f}:\mathbb{R}\to\mathbb{R}^{m}$ | [`forward_difference::vderivative2()`] |
226//! | 2nd partial derivative | $f:\mathbb{R}^{n}\to\mathbb{R}$ | [`forward_difference::spartial_derivative2()`] |
227//! | 2nd partial derivative | $\mathbf{f}:\mathbb{R}^{n}\to\mathbb{R}^{m}$ | [`forward_difference::vpartial_derivative2()`] |
228//! | mixed 2nd partial derivative | $f:\mathbb{R}^{n}\to\mathbb{R}$ | [`forward_difference::mixed_spartial_derivative2()`] |
229//! | mixed 2nd partial derivative | $\mathbf{f}:\mathbb{R}^{n}\to\mathbb{R}^{m}$ | [`forward_difference::mixed_vpartial_derivative2()`] |
230//!
231//! ## Passing Runtime Parameters
232//!
233//! For the finite difference methods, we can simply capture any runtime parameters using closures.
234//!
235//! Examples are included for each function (for example, here is the example for the
236//! [`central_difference::jacobian()`] function):
237//! [`central_difference::jacobian()` - Example Passing Runtime Parameters](fn@central_difference::jacobian#example-passing-runtime-parameters)
238
239// Linter setup.
240#![warn(missing_docs, warnings, clippy::all, clippy::pedantic, clippy::cargo)]
241#![allow(
242    clippy::float_cmp,
243    clippy::multiple_crate_versions,
244    clippy::unreadable_literal
245)]
246
247// Module declarations.
248mod automatic_differentiation;
249pub mod central_difference;
250pub mod constants;
251pub mod forward_difference;
252
253// Module declarations for utils used for testing only.
254#[cfg(test)]
255pub(crate) mod test_utils;
256
257// Re-exports.
258pub use automatic_differentiation::dual::dual::Dual;
259pub use automatic_differentiation::dual::dual_vector::DualVector;
260pub use automatic_differentiation::hyper_dual::hyper_dual::HyperDual;
261pub use automatic_differentiation::hyper_dual::hyper_dual_vector::HyperDualVector;