Skip to main content

geop_ops_assembly/
solving.rs

1//! Solving a part's state as an editor does, the same for every program:
2//! settling it so every constraint holds ([`solve()`]), dragging a placed part
3//! ([`Pull::Point`]), setting a value a step offers to set ([`set`]), and
4//! asking which constraints hold ([`check`]), how free the placed parts are
5//! ([`freedom`]) and which constraints conflict ([`conflicting`]).
6//!
7//! Nothing here knows what the constraints are. What a solve may move and
8//! where it starts from, the steps say in the part itself: each placed part
9//! moves by its pose state ([`crate::placed`]), held where it is as far as
10//! the constraints allow; a step declares any other value a solve moves, or
11//! holds one fixed, as an [`Unknown`]; and the values a user sets directly
12//! — a joint's coordinates — as [`Settings`], for an editor to list.
13//! [`geop_lib_constraints::solve`] solves, encloses and measures, running the
14//! program ([`Rerun`]) where it starts and where it ends, and making only
15//! the constraints it asks again at every state it tries.
16
17use geop_core_program::{
18    element::{ElementReference, HasElements, Kind, whole},
19    placed::INSTANCE_SEPARATOR,
20    reader::Reader,
21    target::{Target, TargetReference},
22    values::{StateValue, StateValues, pose_state},
23};
24use std::collections::{BTreeMap, BTreeSet};
25
26use geop_core_editor::{editing::Pull, editor::Rerun};
27use geop_core_math::{
28    dual::Dual,
29    geop_error::{GeopError, GeopResult},
30    primitives::Pose,
31    scalars::Scalar,
32    solvers::{
33        least_squares::{Rows, Stop},
34        system::{Phase, Structure, Variable, tolerance},
35    },
36    vector::Vector3,
37};
38use geop_lib_constraints::{
39    Bound, Constraint, Constraints,
40    solve::{self, Mobility, Preference, Problem, Run},
41    unknown::Unknowns,
42};
43
44/// A parameter's value, as an input.
45use serde::Serialize;
46
47pub use geop_lib_constraints::unknown::{Unknown, declare_unknowns};
48
49use geop_core_editor::view::PartView;
50use geop_ops_model::placed::{Drawn, Located, drawn, locate, placed_parts};
51
52/// The program of `rerun`, to run again by a solve.
53fn run<'r>(rerun: &'r mut Rerun) -> Run<'r> {
54    Run {
55        runner: rerun.runner,
56        part: &mut *rerun.part,
57        program: rerun.program.clone(),
58        inputs: Default::default(),
59    }
60}
61
62/// What a value a user sets measures.
63#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
64pub enum Measure {
65    /// An angle, in degrees.
66    #[serde(rename = "angle")]
67    Angle,
68    /// A distance.
69    #[serde(rename = "distance")]
70    Distance,
71}
72
73/// A value of the state a user sets directly — a joint's coordinate — and
74/// the limits it is kept within.
75#[derive(Clone, Debug, PartialEq)]
76pub struct Setting {
77    pub state: String,
78    pub measure: Measure,
79    pub min: Option<Scalar>,
80    pub max: Option<Scalar>,
81}
82
83/// Values of the state a user sets directly, as a step declares them for an
84/// editor to list (see [`settings`]): a target of the part, named after
85/// what they are of — the coordinates of a joint, by the joint's name.
86#[derive(Clone, Debug, PartialEq)]
87pub struct Settings {
88    /// What they are of, by name: the joint's mate.
89    pub name: String,
90    /// What an editor calls that: `fore m1`, the mate `m1` of the step
91    /// `fore`.
92    pub label: String,
93    /// What kind of thing that is: `revolute`.
94    pub kind: &'static str,
95    /// What it joins, as the elements it is between read.
96    pub between: Vec<String>,
97    pub values: Vec<Setting>,
98}
99
100/// What the target of [`Settings`] is named, before what they are of.
101const SETTINGS: &str = "settings:";
102
103/// The target declaring the settings of `of`.
104pub fn settings_of(of: &str) -> TargetReference<Settings> {
105    TargetReference::new(format!("{SETTINGS}{of}"))
106}
107
108/// Each joint's own.
109impl Target for Settings {}
110
111/// Read whole.
112impl Kind for Settings {
113    const NAME: &'static str = "settings";
114}
115
116impl HasElements<Settings> for Settings {
117    type Element<'a> = &'a Settings;
118
119    fn element(&self, name: &str) -> GeopResult<&Settings> {
120        whole(self, name)
121    }
122
123    fn names_target(name: &str) -> bool {
124        name.starts_with(SETTINGS)
125    }
126}
127
128/// Every [`Settings`] the part `reader` reads declares, in the order the
129/// steps made them.
130fn all_settings<'a>(reader: &Reader<'a>) -> Vec<&'a Settings> {
131    (reader.targets_of::<Settings>().into_iter())
132        .filter_map(|target| {
133            reader
134                .retrieve_element(&ElementReference::whole(target))
135                .ok()
136        })
137        .collect()
138}
139
140/// A value a user sets, as an editor shows it: where the state has it.
141#[derive(Clone, Debug, PartialEq, Serialize)]
142pub struct SettingState {
143    #[serde(rename = "parameter")]
144    pub state: String,
145    #[serde(rename = "motion")]
146    pub measure: Measure,
147    /// The state's value, or — not given one — zero.
148    pub value: f64,
149    pub min: Option<f64>,
150    pub max: Option<f64>,
151}
152
153/// [`Settings`] as an editor lists them.
154#[derive(Clone, Debug, PartialEq, Serialize)]
155pub struct SettingsState {
156    pub name: String,
157    pub label: String,
158    pub kind: &'static str,
159    pub between: Vec<String>,
160    pub values: Vec<SettingState>,
161}
162
163/// Every value a user sets in the part `reader` reads — and in the parts
164/// placed flexibly in it — grouped as the steps declared them, in the order
165/// they made them, each where the state has it.
166pub fn settings(reader: &Reader) -> Vec<SettingsState> {
167    (all_settings(reader).into_iter())
168        .map(|settings| SettingsState {
169            name: settings.name.clone(),
170            label: settings.label.clone(),
171            kind: settings.kind,
172            between: settings.between.clone(),
173            values: (settings.values.iter())
174                .map(|s| SettingState {
175                    state: s.state.clone(),
176                    measure: s.measure,
177                    value: match reader.retrieve_state(&s.state) {
178                        StateValue::Number(v) => v.to_f64(),
179                        StateValue::Pose(_) => 0.0,
180                    },
181                    min: s.min.map(|v| v.to_f64()),
182                    max: s.max.map(|v| v.to_f64()),
183                })
184                .collect(),
185        })
186        .collect()
187}
188
189/// How a solve went: whether every constraint holds, and how hard it was to
190/// get there.
191#[derive(Clone, Debug, PartialEq)]
192pub struct Report {
193    pub converged: bool,
194    /// The constraints that do not hold, by name.
195    pub failed: Vec<String>,
196    /// The values the solve stopped at a limit of: those the limits held
197    /// at their bounds read.
198    pub at_limit: Vec<String>,
199    /// How many steps the solve took, and each of its minimizations — none
200    /// for a check: why it stopped, after how many steps, and the largest
201    /// residual it left.
202    pub iterations: usize,
203    pub phases: Vec<(Stop, usize, f64)>,
204    /// Every value the solve moved — and the others solved with them — as
205    /// enclosed: the box its true value is in, about where the solve put
206    /// it (see [`solve::Enclosure`]). None for a solve not asked to prove
207    /// it, and for a check.
208    pub enclosed: Option<StateValues>,
209    /// The values no true solution near where the solve ended is proven
210    /// for, and why: the constraints that conflict there, by name, or what
211    /// is singular.
212    pub unproven: Vec<(Vec<String>, String)>,
213    /// How many times the constraints were made: by the solve, and to
214    /// enclose its solution.
215    pub evaluations: [usize; 2],
216    /// The most variables of a group the solve moved (see
217    /// [`solve::Report::largest`]), and the dense linear algebra the solve
218    /// did ([`solve::Report::work`]).
219    pub largest: usize,
220    pub work: u64,
221    /// The groups it solved in: what the next event of a drag solves in
222    /// (see [`solve`]). None found for a check.
223    pub structure: Structure,
224}
225
226/// How free the placed parts are (see [`freedom`]).
227#[derive(Clone, Debug, Default, PartialEq, Serialize)]
228pub struct Freedom {
229    /// Per placed part, by name, how many independent ways it can still
230    /// move: 0 for one held fast or fixed, 6 for one nothing holds.
231    pub parts: BTreeMap<String, usize>,
232    /// How many independent ways the part's state can move as a whole.
233    pub total: usize,
234}
235
236/// The size of a placed part, as it is drawn: what turns a turn into a
237/// length, and what a solve's size is at least. An empty part has none.
238fn size_of(drawn: &PartView) -> Option<Scalar> {
239    if drawn.vertices.is_empty() && drawn.faces.is_empty() {
240        return None;
241    }
242    Some(drawn.extent.size)
243}
244
245/// The size of the largest of the placed parts drawn as `drawn`, at least
246/// one.
247pub fn largest<'i>(drawn: impl IntoIterator<Item = GeopResult<&'i Drawn>>) -> GeopResult<Scalar> {
248    let mut scale = Scalar::ONE;
249    for drawn in drawn {
250        if let Some(size) = size_of(&drawn?.view)
251            && size.definitely_greater(scale)
252        {
253            scale = size;
254        }
255    }
256    Ok(scale)
257}
258
259/// The size of the part `reader` reads: of the largest part placed in it,
260/// at least one. What a constraint holds to
261/// [`geop_core_math::solvers::system::RELATIVE_TOLERANCE`] of.
262pub fn scale_of(reader: &Reader) -> GeopResult<Scalar> {
263    largest(
264        placed_parts(reader)?
265            .iter()
266            .map(|placed| drawn(reader, &placed.name)),
267    )
268}
269
270/// Which constraints hold where the part `reader` reads has its state.
271pub fn check(reader: &Reader) -> GeopResult<Report> {
272    let tolerance = tolerance(scale_of(reader)?)?;
273    let failed: Vec<String> = (Constraints::of(reader)?.iter())
274        .filter(|(_, constraint)| !constraint.holds(tolerance))
275        .map(|(name, _)| name.to_string())
276        .collect();
277    Ok(Report {
278        converged: failed.is_empty(),
279        failed,
280        at_limit: Vec::new(),
281        iterations: 0,
282        phases: Vec::new(),
283        enclosed: None,
284        unproven: Vec::new(),
285        evaluations: [0, 0],
286        largest: 0,
287        work: 0,
288        structure: Structure::default(),
289    })
290}
291
292/// Every value of the state a solve may move in the part `reader` reads,
293/// and how: every unknown the steps declare — the parts placed in it, and
294/// in those, each held where it is as far as the constraints allow — but
295/// those `held`, and of those held where they are only the one that is the
296/// state `only`, if given. What a step holds fixed is not moved.
297fn freed(
298    reader: &Reader,
299    only: Option<&str>,
300    held: &BTreeSet<String>,
301) -> GeopResult<BTreeMap<String, Mobility>> {
302    let mut free: BTreeMap<String, Mobility> = (Unknowns::of(reader)?.iter())
303        .map(|unknown| (unknown.state.clone(), unknown.mobility))
304        .collect();
305    free.retain(|state, mobility| {
306        *mobility != Mobility::Fixed
307            && !held.contains(state)
308            && (*mobility != Mobility::Held || only.is_none_or(|o| o == state))
309    });
310    Ok(free)
311}
312
313/// Whether `value` is no value of the kind `seed` is: a state a solve has
314/// to start from somewhere.
315fn missing(value: Option<&StateValue>, seed: &StateValue) -> bool {
316    !matches!(
317        (value, seed),
318        (Some(StateValue::Number(_)), StateValue::Number(_))
319            | (Some(StateValue::Pose(_)), StateValue::Pose(_))
320    )
321}
322
323/// `state` with the unknowns a solve of the values `free` may move seeded:
324/// — not `all` — each held where it is ([`Mobility::Held`]) where the state
325/// has none, its seed where the run has it already, which builds the same;
326/// — `all` — every one where the state has none, or where the constraint
327/// its seed is unless does not hold: a free choice, which the solve makes
328/// exact.
329fn seeded(
330    reader: &Reader,
331    state: &StateValues,
332    free: &BTreeMap<String, Mobility>,
333    all: bool,
334) -> GeopResult<StateValues> {
335    let mut seeded = state.clone();
336    let tolerance = tolerance(scale_of(reader)?)?;
337    let constraints = Constraints::of(reader)?;
338    for unknown in Unknowns::of(reader)?.iter() {
339        let Some(seed) = &unknown.seed else {
340            continue;
341        };
342        if !free.contains_key(&unknown.state) || !(all || unknown.mobility == Mobility::Held) {
343            continue;
344        }
345        let holds = (unknown.unless.as_deref())
346            .is_some_and(|c| constraints.get(c).is_some_and(|c| c.holds(tolerance)));
347        let unheld = all && unknown.unless.is_some() && !holds;
348        if missing(state.get(&unknown.state), seed) || unheld {
349            seeded.insert(unknown.state.clone(), seed.clone());
350        }
351    }
352    Ok(seeded)
353}
354
355/// Numbers that carry their derivatives by one pose's step: six, as many
356/// as a pose's variables.
357pub type D = Dual<6>;
358
359/// A point of a placed part pulled towards a point in the world: a drag.
360/// The part resists turning — a hundredth as strongly — so that a point
361/// pulled alone moves it rather than spins it.
362struct PointPull {
363    path: String,
364    state: String,
365    local: Vector3,
366    target: Vector3,
367    /// Where the part was, which it resists turning from.
368    start: StateValue,
369    scale: Scalar,
370}
371
372/// How strongly a part pulled by a point resists turning, against the pull.
373const TURN_RESISTANCE: (i64, i64) = (1, 100);
374
375/// Where the placed part at `path` is in the world, as `reader` has the
376/// state, moved by a step of its own pose state: its pose is the innermost
377/// the placement reads, so its step moves the part in its own frame.
378fn stepped(reader: &Reader, path: &str) -> GeopResult<Pose<D>> {
379    let world = locate(reader, path)?.world;
380    let step = Pose::exp_with_derivatives(&Vector3::zero(), &Vector3::zero())?;
381    Ok(world.map(D::cst).compose(&step))
382}
383
384/// The rows `values` as a constraint by `state`'s step.
385fn preference(state: &str, rows: Rows) -> Constraint {
386    Constraint {
387        residuals: rows.values,
388        jacobian: BTreeMap::from([(state.to_string(), rows.jacobian)]),
389        bound: Bound::Zero,
390    }
391}
392
393impl Preference for PointPull {
394    fn evaluate(&self, reader: &Reader<'_>) -> GeopResult<Constraint> {
395        let at = stepped(reader, &self.path)?
396            .apply(&self.local.map(D::cst))
397            .sub(&self.target.map(D::cst));
398        let mut rows = Rows::default();
399        for r in at.to_array() {
400            rows.push(r.v, r.d.to_vec());
401        }
402        let resist = Scalar::from_ratio(TURN_RESISTANCE.0, TURN_RESISTANCE.1)?;
403        let stay = (reader.retrieve_state(&self.state)).stay(&self.start, self.scale)?;
404        // Its turn only: the rows after the position's.
405        for (value, row) in stay.values.into_iter().zip(stay.jacobian).skip(3) {
406            rows.push(
407                value.mul(resist),
408                row.into_iter().map(|d| d.mul(resist)).collect(),
409            );
410        }
411        Ok(preference(&self.state, rows))
412    }
413}
414
415/// A placed part pulled to a pose in the world as a whole.
416struct PosePull {
417    path: String,
418    state: String,
419    target: Pose,
420    scale: Scalar,
421}
422
423impl Preference for PosePull {
424    /// How far it is from the pose, as [`Variable::stay`] measures it: of
425    /// the pose in the world, whose step is that of the part's pose state.
426    fn evaluate(&self, reader: &Reader<'_>) -> GeopResult<Constraint> {
427        let world = StateValue::Pose(locate(reader, &self.path)?.world);
428        let rows = world.stay(&StateValue::Pose(self.target), self.scale)?;
429        Ok(preference(&self.state, rows))
430    }
431}
432
433/// The largest turn, in degrees, a held angle is driven by in one solve:
434/// a value set half a turn away cannot be reached in one, and a linkage
435/// jumping there could fold over into another of its configurations. How
436/// hard a solve tries, never what its answer means.
437const TURN_STEP: f64 = 30.0;
438
439/// The pose state that puts the placed part `located` at `world`: relative
440/// to the part it is placed in.
441fn local_at(located: &Located, world: &Pose) -> Pose {
442    let parent = located.world.compose(&located.local.inverse());
443    parent.inverse().compose(world).map(Scalar::sharpen)
444}
445
446/// The state the solve found, and its report: the limits by the values
447/// they read.
448fn report(reader: &Reader, solved: solve::Solved) -> GeopResult<(StateValues, Report)> {
449    let made = match solved.report.at_limit.is_empty() {
450        true => Constraints::default(),
451        false => Constraints::of(reader)?,
452    };
453    let mut at_limit: Vec<String> = (solved.report.at_limit.iter())
454        .filter_map(|name| made.get(name.as_str()))
455        .flat_map(|c| c.jacobian.keys().cloned())
456        .collect();
457    at_limit.sort();
458    at_limit.dedup();
459    let report = Report {
460        converged: solved.report.converged,
461        failed: solved.report.failed.clone(),
462        at_limit,
463        iterations: solved.report.iterations,
464        phases: (solved.report.phases.iter())
465            .map(|p: &Phase| (p.stop, p.iterations, p.max_residual.to_f64()))
466            .collect(),
467        unproven: (solved.enclosure.as_ref()).map_or_else(Vec::new, |e| e.unproven.clone()),
468        enclosed: solved.enclosure.map(|e| e.state),
469        evaluations: solved.report.evaluations,
470        largest: solved.report.largest,
471        work: solved.report.work,
472        structure: solved.structure,
473    };
474    Ok((solved.state, report))
475}
476
477/// The values of `solved` that `state` does not have, or has otherwise.
478fn changes(state: &StateValues, solved: StateValues) -> StateValues {
479    (solved.into_iter())
480        .filter(|(name, value)| state.get(name) != Some(value))
481        .collect()
482}
483
484/// Where the state has to be for every constraint of the program `rerun`
485/// runs to hold, as little away from where `state` has it as the
486/// constraints allow, after `pulls` pulled it — of the placed parts, only
487/// the one the step `only` placed moving, if given — every value within its
488/// limits and those `pulls` set held there: the values of the state that
489/// changed — the poses of the parts it moved, each relative to the part it
490/// is placed in, and the unknowns the steps declared — and how the solve
491/// went.
492///
493/// The program was last run with `state`; it is left so, for the caller to
494/// run at the state found. A part no solve may move — fixed, or not `only` — goes where its
495/// pulls take it, alone: to a pose pulled to, or moved as far as a point
496/// pulled goes.
497///
498/// A true solution near there is enclosed only where `prove` asks: an
499/// event of a drag is solved again at the next, and only the state it is
500/// let go at is kept (see [`solve::solve`]). It is solved in the groups
501/// `known` — those a solve of the same program found a hair away, the
502/// last event's of a drag ([`Report::structure`]) — where given, rather
503/// than finding them again.
504pub fn solve(
505    rerun: &mut Rerun,
506    state: &StateValues,
507    pulls: &[Pull],
508    only: Option<&str>,
509    prove: bool,
510    known: Option<&Structure>,
511) -> GeopResult<(StateValues, Report)> {
512    let set: BTreeMap<String, Scalar> = (pulls.iter())
513        .filter_map(|pull| match pull {
514            Pull::Value { parameter, value } => Some((parameter.clone(), *value)),
515            Pull::Point { .. } | Pull::Pose { .. } => None,
516        })
517        .collect();
518    let held: BTreeSet<String> = set.keys().cloned().collect();
519    let only = only.map(pose_state);
520    let (free, had, mut start, scale, preferences, turns) = {
521        let reader = rerun.reader();
522        let turns: BTreeSet<String> = (all_settings(&reader).into_iter())
523            .flat_map(|s| s.values.iter())
524            .filter(|s| s.measure == Measure::Angle)
525            .map(|s| s.state.clone())
526            .collect();
527        let free = freed(&reader, only.as_deref(), &held)?;
528        // What the run has: a value seeded where the run had it is no
529        // change.
530        let had = seeded(&reader, state, &free, false)?;
531        let mut start = seeded(&reader, &had, &free, true)?;
532        let scale = scale_of(&reader)?;
533        let placed = placed_parts(&reader)?;
534        let mut preferences: Vec<Box<dyn Preference>> = Vec::new();
535        for pull in pulls {
536            let name = match pull {
537                Pull::Point { parameter, .. } | Pull::Pose { parameter, .. } => parameter,
538                Pull::Value { .. } => continue,
539            };
540            let located = (placed.iter())
541                .find(|p| p.located.state.as_ref() == Some(name))
542                .ok_or_else(|| GeopError::new(format!("no placed part's pose is {name:?}")))?;
543            let path = located.name.clone();
544            let located = &located.located;
545            match (pull, free.contains_key(name)) {
546                (Pull::Point { local, target, .. }, true) => {
547                    preferences.push(Box::new(PointPull {
548                        path,
549                        state: name.clone(),
550                        local: *local,
551                        target: *target,
552                        start: StateValue::Pose(located.local),
553                        scale,
554                    }))
555                }
556                (Pull::Pose { target, .. }, true) => preferences.push(Box::new(PosePull {
557                    path,
558                    state: name.clone(),
559                    target: *target,
560                    scale,
561                })),
562                // Alone: a point pulled moves the part along, unturned, as a
563                // pull resisting turning moves a part nothing else holds.
564                (Pull::Point { local, target, .. }, false) => {
565                    let off = target.sub(&located.world.apply(local));
566                    let world = located
567                        .world
568                        .with_position(located.world.position().add(&off));
569                    start.insert(name.clone(), StateValue::Pose(local_at(located, &world)));
570                }
571                (Pull::Pose { target, .. }, false) => {
572                    start.insert(name.clone(), StateValue::Pose(local_at(located, target)));
573                }
574                (Pull::Value { .. }, _) => {}
575            }
576        }
577        (free, had, start, scale, preferences, turns)
578    };
579    fn problem<'p>(
580        free: &BTreeMap<String, Mobility>,
581        preferences: Vec<&'p dyn Preference>,
582        scale: Scalar,
583        structure: Option<&'p Structure>,
584    ) -> Problem<'p> {
585        Problem {
586            free: free.clone(),
587            preferences,
588            scale,
589            structure,
590        }
591    }
592    // A held angle driven there in stages, the rest following each. Where
593    // each stage lies is a free choice — any values between serve — so the
594    // stages are equal fractions of the way, as few as keep every angle's
595    // move per stage within `TURN_STEP`. The last stage, at the values
596    // themselves, is solved below, with the pulls.
597    let (driven, ranges): (Vec<String>, Vec<(f64, f64)>) = (set.iter())
598        .filter(|(name, _)| turns.contains(*name))
599        .filter_map(|(name, to)| match start.get(name) {
600            Some(StateValue::Number(from)) => Some((name.clone(), (from.to_f64(), to.to_f64()))),
601            _ => None,
602        })
603        .unzip();
604    let stages = (ranges.iter())
605        .map(|(from, to)| ((to - from).abs() / TURN_STEP).ceil() as usize)
606        .max()
607        .unwrap_or(0);
608    // Each stage a hair from the one before: solved in its groups.
609    let mut known = known.cloned();
610    for k in 1..stages {
611        for (name, (from, to)) in driven.iter().zip(&ranges) {
612            let at = from + (to - from) * k as f64 / stages as f64;
613            start.insert(name.clone(), StateValue::Number(Scalar::from_f64(at)));
614        }
615        let staged = problem(&free, Vec::new(), scale, known.as_ref());
616        let solved = solve::solve(run(rerun), &start, &staged, false)?;
617        start = solved.state;
618        known = Some(solved.structure);
619    }
620    for (name, value) in &set {
621        start.insert(name.clone(), StateValue::Number(*value));
622    }
623    let preferences = preferences.iter().map(|p| &**p).collect();
624    let solved = solve::solve(
625        run(rerun),
626        &start,
627        &problem(&free, preferences, scale, known.as_ref()),
628        prove,
629    )?;
630    let (solved, report) = report(&rerun.reader(), solved)?;
631    Ok((changes(&had, solved), report))
632}
633
634/// Solves the state with the values `set` held there — a joint's
635/// coordinates typed in: the parts move to them as a mechanism's would —
636/// every other value a user sets keeping where it is, if the constraints
637/// let it, as along a serial chain; where they do not, round a closed loop,
638/// the others give way.
639pub fn set(
640    rerun: &mut Rerun,
641    state: &StateValues,
642    set: &BTreeMap<String, Scalar>,
643) -> GeopResult<(StateValues, Report)> {
644    let value = |(name, value): (&String, &Scalar)| Pull::Value {
645        parameter: name.clone(),
646        value: *value,
647    };
648    let mut every: BTreeMap<String, Scalar> = BTreeMap::new();
649    {
650        let reader = rerun.reader();
651        let start = seeded(
652            &reader,
653            state,
654            &freed(&reader, None, &set.keys().cloned().collect())?,
655            true,
656        )?;
657        for settings in all_settings(&reader) {
658            for s in &settings.values {
659                if let Some(StateValue::Number(v)) = start.get(&s.state) {
660                    every.insert(s.state.clone(), *v);
661                }
662            }
663        }
664    }
665    every.extend(set.iter().map(|(p, v)| (p.clone(), *v)));
666    let pulls: Vec<Pull> = every.iter().map(value).collect();
667    let (moved, report) = solve(rerun, state, &pulls, None, true, None)?;
668    if report.converged {
669        return Ok((moved, report));
670    }
671    let pulls: Vec<Pull> = set.iter().map(value).collect();
672    solve(rerun, state, &pulls, None, true, None)
673}
674
675/// The problem of every value a solve may move in the program `rerun`
676/// runs, from `state`, but those `held` — and that state, seeded.
677fn every_value(
678    rerun: &Rerun,
679    state: &StateValues,
680    held: &BTreeSet<String>,
681) -> GeopResult<(StateValues, Problem<'static>)> {
682    let reader = rerun.reader();
683    let free = freed(&reader, None, held)?;
684    let start = seeded(&reader, state, &free, true)?;
685    let problem = Problem {
686        free,
687        preferences: Vec::new(),
688        scale: scale_of(&reader)?,
689        structure: None,
690    };
691    Ok((start, problem))
692}
693
694/// How free every placed part of the program `rerun` runs is where `state`
695/// has it, the values `held` held where they are — a joint's coordinates,
696/// to ask what moves without its joints: to first order, as the
697/// constraints leave it, decided over the box an enclosure of the solution
698/// proves (see [`solve::freedom`]). The program was last run with `state`.
699pub fn freedom(
700    rerun: &mut Rerun,
701    state: &StateValues,
702    held: &BTreeSet<String>,
703) -> GeopResult<Freedom> {
704    let (start, problem) = every_value(rerun, state, held)?;
705    let freedom = solve::freedom(run(rerun), &start, &problem)?;
706    let placed = placed_parts(&rerun.reader())?;
707    let states: BTreeMap<&str, &str> = (placed.iter())
708        .filter_map(|p| Some((p.name.as_str(), p.located.state.as_deref()?)))
709        .collect();
710    // A part fixed in a sub-assembly moves as the sub-assembly does: the
711    // first pose round it the solve frees, from the part outwards.
712    let parts = (placed.iter())
713        .map(|placed| {
714            let mut path = placed.name.as_str();
715            let dof = loop {
716                let free = (states.get(path)).and_then(|s| freedom.states.get(*s));
717                if let Some(&dof) = free {
718                    break dof;
719                }
720                match path.rsplit_once(INSTANCE_SEPARATOR) {
721                    Some((outer, _)) => path = outer,
722                    None => break 0,
723                }
724            };
725            (placed.name.clone(), dof)
726        })
727        .collect();
728    Ok(Freedom {
729        parts,
730        total: freedom.total,
731    })
732}
733
734/// A smallest set of the constraints of the program `rerun` runs, last run
735/// with `state`, that cannot all hold together, by name — none where they
736/// all hold after a solve (see [`solve::conflicting`]).
737pub fn conflicting(rerun: &mut Rerun, state: &StateValues) -> GeopResult<Vec<String>> {
738    let (start, problem) = every_value(rerun, state, &BTreeSet::new())?;
739    solve::conflicting(run(rerun), &start, &problem)
740}
741
742/// The values of the state a solve sets: the unknowns the steps declare —
743/// the poses of the placed parts, and of those in the parts placed in them
744/// — what a program's file keeps of its state.
745pub fn solved_states(reader: &Reader) -> GeopResult<Vec<String>> {
746    Ok((Unknowns::of(reader)?.iter())
747        .map(|u| u.state.clone())
748        .collect())
749}