1use std::collections::HashMap;
2
3use indexmap::IndexMap;
4
5use crate::Analysis;
6use crate::Config;
7use crate::Constraint;
8use crate::ConstraintRequest;
9use crate::FailureOutcome;
10use crate::FreedomAnalysis;
11use crate::IdGenerator;
12use crate::NoAnalysis;
13use crate::SolveOutcome;
14use crate::SolveOutcomeAnalysis;
15use crate::Warning;
16use crate::datatypes;
17use crate::datatypes::AngleKind;
18use crate::datatypes::inputs::DatumCircularArc;
19use crate::datatypes::inputs::DatumDistance;
20use crate::datatypes::inputs::DatumLineSegment;
21use crate::datatypes::inputs::DatumPoint;
22use crate::datatypes::outputs::Arc;
23use crate::datatypes::outputs::{Circle, Component, Point};
24use crate::error::TextualError;
25use crate::textual::Label;
26use crate::textual::geometry_variables::DoneState;
27use crate::textual::geometry_variables::GeometryVariables;
28use crate::textual::geometry_variables::PointsState;
29use crate::textual::geometry_variables::VARS_PER_ARC;
30use crate::textual::instruction::*;
31
32use super::Instruction;
33use super::Problem;
34
35impl Problem {
36 pub fn to_constraint_system(&self) -> Result<ConstraintSystem<'_>, TextualError> {
40 let mut id_generator = IdGenerator::default();
41 let mut initial_guesses: GeometryVariables<PointsState> = Default::default();
44 let mut guessmap_points = HashMap::new();
46 guessmap_points.extend(
47 self.point_guesses
48 .iter()
49 .map(|pg| (pg.point.0.clone(), pg.guess)),
50 );
51 for point in &self.inner_points {
52 let Some(guess) = guessmap_points.remove(&point.0) else {
53 return Err(TextualError::MissingGuess {
54 label: point.0.clone(),
55 });
56 };
57 initial_guesses.push_point(&mut id_generator, guess.x, guess.y);
58 }
59 let mut guessmap_scalars = HashMap::new();
60 guessmap_scalars.extend(
61 self.scalar_guesses
62 .iter()
63 .map(|sg| (sg.scalar.0.clone(), sg.guess)),
64 );
65 let mut initial_guesses = initial_guesses.done();
66 for circle in &self.inner_circles {
67 let center_label = format!("{}.center", circle.0);
70 let Some(center_guess) = guessmap_points.remove(¢er_label) else {
71 return Err(TextualError::MissingGuess {
72 label: center_label,
73 });
74 };
75 let radius_label = format!("{}.radius", circle.0);
77 let Some(radius_guess) = guessmap_scalars.remove(&radius_label) else {
78 return Err(TextualError::MissingGuess {
79 label: radius_label,
80 });
81 };
82 initial_guesses.push_circle(
83 &mut id_generator,
84 center_guess.x,
85 center_guess.y,
86 radius_guess,
87 );
88 }
89 let mut initial_guesses = initial_guesses.done();
90 for arc in &self.inner_arcs {
91 let center_label = format!("{}.center", arc.0);
93 let Some(center_guess) = guessmap_points.remove(¢er_label) else {
94 return Err(TextualError::MissingGuess {
95 label: center_label,
96 });
97 };
98 let a_label = format!("{}.a", arc.0);
99 let Some(a_guess) = guessmap_points.remove(&a_label) else {
100 return Err(TextualError::MissingGuess { label: a_label });
101 };
102 let b_label = format!("{}.b", arc.0);
103 let Some(b_guess) = guessmap_points.remove(&b_label) else {
104 return Err(TextualError::MissingGuess { label: b_label });
105 };
106 initial_guesses.push_arc(&mut id_generator, a_guess, b_guess, center_guess);
107 }
108 if !guessmap_points.is_empty() {
109 let labels: Vec<String> = guessmap_points.keys().cloned().collect();
110 return Err(TextualError::UnusedGuesses { labels });
111 }
112 if !guessmap_scalars.is_empty() {
113 let labels: Vec<String> = guessmap_scalars.keys().cloned().collect();
114 return Err(TextualError::UnusedGuesses { labels });
115 }
116
117 let mut constraints = Vec::new();
120 let datum_point_for_label = |label: &Label| -> Result<DatumPoint, TextualError> {
121 if let Some(point_id) = self.inner_points.iter().position(|p| p == &label.0) {
123 let ids = initial_guesses.point_ids(point_id);
124 return Ok(DatumPoint {
125 x_id: ids.x,
126 y_id: ids.y,
127 });
128 }
129 if let Some(circle_id) = self
131 .inner_circles
132 .iter()
133 .position(|circ| format!("{}.center", circ.0) == label.0.as_str())
134 {
135 let center = initial_guesses.circle_ids(circle_id).center;
136 return Ok(DatumPoint {
137 x_id: center.x,
138 y_id: center.y,
139 });
140 }
141 if let Some(arc_id) = self
144 .inner_arcs
145 .iter()
146 .position(|arc| format!("{}.center", arc.0) == label.0.as_str())
147 {
148 let center = initial_guesses.arc_ids(arc_id).center;
149 return Ok(center.into());
150 }
151 if let Some(arc_id) = self
153 .inner_arcs
154 .iter()
155 .position(|arc| format!("{}.a", arc.0) == label.0.as_str())
156 {
157 let start = initial_guesses.arc_ids(arc_id).start;
158 return Ok(start.into());
159 }
160 if let Some(arc_id) = self
162 .inner_arcs
163 .iter()
164 .position(|arc| format!("{}.b", arc.0) == label.0.as_str())
165 {
166 let end = initial_guesses.arc_ids(arc_id).end;
167 return Ok(end.into());
168 }
169 Err(TextualError::UndefinedPoint {
171 label: label.0.clone(),
172 })
173 };
174 let datum_distance_for_label = |label: &Label| -> Result<DatumDistance, TextualError> {
175 if let Some(circle_id) = self
176 .inner_circles
177 .iter()
178 .position(|circ| format!("{}.radius", circ.0) == label.0.as_str())
179 {
180 let ids = initial_guesses.circle_ids(circle_id);
181 return Ok(DatumDistance { id: ids.radius });
182 }
183 Err(TextualError::UndefinedPoint {
184 label: label.0.clone(),
185 })
186 };
187
188 for instr in &self.instructions {
189 match instr {
190 Instruction::DeclarePoint(_) => {}
191 Instruction::DeclareCircle(_) => {}
192 Instruction::DeclareArc(_) => {}
193 Instruction::Line(_) => {}
194 Instruction::CircleRadius(CircleRadius { circle, radius }) => {
195 let circ = &circle.0;
196 let center_id = datum_point_for_label(&Label(format!("{circ}.center")))?;
197 let radius_id = datum_distance_for_label(&Label(format!("{circ}.radius")))?;
198 constraints.push(Constraint::CircleRadius(
199 datatypes::inputs::DatumCircle {
200 center: center_id,
201 radius: radius_id,
202 },
203 *radius,
204 ));
205 }
206 Instruction::ArcRadius(ArcRadius { arc_label, radius }) => {
207 let arc_label = &arc_label.0;
208 let circular_arc = DatumCircularArc {
209 center: datum_point_for_label(&Label(format!("{arc_label}.center")))?,
210 start: datum_point_for_label(&Label(format!("{arc_label}.a")))?,
211 end: datum_point_for_label(&Label(format!("{arc_label}.b")))?,
212 };
213 constraints.push(Constraint::ArcRadius(circular_arc, *radius));
214 }
215 Instruction::IsArc(IsArc { arc_label }) => {
216 let arc_label = &arc_label.0;
217 let circular_arc = DatumCircularArc {
218 center: datum_point_for_label(&Label(format!("{arc_label}.center")))?,
219 start: datum_point_for_label(&Label(format!("{arc_label}.a")))?,
220 end: datum_point_for_label(&Label(format!("{arc_label}.b")))?,
221 };
222 constraints.push(Constraint::Arc(circular_arc));
223 }
224 Instruction::PointLineDistance(PointLineDistance {
225 point,
226 line_p0,
227 line_p1,
228 distance,
229 }) => {
230 let line = DatumLineSegment {
231 p0: datum_point_for_label(line_p0)?,
232 p1: datum_point_for_label(line_p1)?,
233 };
234 let p = datum_point_for_label(point)?;
235 constraints.push(Constraint::PointLineDistance(p, line, *distance));
236 }
237 Instruction::Tangent(Tangent {
238 circle,
239 line_p0,
240 line_p1,
241 }) => {
242 let circ = &circle.0;
243 let center_id = datum_point_for_label(&Label(format!("{circ}.center")))?;
244 let radius_id = datum_distance_for_label(&Label(format!("{circ}.radius")))?;
245 let line = DatumLineSegment {
246 p0: datum_point_for_label(line_p0)?,
247 p1: datum_point_for_label(line_p1)?,
248 };
249 constraints.push(Constraint::LineTangentToCircle(
250 line,
251 datatypes::inputs::DatumCircle {
252 center: center_id,
253 radius: radius_id,
254 },
255 ));
256 }
257 Instruction::FixPointComponent(FixPointComponent {
258 point,
259 component,
260 value,
261 }) => {
262 if let Some(point_id) =
263 self.inner_points.iter().position(|label| label == point)
264 {
265 let ids = initial_guesses.point_ids(point_id);
266 let id = match component {
267 Component::X => ids.x,
268 Component::Y => ids.y,
269 };
270 constraints.push(Constraint::Fixed(id, *value));
271 } else if let Some(circle_label) = point.0.strip_suffix(".center") {
272 if let Some(circle_id) =
273 self.inner_circles.iter().position(|p| p.0 == circle_label)
274 {
275 let center = initial_guesses.circle_ids(circle_id).center;
276 let id = match component {
277 Component::X => center.x,
278 Component::Y => center.y,
279 };
280 constraints.push(Constraint::Fixed(id, *value));
281 }
282 } else {
283 return Err(TextualError::UndefinedPoint {
284 label: point.0.clone(),
285 });
286 }
287 }
288 Instruction::FixCenterPointComponent(FixCenterPointComponent {
289 object,
290 center_component,
291 value,
292 }) => {
293 if let Some(circle_id) =
295 self.inner_circles.iter().position(|label| label == object)
296 {
297 let center = initial_guesses.circle_ids(circle_id).center;
298 let id = match center_component {
299 Component::X => center.x,
300 Component::Y => center.y,
301 };
302 constraints.push(Constraint::Fixed(id, *value));
303 } else if let Some(arc_id) =
305 self.inner_arcs.iter().position(|label| label == object)
306 {
307 let center = initial_guesses.arc_ids(arc_id).center;
308 let id = match center_component {
309 Component::X => center.x,
310 Component::Y => center.y,
311 };
312 constraints.push(Constraint::Fixed(id, *value));
313 } else {
314 return Err(TextualError::UndefinedPoint {
315 label: object.0.clone(),
316 });
317 }
318 }
319 Instruction::Vertical(Vertical { label }) => {
320 let p0 = datum_point_for_label(&label.0)?;
321 let p1 = datum_point_for_label(&label.1)?;
322 constraints.push(Constraint::Vertical(DatumLineSegment { p0, p1 }));
323 }
324 Instruction::PointsCoincident(PointsCoincident { point0, point1 }) => {
325 let p0 = datum_point_for_label(point0)?;
326 let p1 = datum_point_for_label(point1)?;
327 constraints.push(Constraint::PointsCoincident(p0, p1));
328 }
329 Instruction::PointArcCoincident(PointArcCoincident { point, arc }) => {
330 let p = datum_point_for_label(point)?;
331 let arc_label = &arc.0;
332 let datum_arc = DatumCircularArc {
333 center: datum_point_for_label(&Label(format!("{arc_label}.center")))?,
334 start: datum_point_for_label(&Label(format!("{arc_label}.a")))?,
335 end: datum_point_for_label(&Label(format!("{arc_label}.b")))?,
336 };
337 constraints.push(Constraint::PointArcCoincident(datum_arc, p));
338 }
339 Instruction::Midpoint(Midpoint { point0, point1, mp }) => {
340 let p0 = datum_point_for_label(point0)?;
341 let p1 = datum_point_for_label(point1)?;
342 let mp = datum_point_for_label(mp)?;
343 constraints.push(Constraint::Midpoint(DatumLineSegment { p0, p1 }, mp));
344 }
345 Instruction::Symmetric(Symmetric { p0, p1, line }) => {
346 let p0 = datum_point_for_label(p0)?;
347 let p1 = datum_point_for_label(p1)?;
348 let line = (
349 datum_point_for_label(&line.0)?,
350 datum_point_for_label(&line.1)?,
351 );
352 let line = DatumLineSegment {
353 p0: line.0,
354 p1: line.1,
355 };
356 constraints.push(Constraint::Symmetric(line, p0, p1));
357 }
358 Instruction::Horizontal(Horizontal { label }) => {
359 let p0 = datum_point_for_label(&label.0)?;
360 let p1 = datum_point_for_label(&label.1)?;
361 constraints.push(Constraint::Horizontal(DatumLineSegment { p0, p1 }));
362 }
363 Instruction::Distance(Distance { label, distance }) => {
364 let p0 = datum_point_for_label(&label.0)?;
365 let p1 = datum_point_for_label(&label.1)?;
366 constraints.push(Constraint::Distance(p0, p1, *distance));
367 }
368 Instruction::Parallel(Parallel { line0, line1 }) => {
369 let p0 = datum_point_for_label(&line0.0)?;
370 let p1 = datum_point_for_label(&line0.1)?;
371 let p2 = datum_point_for_label(&line1.0)?;
372 let p3 = datum_point_for_label(&line1.1)?;
373 constraints.push(Constraint::lines_parallel([
374 DatumLineSegment { p0, p1 },
375 DatumLineSegment { p0: p2, p1: p3 },
376 ]));
377 }
378 Instruction::LinesEqualLength(LinesEqualLength { line0, line1 }) => {
379 let p0 = datum_point_for_label(&line0.0)?;
380 let p1 = datum_point_for_label(&line0.1)?;
381 let p2 = datum_point_for_label(&line1.0)?;
382 let p3 = datum_point_for_label(&line1.1)?;
383 constraints.push(Constraint::LinesEqualLength(
384 DatumLineSegment { p0, p1 },
385 DatumLineSegment { p0: p2, p1: p3 },
386 ));
387 }
388 Instruction::Perpendicular(Perpendicular { line0, line1 }) => {
389 let p0 = datum_point_for_label(&line0.0)?;
390 let p1 = datum_point_for_label(&line0.1)?;
391 let p2 = datum_point_for_label(&line1.0)?;
392 let p3 = datum_point_for_label(&line1.1)?;
393 constraints.push(Constraint::lines_perpendicular([
394 DatumLineSegment { p0, p1 },
395 DatumLineSegment { p0: p2, p1: p3 },
396 ]));
397 }
398 Instruction::AngleLine(AngleLine {
399 line0,
400 line1,
401 angle,
402 }) => {
403 let p0 = datum_point_for_label(&line0.0)?;
404 let p1 = datum_point_for_label(&line0.1)?;
405 let p2 = datum_point_for_label(&line1.0)?;
406 let p3 = datum_point_for_label(&line1.1)?;
407 constraints.push(Constraint::LinesAtAngle(
408 DatumLineSegment { p0, p1 },
409 DatumLineSegment { p0: p2, p1: p3 },
410 AngleKind::Other(*angle),
411 ));
412 }
413 Instruction::ArcLength(arc_length) => {
414 let arc_label = &arc_length.arc.0;
415 let length = arc_length.distance;
416 let circular_arc = DatumCircularArc {
417 center: datum_point_for_label(&Label(format!("{arc_label}.center")))?,
418 start: datum_point_for_label(&Label(format!("{arc_label}.a")))?,
419 end: datum_point_for_label(&Label(format!("{arc_label}.b")))?,
420 };
421 constraints.push(Constraint::ArcLength(circular_arc, length));
422 }
423 }
424 }
425 let initial_guesses = initial_guesses.done();
426
427 let priority = 0;
430 let constraints = constraints
431 .into_iter()
432 .map(|c| ConstraintRequest::new(c, priority))
433 .collect();
434
435 Ok(ConstraintSystem {
436 constraints,
437 initial_guesses,
438 inner_points: &self.inner_points,
439 inner_circles: &self.inner_circles,
440 inner_arcs: &self.inner_arcs,
441 inner_lines: &self.inner_lines,
442 })
443 }
444}
445
446#[derive(Clone)]
449pub struct ConstraintSystem<'a> {
450 pub constraints: Vec<ConstraintRequest>,
452 initial_guesses: GeometryVariables<DoneState>,
453 inner_points: &'a [Label],
454 inner_circles: &'a [Label],
455 inner_arcs: &'a [Label],
456 inner_lines: &'a [(Label, Label)],
457}
458
459impl ConstraintSystem<'_> {
460 pub fn solve_no_metadata(&self, config: Config) -> Result<SolveOutcome, FailureOutcome> {
462 crate::solve(&self.constraints, self.initial_guesses.variables(), config)
463 }
464
465 fn solve_no_metadata_inner<A: Analysis>(
466 &self,
467 config: Config,
468 ) -> Result<SolveOutcomeAnalysis<A>, FailureOutcome> {
469 crate::solve_with_priority_inner(
470 &self.constraints,
471 self.initial_guesses.variables(),
472 config,
473 )
474 }
475
476 pub fn solve(&self) -> Result<Outcome, FailureOutcome> {
478 self.solve_with_config(Default::default())
479 }
480
481 pub fn solve_with_config_analysis(
483 &self,
484 config: Config,
485 ) -> Result<OutcomeAnalysis, FailureOutcome> {
486 let (analysis, outcome) = self.solve_with_config_inner::<FreedomAnalysis>(config)?;
487 Ok(OutcomeAnalysis { analysis, outcome })
488 }
489
490 pub fn solve_with_config(&self, config: Config) -> Result<Outcome, FailureOutcome> {
492 let (NoAnalysis, outcome) = self.solve_with_config_inner::<NoAnalysis>(config)?;
493 Ok(outcome)
494 }
495
496 fn solve_with_config_inner<A: Analysis>(
497 &self,
498 config: Config,
499 ) -> Result<(A, Outcome), FailureOutcome> {
500 let num_vars = self.initial_guesses.len();
501 let num_eqs = self
502 .constraints
503 .iter()
504 .map(|c| c.constraint().residual_dim())
505 .sum();
506 let SolveOutcomeAnalysis {
508 analysis,
509 outcome:
510 SolveOutcome {
511 iterations,
512 warnings,
513 final_values,
514 unsatisfied,
515 priority_solved,
516 },
517 } = self.solve_no_metadata_inner::<A>(config)?;
518 let num_points = self.inner_points.len();
519 let num_circles = self.inner_circles.len();
520 let num_arcs = self.inner_arcs.len();
521
522 let mut final_points = IndexMap::with_capacity(num_points);
523 for (i, point) in self.inner_points.iter().enumerate() {
524 let x_id = 2 * i;
525 let y_id = 2 * i + 1;
526 let p = Point {
527 x: final_values[x_id],
528 y: final_values[y_id],
529 };
530 final_points.insert(point.0.clone(), p);
531 }
532 let start_of_circles = 2 * self.inner_points.len();
533 let mut final_circles = IndexMap::with_capacity(num_circles);
534 for (i, circle_label) in self.inner_circles.iter().enumerate() {
535 let cx = final_values[start_of_circles + 3 * i]; let cy = final_values[start_of_circles + 3 * i + 1]; let rd = final_values[start_of_circles + 3 * i + 2]; final_circles.insert(
539 circle_label.0.clone(),
540 Circle {
541 radius: rd,
542 center: Point { x: cx, y: cy },
543 },
544 );
545 }
546 let start_of_arcs = start_of_circles + 3 * self.inner_circles.len();
547 let mut final_arcs = IndexMap::with_capacity(num_arcs);
548 for (i, arc_label) in self.inner_arcs.iter().enumerate() {
549 let ax = final_values[start_of_arcs + VARS_PER_ARC * i];
550 let ay = final_values[start_of_arcs + VARS_PER_ARC * i + 1];
551 let bx = final_values[start_of_arcs + VARS_PER_ARC * i + 2];
552 let by = final_values[start_of_arcs + VARS_PER_ARC * i + 3];
553 let cx = final_values[start_of_arcs + VARS_PER_ARC * i + 4];
554 let cy = final_values[start_of_arcs + VARS_PER_ARC * i + 5];
555 final_arcs.insert(
556 arc_label.0.clone(),
557 Arc {
558 center: Point { x: cx, y: cy },
559 a: Point { x: ax, y: ay },
560 b: Point { x: bx, y: by },
561 },
562 );
563 }
564 Ok((
565 analysis,
566 Outcome {
567 priority_solved,
568 unsatisfied,
569 iterations,
570 warnings,
571 points: final_points,
572 circles: final_circles,
573 arcs: final_arcs,
574 num_vars,
575 lines: self.inner_lines.to_vec(),
576 num_eqs,
577 },
578 ))
579 }
580}
581
582#[derive(Debug)]
584pub struct Outcome {
585 pub unsatisfied: Vec<usize>,
587 pub iterations: usize,
589 pub warnings: Vec<Warning>,
591 pub points: IndexMap<String, Point>,
593 pub circles: IndexMap<String, Circle>,
595 pub arcs: IndexMap<String, Arc>,
597 pub lines: Vec<(Label, Label)>,
599 pub num_vars: usize,
601 pub num_eqs: usize,
603 pub priority_solved: u32,
607}
608
609#[derive(Debug)]
611pub struct OutcomeAnalysis {
612 pub analysis: FreedomAnalysis,
614 pub outcome: Outcome,
616}
617
618impl Outcome {
619 pub fn get_point(&self, label: &str) -> Option<Point> {
621 self.points.get(label).copied()
622 }
623
624 pub fn get_circle(&self, label: &str) -> Option<Circle> {
626 self.circles.get(label).copied()
627 }
628
629 pub fn get_arc(&self, label: &str) -> Option<Arc> {
631 self.arcs.get(label).copied()
632 }
633}
634
635impl OutcomeAnalysis {
636 #[cfg(test)]
638 pub fn get_point(&self, label: &str) -> Option<Point> {
639 self.outcome.get_point(label)
640 }
641
642 #[cfg(test)]
644 pub fn get_circle(&self, label: &str) -> Option<Circle> {
645 self.outcome.get_circle(label)
646 }
647
648 #[cfg(test)]
650 pub fn get_arc(&self, label: &str) -> Option<Arc> {
651 self.outcome.get_arc(label)
652 }
653
654 #[cfg(test)]
656 pub fn is_satisfied(&self) -> bool {
657 !self.is_unsatisfied()
658 }
659
660 #[cfg(test)]
662 pub fn is_unsatisfied(&self) -> bool {
663 !self.outcome.unsatisfied.is_empty()
664 }
665}
666
667#[cfg(test)]
668mod tests {
669 use super::*;
670 use crate::textual::{PointGuess, instruction::FixPointComponent};
671
672 fn empty_problem() -> Problem {
673 Problem {
674 instructions: Vec::new(),
675 inner_points: Vec::new(),
676 inner_circles: Vec::new(),
677 inner_arcs: Vec::new(),
678 inner_lines: Vec::new(),
679 point_guesses: Vec::new(),
680 scalar_guesses: Vec::new(),
681 }
682 }
683
684 #[test]
685 fn missing_guess_is_reported() {
686 let mut problem = empty_problem();
687 problem.inner_points.push(Label::from("p"));
688 let err = problem
689 .to_constraint_system()
690 .err()
691 .expect("expected missing guess");
692 assert!(matches!(err, TextualError::MissingGuess { label } if label == "p"));
693 }
694
695 #[test]
696 fn unused_guesses_are_detected() {
697 let mut problem = empty_problem();
698 problem.point_guesses.push(PointGuess {
699 point: Label::from("ghost"),
700 guess: Point { x: 0.0, y: 0.0 },
701 });
702
703 let err = problem
704 .to_constraint_system()
705 .err()
706 .expect("expected unused guess error");
707 match err {
708 TextualError::UnusedGuesses { labels } => {
709 assert_eq!(labels.len(), 1);
710 assert_eq!(labels[0], "ghost");
711 }
712 other => panic!("unexpected error: {other:?}"),
713 }
714 }
715
716 #[test]
717 fn undefined_point_in_instruction_errors() {
718 let mut problem = empty_problem();
719 problem.inner_points.push(Label::from("p"));
720 problem.point_guesses.push(PointGuess {
721 point: Label::from("p"),
722 guess: Point { x: 0.0, y: 0.0 },
723 });
724 problem
725 .instructions
726 .push(Instruction::FixPointComponent(FixPointComponent {
727 point: Label::from("missing"),
728 component: Component::X,
729 value: 2.5,
730 }));
731
732 let err = problem
733 .to_constraint_system()
734 .err()
735 .expect("expected undefined point error");
736 assert!(matches!(err, TextualError::UndefinedPoint { label } if label == "missing"));
737 }
738}