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}