Skip to main content

geop_lib_constraints/
solve.rs

1//! Solving the state a program's [`Constraint`]s are on: the program's side
2//! of the constraint systems of [`geop_core_math::solvers::system`], which
3//! solve, check, enclose and measure them — the same algorithms a sketch's
4//! numbers are solved by.
5//!
6//! The values of the state a solve may move are the caller's to say
7//! ([`Problem::free`]), each a block of the system, stepped along its own
8//! steps ([`geop_core_math::solvers::system::Variable::retract`]: a pose by
9//! the exponential map of a twist). The constraints are those the program
10//! defines, run once at the state a solve starts from: at every state it
11//! tries, only the constraints it asks are made again, each by its step's
12//! generator, of the part as built and the state tried
13//! ([`crate::constraint::Remade`]) — no program is run, and the work
14//! of a solve step is that of the constraints it asks. What a solve
15//! should prefer among the states the constraints allow is the caller's
16//! too: [`Preference`]s pull — a drag — and every value nothing pulls stays
17//! as near where it was as the constraints let it.
18//!
19//! What steps let a solve move is said in the program itself: a step
20//! declares a value of the state ([`crate::unknown::Unknown`]), how a solve
21//! may move it, and where one starts from — so a solve of any program is set
22//! up from the part alone ([`crate::unknown::Unknowns`]).
23//!
24//! Every solve comes with an enclosure of a true solution near where it
25//! ended ([`Solved::enclosure`]), each value in its own step coordinates
26//! around the state found — or with why none is proven: a configuration
27//! singular there, or constraints that conflict, by name.
28
29use geop_core_math::scalars::Scalar;
30use std::{
31    cell::RefCell,
32    collections::{BTreeMap, BTreeSet},
33};
34
35use geop_core_math::{
36    geop_error::GeopResult,
37    solvers::system::{
38        self, Bound, Equations, Linearized, Phase, Structure, System, Term, Variable,
39    },
40};
41
42pub use geop_core_math::solvers::system::Mobility;
43
44use geop_core_program::{
45    part::Part,
46    program::Program,
47    reader::Reader,
48    runner::Runner,
49    values::{Settings, StateValue, StateValues},
50};
51
52use crate::constraint::{Constraint, Constraints, DefinedAt, Remade};
53
54/// A program, with its inputs, and the part it builds: run once, at its
55/// own state, for a solve to make its constraints again of at every state
56/// it tries. A solve leaves the part so; a caller keeping the state found
57/// runs the program there.
58pub struct Run<'r> {
59    pub runner: &'r Runner,
60    pub part: &'r mut Part,
61    pub program: Program,
62    pub inputs: Settings,
63}
64
65thread_local! {
66    static RUNS: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
67}
68
69/// How many times this thread has run a program for a question about its
70/// constraints — a solve, an enclosure, how free its values are — ever.
71/// Each run costs what the program's steps do, so a test can bound the
72/// work a question takes by the runs it makes, rather than time it.
73pub fn runs() -> usize {
74    RUNS.get()
75}
76
77impl Run<'_> {
78    /// The part the program builds, at its own state: what a solve makes
79    /// its constraints again of, at every state it tries. Run again only if
80    /// the part was last run otherwise (see
81    /// [`geop_core_program::runner::Runner::run_with`]).
82    fn built(&mut self) -> &Part {
83        RUNS.set(RUNS.get() + 1);
84        (self.runner).run_with(self.part, &self.program, &self.inputs, None);
85        self.part
86    }
87}
88
89/// What a solve should prefer among the states where the constraints
90/// hold: residuals it wants small — not zero at any price — where the part
91/// is as `reader` reads it, each a length, and their derivatives by the
92/// state. A drag, pulling a point of a placed part towards the pointer.
93pub trait Preference {
94    fn evaluate(&self, reader: &Reader<'_>) -> GeopResult<Constraint>;
95}
96
97/// What a [`solve`] may move, and towards what.
98pub struct Problem<'p> {
99    /// The values the solve may move, by name, and how; it leaves every
100    /// other value — and every one [`Mobility::Fixed`] — as it is.
101    pub free: BTreeMap<String, Mobility>,
102    pub preferences: Vec<&'p dyn Preference>,
103    /// The part's size: what a constraint holds to a part in
104    /// [`geop_core_math::solvers::system::RELATIVE_TOLERANCE`] of, how
105    /// far one step may go, and what a turn is measured against when a
106    /// value stays where it was.
107    pub scale: Scalar,
108    /// The groups a solve of the same system found before, a hair away —
109    /// the last event of a drag — to solve in rather than find them again
110    /// (see [`System::solve_unproven_in`]); found if none is given, or one
111    /// of another system.
112    pub structure: Option<&'p Structure>,
113}
114
115/// How a solve went.
116#[derive(Clone, Debug, PartialEq)]
117pub struct Report {
118    /// Every constraint holds.
119    pub converged: bool,
120    /// The constraints that do not, by name.
121    pub failed: Vec<String>,
122    /// The limits the solve stopped at, by name.
123    pub at_limit: Vec<String>,
124    /// The values it moved, by name.
125    pub moved: Vec<String>,
126    /// The most steps any group took, and per phase the worst of the
127    /// groups: why it stopped, after how many steps, the largest residual
128    /// left.
129    pub iterations: usize,
130    pub phases: Vec<Phase>,
131    /// How many times the constraints were made: by the solve, and to
132    /// enclose its solution — the work, which a test can bound.
133    pub evaluations: [usize; 2],
134    /// The most variables of a group the solve moved — what one step of
135    /// it costs the cube of (see [`System::structure`]).
136    pub largest: usize,
137    /// The dense linear algebra the solve and its enclosure did (see
138    /// [`geop_core_math::solvers::work`]).
139    pub work: u64,
140}
141
142/// An enclosure of a true solution of the constraints: every value it
143/// could have, by name — or, for the values whose groups none is proven
144/// for, why not.
145#[derive(Clone, Debug, PartialEq)]
146pub struct Enclosure {
147    /// Per value enclosed, the box its true value is in.
148    pub state: StateValues,
149    /// Per group of values not proven, its values and why: the constraints
150    /// that conflict there, by name, or what is singular.
151    pub unproven: Vec<(Vec<String>, String)>,
152}
153
154/// Where a solve put the state, how it went, and the enclosure of a true
155/// solution near there of every group it solved — if it was asked to
156/// prove it.
157pub struct Solved {
158    pub state: StateValues,
159    pub report: Report,
160    pub enclosure: Option<Enclosure>,
161    /// The groups it was solved in (see [`Problem::structure`]).
162    pub structure: Structure,
163}
164
165/// How free the values of the state are, where they are now (see
166/// [`freedom`]).
167#[derive(Clone, Debug, PartialEq)]
168pub struct Freedom {
169    /// Per value a solve may move, how many independent ways it can still
170    /// move, the others moving with it as the constraints need.
171    pub states: BTreeMap<String, usize>,
172    /// How many independent ways they can move together.
173    pub total: usize,
174}
175
176/// A part's constraints and preferences, on the values a problem frees: the
177/// [`Equations`] of its system. Each value a solve may move is a block, in
178/// order of their names; each constraint is one by its name, in order.
179struct Solving<'r, 'p> {
180    run: RefCell<Run<'r>>,
181    /// The whole state, as the program was last run with: the blocks'
182    /// values written into it.
183    working: RefCell<StateValues>,
184    /// Per block, the value it is.
185    blocks: Vec<String>,
186    index: BTreeMap<String, usize>,
187    /// Per constraint, its name, what it asks, and where it is defined.
188    constraints: Vec<(String, Bound, DefinedAt)>,
189    /// What the constraints were made again as, at the states tried.
190    remade: Remade,
191    preferences: &'p [&'p dyn Preference],
192}
193
194impl<'r, 'p> Solving<'r, 'p> {
195    /// The system of the program `run` runs, its state at `state`, the
196    /// values `problem` frees as its blocks: and their values where `state`
197    /// has them.
198    fn new(
199        mut run: Run<'r>,
200        state: &StateValues,
201        problem: &'p Problem<'p>,
202    ) -> GeopResult<(Self, Vec<StateValue>)> {
203        let all = Constraints::of(&run.built().current())?;
204        let constraints = (all.iter().zip(all.defining()))
205            .map(|((name, constraint), (_, at))| (name.to_string(), constraint.bound, at.clone()))
206            .collect();
207        let blocks: Vec<String> = (problem.free.iter())
208            .filter(|(_, mobility)| mobility.free())
209            .map(|(name, _)| name.clone())
210            .collect();
211        let values = (blocks.iter())
212            .map(|name| state.get(name).cloned().unwrap_or_default())
213            .collect();
214        let index = (blocks.iter().enumerate())
215            .map(|(k, name)| (name.clone(), k))
216            .collect();
217        let solving = Solving {
218            run: RefCell::new(run),
219            working: RefCell::new(state.clone()),
220            blocks,
221            index,
222            constraints,
223            remade: Remade::default(),
224            preferences: &problem.preferences,
225        };
226        Ok((solving, values))
227    }
228
229    /// The system: how each block may move, and its size.
230    fn system<'s>(&'s self, problem: &Problem) -> System<'s, Self> {
231        System {
232            equations: self,
233            mobility: (self.blocks.iter())
234                .map(|name| problem.free.get(name).copied().unwrap_or(Mobility::Fixed))
235                .collect(),
236            scale: problem.scale,
237        }
238    }
239
240    /// The names of the constraints `which`.
241    fn names(&self, which: &[usize]) -> Vec<String> {
242        (which.iter())
243            .filter_map(|&c| self.constraints.get(c).map(|(name, ..)| name.clone()))
244            .collect()
245    }
246
247    /// `constraint` as a term of the system: its derivatives by the
248    /// blocks it reads.
249    fn linearized(&self, constraint: &Constraint) -> Linearized {
250        Linearized {
251            values: constraint.residuals.clone(),
252            jacobian: (constraint.jacobian.iter())
253                .filter_map(|(state, rows)| Some((*self.index.get(state)?, rows.clone())))
254                .collect(),
255        }
256    }
257}
258
259impl Equations for Solving<'_, '_> {
260    type Value = StateValue;
261
262    fn count(&self) -> (usize, usize) {
263        (self.constraints.len(), self.preferences.len())
264    }
265
266    fn bound(&self, constraint: usize) -> Bound {
267        self.constraints
268            .get(constraint)
269            .map_or(Bound::Zero, |c| c.1)
270    }
271
272    fn evaluate(&self, values: &[StateValue], which: &[Term]) -> Vec<Option<Linearized>> {
273        {
274            let mut working = self.working.borrow_mut();
275            for (name, value) in self.blocks.iter().zip(values) {
276                if working.get(name) != Some(value) {
277                    working.insert(name.clone(), value.clone());
278                }
279            }
280        }
281        let run = self.run.borrow();
282        let working = self.working.borrow();
283        let reader = run.part.current().trying(&working);
284        let asked: Vec<&DefinedAt> = (which.iter())
285            .filter_map(|term| match term {
286                Term::Constraint(c) => self.constraints.get(*c).map(|(.., at)| at),
287                Term::Preference(_) => None,
288            })
289            .collect();
290        let mut made = self.remade.constraints(&reader, &asked).into_iter();
291        (which.iter())
292            .map(|term| match term {
293                Term::Constraint(c) => {
294                    self.constraints.get(*c)?;
295                    Some(self.linearized(&made.next()?.ok()?))
296                }
297                Term::Preference(p) => {
298                    let preference = self.preferences.get(*p)?.evaluate(&reader).ok()?;
299                    Some(self.linearized(&preference))
300                }
301            })
302            .collect()
303    }
304}
305
306/// The enclosure `enclosure` of `program`'s system, by name.
307fn named(program: &Solving, enclosure: system::Enclosure<StateValue>) -> Enclosure {
308    let state = (program.blocks.iter().zip(enclosure.boxes))
309        .filter_map(|(name, boxed)| Some((name.clone(), boxed?)))
310        .collect();
311    let unproven = (enclosure.failures.into_iter())
312        .map(|(blocks, error)| {
313            let values = (blocks.iter())
314                .filter_map(|&b| program.blocks.get(b).cloned())
315                .collect();
316            let name =
317                |c: usize| (program.constraints.get(c)).map_or(format!("#{c}"), |c| c.0.clone());
318            (values, error.named(name).root_message().to_string())
319        })
320        .collect();
321    Enclosure { state, unproven }
322}
323
324/// Where the values `problem` frees have to be — from where `state` has
325/// them — for every constraint the program `run` runs defines to hold, as
326/// near where they are as the constraints allow, pulled as its preferences
327/// ask (see [`System::solve`]), and an enclosure of a true solution near
328/// there. The part is left as the program builds it at its own state.
329///
330/// The state is moved even where the constraints cannot all hold, to where
331/// they come closest; the report says which do not.
332///
333/// Only where `prove` asks is the solution enclosed: a solve shown and
334/// solved again at once — an event of a drag — need not be, and proving
335/// every group it moved costs several times the solve (see
336/// [`System::solve_unproven`]).
337pub fn solve(
338    run: Run<'_>,
339    state: &StateValues,
340    problem: &Problem,
341    prove: bool,
342) -> GeopResult<Solved> {
343    let (program, start) = Solving::new(run, state, problem)?;
344    let system = program.system(problem);
345    let work = geop_core_math::solvers::work();
346    let structure = (problem.structure)
347        .filter(|s| s.fits(start.len(), program.count()))
348        .cloned()
349        .unwrap_or_else(|| system.structure(&start));
350    let solution = system.solve_unproven_in(&start, structure)?;
351    let solution = match prove {
352        true => system.proven(solution)?,
353        false => solution,
354    };
355    let work = geop_core_math::solvers::work() - work;
356    let held = system.held(&solution.values)?;
357    let mut solved = state.clone();
358    let mut moved = Vec::new();
359    for ((name, value), was) in program.blocks.iter().zip(&solution.values).zip(&start) {
360        if value != was {
361            moved.push(name.clone());
362            solved.insert(name.clone(), value.clone());
363        }
364    }
365    let report = Report {
366        converged: held.converged,
367        failed: program.names(&held.failed),
368        at_limit: program.names(&solution.at_limit),
369        moved,
370        iterations: solution.iterations,
371        phases: solution.phases,
372        evaluations: [solution.evaluations, solution.enclosure.evaluations],
373        largest: (solution.solved.iter())
374            .map(|g| {
375                (g.blocks.iter())
376                    .map(|&b| start.get(b).map_or(0, Variable::dimension))
377                    .sum()
378            })
379            .max()
380            .unwrap_or(0),
381        work,
382    };
383    let enclosure = prove.then(|| named(&program, solution.enclosure));
384    Ok(Solved {
385        state: solved,
386        report,
387        enclosure,
388        structure: solution.structure,
389    })
390}
391
392/// An enclosure of a true solution of the constraints of the program `run`
393/// runs near where
394/// `state` has the values `problem` frees — a solution to the solver's
395/// tolerance (see [`System::enclose`]).
396pub fn enclose(run: Run<'_>, state: &StateValues, problem: &Problem) -> GeopResult<Enclosure> {
397    let (program, start) = Solving::new(run, state, problem)?;
398    let enclosure = program.system(problem).enclose(&start)?;
399    let enclosure = named(&program, enclosure);
400    Ok(enclosure)
401}
402
403/// How free the values `problem` frees are where `state` has them: the
404/// directions in which every constraint that must vanish stays put to
405/// first order, and of those, how many move each value — decided over the
406/// box an enclosure of the solution proves (see
407/// [`System::freedom`]). A limit leaves free the motion away from it.
408pub fn freedom(run: Run<'_>, state: &StateValues, problem: &Problem) -> GeopResult<Freedom> {
409    let (program, start) = Solving::new(run, state, problem)?;
410    let freedom = program.system(problem).freedom(&start)?;
411    let states = (program.blocks.iter().cloned())
412        .zip(freedom.blocks)
413        .collect();
414    Ok(Freedom {
415        states,
416        total: freedom.total,
417    })
418}
419
420/// A smallest set of the constraints of the program `run` runs that cannot
421/// all hold together —
422/// none, where they all hold after a solve from `state` (see
423/// [`System::conflicting`]), by name.
424pub fn conflicting(
425    run: Run<'_>,
426    state: &StateValues,
427    problem: &Problem,
428) -> GeopResult<Vec<String>> {
429    let (program, start) = Solving::new(run, state, problem)?;
430    let conflicting = program.system(problem).conflicting(&start)?;
431    let names: BTreeSet<String> = program.names(&conflicting).into_iter().collect();
432    Ok(names.into_iter().collect())
433}