Skip to main content

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/// * `param_type` (optional) - Type of the extra runtime parameter `p` that is passed to `f`.
12///   Defaults to `[f64]` (implying that `f` accepts `p: &[f64]`).
13///
14/// # Warning
15///
16/// `f` cannot be defined as closure. It must be defined as a function.
17///
18/// # Note
19///
20/// The function produced by this macro will perform 1 evaluation of $f(\mathbf{x})$ to evaluate its
21/// partial derivative with respect to $x_{k}$.
22///
23/// # Examples
24///
25/// ## Basic Example
26///
27/// Compute the partial derivative of
28///
29/// $$f(x)=x^{3}\sin{y}$$
30///
31/// with respect to $y$ at $(x,y)=(5,1)$, and compare the result to the true result of
32///
33/// $$\frac{\partial f}{\partial y}\bigg\rvert_{(x,y)=(5,1)}=5^{3}\cos{(1)}$$
34///
35/// First, note that we can rewrite this function as
36///
37/// $$f(\mathbf{x})=x_{0}^{3}\sin{x_{1}}$$
38///
39/// where $\mathbf{x}=(x_{0},x_{1})^{T}$ (note that we use 0-based indexing to aid with the
40/// computational implementation). We are then trying to find
41///
42/// $$\frac{\partial f}{\partial x_{1}}\bigg\rvert_{\mathbf{x}=\mathbf{x}_{0}}$$
43///
44/// where $\mathbf{x}_{0}=(5,1)^{T}$.
45///
46/// #### Using standard vectors
47///
48/// ```
49/// use linalg_traits::{Scalar, Vector};
50///
51/// use numdiff::{get_spartial_derivative, Dual, DualVector};
52///
53/// // Define the function, f(x).
54/// fn f<S: Scalar, V: Vector<S>>(x: &V, _p: &[f64]) -> S {
55///     x.vget(0).powi(3) * x.vget(1).sin()
56/// }
57///
58/// // Define the evaluation point.
59/// let x0 = vec![5.0, 1.0];
60///
61/// // Define the element of the vector (using 0-based indexing) we are differentiating with respect
62/// // to.
63/// let k = 1;
64///
65/// // Autogenerate the function "dfk" that can be used to compute the partial derivative of f(x)
66/// // with respect to xₖ at any point x.
67/// get_spartial_derivative!(f, dfk);
68///
69/// // Verify that the partial derivative function obtained using get_spartial_derivative! computes
70/// // the partial derivative correctly.
71/// assert_eq!(dfk(&x0, k, &[]), 5.0_f64.powi(3) * 1.0_f64.cos());
72/// ```
73///
74/// #### Using other vector types
75///
76/// The function produced by `get_spartial_derivative!` can accept _any_ type for `x0`, as long as
77/// it implements the `linalg_traits::Vector` trait.
78///
79/// ```
80/// use faer::Mat;
81/// use linalg_traits::{Scalar, Vector};
82/// use nalgebra::{dvector, DVector, SVector};
83/// use ndarray::{array, Array1};
84///
85/// use numdiff::{get_spartial_derivative, Dual, DualVector};
86///
87/// // Define the function, f(x).
88/// fn f<S: Scalar, V: Vector<S>>(x: &V, _p: &[f64]) -> S {
89///     x.vget(0).powi(3) * x.vget(1).sin()
90/// }
91///
92/// // Define the element of the vector (using 0-based indexing) we are differentiating with respect
93/// // to.
94/// let k = 1;
95///
96/// // Autogenerate the function "dfk" that can be used to compute the partial derivative of f(x)
97/// // with respect to xₖ at any point x.
98/// get_spartial_derivative!(f, dfk);
99///
100/// // nalgebra::DVector
101/// let x0: DVector<f64> = dvector![5.0, 1.0];
102/// let dfk_eval: f64 = dfk(&x0, k, &[]);
103///
104/// // nalgebra::SVector
105/// let x0: SVector<f64, 2> = SVector::from_slice(&[5.0, 1.0]);
106/// let dfk_eval: f64 = dfk(&x0, k, &[]);
107///
108/// // ndarray::Array1
109/// let x0: Array1<f64> = array![5.0, 1.0];
110/// let dfk_eval: f64 = dfk(&x0, k, &[]);
111///
112/// // faer::Mat
113/// let x0: Mat<f64> = Mat::from_slice(&[5.0, 1.0]);
114/// let dfk_eval: f64 = dfk(&x0, k, &[]);
115/// ```
116///
117/// ## Example Passing Runtime Parameters
118///
119/// Compute the partial derivative of a parameterized function
120///
121/// $$f(\mathbf{x})=ax_{0}^{2}+bx_{1}^{2}+cx_{0}x_{1}+d\sin(ex_{0})$$
122///
123/// where $a$, $b$, $c$, $d$, and $e$ are runtime parameters. The partial derivatives are:
124///
125/// * $\dfrac{\partial f}{\partial x_{0}}=2ax_{0}+cx_{1}+de\cos(ex_{0})$
126/// * $\dfrac{\partial f}{\partial x_{1}}=2bx_{1}+cx_{0}$
127///
128/// ```
129/// use linalg_traits::{Scalar, Vector};
130/// use numtest::*;
131///
132/// use numdiff::{get_spartial_derivative, Dual, DualVector};
133///
134/// // Define the function, f(x).
135/// fn f<S: Scalar, V: Vector<S>>(x: &V, p: &[f64]) -> S {
136///     let a = S::new(p[0]);
137///     let b = S::new(p[1]);
138///     let c = S::new(p[2]);
139///     let d = S::new(p[3]);
140///     let e = S::new(p[4]);
141///     a * x.vget(0).powi(2)
142///         + b * x.vget(1).powi(2)
143///         + c * x.vget(0) * x.vget(1)
144///         + d * (e * x.vget(0)).sin()
145/// }
146///
147/// // Define individual parameters.
148/// let a = 1.5;
149/// let b = 2.0;
150/// let c = 0.8;
151/// let d = 3.0;
152/// let e = 0.5;
153///
154/// // Parameter vector.
155/// let p = [a, b, c, d, e];
156///
157/// // Evaluation point.
158/// let x0 = vec![1.0, -0.5];
159///
160/// // Autogenerate the partial derivative function.
161/// get_spartial_derivative!(f, dfk);
162///
163/// // True partial derivative functions.
164/// let df_dx0_true = |x: &[f64]| 2.0 * a * x[0] + c * x[1] + d * e * (e * x[0]).cos();
165/// let df_dx1_true = |x: &[f64]| 2.0 * b * x[1] + c * x[0];
166///
167/// // Compute ∂f/∂x₀ at x₀ and compare with true function.
168/// let df_dx0: f64 = dfk(&x0, 0, &p);
169/// let expected_df_dx0 = df_dx0_true(&x0);
170/// assert_equal_to_decimal!(df_dx0, expected_df_dx0, 14);
171///
172/// // Compute ∂f/∂x₁ at x0 and compare with true function.
173/// let df_dx1: f64 = dfk(&x0, 1, &p);
174/// let expected_df_dx1 = df_dx1_true(&x0);
175/// assert_equal_to_decimal!(df_dx1, expected_df_dx1, 15);
176/// ```
177///
178/// ## Example Passing Custom Parameter Types
179///
180/// Use a custom parameter struct instead of `f64` values.
181///
182/// ```
183/// use linalg_traits::{Scalar, Vector};
184/// use numtest::*;
185///
186/// use numdiff::{get_spartial_derivative, Dual, DualVector};
187///
188/// struct Data {
189///     a: f64,
190///     b: f64,
191///     c: f64,
192///     d: f64,
193///     e: f64,
194/// }
195///
196/// // Define the function, f(x).
197/// fn f<S: Scalar, V: Vector<S>>(x: &V, p: &Data) -> S {
198///     let a = S::new(p.a);
199///     let b = S::new(p.b);
200///     let c = S::new(p.c);
201///     let d = S::new(p.d);
202///     let e = S::new(p.e);
203///     a * x.vget(0).powi(2)
204///         + b * x.vget(1).powi(2)
205///         + c * x.vget(0) * x.vget(1)
206///         + d * (e * x.vget(0)).sin()
207/// }
208///
209/// // Runtime parameter struct.
210/// let p = Data {
211///     a: 1.5,
212///     b: 2.0,
213///     c: 0.8,
214///     d: 3.0,
215///     e: 0.5,
216/// };
217///
218/// // Evaluation point.
219/// let x0 = vec![1.0, -0.5];
220///
221/// // Autogenerate the partial derivative function, telling the macro to expect a runtime parameter
222/// // of type &Data.
223/// get_spartial_derivative!(f, dfk, Data);
224///
225/// // True partial derivative functions.
226/// let df_dx0_true = |x: &[f64]| {
227///     2.0 * p.a * x[0] + p.c * x[1] + p.d * p.e * (p.e * x[0]).cos()
228/// };
229/// let df_dx1_true = |x: &[f64]| 2.0 * p.b * x[1] + p.c * x[0];
230///
231/// // Compute the partial derivatives using both the automatically generated partial derivative
232/// // function and the true partial derivative functions, and compare the results.
233/// let df_dx0: f64 = dfk(&x0, 0, &p);
234/// let df_dx1: f64 = dfk(&x0, 1, &p);
235/// assert_equal_to_decimal!(df_dx0, df_dx0_true(&x0), 14);
236/// assert_equal_to_decimal!(df_dx1, df_dx1_true(&x0), 15);
237/// ```
238#[macro_export]
239macro_rules! get_spartial_derivative {
240    ($f:ident, $func_name:ident) => {
241        get_spartial_derivative!($f, $func_name, [f64]);
242    };
243    ($f:ident, $func_name:ident, $param_type:ty) => {
244        /// Partial derivative of a multivariate, scalar-valued function `f: ℝⁿ → ℝ`.
245        ///
246        /// This function is generated for a specific function `f` using the
247        /// `numdiff::get_spartial_derivative!` macro.
248        ///
249        /// # Arguments
250        ///
251        /// * `x0` - Evaluation point, `x₀ ∈ ℝⁿ`.
252        /// * `k` - Element of `x` to differentiate with respect to. Note that this uses 0-based
253        ///   indexing (e.g. `x = (x₀,...,xₖ,...,xₙ₋₁)ᵀ`).
254        /// * `p` - Extra runtime parameter. This is a parameter (can be of any arbitrary type)
255        ///   defined at runtime that the function may depend on but is not differentiated with
256        ///   respect to.
257        ///
258        /// # Returns
259        ///
260        /// Partial derivative of `f` with respect to `xₖ`, evaluated at `x = x₀`.
261        ///
262        /// `(∂f/∂xₖ)|ₓ₌ₓ₀ ∈ ℝ`
263        fn $func_name<S, V>(x0: &V, k: usize, p: &$param_type) -> f64
264        where
265            S: Scalar,
266            V: Vector<S>,
267        {
268            // Promote the evaluation point to a vector of dual numbers.
269            let mut x0_dual = x0.clone().to_dual_vector();
270
271            // Take a unit step forward in the kth dual direction.
272            x0_dual.vset(k, Dual::new(x0_dual.vget(k).get_real(), 1.0));
273
274            // Evaluate the function at the dual number.
275            let f_x0 = $f(&x0_dual, p);
276
277            // Partial derivative of f with respect to xₖ.
278            f_x0.get_dual()
279        }
280    };
281}
282
283#[cfg(test)]
284mod tests {
285    use crate::{Dual, DualVector};
286    use linalg_traits::{Scalar, Vector};
287    use nalgebra::SVector;
288    use numtest::*;
289
290    #[test]
291    fn test_spartial_derivative_1() {
292        // Function to take the partial derivative of.
293        fn f<S: Scalar, V: Vector<S>>(x: &V, _p: &[f64]) -> S {
294            x.vget(0).powi(2)
295        }
296
297        // Evaluation point.
298        let x0 = vec![2.0];
299
300        // Element to differentiate with respect to.
301        let k = 0;
302
303        // True partial derivative function.
304        let dfk = |x: &Vec<f64>| 2.0 * x[0];
305
306        // Partial derivative function obtained via forward-mode automatic differentiation.
307        get_spartial_derivative!(f, dfk_autodiff);
308
309        // Evaluate the partial derivative using both functions.
310        let dfk_eval_autodiff: f64 = dfk_autodiff(&x0, k, &[]);
311        let dfk_eval: f64 = dfk(&x0);
312
313        // Test autodiff partial derivative against true partial derivative.
314        assert_eq!(dfk_eval_autodiff, dfk_eval);
315    }
316
317    #[test]
318    fn test_spartial_derivative_2() {
319        // Function to take the partial derivative of.
320        fn f<S: Scalar, V: Vector<S>>(x: &V, _p: &[f64]) -> S {
321            x.vget(0).powi(3) * x.vget(1).powi(3)
322        }
323
324        // Evaluation point.
325        let x0: SVector<f64, 2> = SVector::from_slice(&[3.0, 2.0]);
326
327        // Element to differentiate with respect to.
328        let k = 1;
329
330        // True partial derivative function.
331        let dfk = |x: &SVector<f64, 2>| 3.0 * x[0].powi(3) * x[1].powi(2);
332
333        // Partial derivative function obtained via forward-mode automatic differentiation.
334        get_spartial_derivative!(f, dfk_autodiff);
335
336        // Evaluate the partial derivative using both functions.
337        let dfk_eval_autodiff: f64 = dfk_autodiff(&x0, k, &[]);
338        let dfk_eval: f64 = dfk(&x0);
339
340        // Test autodiff partial derivative against true partial derivative.
341        assert_eq!(dfk_eval_autodiff, dfk_eval);
342    }
343
344    #[test]
345    fn test_spartial_derivative_3() {
346        /// Function to take the partial derivative of.
347        #[allow(clippy::many_single_char_names)]
348        fn f<S: Scalar, V: Vector<S>>(x: &V, p: &[f64]) -> S {
349            let a = S::new(p[0]);
350            let b = S::new(p[1]);
351            let c = S::new(p[2]);
352            a * (b * x.vget(0)).exp() + c * x.vget(1).powi(3)
353        }
354
355        // Parameter vector.
356        let p = [2.5, 0.3, -1.2];
357
358        // Evaluation point.
359        let x0: SVector<f64, 2> = SVector::from_slice(&[1.5, 2.0]);
360
361        // Function to take the partial derivative of.
362        let k = 0;
363
364        // True partial derivative function.
365        let dfk = |x: &SVector<f64, 2>, p: &[f64]| p[0] * p[1] * (p[1] * x[0]).exp();
366
367        // Partial derivative function obtained via forward-mode automatic differentiation.
368        get_spartial_derivative!(f, dfk_autodiff);
369
370        // Evaluate the partial derivative using both functions.
371        let dfk_eval_autodiff: f64 = dfk_autodiff(&x0, k, &p);
372        let dfk_eval: f64 = dfk(&x0, &p);
373
374        // Test autodiff partial derivative against true partial derivative.
375        assert_eq!(dfk_eval_autodiff, dfk_eval);
376    }
377
378    #[test]
379    fn test_spartial_derivative_custom_params() {
380        struct Data {
381            a: f64,
382            b: f64,
383            c: f64,
384            d: f64,
385            e: f64,
386        }
387
388        // Function to take the partial derivative of.
389        #[allow(clippy::many_single_char_names)]
390        fn f<S: Scalar, V: Vector<S>>(x: &V, p: &Data) -> S {
391            let a = S::new(p.a);
392            let b = S::new(p.b);
393            let c = S::new(p.c);
394            let d = S::new(p.d);
395            let e = S::new(p.e);
396            a * x.vget(0).powi(2)
397                + b * x.vget(1).powi(2)
398                + c * x.vget(0) * x.vget(1)
399                + d * (e * x.vget(0)).sin()
400        }
401
402        // Runtime parameter struct.
403        let p = Data {
404            a: 1.5,
405            b: 2.0,
406            c: 0.8,
407            d: 3.0,
408            e: 0.5,
409        };
410
411        // Evaluation point.
412        let x0 = vec![1.0, -0.5];
413
414        // Partial derivative function obtained via forward-mode automatic differentiation.
415        get_spartial_derivative!(f, dfk, Data);
416
417        // True partial derivative functions.
418        let df_dx0_true =
419            |x: &[f64]| 2.0 * p.a * x[0] + p.c * x[1] + p.d * p.e * (p.e * x[0]).cos();
420        let df_dx1_true = |x: &[f64]| 2.0 * p.b * x[1] + p.c * x[0];
421
422        // Evaluate the partial derivatives using both functions.
423        let df_dx0: f64 = dfk(&x0, 0, &p);
424        let df_dx1: f64 = dfk(&x0, 1, &p);
425
426        // Test autodiff partial derivatives against true partial derivatives.
427        assert_equal_to_decimal!(df_dx0, df_dx0_true(&x0), 14);
428        assert_equal_to_decimal!(df_dx1, df_dx1_true(&x0), 15);
429    }
430}