diffurch/
polynomial.rs

1//! Defines [crate::polynomial!] macro and [crate::polynomial::Differentiable] conatiner for pairs
2//! of closures.
3
4/// Tuple struct to hold a one-variable function and its derivative.
5#[derive(Clone)]
6pub struct Differentiable<F, DF>(pub F, pub DF);
7
8impl<Ret, F: FnOnce(f64) -> Ret, DF> FnOnce<(f64,)> for Differentiable<F, DF> {
9    type Output = Ret;
10
11    extern "rust-call" fn call_once(self, args: (f64,)) -> Self::Output {
12        (self.0)(args.0)
13    }
14}
15impl<Ret, F: FnMut(f64) -> Ret, DF> FnMut<(f64,)> for Differentiable<F, DF> {
16    extern "rust-call" fn call_mut(&mut self, args: (f64,)) -> Self::Output {
17        (self.0)(args.0)
18    }
19}
20impl<Ret, F: Fn(f64) -> Ret, DF> Fn<(f64,)> for Differentiable<F, DF> {
21    extern "rust-call" fn call(&self, args: (f64,)) -> Self::Output {
22        (self.0)(args.0)
23    }
24}
25
26impl<F, DF, Ret> Differentiable<F, DF>
27where
28    DF: Fn<(f64,), Output = Ret>,
29{
30    /// Evaluate derivative
31    pub fn d(&self, t: f64) -> Ret {
32        (self.1)(t)
33    }
34}
35
36/// Produce a fn(f64) -> f64 closure that represents a polynomial function with given coefficients.
37///
38/// # Examples:
39/// ```rust
40/// use diffurch::polynomial_closure;
41///
42/// let p0 = polynomial_closure![1., 0., -1./2., 0., 1./24.];
43/// let p1 = |t: f64| 1. - t*t/2. + t.powi(4)/24.;
44/// let p2 = |t: f64| 1. + t * (t * ( - 1./2. + t * (t * (1./24.))));
45///
46/// for i in 0..10 {
47///     let t = i as f64;
48///     assert!((p0(t) - p1(t)).abs() < 1e-13); // not exact due to rounding errors
49///     assert_eq!(p0(t), p2(t));
50/// }
51/// ```
52#[macro_export]
53macro_rules! polynomial_closure {
54    () => {
55        |_t: f64| { 0. }
56    };
57    ($($coef:expr),+ $(,)?) => {
58        |t: f64| {
59            [$($coef),+].into_iter().rev()
60            .reduce(|acc: f64, c: f64| c + t * acc).unwrap()
61        }
62    };
63}
64
65/// Same as [crate::polynomial_closure], but produces a closure, that corresponds to differentiated
66/// polynomial function.
67#[macro_export]
68macro_rules! polynomial_derivative_closure {
69    () => {
70        |_t: f64| { 0. }
71    };
72    ($coef:expr) => {
73        |_t: f64| { 0. }
74    };
75    ($($coef:expr),+ $(,)?) => {
76        |t: f64| {
77            let coef = [$($coef),+];
78            let last = *coef.last().unwrap();
79            coef.into_iter().enumerate().skip(1).rev().skip(1)
80            .fold(last * (coef.len()-1) as f64, |acc: f64, (n, c): (usize, f64)| n as f64 * c + t * acc)
81        }
82    };
83}
84
85/// Produces [crate::util::with_derivative::Differentiable] that holds a polynomial, produced by
86/// [crate::polynomial_closure] and its derivative closure, produced by
87/// [crate::polynomial_derivative_closure].
88///
89/// # Example
90/// ```rust
91/// use diffurch::polynomial;
92///
93/// let p0 = polynomial![1., 0., -1./2., 0., 1./24.];
94/// let p1 = |t: f64| 1. - t*t/2. + t.powi(4)/24.;
95/// let p2 = |t: f64| 1. + t * (t * ( - 1./2. + t * (t * (1./24.))));
96///
97/// let d1 = |t: f64| -t + t.powi(3)/6.;
98/// let d2 = |t: f64| t * (-1. + t*t*(1./6.));
99///
100/// for i in 0..10 {
101///     let t = i as f64;
102///     assert!((p0(t) - p1(t)).abs() < 1e-13); // not exact due to rounding errors
103///     assert_eq!(p0(t), p2(t));
104///
105///     assert!((p0.d(t) - d1(t)).abs() < 1e-13); // not exact due to rounding errors
106///     assert_eq!(p0.d(t), d2(t));
107/// }
108/// ```
109///
110/// # Example
111/// ```rust
112/// let geometric_series = diffurch::polynomial![
113///     1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
114///     1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
115///     1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
116///     1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
117/// ];
118///
119/// // check for some numbers that do not run into rounding errors for some reason
120/// for t in [1./2., 1./4., 1./5., 1./6. as f64] {
121///     assert_eq!(geometric_series(t), 1. / (1. - t));
122///     assert_eq!(geometric_series.d(t), (1. / (1. - t)).powi(2));
123/// }
124/// ```
125///
126#[macro_export]
127macro_rules! polynomial {
128    ($($coef:expr),* $(,)?) => {
129        $crate::polynomial::Differentiable(
130            $crate::polynomial_closure![$($coef),*],
131            $crate::polynomial_derivative_closure![$($coef),*]
132        )
133    };
134    ($t:ident => $($coef:expr),+) => {
135        [$($coef),+].into_iter().rev()
136            .reduce(|acc: f64, c: f64| c + $t * acc).unwrap()
137    };
138}
139
140#[cfg(test)]
141mod tests {
142    // use super::*;
143    #[test]
144    fn empty_polynomial() {
145        let p = polynomial![];
146        assert_eq!(p(0.), 0.);
147        assert_eq!(p(1.), 0.);
148        assert_eq!(p.d(0.), 0.);
149        assert_eq!(p.d(1.), 0.);
150    }
151
152    #[test]
153    fn constant_polynomail_evauation() {
154        let p = polynomial_closure![42.];
155        assert_eq!(p(0.), 42.);
156        assert_eq!(p(1.), 42.);
157    }
158
159    #[test]
160    fn linear_polynomial_evaluation() {
161        let p = polynomial_closure![42., 2.];
162        assert_eq!(p(0.), 42.);
163        assert_eq!(p(69.), 42. + 2. * 69.);
164    }
165
166    #[test]
167    fn quadratic_polynomial_evaluation() {
168        let p = polynomial_closure![-1., 0., 1.];
169        assert_eq!(p(0.), -1.);
170        assert_eq!(p(-1.), 0.);
171        assert_eq!(p(1.), 0.);
172    }
173
174    #[test]
175    fn geometric_sum() {
176        let geometric_series = polynomial_closure![
177            1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
178            1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
179            1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
180            1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
181        ];
182
183        assert_eq!(geometric_series(1. / 2.), 1. / (1. - 1. / 2.));
184        assert_eq!(geometric_series(1. / 3.), 1.5);
185        assert_eq!(geometric_series(1. / 4.), 1. / (1. - 1. / 4.));
186        assert_eq!(geometric_series(1. / 5.), 1. / (1. - 1. / 5.));
187        assert_eq!(geometric_series(1. / 6.), 1. / (1. - 1. / 6.));
188    }
189
190    #[test]
191    fn constant_polynomail_derivative() {
192        let p = polynomial_derivative_closure![42.];
193        assert_eq!(p(0.), 0.);
194        assert_eq!(p(1.), 0.);
195    }
196
197    #[test]
198    fn linear_polynomial_derivative() {
199        let p = polynomial_derivative_closure![42., 2.];
200        assert_eq!(p(0.), 2.);
201        assert_eq!(p(69.), 2.);
202    }
203
204    #[test]
205    fn quadratic_polynomial_derivative() {
206        let p = polynomial_derivative_closure![-1., 0., 1.];
207        assert_eq!(p(0.), 2. * 0.);
208        assert_eq!(p(-1.), 2. * (-1.));
209        assert_eq!(p(1.), 2. * 1.);
210    }
211
212    #[test]
213    fn geometric_sum_derivative() {
214        let geometric_series = polynomial_derivative_closure![
215            1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
216            1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
217            1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
218            1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
219        ];
220
221        assert_eq!(geometric_series(1. / 2.), (1f64 / (1. - 1. / 2.)).powi(2));
222        assert_eq!(geometric_series(1. / 3.), 1.5f64.powi(2));
223        assert_eq!(geometric_series(1. / 4.), (1f64 / (1. - 1. / 4.)).powi(2));
224        assert_eq!(geometric_series(1. / 5.), (1f64 / (1. - 1. / 5.)).powi(2));
225        assert_eq!(geometric_series(1. / 6.), (1f64 / (1. - 1. / 6.)).powi(2));
226    }
227}