Skip to main content

geop_core_math/solvers/
minimize.rs

1//! Minimizing a merit over a point the caller is free to choose: the
2//! nearest point of a curve or a surface. The point found is a choice, not
3//! an enclosure of a true minimum — what it is chosen for is measured at it
4//! afterwards — so the merit is a plain number.
5
6use crate::{
7    geop_error::GeopError,
8    scalars::Scalar,
9    solvers::probe::{Probe, halve},
10};
11
12/// Damped descent from `start`, at most `steps` steps. `direction(x)`
13/// gives the step from `x` as the point it leads to at a `scale` of the
14/// full step (`None` where there is none to take); a step is taken at the
15/// first of `1, 1/2, 1/4, …` (`halvings` of them) at which it definitely
16/// lowers `merit`, and the descent stops where none does. What is
17/// returned is never worse than `start`.
18pub fn descend<X: Clone, E: From<GeopError>, D: Fn(Scalar) -> X>(
19    start: X,
20    steps: usize,
21    halvings: usize,
22    merit: impl Fn(&X) -> Result<f64, E>,
23    mut direction: impl FnMut(&X) -> Result<Option<D>, E>,
24) -> Result<X, E> {
25    let mut x = start;
26    let mut current = merit(&x)?;
27    for _ in 0..steps {
28        let Some(to) = direction(&x)? else {
29            break;
30        };
31        let lower = halve::<_, E>(Scalar::ONE, halvings, |&scale| {
32            let next = to(scale);
33            let m = merit(&next)?;
34            Ok(if m < current {
35                Probe::Found((next, m))
36            } else {
37                Probe::Next
38            })
39        })?;
40        let Some((next, m)) = lower else {
41            break;
42        };
43        (x, current) = (next, m);
44    }
45    Ok(x)
46}