1use crate::{
24 geometry::{Arc, line_distance},
25 profile::basis_at,
26 sketch::{Constraint, ConstraintId, CurveId, CurveKind, Enclosure, Point, PointId, Sketch},
27 variables::{PointLayout, Residuals, SketchPoint, Standing, Variables},
28};
29use geop_core_math::solvers::system::{self, Phase};
30use geop_core_math::{
31 geop_error::{GeopError, GeopResult},
32 scalars::{Number, Scalar, as_f64},
33 vector::Vector2,
34};
35
36const MAX_LOCAL_VARS: usize = 17;
41
42type Dual = geop_core_math::dual::Dual<MAX_LOCAL_VARS>;
45
46use serde::{Deserialize, Serialize};
47use std::collections::BTreeMap;
48
49struct Layout {
52 points: PointLayout<2>,
53 curve_var: BTreeMap<CurveId, usize>,
55}
56
57impl SketchPoint<2> for Point {
58 fn fixed(&self) -> bool {
59 self.fixed
60 }
61
62 fn coordinates(&self) -> [Scalar; 2] {
63 [self.x, self.y]
64 }
65
66 fn set_coordinates(&mut self, [x, y]: [Scalar; 2]) {
67 (self.x, self.y) = (x, y);
68 }
69}
70
71impl Layout {
72 fn new(sketch: &Sketch) -> Self {
73 let mut points = PointLayout::new(&sketch.points, &sketch.point_classes());
74 let curve_var = sketch
75 .curves
76 .iter()
77 .filter(|(_, c)| matches!(c.kind, CurveKind::Arc { .. } | CurveKind::Circle { .. }))
78 .map(|(&id, c)| (id, points.push(!c.fixed)))
79 .collect();
80 Layout { points, curve_var }
81 }
82
83 fn read(&self, sketch: &Sketch) -> GeopResult<Vec<Scalar>> {
87 let mut x = vec![Scalar::ZERO; self.points.len()];
88 self.points.read(&sketch.points, &mut x);
89 for (id, c) in &sketch.curves {
90 match c.kind {
91 CurveKind::Arc { sweep, .. } => {
92 x[self.curve_var[id]] = sweep.div(Scalar::TWO)?.sharpen()
93 }
94 CurveKind::Circle { radius, .. } => x[self.curve_var[id]] = radius,
95 _ => {}
96 }
97 }
98 Ok(x)
99 }
100
101 fn write(&self, sketch: &mut Sketch, x: &[Scalar]) {
104 self.points.write(&mut sketch.points, x);
105 for (id, c) in sketch.curves.iter_mut().filter(|(_, c)| !c.fixed) {
106 match &mut c.kind {
107 CurveKind::Arc { sweep, .. } => {
108 *sweep = Scalar::TWO.mul(x[self.curve_var[id]]).sharpen()
109 }
110 CurveKind::Circle { radius, .. } => *radius = x[self.curve_var[id]].abs().sharpen(),
111 _ => {}
112 }
113 }
114 }
115
116 fn curve_vars(&self, sketch: &Sketch, c: CurveId, out: &mut Vec<usize>) {
118 for p in sketch.curves[&c].points() {
119 self.points.point_vars(p, out);
120 }
121 out.extend(self.curve_var.get(&c));
122 }
123}
124
125struct Geo<'a, T> {
128 sketch: &'a Sketch,
129 layout: &'a Layout,
130 seeded: &'a [(usize, T)],
131}
132
133impl<T: Number> Geo<'_, T> {
134 fn var(&self, i: usize) -> T {
135 self.seeded
136 .iter()
137 .find(|(j, _)| *j == i)
138 .map(|(_, t)| *t)
139 .expect("every variable a constraint reads is seeded")
140 }
141 fn point(&self, p: PointId) -> Vector2<T> {
142 let i = self.layout.points.point_var[&p];
143 Vector2::from_array([self.var(i), self.var(i + 1)])
144 }
145 fn line(&self, c: CurveId) -> (Vector2<T>, Vector2<T>) {
146 match &self.sketch.curves[&c].kind {
147 CurveKind::Line { start, end } => (self.point(*start), self.point(*end)),
148 k => unreachable!("validated to be a line: {k:?}"),
149 }
150 }
151 fn arc(&self, c: CurveId) -> Option<Arc<T>> {
152 match &self.sketch.curves[&c].kind {
153 CurveKind::Arc { start, end, .. } => Some(Arc {
154 s: self.point(*start),
155 e: self.point(*end),
156 half: self.var(self.layout.curve_var[&c]),
157 }),
158 _ => None,
159 }
160 }
161 fn round(&self, c: CurveId) -> GeopResult<(Vector2<T>, T)> {
163 Ok(match &self.sketch.curves[&c].kind {
164 CurveKind::Circle { center, .. } => (
165 self.point(*center),
166 self.var(self.layout.curve_var[&c]).abs(),
167 ),
168 CurveKind::Arc { .. } => {
169 let a = self.arc(c).unwrap();
170 (a.center()?, a.radius()?)
171 }
172 k => unreachable!("validated to be a circle or arc: {k:?}"),
173 })
174 }
175 fn end_tangent(&self, c: CurveId, at_end: bool) -> GeopResult<Vector2<T>> {
178 Ok(match &self.sketch.curves[&c].kind {
179 CurveKind::Line { .. } => {
180 let (s, e) = self.line(c);
181 e.sub(&s).normalize()?
182 }
183 CurveKind::Arc { .. } => {
184 let a = self.arc(c).unwrap();
185 if at_end {
186 a.tangent_end()?
187 } else {
188 a.tangent_start()?
189 }
190 }
191 CurveKind::Spline { control_points, .. } => {
192 let n = control_points.len();
193 let (p, q) = if at_end {
194 (control_points[n - 2], control_points[n - 1])
195 } else {
196 (control_points[0], control_points[1])
197 };
198 self.point(q).sub(&self.point(p)).normalize()?
199 }
200 k => unreachable!("validated to be an open curve: {k:?}"),
201 })
202 }
203}
204
205#[derive(Clone, Copy, Debug)]
208enum TangentMode {
209 Endpoint { a_end: bool, b_end: bool },
211 LineRound { line: CurveId, round: CurveId },
213 RoundRound { internal: bool },
217}
218
219struct Prepared<'a> {
221 id: ConstraintId,
222 constraint: &'a Constraint,
223 vars: Vec<usize>,
224 tangent: Option<TangentMode>,
225 offset: Option<OffsetBasis>,
228}
229
230struct OffsetBasis {
233 a: Vec<(PointId, Scalar, Scalar)>,
234 b: Vec<(PointId, Scalar, Scalar)>,
235}
236
237fn spline_basis(
240 sketch: &Sketch,
241 curve: CurveId,
242 t: Scalar,
243 derivative: bool,
244) -> GeopResult<Vec<(PointId, Scalar, Scalar)>> {
245 let CurveKind::Spline {
246 control_points,
247 shape,
248 } = &sketch.curve(curve)?.kind
249 else {
250 return Err(GeopError::new(format!("curve {curve} is no spline")));
251 };
252 Ok(
253 basis_at(control_points.len(), shape.as_ref(), t, derivative)?
254 .into_iter()
255 .map(|(j, value, slope)| (control_points[j], value, slope))
256 .collect(),
257 )
258}
259
260fn both_fixed(sketch: &Sketch, a: PointId, b: PointId) -> bool {
264 sketch.points[&a].fixed && sketch.points[&b].fixed
265}
266
267struct Problem<'a> {
269 sketch: &'a Sketch,
270 layout: Layout,
271 constraints: Vec<Prepared<'a>>,
272 scale: Scalar,
274}
275
276impl<'a> Problem<'a> {
277 fn new(sketch: &'a Sketch) -> GeopResult<Self> {
278 sketch.validate()?;
279 let layout = Layout::new(sketch);
280 let x0 = layout.read(sketch)?;
281
282 let span = |k: usize| {
285 let values = || sketch.points.values().map(move |p| p.xy()[k]);
286 let hi = values().reduce(Scalar::max).unwrap_or(Scalar::ZERO);
287 let lo = values().reduce(Scalar::min).unwrap_or(Scalar::ZERO);
288 hi.sub(lo)
289 };
290 let diagonal = span(0).mul(span(0)).add(span(1).mul(span(1))).sqrt()?;
291 let scale = sketch
292 .curves
293 .values()
294 .filter_map(|c| match c.kind {
295 CurveKind::Circle { radius, .. } => Some(Scalar::TWO.mul(radius.abs())),
296 _ => None,
297 })
298 .fold(diagonal, Scalar::max);
299 let scale = if scale.is_finite() && scale.definitely_greater(Scalar::ZERO) {
300 scale
301 } else {
302 Scalar::ONE
303 };
304
305 let mut constraints = Vec::new();
306 for (&id, c) in &sketch.constraints {
307 use Constraint::*;
308 let mut vars = Vec::new();
309 let mut offset_basis = None;
310 let pt = |p: &PointId, vars: &mut Vec<usize>| layout.points.point_vars(*p, vars);
311 match c {
312 Coincident { a, b } if both_fixed(sketch, *a, *b) => {}
315 Coincident { .. } => continue,
317 Fix { point, .. } => pt(point, &mut vars),
318 Distance { a, b, .. } | DistanceX { a, b, .. } | DistanceY { a, b, .. } => {
319 pt(a, &mut vars);
320 pt(b, &mut vars);
321 }
322 PointOnCurve { point, curve }
323 | Midpoint { point, curve }
324 | Center { point, curve }
325 | PointLineDistance {
326 point, line: curve, ..
327 } => {
328 pt(point, &mut vars);
329 layout.curve_vars(sketch, *curve, &mut vars);
330 }
331 Symmetric { a, b, line } | Moved { a, b, by: line } => {
332 pt(a, &mut vars);
333 pt(b, &mut vars);
334 layout.curve_vars(sketch, *line, &mut vars);
335 }
336 EqualSweep { a, b } => vars.extend([layout.curve_var[a], layout.curve_var[b]]),
338 Horizontal { line: curve }
339 | Vertical { line: curve }
340 | Length { curve, .. }
341 | Radius { curve, .. }
342 | Diameter { curve, .. } => layout.curve_vars(sketch, *curve, &mut vars),
343 Parallel { a, b }
344 | Perpendicular { a, b }
345 | Collinear { a, b }
346 | Tangent { a, b }
347 | Equal { a, b }
348 | Concentric { a, b }
349 | Offset { a, b, .. }
350 | Angle { a, b, .. } => {
351 layout.curve_vars(sketch, *a, &mut vars);
352 layout.curve_vars(sketch, *b, &mut vars);
353 }
354 OffsetAt {
357 a, at, b, on, by, ..
358 } => {
359 let basis = OffsetBasis {
360 a: spline_basis(sketch, *a, *at, true)?,
361 b: spline_basis(sketch, *b, *on, false)?,
362 };
363 for (p, ..) in basis.a.iter().chain(&basis.b) {
364 pt(p, &mut vars);
365 }
366 vars.push(layout.curve_var[by]);
367 offset_basis = Some(basis);
368 }
369 }
370 vars.sort_unstable();
371 vars.dedup();
372 if vars.len() > MAX_LOCAL_VARS {
373 return Err(GeopError::new(format!(
374 "constraint {c:?} depends on {} variables, more than the supported {MAX_LOCAL_VARS}",
375 vars.len()
376 )));
377 }
378 let tangent = match c {
379 Tangent { a, b } => Some(Self::tangent_mode(sketch, &layout, &x0, *a, *b)?),
380 _ => None,
381 };
382 constraints.push(Prepared {
383 id,
384 constraint: c,
385 vars,
386 tangent,
387 offset: offset_basis,
388 });
389 }
390
391 Ok(Problem {
392 sketch,
393 layout,
394 constraints,
395 scale,
396 })
397 }
398
399 fn tangent_mode(
400 sketch: &Sketch,
401 layout: &Layout,
402 x: &[Scalar],
403 a: CurveId,
404 b: CurveId,
405 ) -> GeopResult<TangentMode> {
406 if let Some((a_end, b_end)) = sketch.shared_endpoint(a, b)? {
407 return Ok(TangentMode::Endpoint { a_end, b_end });
408 }
409 let is_line = |c: CurveId| matches!(sketch.curves[&c].kind, CurveKind::Line { .. });
410 if is_line(a) {
411 return Ok(TangentMode::LineRound { line: a, round: b });
412 }
413 if is_line(b) {
414 return Ok(TangentMode::LineRound { line: b, round: a });
415 }
416 let seeded: Vec<(usize, Scalar)> = x.iter().copied().enumerate().collect();
417 let geo = Geo {
418 sketch,
419 layout,
420 seeded: &seeded,
421 };
422 let ((ca, ra), (cb, rb)) = (geo.round(a)?, geo.round(b)?);
423 let d = ca.sub(&cb).try_norm()?;
424 let internal = d.sub(ra.sub(rb).abs()).abs();
425 let external = d.sub(ra.add(rb)).abs();
426 Ok(TangentMode::RoundRound {
427 internal: internal.definitely_less(external),
428 })
429 }
430
431 fn residuals(
432 &self,
433 p: &Prepared<'_>,
434 geo: &Geo<'_, Dual>,
435 out: &mut Vec<Dual>,
436 ) -> GeopResult<()> {
437 use Constraint::*;
438 let c = Dual::cst;
439 let scale = c(self.scale);
440 let half = c(Scalar::ONE.div(Scalar::TWO)?);
441 match *p.constraint {
442 Coincident { a, b } => {
443 let (pa, pb) = (&self.sketch.points[&a], &self.sketch.points[&b]);
444 out.extend([c(pb.x.sub(pa.x)), c(pb.y.sub(pa.y))]);
445 }
446 Fix { point, x, y } => {
447 let q = geo.point(point);
448 out.extend([q[0].sub(c(x)), q[1].sub(c(y))]);
449 }
450 Distance { a, b, value } => {
451 out.push(geo.point(b).sub(&geo.point(a)).try_norm()?.sub(c(value)))
452 }
453 DistanceX { a, b, value } => {
454 out.push(geo.point(b)[0].sub(geo.point(a)[0]).sub(c(value)))
455 }
456 DistanceY { a, b, value } => {
457 out.push(geo.point(b)[1].sub(geo.point(a)[1]).sub(c(value)))
458 }
459 PointOnCurve { point, curve } => {
460 let q = geo.point(point);
461 out.push(match &self.sketch.curves[&curve].kind {
462 CurveKind::Line { .. } => {
463 let (s, e) = geo.line(curve);
464 line_distance(s, e, q)?
465 }
466 CurveKind::Arc { .. } => geo.arc(curve).unwrap().circle_residual(q)?,
467 _ => {
468 let (c, r) = geo.round(curve)?;
469 q.sub(&c).try_norm()?.sub(r)
470 }
471 });
472 }
473 Midpoint { point, curve } => {
474 let q = geo.point(point);
475 let m = match geo.arc(curve) {
476 Some(a) => a.arc_mid()?,
477 None => {
478 let (s, e) = geo.line(curve);
479 s.add(&e).prod_scalar(half)
480 }
481 };
482 out.extend([q[0].sub(m[0]), q[1].sub(m[1])]);
483 }
484 Center { point, curve } => {
485 let (q, center) = (geo.point(point), geo.round(curve)?.0);
486 out.extend([q[0].sub(center[0]), q[1].sub(center[1])]);
487 }
488 Symmetric { a, b, line } => {
489 let (pa, pb) = (geo.point(a), geo.point(b));
490 let (s, e) = geo.line(line);
491 let mid = pa.add(&pb).prod_scalar(half);
492 out.push(line_distance(s, e, mid)?);
493 out.push(pb.sub(&pa).prod_dot(&e.sub(&s).normalize()?));
494 }
495 Moved { a, b, by } => {
496 let from = geo.point(a);
501 let image = match geo.arc(by) {
502 Some(arc) => {
503 let turn = arc.half.mul(c(Scalar::TWO));
504 arc.e.add(&from.sub(&arc.s).rotate(turn.cos(), turn.sin()))
505 }
506 None => {
507 let (s, e) = geo.line(by);
508 e.add(&from.sub(&s))
509 }
510 };
511 let to = geo.point(b);
512 out.extend([to[0].sub(image[0]), to[1].sub(image[1])]);
513 }
514 EqualSweep { a, b } => {
515 let half = |k: CurveId| geo.var(self.layout.curve_var[&k]);
516 out.push(half(b).sub(half(a)).mul(scale));
517 }
518 OffsetAt { by, across, .. } => {
519 let basis = p.offset.as_ref().expect("prepared with its basis");
520 let sum = |terms: &[(PointId, Scalar, Scalar)], slope: bool| {
521 terms.iter().fold(
522 Vector2::from_array([c(Scalar::ZERO); 2]),
523 |acc, &(q, v, d)| {
524 acc.add(&geo.point(q).prod_scalar(c(if slope { d } else { v })))
525 },
526 )
527 };
528 let tangent = sum(&basis.a, true).normalize()?;
529 let normal = Vector2::from_array([tangent[1].neg(), tangent[0]]);
530 let off = sum(&basis.b, false).sub(&sum(&basis.a, false));
531 if !across {
532 out.push(off.prod_dot(&tangent));
533 }
534 let radius = geo.var(self.layout.curve_var[&by]).abs();
535 out.push(off.prod_dot(&normal).abs().sub(radius));
536 }
537 Offset { a, b, value } => match self.sketch.curves[&a].kind {
538 CurveKind::Line { .. } => {
539 let (sa, ea) = geo.line(a);
542 let (sb, eb) = geo.line(b);
543 let (ds, de) = (line_distance(sa, ea, sb)?, line_distance(sa, ea, eb)?);
544 out.push(ds.sub(de));
545 out.push(ds.abs().sub(c(value)));
546 }
547 _ => {
548 let ((ca, ra), (cb, rb)) = (geo.round(a)?, geo.round(b)?);
549 out.extend([ca[0].sub(cb[0]), ca[1].sub(cb[1])]);
550 out.push(rb.sub(ra).abs().sub(c(value)));
551 }
552 },
553 PointLineDistance { point, line, value } => {
554 let (s, e) = geo.line(line);
555 out.push(line_distance(s, e, geo.point(point))?.abs().sub(c(value)));
556 }
557 Horizontal { line } => {
558 let (s, e) = geo.line(line);
559 out.push(e[1].sub(s[1]));
560 }
561 Vertical { line } => {
562 let (s, e) = geo.line(line);
563 out.push(e[0].sub(s[0]));
564 }
565 Parallel { a, b } | Perpendicular { a, b } | Angle { a, b, .. } => {
566 let (sa, ea) = geo.line(a);
567 let (sb, eb) = geo.line(b);
568 let (ua, ub) = (ea.sub(&sa).normalize()?, eb.sub(&sb).normalize()?);
569 let (cross, dot) = (ua.prod_cross(&ub), ua.prod_dot(&ub));
570 let r = match *p.constraint {
572 Parallel { .. } => cross,
573 Perpendicular { .. } => dot,
574 Angle { value, .. } => cross.mul(c(value.cos())).sub(dot.mul(c(value.sin()))),
575 _ => unreachable!(),
576 };
577 out.push(r.mul(scale));
578 }
579 Collinear { a, b } => {
580 let (sa, ea) = geo.line(a);
581 let (sb, eb) = geo.line(b);
582 out.push(line_distance(sa, ea, sb)?);
583 out.push(line_distance(sa, ea, eb)?);
584 }
585 Tangent { a, b } => match p.tangent.unwrap() {
586 TangentMode::Endpoint { a_end, b_end } => {
587 let (ta, tb) = (geo.end_tangent(a, a_end)?, geo.end_tangent(b, b_end)?);
588 out.push(ta.prod_cross(&tb).mul(scale));
589 }
590 TangentMode::LineRound { line, round } => {
591 let (s, e) = geo.line(line);
592 let (c, r) = geo.round(round)?;
593 out.push(line_distance(s, e, c)?.abs().sub(r));
594 }
595 TangentMode::RoundRound { internal } => {
596 let ((ca, ra), (cb, rb)) = (geo.round(a)?, geo.round(b)?);
597 let d = ca.sub(&cb).try_norm()?;
598 out.push(if internal {
599 d.sub(ra.sub(rb).abs())
600 } else {
601 d.sub(ra.add(rb))
602 });
603 }
604 },
605 Equal { a, b } => {
606 let size = |c: CurveId| -> GeopResult<Dual> {
607 Ok(match &self.sketch.curves[&c].kind {
608 CurveKind::Line { .. } => {
609 let (s, e) = geo.line(c);
610 e.sub(&s).try_norm()?
611 }
612 _ => geo.round(c)?.1,
613 })
614 };
615 out.push(size(a)?.sub(size(b)?));
616 }
617 Concentric { a, b } => {
618 let (ca, cb) = (geo.round(a)?.0, geo.round(b)?.0);
619 out.extend([ca[0].sub(cb[0]), ca[1].sub(cb[1])]);
620 }
621 Length { curve, value } => {
622 let length = match geo.arc(curve) {
623 Some(a) => a.length()?,
624 None => {
625 let (s, e) = geo.line(curve);
626 e.sub(&s).try_norm()?
627 }
628 };
629 out.push(length.sub(c(value)));
630 }
631 Radius { curve, value } => out.push(match geo.arc(curve) {
632 Some(a) => c(Scalar::TWO.mul(value))
635 .mul(a.half.sin().abs())
636 .sub(a.chord_length()?),
637 None => geo.round(curve)?.1.sub(c(value)),
638 }),
639 Diameter { curve, value } => out.push(match geo.arc(curve) {
640 Some(a) => c(value).mul(a.half.sin().abs()).sub(a.chord_length()?),
642 None => geo.round(curve)?.1.sub(c(value.div(Scalar::TWO)?)),
643 }),
644 }
645 Ok(())
646 }
647}
648
649impl<'a> Residuals<MAX_LOCAL_VARS> for Problem<'a> {
650 fn offsets(&self) -> Vec<Option<usize>> {
651 self.layout.points.offsets()
652 }
653
654 fn constraints(&self) -> usize {
655 self.constraints.len()
656 }
657
658 fn reads(&self, c: usize) -> &[usize] {
659 &self.constraints[c].vars
660 }
661
662 fn residuals(&self, c: usize, values: &[Dual], out: &mut Vec<Dual>) -> GeopResult<()> {
663 let prepared = &self.constraints[c];
664 let seeded: Vec<(usize, Dual)> = prepared
665 .vars
666 .iter()
667 .copied()
668 .zip(values.iter().copied())
669 .collect();
670 let geo = Geo {
671 sketch: self.sketch,
672 layout: &self.layout,
673 seeded: &seeded,
674 };
675 Problem::residuals(self, prepared, &geo, out)
676 }
677
678 fn name(&self, c: usize) -> String {
679 let c = &self.constraints[c];
680 format!("{} {:?}", c.id, c.constraint)
681 }
682
683 fn scale(&self) -> Scalar {
684 self.scale
685 }
686}
687
688type Planar<'p, 'a> = Variables<'p, Problem<'a>, MAX_LOCAL_VARS>;
690
691#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
693pub struct SolveReport {
694 pub converged: bool,
698 #[serde(with = "as_f64")]
701 pub max_residual: Scalar,
702 pub iterations: usize,
703 pub phases: Vec<Phase>,
705 pub dof: usize,
710 pub free_points: BTreeMap<PointId, bool>,
712 pub free_curves: BTreeMap<CurveId, bool>,
715 pub failed_constraints: Vec<ConstraintId>,
718 #[serde(default)]
722 pub unproven: Vec<String>,
723}
724
725impl Sketch {
726 pub fn enclose(&self) -> GeopResult<Enclosure> {
736 let ctx = |e: GeopError| e.with_context("enclosing the sketch's solution");
737 let problem = Problem::new(self).map_err(ctx)?;
738 let params = problem.layout.read(self)?;
739 let variables = Planar::new(&problem, params, Vec::new());
740 let start = variables.start();
741 if !variables.system().held(&start)?.converged {
742 return Ok(Enclosure::as_drawn(self));
743 }
744 let x = variables.enclosed(&start).map_err(ctx)?;
746 let layout = &problem.layout;
747 Ok(Enclosure {
748 points: (layout.points.point_var.keys())
749 .map(|&p| (p, Vector2::from_array(layout.points.point(&x, p))))
750 .collect(),
751 params: self
752 .curves
753 .iter()
754 .filter_map(|(&id, c)| {
755 let v = x[*layout.curve_var.get(&id)?];
756 Some(match c.kind {
757 CurveKind::Arc { .. } => (id, v.mul(Scalar::TWO)),
758 _ => (id, v.abs()),
759 })
760 })
761 .collect(),
762 })
763 }
764
765 pub fn solve(&mut self) -> GeopResult<SolveReport> {
772 self.solve_with_drag(&[])
773 }
774
775 pub fn solve_with_drag(&mut self, drags: &[(PointId, Vector2)]) -> GeopResult<SolveReport> {
778 let problem = Problem::new(self)?;
779 let start = problem.layout.read(self)?;
780 let (variables, solved, x) =
781 Planar::dragged(&problem, start, &problem.layout.points.point_var, drags)?;
782 let report = variables.report(&solved.values, solved.phases, solved.enclosure)?;
783 let x: Vec<Scalar> = x.into_iter().map(Scalar::sharpen).collect();
786 problem.layout.write(self, &x);
787 Ok(report)
788 }
789}
790
791impl Planar<'_, '_> {
792 fn report(
795 &self,
796 blocks: &[Scalar],
797 phases: Vec<Phase>,
798 solved: system::Enclosure<Scalar>,
799 ) -> GeopResult<SolveReport> {
800 let Standing {
801 held,
802 dof,
803 free: free_vars,
804 unproven,
805 } = self.standing(blocks, Some(solved))?;
806 let problem = self.problem;
807 let failed_constraints = (held.failed.iter())
808 .map(|&i| problem.constraints[i].id)
809 .collect::<Vec<_>>();
810 let points = &problem.layout.points;
811 let free_points = (points.point_var.keys())
812 .map(|&p| (p, points.point(&free_vars, p).contains(&true)))
813 .collect();
814 let free_curves = problem
815 .sketch
816 .curves
817 .keys()
818 .map(|&c| {
819 let mut vars = Vec::new();
820 problem.layout.curve_vars(problem.sketch, c, &mut vars);
821 (c, vars.iter().any(|&i| free_vars[i]))
822 })
823 .collect();
824 Ok(SolveReport {
825 converged: failed_constraints.is_empty(),
826 max_residual: held.max_residual,
827 iterations: phases.iter().map(|p| p.iterations).sum(),
828 phases,
829 dof,
830 free_points,
831 free_curves,
832 failed_constraints,
833 unproven,
834 })
835 }
836}
837
838#[cfg(test)]
839mod tests {
840 use std::f64::consts::PI;
841
842 use geop_core_math::{
843 scalars::Scalar,
844 solvers::system::{Equations, Term},
845 };
846
847 use super::*;
848
849 fn n(x: f64) -> Scalar {
850 Scalar::from_f64(x)
851 }
852
853 #[test]
858 fn residuals_are_sharp_where_the_points_are() {
859 let mut s = Sketch::new();
860 let a = s.add_point(n(-1.0), n(0.1));
861 let b = s.add_point(n(1.2), n(-0.1));
862 let c = s.add_point(n(0.1), n(1.5));
863 let base = s.add_line(a, b);
864 let left = s.add_line(c, a);
865 let right = s.add_line(c, b);
866 let m = s.add_point(n(0.0), n(0.0));
867 let axis_top = s.add_point(n(0.0), n(2.0));
868 let axis = s.add_line(m, axis_top);
869 s.constrain(Constraint::Midpoint {
870 point: m,
871 curve: base,
872 });
873 s.constrain(Constraint::Symmetric { a, b, line: axis });
874 s.constrain(Constraint::Equal { a: left, b: right });
875 s.constrain(Constraint::Angle {
876 a: left,
877 b: right,
878 value: n(PI / 3.0),
879 });
880 let problem = Problem::new(&s).unwrap();
881 let params = problem.layout.read(&s).unwrap();
882 let variables = Planar::new(&problem, params, Vec::new());
883 let terms: Vec<Term> = (0..problem.constraints.len())
884 .map(Term::Constraint)
885 .collect();
886 for (k, made) in variables
887 .evaluate(&variables.start(), &terms)
888 .iter()
889 .enumerate()
890 {
891 let made = made.as_ref().unwrap();
892 let (v, row) = (&made.values, &made.jacobian);
893 for x in (v.iter()).chain(row.iter().flat_map(|(_, rows)| rows.iter().flatten())) {
894 let width = x.width().to_f64();
895 assert!(
896 width <= 1e-12 * (1.0 + x.to_f64().abs()),
897 "residual {k}: {x:?} in {v:?}, {row:?}"
898 );
899 }
900 }
901 }
902
903 fn rectangle_said_twice(s: &mut Sketch, level: bool) -> Vec<CurveId> {
908 let p = [[0.0, 0.0], [2.05, 0.1], [1.9, 1.07], [-0.08, 0.95]]
909 .map(|q| s.add_point(n(q[0]), n(q[1])));
910 let l: Vec<CurveId> = (0..4).map(|i| s.add_line(p[i], p[(i + 1) % 4])).collect();
911 s.constrain(Constraint::Fix {
912 point: p[0],
913 x: n(0.0),
914 y: n(0.0),
915 });
916 for i in 0..4 {
917 s.constrain(Constraint::Perpendicular {
918 a: l[i],
919 b: l[(i + 1) % 4],
920 });
921 }
922 for i in 0..2 {
923 s.constrain(Constraint::Parallel {
924 a: l[i],
925 b: l[i + 2],
926 });
927 }
928 if level {
929 s.constrain(Constraint::Horizontal { line: l[0] });
930 }
931 for (i, value) in [(0, 2.0), (1, 1.0)] {
932 s.constrain(Constraint::Length {
933 curve: l[i],
934 value: n(value),
935 });
936 }
937 l
938 }
939
940 fn far_corner(s: &Sketch, sides: &[CurveId], e: &Enclosure) -> Vector2 {
943 let (_, end) = s.curves[&sides[1]].endpoints().unwrap();
944 e.points[&end]
945 }
946
947 #[test]
957 fn a_line_closing_on_two_projected_edges_of_one_side_is_proven() {
958 let given = |x: f64| Scalar::new(x - 1e-13, x + 1e-13);
959 let mut s = Sketch::new();
960 let fixed = |s: &mut Sketch, x: f64, y: f64| s.add_fixed_point(given(x), given(y));
961 let upper = {
962 let a = fixed(&mut s, -25.000000000000004, 14.52958965872012);
963 let b = fixed(&mut s, -24.999999999999993, -11.926666666666662);
964 s.add_line(a, b)
965 };
966 let lower = {
967 let a = fixed(&mut s, -24.999999999999993, -62.64416256853966);
968 let b = fixed(&mut s, -25.000000000000007, -11.926666666666666);
969 s.add_line(a, b)
970 };
971 for c in [upper, lower] {
972 let curve = s.curves.get_mut(&c).unwrap();
973 (curve.fixed, curve.construction) = (true, true);
974 }
975 let p24 = s.add_point(n(-24.999999999999993), n(-19.650020988468217));
976 let p26 = s.add_point(n(-13.56352402378605), n(-23.519644932457258));
977 let p28 = s.add_point(n(-13.56352402378605), n(-35.413048566902255));
978 let p36 = s.add_point(n(-24.999999999999996), n(-39.2826725108913));
979 let l27 = s.add_line(p24, p26);
980 let a29 = s.add_arc(p26, p28, n(-2.489060692764724));
981 let l38 = s.add_line(p28, p36);
982 let l40 = s.add_line(p36, p24);
983 for c in [
984 Constraint::PointOnCurve {
985 point: p24,
986 curve: upper,
987 },
988 Constraint::Tangent { a: l27, b: a29 },
989 Constraint::PointOnCurve {
990 point: p36,
991 curve: lower,
992 },
993 Constraint::Tangent { a: a29, b: l38 },
994 Constraint::Vertical { line: l40 },
995 Constraint::Tangent { a: l38, b: a29 },
996 Constraint::DistanceX {
997 a: p26,
998 b: p28,
999 value: n(0.0),
1000 },
1001 ] {
1002 s.constrain(c);
1003 }
1004 let report = s.solve().unwrap();
1005 assert!(report.converged, "{report:?}");
1006 s.enclose().unwrap();
1007 }
1008
1009 #[test]
1014 fn a_rectangle_said_twice_is_proven() {
1015 let mut s = Sketch::new();
1016 let sides = rectangle_said_twice(&mut s, true);
1017 let report = s.solve().unwrap();
1018 assert!(report.converged && report.dof == 0, "{report:?}");
1019 assert!(report.unproven.is_empty(), "{report:?}");
1020 let e = s.enclose().unwrap();
1021 let corner = far_corner(&s, &sides, &e);
1022 assert!(
1023 corner[0].could_be_equal(n(2.0)) && corner[1].could_be_equal(n(1.0)),
1024 "{corner:?}"
1025 );
1026 assert!(corner[0].width().to_f64() < 1e-12, "{corner:?}");
1027
1028 let mut s = Sketch::new();
1029 let sides = rectangle_said_twice(&mut s, false);
1030 let report = s.solve().unwrap();
1031 assert!(report.converged && report.dof == 1, "{report:?}");
1032 let e = s.enclose().unwrap();
1033 let corner = far_corner(&s, &sides, &e);
1034 let diagonal = corner[0].mul(corner[0]).add(corner[1].mul(corner[1]));
1035 assert!(diagonal.could_be_equal(n(5.0)), "{diagonal:?}");
1036 }
1037
1038 #[test]
1042 fn conflicting_lengths_are_named() {
1043 let mut s = Sketch::new();
1044 let sides = rectangle_said_twice(&mut s, true);
1045 let top = s.constrain(Constraint::Length {
1046 curve: sides[2],
1047 value: n(2.0 + 1e-11),
1048 });
1049 let report = s.solve().unwrap();
1050 assert!(report.converged, "{report:?}");
1051 let [why] = &report.unproven[..] else {
1053 panic!("{report:?}");
1054 };
1055 assert!(why.contains("conflict"), "{why}");
1056 let e = s.enclose().unwrap_err();
1057 let message = e.root_message();
1058 let bottom = s
1059 .constraints
1060 .iter()
1061 .find(|(_, c)| matches!(c, Constraint::Length { curve, .. } if *curve == sides[0]))
1062 .map(|(id, _)| *id)
1063 .unwrap();
1064 assert!(message.contains("conflict"), "{message}");
1065 for id in [top, bottom] {
1066 assert!(message.contains(&format!("{id} ")), "{id}: {message}");
1067 }
1068 }
1069}