Skip to main content

geop_core_math/solvers/system/
mod.rs

1//! Constraint systems: values a solve moves, in blocks ([`Variable`]s — a
2//! number, a pose), and terms on them ([`Equations`]) — constraints that
3//! hold where their residuals vanish (or, for a limit, stay at most zero),
4//! and preferences a solve pulls towards among where the constraints hold.
5//! Written once, for every system of the kernel: a sketch's numbers
6//! (`geop_core_sketch`) and a program's state, where its parts are
7//! (`geop_core_program::solve`), are each only an [`Equations`].
8//!
9//! On a [`System`] — the equations, how each block may move, the system's
10//! size — the algorithms are generic:
11//!
12//! - **Groups.** Blocks no term ties together are solved, enclosed and
13//!   measured apart ([`System::structure`]): a plate with hundreds of
14//!   screws is hundreds of systems of one screw. The answer is the same,
15//!   since what a solve minimizes is a sum over the groups; a group whose
16//!   constraints hold and that nothing pulls is not solved at all. Within a
17//!   group, a block whose constraints only hang it off others — a screw on
18//!   a bracket, the bracket on a link — is a pendant: solved and enclosed
19//!   after them, alone ([`structure`]), so a chain of links carrying
20//!   hundreds of parts is a small system and hundreds of tiny ones, not one
21//!   dense system of every part. The groups of a layer are worked on in
22//!   lockstep ([`lockstep`]): one evaluation answers every group waiting,
23//!   so the evaluations a solve takes do not grow with the number of
24//!   groups.
25//! - **Solve** ([`System::solve`]) by the driver every solve shares
26//!   ([`driver`]): the constraints held exactly, the
27//!   preferences and staying put ([`Variable::stay`]) only choosing among
28//!   where they hold. Each block moves by steps along it
29//!   ([`Variable::retract`]). A limit ([`Bound::AtMost`]) is kept by an
30//!   active set: solved without the limits first, every one the solve ends
31//!   beyond is held at its bound, and the group is solved again from where
32//!   it started.
33//! - **What holds** ([`System::held`]), to [`RELATIVE_TOLERANCE`]
34//!   of the size.
35//! - **Enclosure** ([`System::enclose`]): a rigorous box around a true
36//!   solution near where a solve ended — Krawczyk's test over the
37//!   independent rows, chosen over a box — or why there is none to be had:
38//!   a singular configuration, or constraints that conflict, by name.
39//! - **Freedom** ([`System::freedom`]): how many independent ways each
40//!   block can still move, decided over the box the enclosure proved. A
41//!   limit leaves the motion away from it free.
42//! - **Conflicts** ([`System::conflicting`]): a smallest set of constraints
43//!   that cannot all hold together.
44//!
45//! A block is enclosed in its own step coordinates around where it is: a
46//! value `x = retract(x̃, δ)` for a box of steps `δ`. The Jacobian of a
47//! term by `δ` is its Jacobian by the steps at `x` — what [`Equations`]
48//! gives — times the differential of the retraction
49//! ([`Variable::differential`]): the identity for a number, the right
50//! Jacobian of the exponential map for a pose.
51
52use crate::{
53    geop_error::{GeopError, GeopResult},
54    scalars::Scalar,
55    solvers::least_squares::Rows,
56};
57
58mod driver;
59mod enclose;
60mod lockstep;
61mod structure;
62
63pub use driver::{DAMPING, Mobility, Phase, RELATIVE_TOLERANCE, tolerance};
64use driver::{Values, largest};
65
66pub use enclose::{EncloseError, Enclosure};
67use lockstep::{Batch, Made};
68pub use structure::Structure;
69
70/// A value a solve moves: a block of variables, stepped along its own
71/// tangent space.
72pub trait Variable: Clone + std::fmt::Debug {
73    /// How many numbers a step of it takes. None, for a value no solve
74    /// moves.
75    fn dimension(&self) -> usize;
76
77    /// Where `step` — [`Variable::dimension`] numbers — takes it: the value
78    /// a solver tries next, a free choice, so sharp.
79    fn retract(&self, step: &[Scalar]) -> GeopResult<Self>;
80
81    /// Every value a step in the box `step` takes it to: an enclosure,
82    /// nothing sharpened — of the value it stands for, which a sharp value
83    /// may only approximate (a rotation's unit quaternion).
84    fn retract_over(&self, step: &[Scalar]) -> GeopResult<Self>;
85
86    /// The differential of the retraction over the box `step`: the matrix
87    /// `D`, one row per number of a step, such that for every `δ` in the box
88    /// a step `dδ` further, `retract(δ + dδ)`, is to first order the step
89    /// `D dδ` from `retract(δ)` — an enclosure for every `δ` in the box.
90    /// `None` for the identity: a step that adds to the value.
91    fn differential(&self, step: &[Scalar]) -> GeopResult<Option<Vec<Vec<Scalar>>>>;
92
93    /// What keeps it near `at` where nothing else decides where it goes:
94    /// residuals that vanish there, each a length — a turn measured where
95    /// it moves a point `scale` from its axis — and their derivatives by
96    /// its step.
97    fn stay(&self, at: &Self, scale: Scalar) -> GeopResult<Rows>;
98
99    /// Whether it is no different from `other`: a step that changed nothing.
100    fn same(&self, other: &Self) -> bool;
101
102    /// Where it goes carried along by `leader` moving from `from` to `to`,
103    /// as if fastened to it — a seed for a solve of what hangs off the
104    /// leader (see [`Structure`]), a free choice, so sharp. `None` for a
105    /// value nothing carries: a number.
106    fn carried(&self, from: &Self, to: &Self) -> Option<Self> {
107        let _ = (from, to);
108        None
109    }
110}
111
112/// A number steps by what is added to it.
113impl Variable for Scalar {
114    fn dimension(&self) -> usize {
115        1
116    }
117
118    fn retract(&self, step: &[Scalar]) -> GeopResult<Scalar> {
119        Ok(self.add(*one(step)?).sharpen())
120    }
121
122    fn retract_over(&self, step: &[Scalar]) -> GeopResult<Scalar> {
123        let d = *one(step)?;
124        Ok(match d.is_zero() && d.is_sharp() {
125            true => *self,
126            false => self.add(d),
127        })
128    }
129
130    fn differential(&self, step: &[Scalar]) -> GeopResult<Option<Vec<Vec<Scalar>>>> {
131        one(step)?;
132        Ok(None)
133    }
134
135    fn stay(&self, at: &Scalar, _: Scalar) -> GeopResult<Rows> {
136        let mut rows = Rows::default();
137        rows.push(self.sub(*at), vec![Scalar::ONE]);
138        Ok(rows)
139    }
140
141    fn same(&self, other: &Scalar) -> bool {
142        self.could_be_equal(*other)
143    }
144}
145
146/// The one number of a step of a number.
147fn one(step: &[Scalar]) -> GeopResult<&Scalar> {
148    match step {
149        [d] => Ok(d),
150        _ => Err(GeopError::new(format!(
151            "a step of a number is one number, not {}",
152            step.len()
153        ))),
154    }
155}
156
157/// What a constraint asks of its residuals.
158#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
159pub enum Bound {
160    /// That they vanish: it holds where they are zero.
161    #[default]
162    Zero,
163    /// That none is above zero: a limit, which holds anywhere short of it.
164    AtMost,
165}
166
167impl Bound {
168    /// Whether the residual `r` holds — within `tolerance` of zero, or,
169    /// for a limit, not definitely above `tolerance`.
170    pub fn holds(self, r: Scalar, tolerance: Scalar) -> bool {
171        r.is_finite()
172            && match self {
173                Bound::Zero => !r.abs().definitely_greater(tolerance),
174                Bound::AtMost => !r.definitely_greater(tolerance),
175            }
176    }
177}
178
179/// A term of a system: a constraint or a preference, by index.
180#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
181pub enum Term {
182    Constraint(usize),
183    Preference(usize),
184}
185
186/// A term's residuals where the blocks are, each a length, and their
187/// derivatives: per block it reads, by index, one row per residual — its
188/// derivative by that block's step from where it is.
189#[derive(Clone, Debug, Default, PartialEq)]
190pub struct Linearized {
191    pub values: Vec<Scalar>,
192    pub jacobian: Vec<(usize, Vec<Vec<Scalar>>)>,
193}
194
195/// The terms of a system: what an adapter — a sketch, a program — says of
196/// its constraints and preferences.
197pub trait Equations {
198    type Value: Variable;
199
200    /// How many constraints there are, and how many preferences.
201    fn count(&self) -> (usize, usize);
202
203    /// What the constraint `constraint` asks of its residuals.
204    fn bound(&self, constraint: usize) -> Bound;
205
206    /// The terms `which`, with every block at `values`: each its residuals
207    /// and their derivatives, or `None` where it cannot be made there (a
208    /// residual undecidable — a direction of zero length — or the term not
209    /// made at all).
210    fn evaluate(&self, values: &[Self::Value], which: &[Term]) -> Vec<Option<Linearized>>;
211}
212
213/// Blocks a solve works on together, and the terms on them (see
214/// [`Structure`]): a core no term ties to another, or a pendant hanging
215/// off blocks of earlier layers.
216#[derive(Clone, Debug, Default, PartialEq, Eq)]
217pub struct Group {
218    /// Its blocks, in order: what its variables are, one after another.
219    pub blocks: Vec<usize>,
220    pub constraints: Vec<usize>,
221    pub preferences: Vec<usize>,
222    /// The blocks outside it its terms read: what it hangs off. None for a
223    /// core.
224    pub on: Vec<usize>,
225    /// Its layer: none for a core, one past every group it hangs off for a
226    /// pendant.
227    pub layer: usize,
228    /// The component it is part of, by index (see
229    /// [`Structure::components`]).
230    pub component: usize,
231    /// For a pendant, how many ways its terms leave it to move, what it
232    /// hangs off held.
233    pub own: usize,
234}
235
236/// Which constraints hold, and the largest residual: an upper bound,
237/// infinite where one cannot be made.
238#[derive(Clone, Debug, PartialEq)]
239pub struct Held {
240    /// Every constraint holds, to [`RELATIVE_TOLERANCE`] of
241    /// the size.
242    pub converged: bool,
243    pub max_residual: Scalar,
244    /// The constraints that do not hold, by index.
245    pub failed: Vec<usize>,
246}
247
248/// Where a solve put the blocks, and how it went.
249#[derive(Debug)]
250pub struct Solution<V: Variable> {
251    pub values: Vec<V>,
252    /// The groups moved: those a preference pulled, whose constraints did
253    /// not hold, or that hang off a group that moved.
254    pub solved: Vec<Group>,
255    /// The limits the solve held at their bounds, by index.
256    pub at_limit: Vec<usize>,
257    /// The most steps any group took, and per phase the worst of the
258    /// groups: why it stopped, after how many steps, the largest residual
259    /// left.
260    pub iterations: usize,
261    pub phases: Vec<Phase>,
262    /// How many times the solve made the terms — every group's at once:
263    /// the work it took, its enclosure's apart.
264    pub evaluations: usize,
265    /// An enclosure of a true solution near where the solved groups ended
266    /// (see [`System::enclose`]) — of those only: the others were not
267    /// moved, and enclosing them is the caller's to ask
268    /// ([`System::enclose_rest`]). Of none, for a solve not proven
269    /// ([`System::solve_unproven`]).
270    pub enclosure: Enclosure<V>,
271    /// The groups it was solved in: what a solve of the same system a hair
272    /// away can take as it is ([`System::solve_unproven_in`]).
273    pub structure: Structure,
274}
275
276/// How free the blocks are (see [`System::freedom`]).
277#[derive(Clone, Debug, PartialEq)]
278pub struct Freedom {
279    /// Per block, how many independent ways it can still move, the others
280    /// moving with it as the constraints need.
281    pub blocks: Vec<usize>,
282    /// How many independent ways they can move together.
283    pub total: usize,
284}
285
286/// A system: its equations, how each block may move — every one a solve
287/// may move, [`Mobility::Held`] or [`Mobility::Follows`] — and its size.
288pub struct System<'e, E: Equations> {
289    pub equations: &'e E,
290    pub mobility: Vec<Mobility>,
291    /// What a constraint holds to [`RELATIVE_TOLERANCE`] of,
292    /// how far one step may go, and what a turn is measured against when a
293    /// value stays where it was.
294    pub scale: Scalar,
295}
296
297/// Every term of a system with `count` constraints and preferences.
298fn every_term((constraints, preferences): (usize, usize)) -> Vec<Term> {
299    (0..constraints)
300        .map(Term::Constraint)
301        .chain((0..preferences).map(Term::Preference))
302        .collect()
303}
304
305impl<E: Equations> System<'_, E> {
306    /// Which constraints hold at `at`.
307    pub fn held(&self, at: &[E::Value]) -> GeopResult<Held> {
308        let (count, _) = self.equations.count();
309        let constraints: Vec<Term> = (0..count).map(Term::Constraint).collect();
310        let tolerance = tolerance(self.scale)?;
311        let mut failed = Vec::new();
312        let mut values = Vec::new();
313        for (c, made) in self.equations.evaluate(at, &constraints).iter().enumerate() {
314            let bound = self.equations.bound(c);
315            let holds = match made {
316                Some(made) => made.values.iter().fold(true, |holds, &r| {
317                    values.push(r);
318                    holds && bound.holds(r, tolerance)
319                }),
320                None => {
321                    values.push(Scalar::INFINITY);
322                    false
323                }
324            };
325            if !holds {
326                failed.push(c);
327            }
328        }
329        Ok(Held {
330            converged: failed.is_empty(),
331            max_residual: largest(values),
332            failed,
333        })
334    }
335
336    /// Where the blocks have to be, from `start`, for every constraint to
337    /// hold, as near where the preferences pull them and, among those, as
338    /// near `start` as the constraints allow — and an enclosure of a true
339    /// solution near there.
340    ///
341    /// Layer by layer (see [`Structure`]): the cores first, then each
342    /// pendant, alone, from where what it hangs off carried it
343    /// ([`Variable::carried`]) — solved only where its constraints do not
344    /// hold there, or where its own freedom has to stay as near where it
345    /// was as they let it. A component a pendant of which cannot follow
346    /// after all is solved again as one.
347    ///
348    /// The blocks are moved even where the constraints cannot all hold, to
349    /// where they come closest; [`System::held`] says which do not.
350    pub fn solve(&self, start: &[E::Value]) -> GeopResult<Solution<E::Value>> {
351        self.proven(self.solve_unproven(start)?)
352    }
353
354    /// `solution`, a solve of it, with the enclosure of every group it
355    /// solved (see [`System::solve`]).
356    pub fn proven(&self, mut solution: Solution<E::Value>) -> GeopResult<Solution<E::Value>> {
357        solution.enclosure = self.enclose_groups(&solution.values, &solution.solved)?;
358        Ok(solution)
359    }
360
361    /// [`System::solve`], without the enclosure: its [`Solution::enclosure`]
362    /// encloses nothing. For a solve whose answer is shown and solved
363    /// again at once — an event of a drag — where proving every group it
364    /// moved would cost several times the solve; the answer kept is proven.
365    pub fn solve_unproven(&self, start: &[E::Value]) -> GeopResult<Solution<E::Value>> {
366        self.solve_unproven_in(start, self.structure(start))
367    }
368
369    /// [`System::solve_unproven`] in the groups of `structure`: the
370    /// structure a solve of the same system found before, a hair away — an
371    /// event of a drag, the last's — rather than found again, which makes
372    /// every term twice. A pendant that cannot follow there after all is
373    /// solved with its component, as ever (see [`System::solve`]).
374    pub fn solve_unproven_in(
375        &self,
376        start: &[E::Value],
377        structure: Structure,
378    ) -> GeopResult<Solution<E::Value>> {
379        let tolerance = tolerance(self.scale)?;
380        let mut values = start.to_vec();
381        let mut solution = Solution {
382            values: Vec::new(),
383            solved: Vec::new(),
384            at_limit: Vec::new(),
385            iterations: 0,
386            phases: Vec::new(),
387            evaluations: 0,
388            enclosure: Enclosure::empty(self.mobility.len()),
389            structure: Structure::default(),
390        };
391        let mut failed: Vec<usize> = Vec::new();
392        for layer in 0..structure.layers() {
393            let groups: Vec<&Group> = (structure.layer(layer).into_iter())
394                .map(|g| &structure.groups[g])
395                .collect();
396            let base = values.clone();
397            let (ended, evaluations) = self.solve_groups(start, &base, &groups, tolerance)?;
398            solution.evaluations += evaluations;
399            for (group, ended) in groups.into_iter().zip(ended) {
400                let Some(ended) = ended else {
401                    continue;
402                };
403                if !ended.holds && group.layer > 0 && !failed.contains(&group.component) {
404                    failed.push(group.component);
405                }
406                solution.record(&mut values, group.clone(), ended);
407            }
408        }
409        if !failed.is_empty() {
410            // Solved again as one, from the start.
411            solution.solved.retain(|g| !failed.contains(&g.component));
412            let whole: Vec<&Group> = (failed.iter()).map(|&c| &structure.components[c]).collect();
413            for group in &whole {
414                for &b in &group.blocks {
415                    values[b] = start[b].clone();
416                }
417            }
418            let base = values.clone();
419            let (ended, evaluations) = self.solve_groups(start, &base, &whole, tolerance)?;
420            solution.evaluations += evaluations;
421            for (group, ended) in whole.into_iter().zip(ended) {
422                if let Some(ended) = ended {
423                    solution.record(&mut values, group.clone(), ended);
424                }
425            }
426        }
427        solution.at_limit.sort_unstable();
428        solution.at_limit.dedup();
429        solution.values = values;
430        solution.structure = structure;
431        Ok(solution)
432    }
433
434    /// The groups `groups` — of one layer — solved in lockstep, every
435    /// block outside them where `base` has it, from where the solve
436    /// started at `start`: per group where it ended, if it moved; and how
437    /// many evaluations that took.
438    fn solve_groups(
439        &self,
440        start: &[E::Value],
441        base: &[E::Value],
442        groups: &[&Group],
443        tolerance: Scalar,
444    ) -> GeopResult<Layer<E::Value>> {
445        let batch = Batch::new(self, base);
446        let ended = batch.run(groups.iter().map(|&group| {
447            let batch = &batch;
448            async move {
449                let solving = Solving::new(self, base, group, batch);
450                let (first, carried) = solving.carried(start);
451                let holds = solving.check(&first, &group.constraints, tolerance).await.1;
452                // What its own freedom does, carried, is decided by where
453                // it was held.
454                let own = carried
455                    && group.own > 0
456                    && (group.blocks.iter()).any(|&b| self.mobility[b] == Mobility::Held);
457                if group.preferences.is_empty() && holds && !own {
458                    return Ok(carried.then_some(Ended {
459                        values: first,
460                        phases: Vec::new(),
461                        active: Vec::new(),
462                        holds,
463                    }));
464                }
465                let mut active = Vec::new();
466                loop {
467                    let (solved, phases) = solving.solve(first.clone(), &active, &[]).await?;
468                    let beyond = solving.beyond(&solved, &active, tolerance).await;
469                    if beyond.is_empty() {
470                        let holds = solving
471                            .check(&solved, &group.constraints, tolerance)
472                            .await
473                            .1;
474                        return Ok(Some(Ended {
475                            values: solved,
476                            phases,
477                            active,
478                            holds,
479                        }));
480                    }
481                    active.extend(beyond);
482                }
483            }
484        }))?;
485        Ok((ended, batch.evaluations()))
486    }
487
488    /// An enclosure of a true solution near `at` — a solution to the
489    /// solver's tolerance — group by group (see [`Enclosure`]).
490    pub fn enclose(&self, at: &[E::Value]) -> GeopResult<Enclosure<E::Value>> {
491        self.enclose_groups(at, &self.structure(at).groups)
492    }
493
494    /// `enclosure` — of the blocks at `at`, as [`Solution::enclosure`] is —
495    /// with every group it does not cover enclosed too: what a solve did
496    /// not move, where the caller needs every group (see [`System::enclose`]).
497    pub fn enclose_rest(
498        &self,
499        at: &[E::Value],
500        enclosure: Enclosure<E::Value>,
501    ) -> GeopResult<Enclosure<E::Value>> {
502        let covered: Vec<bool> = (0..self.mobility.len())
503            .map(|b| {
504                enclosure.boxes[b].is_some()
505                    || (enclosure.failures.iter()).any(|(blocks, _)| blocks.contains(&b))
506            })
507            .collect();
508        let rest: Vec<Group> = (self.structure(at).groups.into_iter())
509            .filter(|group| !group.blocks.iter().all(|&b| covered[b]))
510            .collect();
511        self.enclose_into(at, &rest, enclosure)
512    }
513
514    /// How free the blocks are where they are at `at`: the directions in
515    /// which every constraint that must vanish stays put to first order —
516    /// the null space of their Jacobian — and of those, how many move each
517    /// block. Decided over the box an enclosure of the solution near `at`
518    /// proves, where one is: a redundant constraint, which looks
519    /// independent at a point a hair off the solution, does not take a
520    /// freedom away. Where none is proven, at `at`.
521    pub fn freedom(&self, at: &[E::Value]) -> GeopResult<Freedom> {
522        let enclosure = self.enclose(at)?;
523        Ok(Freedom {
524            blocks: enclosure.freedom,
525            total: enclosure.total,
526        })
527    }
528
529    /// A smallest set of the constraints that cannot all hold together —
530    /// none where they all hold after a solve from `start`, by index.
531    ///
532    /// Group by group: one whose constraints do not hold after a solve is
533    /// enclosed where it ended, and the conflict it proves names the
534    /// constraints that cannot hold together with those they depend on (see
535    /// [`EncloseError::Conflict`]). Where nothing is proven — a singular
536    /// configuration — each constraint of the group is left out in turn,
537    /// and kept out where the rest still cannot all hold: as many solves as
538    /// there are constraints, so for a dialog to show, never for every drag.
539    pub fn conflicting(&self, start: &[E::Value]) -> GeopResult<Vec<usize>> {
540        let tolerance = tolerance(self.scale)?;
541        let groups = self.structure(start).groups;
542        let batch = Batch::new(self, start);
543        let found = batch.run(groups.iter().map(|group| {
544            let batch = &batch;
545            async move { self.conflicting_in(start, group, batch, tolerance).await }
546        }))?;
547        let mut conflicting: Vec<usize> = found.into_iter().flatten().collect();
548        conflicting.sort_unstable();
549        conflicting.dedup();
550        Ok(conflicting)
551    }
552
553    /// [`System::conflicting`] of the group `group`, from `start`.
554    async fn conflicting_in(
555        &self,
556        start: &[E::Value],
557        group: &Group,
558        batch: &Batch<'_, '_, E>,
559        tolerance: Scalar,
560    ) -> GeopResult<Vec<usize>> {
561        let solving = Solving::new(self, start, group, batch);
562        let first = solving.start();
563        if (solving.check(&first, &group.constraints, tolerance).await).1 {
564            return Ok(Vec::new());
565        }
566        let holds = async |left_out: &[usize]| -> GeopResult<(Vec<E::Value>, bool)> {
567            let (solved, _) = solving.solve(first.clone(), &[], left_out).await?;
568            let kept: Vec<usize> = (group.constraints.iter())
569                .filter(|c| !left_out.contains(c))
570                .copied()
571                .collect();
572            let holds = solving.check(&solved, &kept, tolerance).await.1;
573            Ok((solved, holds))
574        };
575        let (solved, all) = holds(&[]).await?;
576        if all {
577            return Ok(Vec::new());
578        }
579        let mut at = start.to_vec();
580        for (&block, value) in group.blocks.iter().zip(&solved) {
581            at[block] = value.clone();
582        }
583        let proven = self.enclose_group(&at, group, batch).await?;
584        if let Err(EncloseError::Conflict {
585            conflicting, with, ..
586        }) = proven.boxes
587        {
588            return Ok(conflicting
589                .into_iter()
590                .map(|(c, _)| c)
591                .chain(with)
592                .collect());
593        }
594        let mut left_out = Vec::new();
595        for &c in &group.constraints {
596            left_out.push(c);
597            if holds(&left_out).await?.1 {
598                left_out.pop();
599            }
600        }
601        Ok((group.constraints.iter())
602            .filter(|c| !left_out.contains(c))
603            .copied()
604            .collect())
605    }
606}
607
608/// Per group of a layer where its solve ended, if it moved; and how many
609/// evaluations the layer took.
610type Layer<V> = (Vec<Option<Ended<V>>>, usize);
611
612/// Where a group's solve ended: its values, the phases that put them
613/// there, the limits held at their bounds, and whether its constraints
614/// hold there.
615struct Ended<V> {
616    values: Vec<V>,
617    phases: Vec<Phase>,
618    active: Vec<usize>,
619    holds: bool,
620}
621
622impl<V: Variable> Solution<V> {
623    /// `group` moved as `ended` says: its values written into `values`.
624    fn record(&mut self, values: &mut [V], group: Group, ended: Ended<V>) {
625        self.iterations = (self.iterations).max(ended.phases.iter().map(|p| p.iterations).sum());
626        worst(&mut self.phases, ended.phases);
627        for (&block, value) in group.blocks.iter().zip(ended.values) {
628            values[block] = value;
629        }
630        self.at_limit.extend(ended.active);
631        self.solved.push(group);
632    }
633}
634
635/// `phases` with each of `group`'s phases merged into the one of its
636/// index: the most steps, and the largest residual.
637fn worst(phases: &mut Vec<Phase>, group: Vec<Phase>) {
638    for (k, phase) in group.into_iter().enumerate() {
639        match phases.get_mut(k) {
640            None => phases.push(phase),
641            Some(worst) => {
642                if phase.iterations > worst.iterations {
643                    worst.stop = phase.stop;
644                    worst.iterations = phase.iterations;
645                }
646                if phase.max_residual.could_be_greater(worst.max_residual) {
647                    worst.max_residual = phase.max_residual;
648                }
649            }
650        }
651    }
652}
653
654/// One group of a system being worked on: the blocks outside it as they
655/// stay, and where its variables are.
656struct Solving<'s, 'e, E: Equations> {
657    system: &'s System<'e, E>,
658    base: &'s [E::Value],
659    group: &'s Group,
660    /// Where its terms are asked, with every other group's.
661    batch: &'s Batch<'s, 'e, E>,
662    /// Per block of the group, where its variables start.
663    columns: Vec<usize>,
664    /// How many variables the group has.
665    n: usize,
666    space: Values<E::Value>,
667}
668
669impl<'s, 'e, E: Equations> Solving<'s, 'e, E> {
670    fn new(
671        system: &'s System<'e, E>,
672        base: &'s [E::Value],
673        group: &'s Group,
674        batch: &'s Batch<'s, 'e, E>,
675    ) -> Self {
676        let mut columns = Vec::with_capacity(group.blocks.len());
677        let mut n = 0;
678        for &block in &group.blocks {
679            columns.push(n);
680            n += base[block].dimension();
681        }
682        Self {
683            system,
684            base,
685            group,
686            batch,
687            columns,
688            n,
689            space: Values(std::marker::PhantomData),
690        }
691    }
692
693    /// The group's values where it starts.
694    fn start(&self) -> Vec<E::Value> {
695        (self.group.blocks.iter())
696            .map(|&b| self.base[b].clone())
697            .collect()
698    }
699
700    /// Where a solve of the group starts: for a pendant, carried along by
701    /// the first block it hangs off that moved since `start` and can carry
702    /// it — and whether one did; otherwise where it is.
703    fn carried(&self, start: &[E::Value]) -> (Vec<E::Value>, bool) {
704        let mut carried = false;
705        let values = (self.group.blocks.iter())
706            .map(|&b| {
707                let by = (self.group.on.iter()).find_map(|&s| {
708                    (!self.base[s].same(&start[s]))
709                        .then(|| self.base[b].carried(&start[s], &self.base[s]))
710                        .flatten()
711                });
712                carried |= by.is_some();
713                by.unwrap_or_else(|| self.base[b].clone())
714            })
715            .collect();
716        (values, carried)
717    }
718
719    /// The terms `which` with the group at `values` (see
720    /// [`Equations::evaluate`]), asked with every other group's.
721    async fn made(&self, values: &[E::Value], which: &[Term]) -> Made {
722        self.batch.ask(&self.group.blocks, values, which).await
723    }
724
725    /// Where the variables of `block` are among the group's, if it is one
726    /// of its blocks.
727    fn column(&self, block: usize) -> Option<usize> {
728        let at = self.group.blocks.binary_search(&block).ok()?;
729        self.columns.get(at).copied()
730    }
731
732    /// The residuals of `term` as rows over the group's variables — each
733    /// block's derivatives where its variables are, each composed with
734    /// `differential` (per block of the group, `None` for the identity);
735    /// those of blocks outside the group left out.
736    fn rows<'d>(
737        &self,
738        term: &Linearized,
739        differential: &dyn Fn(usize) -> Option<&'d Vec<Vec<Scalar>>>,
740    ) -> Rows {
741        let mut rows = Rows::default();
742        for (r, &value) in term.values.iter().enumerate() {
743            let mut row = vec![Scalar::ZERO; self.n];
744            let mut set = vec![false; self.n];
745            for (block, derivatives) in &term.jacobian {
746                let (Some(column), Some(local)) = (self.column(*block), derivatives.get(r)) else {
747                    continue;
748                };
749                let own = match differential(*block) {
750                    None => local.clone(),
751                    Some(d) => compose(local, d),
752                };
753                for (k, v) in own.into_iter().enumerate() {
754                    let at = column + k;
755                    if at >= self.n {
756                        break;
757                    }
758                    // A block read twice adds its derivatives; read once,
759                    // they are its own, not a zero added to.
760                    row[at] = match set[at] {
761                        true => row[at].add(v),
762                        false => v,
763                    };
764                    set[at] = true;
765                }
766            }
767            rows.push(value, row);
768        }
769        rows
770    }
771
772    /// The constraints `which` with the group at `values`; `None` if one
773    /// cannot be made there.
774    async fn constraints_at(
775        &self,
776        values: &[E::Value],
777        which: &[usize],
778    ) -> Option<Vec<Linearized>> {
779        let terms: Vec<Term> = which.iter().map(|&c| Term::Constraint(c)).collect();
780        self.made(values, &terms).await.into_iter().collect()
781    }
782
783    /// The largest residual of the constraints `which` at `values`, and
784    /// whether they all hold there.
785    async fn check(
786        &self,
787        values: &[E::Value],
788        which: &[usize],
789        tolerance: Scalar,
790    ) -> (Scalar, bool) {
791        let Some(made) = self.constraints_at(values, which).await else {
792            return (Scalar::INFINITY, false);
793        };
794        let holds = (which.iter().zip(&made)).all(|(&c, made)| {
795            let bound = self.system.equations.bound(c);
796            made.values.iter().all(|&r| bound.holds(r, tolerance))
797        });
798        (
799            largest(made.iter().flat_map(|m| m.values.iter().copied())),
800            holds,
801        )
802    }
803
804    /// The limits of the group not held at their bounds that `values` is
805    /// beyond.
806    async fn beyond(&self, values: &[E::Value], active: &[usize], tolerance: Scalar) -> Vec<usize> {
807        let limits: Vec<usize> = (self.group.constraints.iter())
808            .filter(|c| !active.contains(c))
809            .filter(|&&c| self.system.equations.bound(c) == Bound::AtMost)
810            .copied()
811            .collect();
812        let Some(made) = self.constraints_at(values, &limits).await else {
813            return Vec::new();
814        };
815        (limits.iter().zip(&made))
816            .filter(|(_, made)| !(made.values.iter()).all(|&r| Bound::AtMost.holds(r, tolerance)))
817            .map(|(&c, _)| c)
818            .collect()
819    }
820}
821
822/// The row `local` times the matrix `d`: a derivative by a block's steps
823/// at a point, as one by the steps from where the retraction started.
824fn compose(local: &[Scalar], d: &[Vec<Scalar>]) -> Vec<Scalar> {
825    let columns = d.first().map_or(0, Vec::len);
826    (0..columns)
827        .map(|j| {
828            let mut sum: Option<Scalar> = None;
829            for (l, row) in local.iter().zip(d) {
830                let Some(&e) = row.get(j) else {
831                    continue;
832                };
833                let term = l.mul(e);
834                sum = Some(match sum {
835                    None => term,
836                    Some(s) => s.add(term),
837                });
838            }
839            sum.unwrap_or(Scalar::ZERO)
840        })
841        .collect()
842}
843
844#[cfg(test)]
845mod tests;