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}