1use 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
44use 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
52fn 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#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
64pub enum Measure {
65 #[serde(rename = "angle")]
67 Angle,
68 #[serde(rename = "distance")]
70 Distance,
71}
72
73#[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#[derive(Clone, Debug, PartialEq)]
87pub struct Settings {
88 pub name: String,
90 pub label: String,
93 pub kind: &'static str,
95 pub between: Vec<String>,
97 pub values: Vec<Setting>,
98}
99
100const SETTINGS: &str = "settings:";
102
103pub fn settings_of(of: &str) -> TargetReference<Settings> {
105 TargetReference::new(format!("{SETTINGS}{of}"))
106}
107
108impl Target for Settings {}
110
111impl 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
128fn 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#[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 pub value: f64,
149 pub min: Option<f64>,
150 pub max: Option<f64>,
151}
152
153#[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
163pub 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#[derive(Clone, Debug, PartialEq)]
192pub struct Report {
193 pub converged: bool,
194 pub failed: Vec<String>,
196 pub at_limit: Vec<String>,
199 pub iterations: usize,
203 pub phases: Vec<(Stop, usize, f64)>,
204 pub enclosed: Option<StateValues>,
209 pub unproven: Vec<(Vec<String>, String)>,
213 pub evaluations: [usize; 2],
216 pub largest: usize,
220 pub work: u64,
221 pub structure: Structure,
224}
225
226#[derive(Clone, Debug, Default, PartialEq, Serialize)]
228pub struct Freedom {
229 pub parts: BTreeMap<String, usize>,
232 pub total: usize,
234}
235
236fn 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
245pub 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
259pub 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
270pub 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
292fn 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
313fn 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
323fn 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
355pub type D = Dual<6>;
358
359struct PointPull {
363 path: String,
364 state: String,
365 local: Vector3,
366 target: Vector3,
367 start: StateValue,
369 scale: Scalar,
370}
371
372const TURN_RESISTANCE: (i64, i64) = (1, 100);
374
375fn 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
384fn 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 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
415struct PosePull {
417 path: String,
418 state: String,
419 target: Pose,
420 scale: Scalar,
421}
422
423impl Preference for PosePull {
424 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
433const TURN_STEP: f64 = 30.0;
438
439fn 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
446fn 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
477fn changes(state: &StateValues, solved: StateValues) -> StateValues {
479 (solved.into_iter())
480 .filter(|(name, value)| state.get(name) != Some(value))
481 .collect()
482}
483
484pub 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 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 (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 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 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
634pub 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
675fn 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
694pub 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 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
734pub 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
742pub fn solved_states(reader: &Reader) -> GeopResult<Vec<String>> {
746 Ok((Unknowns::of(reader)?.iter())
747 .map(|u| u.state.clone())
748 .collect())
749}