thermolib/
algorithms.rs

1/// Reference:
2/// Richard Brent,
3/// Algorithms for Minimization Without Derivatives.
4pub fn brent_zero<F>(mut f: F, a: f64, b: f64) -> f64
5where
6    F: FnMut(f64) -> f64,
7{
8    let macheps = f64::EPSILON.sqrt();
9    let (mut a, mut fa) = (a, f(a));
10    let (mut b, mut fb) = (b, f(b));
11    if (fa * fb) > 0.0 {
12        return f64::NAN;
13    }
14    let (mut c, mut fc) = (a, fa);
15    let (mut d, mut e) = (b - a, b - a);
16    let (mut tol, mut m, mut p, mut q, mut r, mut s);
17    loop {
18        if fc.abs() < fb.abs() {
19            (a, fa) = (b, fb);
20            (b, fb) = (c, fc);
21            (c, fc) = (a, fa);
22        }
23        tol = macheps * (2.0 * b.abs() + 10.0);
24        m = 0.5 * (c - b);
25        if m.abs() <= tol || fb == 0.0 {
26            return b;
27        }
28        if e.abs() < tol || fa.abs() <= fb.abs() {
29            e = m;
30            d = e;
31        } else {
32            s = fb / fa;
33            if a == c {
34                p = 2.0 * m * s;
35                q = 1.0 - s;
36            } else {
37                q = fa / fc;
38                r = fb / fc;
39                p = s * (2.0 * m * q * (q - r) - (b - a) * (r - 1.0));
40                q = (q - 1.0) * (r - 1.0) * (s - 1.0);
41            }
42            if 0.0 < p {
43                q = -q;
44            } else {
45                p = -p;
46            }
47            s = e;
48            e = d;
49            if 2.0 * p < 3.0 * m * q - (tol * q).abs() && p < (0.5 * s * q).abs() {
50                d = p / q;
51            } else {
52                e = m;
53                d = e;
54            }
55        }
56        (a, fa) = (b, fb);
57        b += if tol < d.abs() {
58            d
59        } else if 0.0 < m {
60            tol
61        } else {
62            -tol
63        };
64        fb = f(b);
65        if (0.0 < fb && 0.0 < fc) || (fb.is_sign_negative() && fc.is_sign_negative()) {
66            (c, fc) = (a, fa);
67            e = b - a;
68            d = e;
69        }
70    }
71}
72/// Romberg Numerical Differentiation
73/// Reference:
74/// C. J. F. RIDDERS
75/// Accurate computation of F'(x) and F'(x) F"(x)
76/// Adv. Eng. Software, 1982, Vol. 4, No. 2, 75-76.
77const N_DIM: usize = 11;
78pub fn romberg_diff<F>(mut f: F, x: f64) -> f64
79where
80    F: FnMut(f64) -> f64,
81{
82    let tol = f64::EPSILON.sqrt() * 2.0;
83    let mut fx: [[f64; N_DIM]; N_DIM] = [[0.0; N_DIM]; N_DIM];
84    let mut h: f64 = if x > 1.0 { 0.01 * x } else { 0.1 * x };
85    fx[0][0] = (f(x + h) - f(x - h)) / 2.0 / h;
86    for i in 1..N_DIM {
87        h /= 2.0;
88        fx[0][i] = (f(x + h) - f(x - h)) / 2.0 / h;
89        for j in 1..i + 1 {
90            fx[j][i] = (fx[j - 1][i] * 4_f64.powi(j as i32) - fx[j - 1][i - 1])
91                / (4_f64.powi(j as i32) - 1.0);
92        }
93        if (fx[i][i] / fx[i - 1][i - 1] - fx[i - 1][i - 1] / fx[i][i]).abs() < tol {
94            return fx[i][i];
95        }
96    }
97    fx[N_DIM - 1][N_DIM - 1]
98}
99/// y = c0 + c1 * x
100/// s = min{ 0.5 * sum[ ( c0 + c1 * xi - yi )^2 ] }
101///
102/// ds/dc0 = sum[ ( c0 + c1 * xi - yi ) ]
103///        = c0 * n + c1 * sum(xi) - sum(yi)
104/// ds/dc1 = sum[ ( c0 + c1 * xi - yi ) * xi ]
105///        = c0 * sum(xi) + c1 * sum(xi*xi) - sum(yi*xi)
106///
107/// | n       sum(xi)    | |c0| = | sum(yi)    |
108/// | sum(xi) sum(xi*xi) | |c1| = | sum(yi*xi) |
109///
110/// ci = | n       sum(xi)    |
111///      | sum(xi) sum(xi*xi) | = n*sum(xi*xi) - sum(xi)*sum(xi)
112///
113/// c0 = | sum(yi)    sum(xi)    |
114///      | sum(yi*xi) sum(xi*xi) | / ci
115/// c1 = | n       sum(yi)    |
116///      | sum(xi) sum(yi*xi) | / ci
117///
118/// c0 = frac{ sum(yi)*sum(xi*xi) - sum(yi*xi)*sum(xi) }
119///          { n*sum(xi*xi) - sum(xi)*sum(xi) }
120/// c1 = frac{ n*sum(yi*xi) - sum(xi)*sum(yi) }
121///          { n*sum(xi*xi) - sum(xi)*sum(xi) }
122///
123pub fn poly1fit(x: &[f64], y: &[f64]) -> (f64, f64) {
124    let (mut sum_xi, mut sum_xixi) = (0.0, 0.0);
125    let (mut sum_yi, mut sum_yixi) = (0.0, 0.0);
126    let num: f64 = (0..usize::min(x.len(), y.len()))
127        .map(|i| {
128            sum_xi += x[i];
129            sum_yi += y[i];
130            sum_xixi += x[i] * x[i];
131            sum_yixi += y[i] * x[i];
132        })
133        .count() as f64;
134    let ci = num * sum_xixi - sum_xi * sum_xi;
135    if ci.abs() > 0.01 {
136        (
137            (sum_yi * sum_xixi - sum_yixi * sum_xi) / ci,
138            (num * sum_yixi - sum_xi * sum_yi) / ci,
139        )
140    } else {
141        let ata = vec![vec![num, sum_xi], vec![sum_xi, sum_xixi]];
142        let atb = vec![sum_yi, sum_yixi];
143        let result = solve_equ(ata, atb);
144        (result[0], result[1])
145    }
146}
147/// y = c0 + c1 * x + c2 * x * x
148/// s = min{ 0.5 * sum[ ( c0 + c1 * xi + c2 * xi * xi - yi )^2 ] }
149///
150/// ds/dc0 = sum[ ( c0 + c1 * xi + c2 * xi * xi - yi ) ]
151///        = c0 * n + c1 * sum(xi) + c2 * sum(xi*xi) - sum(yi)
152/// ds/dc1 = sum[ ( c0 + c1 * xi + c2 * xi * xi - yi ) * xi ]
153///        = c0 * sum(xi) + c1 * sum(xi*xi) + c2 * sum(xi*xi*xi) - sum(yi*xi)
154/// ds/dc2 = sum[ ( c0 + c1 * xi + c2 * xi * xi - yi ) * xi * xi ]
155///        = c0 * sum(xi*xi) + c1 * sum(xi*xi*xi) + c2 * sum(xi*xi*xi*xi) - sum(yi*xi*xi)
156///
157/// | n          sum(xi)       sum(xi*xi)       | |c0| = | sum(yi)       |
158/// | sum(xi)    sum(xi*xi)    sum(xi*xi*xi)    | |c1| = | sum(yi*xi)    |
159/// | sum(xi*xi) sum(xi*xi*xi) sum(xi*xi*xi*xi) | |c2| = | sum(yi*xi*xi) |
160///
161/// ci = | n          sum(xi)       sum(xi*xi)       |
162///      | sum(xi)    sum(xi*xi)    sum(xi*xi*xi)    |
163///      | sum(xi*xi) sum(xi*xi*xi) sum(xi*xi*xi*xi) |
164///    = n*[ sum(xi*xi)*sum(xi*xi*xi*xi) - sum(xi*xi*xi)*sum(xi*xi*xi) ]
165///    + sum(xi)*[ sum(xi*xi*xi)*sum(xi*xi) - sum(xi)*sum(xi*xi*xi*xi) ]
166///    + sum(xi*xi)*[ sum(xi)*sum(xi*xi*xi) - sum(xi*xi)*sum(xi*xi) ]
167///
168/// c0 = | sum(yi)       sum(xi)       sum(xi*xi)       |
169///      | sum(yi*xi)    sum(xi*xi)    sum(xi*xi*xi)    |
170///      | sum(yi*xi*xi) sum(xi*xi*xi) sum(xi*xi*xi*xi) |
171/// c1 = | n          sum(yi)       sum(xi*xi)       |
172///      | sum(xi)    sum(yi*xi)    sum(xi*xi*xi)    |
173///      | sum(xi*xi) sum(yi*xi*xi) sum(xi*xi*xi*xi) |
174/// c2 = | n          sum(xi)       sum(yi)       |
175///      | sum(xi)    sum(xi*xi)    sum(yi*xi)    |
176///      | sum(xi*xi) sum(xi*xi*xi) sum(yi*xi*xi) |
177///
178pub fn poly2fit(x: &[f64], y: &[f64]) -> (f64, f64, f64) {
179    let (mut sum_x1, mut sum_x2, mut sum_x3, mut sum_x4) = (0.0, 0.0, 0.0, 0.0);
180    let (mut sum_y1x0, mut sum_y1x1, mut sum_y1x2) = (0.0, 0.0, 0.0);
181    let num: f64 = (0..usize::min(x.len(), y.len()))
182        .map(|i| {
183            let (x1, x2) = (x[i], x[i] * x[i]);
184            sum_x1 += x1;
185            sum_x2 += x2;
186            sum_x3 += x1 * x2;
187            sum_x4 += x2 * x2;
188            sum_y1x0 += y[i];
189            sum_y1x1 += y[i] * x1;
190            sum_y1x2 += y[i] * x2;
191        })
192        .count() as f64;
193    let ci = num * (sum_x2 * sum_x4 - sum_x3 * sum_x3)
194        + sum_x1 * (sum_x3 * sum_x2 - sum_x1 * sum_x4)
195        + sum_x2 * (sum_x1 * sum_x3 - sum_x2 * sum_x2);
196    if ci.abs() > 0.01 {
197        (
198            (sum_y1x0 * (sum_x2 * sum_x4 - sum_x3 * sum_x3)
199                + sum_y1x1 * (sum_x3 * sum_x2 - sum_x1 * sum_x4)
200                + sum_y1x2 * (sum_x1 * sum_x3 - sum_x2 * sum_x2))
201                / ci,
202            (num * (sum_y1x1 * sum_x4 - sum_y1x2 * sum_x3)
203                + sum_x1 * (sum_y1x2 * sum_x2 - sum_y1x0 * sum_x4)
204                + sum_x2 * (sum_y1x0 * sum_x3 - sum_y1x1 * sum_x2))
205                / ci,
206            (num * (sum_x2 * sum_y1x2 - sum_x3 * sum_y1x1)
207                + sum_x1 * (sum_x3 * sum_y1x0 - sum_x1 * sum_y1x2)
208                + sum_x2 * (sum_x1 * sum_y1x1 - sum_x2 * sum_y1x0))
209                / ci,
210        )
211    } else {
212        let ata = vec![
213            vec![num, sum_x1, sum_x2],
214            vec![sum_x1, sum_x2, sum_x3],
215            vec![sum_x2, sum_x3, sum_x4],
216        ];
217        let atb = vec![sum_y1x0, sum_y1x1, sum_y1x2];
218        let result = solve_equ(ata, atb);
219        (result[0], result[1], result[2])
220    }
221}
222/// Solver -> Ax=b
223fn solve_equ(mut a: Vec<Vec<f64>>, mut b: Vec<f64>) -> Vec<f64> {
224    let n = a.len();
225    let (mut index, mut scale);
226    for k in 0..n {
227        index = k;
228        for i in k + 1..n {
229            if a[i][k] > a[index][k] {
230                index = i;
231            }
232        }
233        if index > k {
234            a.swap(k, index);
235            b.swap(k, index);
236        }
237        for i in k + 1..n {
238            scale = a[i][k] / a[k][k];
239            for j in k..n {
240                a[i][j] -= scale * a[k][j];
241            }
242            b[i] -= scale * b[k];
243        }
244    }
245    let mut x = vec![0.0; b.len()];
246    for k in (0..n).rev() {
247        x[k] = (b[k]
248            - a[k][k + 1..]
249                .iter()
250                .zip(x[k + 1..].iter())
251                .map(|(aj, xj)| aj * xj)
252                .sum::<f64>())
253            / a[k][k];
254    }
255    x
256}
257/// unit test
258#[cfg(test)]
259mod tests {
260    use super::*;
261    #[test]
262    fn test_algorithms() {
263        // test brent_zero
264        let x0 = brent_zero(|x: f64| x.sin() - x / 2.0, 1.0, 2.0);
265        let (x1, digits) = (1.8954942796e+00, 10_f64.powi(10));
266        assert_eq!(x1, (x0 * digits).round() / digits);
267        let x0 = brent_zero(|x: f64| 2.0 * x - (-x).exp(), 0.0, 1.0);
268        let (x1, digits) = (3.5173365631e-01, 10_f64.powi(11));
269        assert_eq!(x1, (x0 * digits).round() / digits);
270        let x0 = brent_zero(|x: f64| x * (-x).exp(), -1.0, 0.5);
271        let (x1, digits) = (-4.0352160429e-10, 10_f64.powi(20));
272        assert_eq!(x1, (x0 * digits).round() / digits);
273        let x0 = brent_zero(|x: f64| x.exp() - 1.0 / 100.0 / x / x, 0.0001, 20.0);
274        let (x1, digits) = (9.5344620258e-02, 10_f64.powi(12));
275        assert_eq!(x1, (x0 * digits).round() / digits);
276        let x0 = brent_zero(|x: f64| (x + 3.0) * (x - 1.0) * (x - 1.0), -5.0, 2.0);
277        let (x1, digits) = (-3.0000000000e+00, 10_f64.powi(10));
278        assert_eq!(x1, (x0 * digits).round() / digits);
279        // test romberg_diff
280        let (df0, digits) = (140.7377356, 10_f64.powi(7)); // real = 140.7377355
281        let df1 = romberg_diff(|x: f64| x.exp() / (x.sin() - x.powi(2)), 1.0);
282        assert_eq!(df0, (df1 * digits).round() / digits);
283        let (df0, digits) = (2.718281828, 10_f64.powi(9));
284        let df1 = romberg_diff(|x: f64| x.exp(), 1.0);
285        assert_eq!(df0, (df1 * digits).round() / digits);
286        let (df0, digits) = (22026.46579, 10_f64.powi(5));
287        let df1 = romberg_diff(|x: f64| x.exp(), 10.0);
288        assert_eq!(df0, (df1 * digits).round() / digits);
289        // test poly1fit -> y = 1 + 3 * x
290        let (c0, c1) = poly1fit(
291            &vec![1.0, 2.0, 3.0, 4.0, 5.0],
292            &vec![4.0, 7.0, 10.0, 13.0, 16.0],
293        );
294        assert_eq!(c0, 1.0);
295        assert_eq!(c1, 3.0);
296        // test poly2fit -> y = 1 + 3 * x + 5 * x * x
297        let (c0, c1, c2) = poly2fit(
298            &vec![1.0, 2.0, 3.0, 4.0, 5.0],
299            &vec![9.0, 27.0, 55.0, 93.0, 141.0],
300        );
301        assert_eq!(c0, 1.0);
302        assert_eq!(c1, 3.0);
303        assert_eq!(c2, 5.0);
304    }
305}