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}