numdiff/automatic_differentiation/gradient.rs
1/// Get a function that returns the gradient of the provided multivariate, scalar-valued function.
2///
3/// The gradient is computed using forward-mode automatic differentiation.
4///
5/// # Arguments
6///
7/// * `f` - Multivariate, scalar-valued function, $f:\mathbb{R}^{n}\to\mathbb{R}$.
8/// * `func_name` - Name of the function that will return the gradient of $f(\mathbf{x})$ at any
9/// point $\mathbf{x}\in\mathbb{R}^{n}$.
10///
11/// # Warning
12///
13/// `f` cannot be defined as closure. It must be defined as a function.
14///
15/// # Note
16///
17/// The function produced by this macro will perform $n$ evaluations of $f(\mathbf{x})$ to evaluate
18/// its gradient.
19///
20/// # Examples
21///
22/// ## Basic Example
23///
24/// Compute the gradient of
25///
26/// $$f(\mathbf{x})=x_{0}^{5}+\sin^{3}{x_{1}}$$
27///
28/// at $\mathbf{x}=(5,8)^{T}$, and compare the result to the true result of
29///
30/// $$
31/// \nabla f\left((5,8)^{T}\right)=
32/// \begin{bmatrix}
33/// 3125 \\\\
34/// 3\sin^{2}{(8)}\cos{(8)}
35/// \end{bmatrix}
36/// $$
37///
38/// #### Using standard vectors
39///
40/// ```
41/// use linalg_traits::{Scalar, Vector};
42/// use numtest::*;
43///
44/// use numdiff::{get_gradient, Dual, DualVector};
45///
46/// // Define the function, f(x).
47/// fn f<S: Scalar, V: Vector<S>>(x: &V, _p: &[f64]) -> S {
48/// x.vget(0).powi(5) + x.vget(1).sin().powi(3)
49/// }
50///
51/// // Define the evaluation point.
52/// let x0 = vec![5.0, 8.0];
53///
54/// // Parameter vector (empty for this example).
55/// let p = [];
56///
57/// // Autogenerate the function "g" that can be used to compute the gradient of f(x) at any point
58/// // x.
59/// get_gradient!(f, g);
60///
61/// // Function defining the true gradient of f(x).
62/// let g_true = |x: &Vec<f64>| vec![5.0 * x[0].powi(4), 3.0 * x[1].sin().powi(2) * x[1].cos()];
63///
64/// // Evaluate the gradient using "g".
65/// let g_eval: Vec<f64> = g(&x0, &p);
66///
67/// // Verify that the gradient function obtained using get_gradient! computes the gradient
68/// // correctly.
69/// assert_arrays_equal_to_decimal!(g(&x0, &p), g_true(&x0), 15);
70/// ```
71///
72/// #### Using other vector types
73///
74/// The function produced by `get_gradient!` can accept _any_ type for `x0`, as long as it
75/// implements the `linalg_traits::Vector` trait.
76///
77/// ```
78/// use faer::Mat;
79/// use linalg_traits::{Scalar, Vector};
80/// use nalgebra::{dvector, DVector, SVector};
81/// use ndarray::{array, Array1};
82///
83/// use numdiff::{get_gradient, Dual, DualVector};
84///
85/// // Define the function, f(x).
86/// fn f<S: Scalar, V: Vector<S>>(x: &V, _p: &[f64]) -> S {
87/// x.vget(0).powi(5) + x.vget(1).sin().powi(3)
88/// }
89///
90/// // Parameter vector (empty for this example).
91/// let p = [];
92///
93/// // Autogenerate the function "g" that can be used to compute the gradient of f(x) at any point
94/// // x.
95/// get_gradient!(f, g);
96///
97/// // nalgebra::DVector
98/// let x0: DVector<f64> = dvector![5.0, 8.0];
99/// let g_eval: DVector<f64> = g(&x0, &p);
100///
101/// // nalgebra::SVector
102/// let x0: SVector<f64, 2> = SVector::from_slice(&[5.0, 8.0]);
103/// let g_eval: SVector<f64, 2> = g(&x0, &p);
104///
105/// // ndarray::Array1
106/// let x0: Array1<f64> = array![5.0, 8.0];
107/// let g_eval: Array1<f64> = g(&x0, &p);
108///
109/// // faer::Mat
110/// let x0: Mat<f64> = Mat::from_slice(&[5.0, 8.0]);
111/// let g_eval: Mat<f64> = g(&x0, &p);
112/// ```
113///
114/// ## Example Passing Runtime Parameters
115///
116/// Compute the gradient of a parameterized function
117///
118/// $$f(\mathbf{x})=ax_{0}^{2}+bx_{1}^{2}+cx_{0}x_{1}+d$$
119///
120/// where $a$, $b$, $c$, and $d$ are runtime parameters. Compare the result against the true
121/// gradient of
122///
123/// $$\nabla f=\begin{bmatrix}2ax_{0}+cx_{1}\\\\2bx_{1}+cx_{0}\end{bmatrix}$$
124///
125/// ```
126/// use linalg_traits::{Scalar, Vector};
127/// use numtest::*;
128///
129/// use numdiff::{get_gradient, Dual, DualVector};
130///
131/// // Define the function, f(x).
132/// fn f<S: Scalar, V: Vector<S>>(x: &V, p: &[f64]) -> S {
133/// let a = S::new(p[0]);
134/// let b = S::new(p[1]);
135/// let c = S::new(p[2]);
136/// let d = S::new(p[3]);
137/// a * x.vget(0).powi(2) + b * x.vget(1).powi(2) + c * x.vget(0) * x.vget(1) + d
138/// }
139///
140/// // Parameter vector.
141/// let a = 2.0;
142/// let b = 1.5;
143/// let c = 0.8;
144/// let d = -3.0;
145/// let p = [a, b, c, d];
146///
147/// // Evaluation point.
148/// let x0 = vec![1.0, -2.0];
149///
150/// // Autogenerate the gradient function.
151/// get_gradient!(f, g);
152///
153/// // True gradient function.
154/// let g_true = |x: &Vec<f64>| vec![2.0 * a * x[0] + c * x[1], 2.0 * b * x[1] + c * x[0]];
155///
156/// // Compute the gradient using both the automatically generated gradient function and the true
157/// // gradient function, and compare the results.
158/// let g_eval: Vec<f64> = g(&x0, &p);
159/// let g_eval_true: Vec<f64> = g_true(&x0);
160/// assert_arrays_equal_to_decimal!(g_eval, g_eval_true, 15);
161/// ```
162#[macro_export]
163macro_rules! get_gradient {
164 ($f:ident, $func_name:ident) => {
165 /// Gradient of a multivariate, scalar-valued function `f: ℝⁿ → ℝ`.
166 ///
167 /// This function is generated for a specific function `f` using the
168 /// `numdiff::get_gradient!` macro.
169 ///
170 /// # Arguments
171 ///
172 /// * `x0` - Evaluation point, `x₀ ∈ ℝⁿ`.
173 /// * `p` - Parameter vector. This is a vector of additional runtime parameters that the
174 /// function may depend on but is not differentiated with respect to.
175 ///
176 /// # Returns
177 ///
178 /// Gradient of `f` with respect to `x`, evaluated at `x = x₀`.
179 ///
180 /// `∇f(x₀) ∈ ℝⁿ`
181 fn $func_name<S, V>(x0: &V, p: &[f64]) -> V::Vectorf64
182 where
183 S: Scalar,
184 V: Vector<S>,
185 {
186 // Preallocate the vector to store the gradient.
187 let mut g: V::Vectorf64 = x0.new_vector_f64();
188
189 // Promote the evaluation point to a vector of dual numbers.
190 let mut x0_dual = x0.clone().to_dual_vector();
191
192 // Variable to store the original value of the evaluation point in the kth dual
193 // direction.
194 let mut x0k: Dual;
195
196 // Evaluate the gradient.
197 for k in 0..x0_dual.len() {
198 // Original value of the evaluation point in the kth dual direction.
199 x0k = x0_dual.vget(k);
200
201 // Take a unit step forward in the kth dual direction.
202 x0_dual.vset(k, Dual::new(x0k.get_real(), 1.0));
203
204 // Partial derivative of f with respect to xₖ.
205 g.vset(k, $f(&x0_dual, p).get_dual());
206
207 // Reset the evaluation point.
208 x0_dual.vset(k, x0k);
209 }
210
211 // Return the result.
212 g
213 }
214 };
215}
216
217#[cfg(test)]
218mod tests {
219 use crate::automatic_differentiation::dual::Dual;
220 use crate::automatic_differentiation::dual_vector::DualVector;
221 use linalg_traits::{Scalar, Vector};
222 use nalgebra::DVector;
223 use ndarray::{Array1, array};
224 use numtest::*;
225
226 #[test]
227 fn test_gradient_1() {
228 // Function to find the gradient of.
229 fn f<S: Scalar, V: Vector<S>>(x: &V, _p: &[f64]) -> S {
230 x.vget(0).powi(2)
231 }
232
233 // Evaluation point.
234 let x0 = vec![2.0];
235
236 // Parameter vector (empty for this test).
237 let p = [];
238
239 // True gradient function.
240 let g = |x: &Vec<f64>| vec![2.0 * x[0]];
241
242 // Gradient function obtained via forward-mode automatic differentiation.
243 get_gradient!(f, g_autodiff);
244
245 // Evaluate the gradient using both functions.
246 let g_eval_autodiff: Vec<f64> = g_autodiff(&x0, &p);
247 let g_eval: Vec<f64> = g(&x0);
248
249 // Test autodiff gradient against true gradient.
250 assert_arrays_equal_to_decimal!(g_eval_autodiff, g_eval, 16);
251 }
252
253 #[test]
254 fn test_gradient_2() {
255 // Function to find the gradient of.
256 fn f<S: Scalar, V: Vector<S>>(x: &V, _p: &[f64]) -> S {
257 x.vget(0).powi(2) + x.vget(1).powi(3)
258 }
259
260 // Evaluation point.
261 let x0: DVector<f64> = DVector::from_slice(&[1.0, 2.0]);
262
263 // Parameter vector (empty for this test).
264 let p = [];
265
266 // True gradient function.
267 let g = |x: &DVector<f64>| DVector::<f64>::from_slice(&[2.0 * x[0], 3.0 * x[1].powi(2)]);
268
269 // Gradient function obtained via forward-mode automatic differentiation.
270 get_gradient!(f, g_autodiff);
271
272 // Evaluate the gradient using both functions.
273 let g_eval_autodiff: DVector<f64> = g_autodiff(&x0, &p);
274 let g_eval: DVector<f64> = g(&x0);
275
276 // Test autodiff gradient against true gradient.
277 assert_arrays_equal_to_decimal!(g_eval_autodiff, g_eval, 16);
278 }
279
280 #[test]
281 fn test_gradient_3() {
282 // Function to find the gradient of.
283 fn f<S: Scalar, V: Vector<S>>(x: &V, _p: &[f64]) -> S {
284 x.vget(0).powi(5) + x.vget(1).sin().powi(3)
285 }
286
287 // Evaluation point.
288 let x0 = array![5.0, 8.0];
289
290 // Parameter vector (empty for this test).
291 let p = [];
292
293 // True gradient function.
294 let g = |x: &Array1<f64>| array![5.0 * x[0].powi(4), 3.0 * x[1].sin().powi(2) * x[1].cos()];
295
296 // Gradient function obtained via forward-mode automatic differentiation.
297 get_gradient!(f, g_autodiff);
298
299 // Evaluate the gradient using both functions.
300 let g_eval_autodiff: Array1<f64> = g_autodiff(&x0, &p);
301 let g_eval: Array1<f64> = g(&x0);
302
303 // Test autodiff gradient against true gradient.
304 assert_arrays_equal_to_decimal!(g_eval_autodiff, g_eval, 16);
305 }
306
307 #[test]
308 fn test_gradient_4() {
309 // Function to find the gradient of.
310 fn f<S: Scalar, V: Vector<S>>(x: &V, p: &[f64]) -> S {
311 let alpha = S::new(p[0]);
312 let beta = S::new(p[1]);
313 let gamma = S::new(p[2]);
314 (alpha * x.vget(0)).exp() * (beta * x.vget(1)).sin() + gamma * x.vget(0) * x.vget(1)
315 }
316
317 // Parameter vector.
318 let p = [0.5, 2.0, 1.2];
319
320 // Evaluation point.
321 let x0 = DVector::from_vec(vec![1.0, 0.5]);
322
323 // True gradient function.
324 let g = |x: &DVector<f64>, p: &[f64]| {
325 let exp_term = (p[0] * x[0]).exp();
326 let sin_term = (p[1] * x[1]).sin();
327 let cos_term = (p[1] * x[1]).cos();
328 DVector::from_vec(vec![
329 p[0] * exp_term * sin_term + p[2] * x[1],
330 exp_term * p[1] * cos_term + p[2] * x[0],
331 ])
332 };
333
334 // Gradient function obtained via forward-mode automatic differentiation.
335 get_gradient!(f, g_autodiff);
336
337 // Evaluate the gradient using both methods.
338 let g_eval: DVector<f64> = g(&x0, &p);
339 let g_eval_autodiff: DVector<f64> = g_autodiff(&x0, &p);
340
341 // Test autodiff gradient against true gradient.
342 assert_arrays_equal_to_decimal!(g_eval_autodiff, g_eval, 14);
343 }
344}