numdiff/forward_difference/derivative/vector_valued.rs
1use crate::constants::SQRT_EPS;
2use linalg_traits::Vector;
3
4/// Derivative of a univariate, vector-valued function using the forward difference approximation.
5///
6/// # Arguments
7///
8/// * `f` - Univariate, vector-valued function, $\mathbf{f}:\mathbb{R}\to\mathbb{R}^{m}$.
9/// * `x0` - Evaluation point, $x_{0}\in\mathbb{R}$.
10/// * `h` - Relative step size, $h\in\mathbb{R}$. Defaults to [`SQRT_EPS`].
11///
12/// # Returns
13///
14/// Derivative of $\mathbf{f}$ with respect to $x$, evaluated at $x=x_{0}$.
15///
16/// $$\frac{d\mathbf{f}}{dx}\bigg\rvert_{x=x_{0}}\in\mathbb{R}^{m}$$
17///
18/// # Note
19///
20/// This function performs 2 evaluations of $f(x)$.
21///
22/// # Example
23///
24/// Approximate the derivative of
25///
26/// $$f(t)=\begin{bmatrix}\sin{t}\\\\\cos{t}\end{bmatrix}$$
27///
28/// at $t=1$, and compare the result to the true result of
29///
30/// $$\frac{d\mathbf{f}}{dt}\bigg\rvert_{t=1}=\begin{bmatrix}\cos{(1)}\\\\-\sin{(1)}\end{bmatrix}$$
31///
32/// #### Using standard vectors
33///
34/// ```
35/// use numtest::*;
36///
37/// use numdiff::forward_difference::vderivative;
38///
39/// // Define the function, f(t).
40/// let f = |t: f64| vec![t.sin(), t.cos()];
41///
42/// // Approximate the derivative of f(t) at the evaluation point.
43/// let df: Vec<f64> = vderivative(&f, 1.0, None);
44///
45/// // True derivative of f(t) at the evaluation point.
46/// let df_true: Vec<f64> = vec![1.0_f64.cos(), -1.0_f64.sin()];
47///
48/// // Check the accuracy of the derivative approximation.
49/// assert_arrays_equal_to_decimal!(df, df_true, 8);
50/// ```
51///
52/// #### Using other vector types
53///
54/// We can also use other types of vectors, such as `nalgebra::SVector`, `nalgebra::DVector`,
55/// `ndarray::Array1`, `faer::Mat`, or any other type of vector that implements the
56/// `linalg_traits::Vector` trait.
57///
58/// ```
59/// use faer::Mat;
60/// use linalg_traits::Vector; // to provide from_slice method for faer::Mat
61/// use nalgebra::{dvector, DVector, SVector};
62/// use ndarray::{array, Array1};
63/// use numtest::*;
64///
65/// use numdiff::forward_difference::vderivative;
66///
67/// let df_true: Vec<f64> = vec![1.0_f64.cos(), -1.0_f64.sin()];
68///
69/// // nalgebra::DVector
70/// let f_dvector = |t: f64| dvector![t.sin(), t.cos()];
71/// let df_dvector: DVector<f64> = vderivative(&f_dvector, 1.0, None);
72/// assert_arrays_equal_to_decimal!(df_dvector, df_true, 8);
73///
74/// // nalgebra::SVector
75/// let f_svector = |t: f64| SVector::<f64, 2>::from_row_slice(&[t.sin(), t.cos()]);
76/// let df_svector: SVector<f64, 2> = vderivative(&f_svector, 1.0, None);
77/// assert_arrays_equal_to_decimal!(df_svector, df_true, 8);
78///
79/// // ndarray::Array1
80/// let f_array1 = |t: f64| array![t.sin(), t.cos()];
81/// let df_array1: Array1<f64> = vderivative(&f_array1, 1.0, None);
82/// assert_arrays_equal_to_decimal!(df_array1, df_true, 8);
83///
84/// // faer::Mat
85/// let f_mat = |t: f64| Mat::from_slice(&[t.sin(), t.cos()]);
86/// let df_mat: Mat<f64> = vderivative(&f_mat, 1.0, None);
87/// assert_arrays_equal_to_decimal!(df_mat.as_slice(), df_true, 8);
88/// ```
89///
90/// #### Modifying the relative step size
91///
92/// We can also modify the relative step size. Choosing a coarser relative step size, we get a worse
93/// approximation.
94///
95/// ```
96/// use numtest::*;
97///
98/// use numdiff::forward_difference::vderivative;
99///
100/// let f = |t: f64| vec![t.sin(), t.cos()];
101///
102/// let df: Vec<f64> = vderivative(&f, 1.0, Some(0.001));
103/// let df_true: Vec<f64> = vec![1.0_f64.cos(), -1.0_f64.sin()];
104///
105/// assert_arrays_equal_to_decimal!(df, df_true, 3);
106/// ```
107pub fn vderivative<V>(f: &impl Fn(f64) -> V, x0: f64, h: Option<f64>) -> V
108where
109 V: Vector<f64>,
110{
111 // Default the relative step size to h = √(ε) if not specified.
112 let h = h.unwrap_or(*SQRT_EPS);
113
114 // Absolute step size.
115 let dx = h * (1.0 + x0.abs());
116
117 // Evaluate the derivative.
118 (f(x0 + dx).sub(&f(x0))).div(dx)
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124 use numtest::*;
125
126 #[test]
127 fn test_vderivative() {
128 let f = |x: f64| vec![x.sin(), x.cos()];
129 let x0 = 2.0;
130 let df = |x: f64| vec![x.cos(), -x.sin()];
131 assert_arrays_equal_to_decimal!(vderivative(&f, x0, None), df(x0), 7);
132 }
133}