Skip to main content

geop_core_math/solvers/
iterate.rs

1//! Fixed-point iteration from sharp seeds: the loop every Newton-type
2//! method of the kernel runs — foot points, a ball rolled between faces,
3//! a vertex moved onto offset surfaces, a traced curve's corrector.
4//!
5//! Each step takes a seed to the next iterate. The iterate is kept twice
6//! (see "Sharpen only where the value is a free choice" in `AGENTS.md`):
7//! as the *seed* of the next step, sharpened — any point of it serves
8//! equally well, and left wide each step would compound the width of the
9//! last — and as the *answer*, with the width the step honestly gave it.
10//! Which of the two a caller returns says whether its value is a free
11//! choice (a construction aid, a seed for something else) or an answer
12//! that later interval tests compare against.
13//!
14//! A deterministic iteration whose sharp seed repeats one before it is
15//! done: from there on the seeds repeat with that period, so what the
16//! remaining steps would end at is known ([`Repeat`]). Not a convergence
17//! tolerance — the seed is bit for bit one seen before.
18
19use crate::{scalars::Scalar, vector::Vector};
20
21/// A value an iteration can tell apart from another, bit for bit: what
22/// [`Repeat`] compares seeds with.
23pub trait Iterate: Clone {
24    /// Whether `self` and `other` are the same value — the same enclosure,
25    /// not merely overlapping ones.
26    fn same(&self, other: &Self) -> bool;
27}
28
29impl Iterate for Scalar {
30    fn same(&self, other: &Self) -> bool {
31        self.is_subset_of(*other) && other.is_subset_of(*self)
32    }
33}
34
35impl Iterate for f64 {
36    fn same(&self, other: &Self) -> bool {
37        self == other
38    }
39}
40
41impl<T: Iterate, const N: usize> Iterate for [T; N] {
42    fn same(&self, other: &Self) -> bool {
43        self.iter().zip(other).all(|(a, b)| a.same(b))
44    }
45}
46
47impl<A: Iterate, B: Iterate> Iterate for (A, B) {
48    fn same(&self, other: &Self) -> bool {
49        self.0.same(&other.0) && self.1.same(&other.1)
50    }
51}
52
53impl<const N: usize> Iterate for Vector<N> {
54    fn same(&self, other: &Self) -> bool {
55        (0..N).all(|i| self[i].same(&other[i]))
56    }
57}
58
59/// When an iteration stops because a seed repeats.
60#[derive(Clone, Copy, Debug, PartialEq, Eq)]
61pub enum Repeat {
62    /// Never: every step is taken.
63    Never,
64    /// When a step returns the seed it started from. Safe for a step that
65    /// keeps state of its own beside the seed (a foot point's parameters
66    /// carried along as the next projection's start), as long as that
67    /// state is a function of the seed: the step would only repeat itself.
68    FixedPoint,
69    /// When a seed repeats any one before it: the seeds are periodic from
70    /// there, and the last step's answer is the one taken at the same place
71    /// in the period. Only for a step that is a function of its seed alone.
72    Cycle,
73}
74
75/// What one step found at its seed.
76pub enum Step<X> {
77    /// The next iterate, as the step computed it: the caller's `settle`
78    /// makes a seed and an answer of it.
79    Next(X),
80    /// The seed meets what the iteration was asked for: stop here.
81    Done,
82    /// No step can be taken from the seed — a singular Jacobian, a
83    /// residual with no direction to go.
84    Stuck,
85}
86
87/// How an iteration ended.
88#[derive(Clone, Copy, Debug, PartialEq, Eq)]
89pub enum End {
90    /// A step said [`Step::Done`].
91    Done,
92    /// A step said [`Step::Stuck`].
93    Stuck,
94    /// Every step was taken, or a [`Repeat`] showed where they would end.
95    Exhausted,
96}
97
98/// Where an iteration ended: its last seed — sharp, the start of a step
99/// not taken — and the answer the last step taken gave, or the initial
100/// answer if none was.
101#[derive(Clone, Debug)]
102pub struct Iterated<X> {
103    pub seed: X,
104    pub answer: X,
105    pub end: End,
106}
107
108/// Iterates `step` from `seed` at most `steps` times. Each [`Step::Next`]
109/// is made into the next seed and the answer by `settle` — typically the
110/// iterate sharpened and clamped into a domain, and the iterate clamped
111/// (sharpen *before* clamping: `clamped`'s comparisons are exact on a sharp
112/// value, so the seed stays in the domain). `answer` is what is returned
113/// as the answer if no step is taken. The iteration stops early, with the
114/// result it would have reached, where `repeat` says.
115pub fn iterate<X: Iterate, E>(
116    seed: X,
117    answer: X,
118    steps: usize,
119    repeat: Repeat,
120    settle: impl Fn(&X) -> (X, X),
121    mut step: impl FnMut(&X) -> Result<Step<X>, E>,
122) -> Result<Iterated<X>, E> {
123    let (mut seed, mut answer) = (seed, answer);
124    // Every step's seed and the answer it gave, in order.
125    let mut taken: Vec<(X, X)> = Vec::new();
126    for _ in 0..steps {
127        let next = match step(&seed)? {
128            Step::Next(next) => next,
129            Step::Done => {
130                return Ok(Iterated {
131                    seed,
132                    answer,
133                    end: End::Done,
134                });
135            }
136            Step::Stuck => {
137                return Ok(Iterated {
138                    seed,
139                    answer,
140                    end: End::Stuck,
141                });
142            }
143        };
144        let (next_seed, next_answer) = settle(&next);
145        taken.push((seed, next_answer.clone()));
146        let repeated = match repeat {
147            Repeat::Never => None,
148            Repeat::FixedPoint => {
149                let k = taken.len() - 1;
150                taken[k].0.same(&next_seed).then_some(k)
151            }
152            Repeat::Cycle => taken.iter().position(|(s, _)| s.same(&next_seed)),
153        };
154        if let Some(k) = repeated {
155            let period = taken.len() - k;
156            if period == 1 {
157                // A fixed point: the seed just made is the one returned.
158                return Ok(Iterated {
159                    seed: next_seed,
160                    answer: next_answer,
161                    end: End::Exhausted,
162                });
163            }
164            let last = k + (steps - 1 - k) % period;
165            let after = k + (steps - k) % period;
166            return Ok(Iterated {
167                seed: taken[after].0.clone(),
168                answer: taken[last].1.clone(),
169                end: End::Exhausted,
170            });
171        }
172        seed = next_seed;
173        answer = next_answer;
174    }
175    Ok(Iterated {
176        seed,
177        answer,
178        end: End::Exhausted,
179    })
180}
181
182#[cfg(test)]
183mod tests {
184    use super::*;
185
186    /// A cycle stops the iteration early with what running every step
187    /// would have ended at.
188    #[test]
189    fn a_cycle_ends_where_every_step_would() {
190        // Seeds cycle 0 -> 1 -> 2 -> 1 -> 2 ...; the answer is the seed
191        // plus a tenth.
192        let next = |x: &f64| Step::Next(if *x == 2.0 { 1.0 } else { x + 1.0 });
193        let settle = |x: &f64| (*x, x + 0.1);
194        for steps in 0..9 {
195            let run = |repeat| {
196                iterate::<f64, ()>(0.0, -1.0, steps, repeat, settle, |x| Ok(next(x))).unwrap()
197            };
198            let (never, cycle) = (run(Repeat::Never), run(Repeat::Cycle));
199            assert_eq!((never.seed, never.answer), (cycle.seed, cycle.answer));
200        }
201    }
202
203    /// A fixed point stops the iteration at the step that found it.
204    #[test]
205    fn a_fixed_point_stops_at_once() {
206        let mut calls = 0;
207        let out = iterate::<f64, ()>(
208            3.0,
209            3.0,
210            50,
211            Repeat::FixedPoint,
212            |x| (*x, *x),
213            |x| {
214                calls += 1;
215                Ok(Step::Next((x / 2.0).max(1.0)))
216            },
217        )
218        .unwrap();
219        assert_eq!(out.seed, 1.0);
220        assert_eq!(calls, 3);
221    }
222}