numdiff/automatic_differentiation/partial_derivative/
scalar_valued.rs

1/// Get a function that returns the partial derivative of the provided multivariate, scalar-valued
2/// function.
3///
4/// The partial derivative is computed using forward-mode automatic differentiation.
5///
6/// # Arguments
7///
8/// * `f` - Multivariate, scalar-valued function, $f:\mathbb{R}^{n}\to\mathbb{R}$.
9/// * `func_name` - Name of the function that will return the partial derivative of $f(\mathbf{x})$
10///                 with respect to $x_{k}$ at any point $\mathbf{x}\in\mathbb{R}^{n}$.
11///
12/// # Warning
13///
14/// `f` cannot be defined as closure. It must be defined as a function.
15///
16/// # Note
17///
18/// The function produced by this macro will perform 1 evaluation of $f(\mathbf{x})$ to evaluate its
19/// partial derivative with respect to $x_{k}$.
20///
21/// # Example
22///
23/// Compute the partial derivative of
24///
25/// $$f(x)=x^{3}\sin{y}$$
26///
27/// with respect to $y$ at $(x,y)=(5,1)$, and compare the result to the true result of
28///
29/// $$\frac{\partial f}{\partial y}\bigg\rvert_{(x,y)=(5,1)}=5^{3}\cos{(1)}$$
30///
31/// First, note that we can rewrite this function as
32///
33/// $$f(\mathbf{x})=x_{0}^{3}\sin{x_{1}}$$
34///
35/// where $\mathbf{x}=(x_{0},x_{1})^{T}$ (note that we use 0-based indexing to aid with the
36/// computational implementation). We are then trying to find
37///
38/// $$\frac{\partial f}{\partial x_{1}}\bigg\rvert_{\mathbf{x}=\mathbf{x}_{0}}$$
39///
40/// where $\mathbf{x}_{0}=(5,1)^{T}$.
41///
42/// #### Using standard vectors
43///
44/// ```
45/// use linalg_traits::{Scalar, Vector};
46///
47/// use numdiff::{get_spartial_derivative, Dual, DualVector};
48///
49/// // Define the function, f(x).
50/// fn f<S: Scalar, V: Vector<S>>(x: &V) -> S {
51///     x.vget(0).powi(3) * x.vget(1).sin()
52/// }
53///
54/// // Define the evaluation point.
55/// let x0 = vec![5.0, 1.0];
56///
57/// // Define the element of the vector (using 0-based indexing) we are differentiating with respect
58/// // to.
59/// let k = 1;
60///
61/// // Autogenerate the function "dfk" that can be used to compute the partial derivative of f(x)
62/// // with respect to xₖ at any point x.
63/// get_spartial_derivative!(f, dfk);
64///
65/// // Verify that the partial derivative function obtained using get_spartial_derivative! computes
66/// // the partial derivative correctly.
67/// assert_eq!(dfk(&x0, k), 5.0_f64.powi(3) * 1.0_f64.cos());
68/// ```
69///
70/// #### Using other vector types
71///
72/// The function produced by `get_spartial_derivative!` can accept _any_ type for `x0`, as long as
73/// it implements the `linalg_traits::Vector` trait.
74///
75/// ```
76/// use faer::Mat;
77/// use linalg_traits::{Scalar, Vector};
78/// use nalgebra::{dvector, DVector, SVector};
79/// use ndarray::{array, Array1};
80///
81/// use numdiff::{get_spartial_derivative, Dual, DualVector};
82///
83/// // Define the function, f(x).
84/// fn f<S: Scalar, V: Vector<S>>(x: &V) -> S {
85///     x.vget(0).powi(3) * x.vget(1).sin()
86/// }
87///
88/// // Define the element of the vector (using 0-based indexing) we are differentiating with respect
89/// // to.
90/// let k = 1;
91///
92/// // Autogenerate the function "dfk" that can be used to compute the partial derivative of f(x)
93/// // with respect to xₖ at any point x.
94/// get_spartial_derivative!(f, dfk);
95///
96/// // nalgebra::DVector
97/// let x0: DVector<f64> = dvector![5.0, 1.0];
98/// let dfk_eval: f64 = dfk(&x0, k);
99///
100/// // nalgebra::SVector
101/// let x0: SVector<f64, 2> = SVector::from_slice(&[5.0, 1.0]);
102/// let dfk_eval: f64 = dfk(&x0, k);
103///
104/// // ndarray::Array1
105/// let x0: Array1<f64> = array![5.0, 1.0];
106/// let dfk_eval: f64 = dfk(&x0, k);
107///
108/// // faer::Mat
109/// let x0: Mat<f64> = Mat::from_slice(&[5.0, 1.0]);
110/// let dfk_eval: f64 = dfk(&x0, k);
111/// ```
112#[macro_export]
113macro_rules! get_spartial_derivative {
114    ($f:ident, $func_name:ident) => {
115        /// Partial derivative of a multivariate, scalar-valued function `f: ℝⁿ → ℝ`.
116        ///
117        /// This function is generated for a specific function `f` using the
118        /// `numdiff::get_spartial_derivative!` macro.
119        ///
120        /// # Arguments
121        ///
122        /// `x0` - Evaluation point, `x₀ ∈ ℝⁿ`.
123        /// `k` - Element of `x` to differentiate with respect to. Note that this uses 0-based
124        ///       indexing (e.g. `x = (x₀,...,xₖ,...,xₙ₋₁)ᵀ).
125        ///
126        /// # Returns
127        ///
128        /// Partial derivative of `f` with respect to `xₖ`, evaluated at `x = x₀`.
129        ///
130        /// `(∂f/∂xₖ)|ₓ₌ₓ₀ ∈ ℝ`
131        fn $func_name<S, V>(x0: &V, k: usize) -> f64
132        where
133            S: Scalar,
134            V: Vector<S>,
135        {
136            // Promote the evaluation point to a vector of dual numbers.
137            let mut x0_dual = x0.clone().to_dual_vector();
138
139            // Take a unit step forward in the kth dual direction.
140            x0_dual.vset(k, Dual::new(x0_dual.vget(k).get_real(), 1.0));
141
142            // Evaluate the function at the dual number.
143            let f_x0 = $f(&x0_dual);
144
145            // Partial derivative of f with respect to xₖ.
146            f_x0.get_dual()
147        }
148    };
149}
150
151#[cfg(test)]
152mod tests {
153    use crate::{Dual, DualVector};
154    use linalg_traits::{Scalar, Vector};
155    use nalgebra::SVector;
156
157    #[test]
158    fn test_spartial_derivative_1() {
159        // Function to take the partial derivative of.
160        fn f<S: Scalar, V: Vector<S>>(x: &V) -> S {
161            x.vget(0).powi(2)
162        }
163
164        // Evaluation point.
165        let x0 = vec![2.0];
166
167        // Element to differentiate with respect to.
168        let k = 0;
169
170        // True partial derivative function.
171        let dfk = |x: &Vec<f64>| 2.0 * x[0];
172
173        // Partial derivative function obtained via forward-mode automatic differentiation.
174        get_spartial_derivative!(f, dfk_autodiff);
175
176        // Evaluate the partial derivative using both functions.
177        let dfk_eval_autodiff: f64 = dfk_autodiff(&x0, k);
178        let dfk_eval: f64 = dfk(&x0);
179
180        // Test autodiff partial derivative against true partial derivative.
181        assert_eq!(dfk_eval_autodiff, dfk_eval);
182    }
183
184    #[test]
185    fn test_spartial_derivative_2() {
186        // Function to take the partial derivative of.
187        fn f<S: Scalar, V: Vector<S>>(x: &V) -> S {
188            x.vget(0).powi(3) * x.vget(1).powi(3)
189        }
190
191        // Evaluation point.
192        let x0: SVector<f64, 2> = SVector::from_slice(&[3.0, 2.0]);
193
194        // Element to differentiate with respect to.
195        let k = 1;
196
197        // True partial derivative function.
198        let dfk = |x: &SVector<f64, 2>| 3.0 * x[0].powi(3) * x[1].powi(2);
199
200        // Partial derivative function obtained via forward-mode automatic differentiation.
201        get_spartial_derivative!(f, dfk_autodiff);
202
203        // Evaluate the partial derivative using both functions.
204        let dfk_eval_autodiff: f64 = dfk_autodiff(&x0, k);
205        let dfk_eval: f64 = dfk(&x0);
206
207        // Test autodiff partial derivative against true partial derivative.
208        assert_eq!(dfk_eval_autodiff, dfk_eval);
209    }
210}