axiolid_nurbs/projection.rs
1//! Explicit policy and honest outcomes for bounded inverse queries.
2
3use axiolid_contracts::{GeomError, GeomResult};
4use axiolid_core::{Point2, Point3, Scalar, Tolerance};
5
6#[derive(Debug, Clone, Copy, PartialEq)]
7/// Explicit work and convergence policy for bounded projection.
8///
9/// The search samples every active knot span, then runs Newton refinement from
10/// each start until one of these budgets is reached. There is deliberately no
11/// context-free `Default`.
12pub struct ProjectionOptions {
13 tolerance: Tolerance,
14 samples_per_span: u16,
15 max_iterations: u16,
16 max_starts: u32,
17}
18
19impl ProjectionOptions {
20 /// Construct a non-vacuous projection policy.
21 ///
22 /// Returns an error when any budget is zero.
23 pub fn new(
24 tolerance: Tolerance,
25 samples_per_span: u16,
26 max_iterations: u16,
27 max_starts: u32,
28 ) -> GeomResult<Self> {
29 if samples_per_span == 0 || max_iterations == 0 || max_starts == 0 {
30 return Err(GeomError::InvalidInput(
31 "projection budgets must be non-zero".to_owned(),
32 ));
33 }
34 Ok(Self {
35 tolerance,
36 samples_per_span,
37 max_iterations,
38 max_starts,
39 })
40 }
41 /// Linear/angular convergence tolerance.
42 pub const fn tolerance(self) -> Tolerance {
43 self.tolerance
44 }
45 /// Uniform subdivisions used to seed each active knot span.
46 pub const fn samples_per_span(self) -> u16 {
47 self.samples_per_span
48 }
49 /// Maximum Newton updates for one start.
50 pub const fn max_iterations(self) -> u16 {
51 self.max_iterations
52 }
53 /// Aggregate start limit across all spans (or span pairs).
54 pub const fn max_starts(self) -> u32 {
55 self.max_starts
56 }
57}
58
59#[non_exhaustive]
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
61/// Outcome of the selected bounded local solve.
62///
63/// This status does not certify global optimality.
64pub enum ProjectionStatus {
65 /// First-order stationarity or tolerance-sized movement was reached.
66 Converged,
67 /// The selected candidate remained best when its iteration budget ended.
68 BudgetExhausted,
69}
70
71#[derive(Debug, Clone, Copy, PartialEq)]
72/// Best planar-curve projection candidate found within the supplied budget.
73pub struct CurveProjection2 {
74 /// Native curve parameter.
75 pub parameter: Scalar,
76 /// Evaluated point at `parameter`.
77 pub point: Point2,
78 /// Euclidean distance from the target.
79 pub distance: Scalar,
80 /// Newton updates used by the selected start.
81 pub iterations: u16,
82 /// Whether the selected parameter tuple touches its active domain boundary.
83 pub on_boundary: bool,
84 /// Local-solve outcome; not a global-optimum certificate.
85 pub status: ProjectionStatus,
86}
87
88#[derive(Debug, Clone, Copy, PartialEq)]
89/// Best spatial-curve projection candidate found within the supplied budget.
90pub struct CurveProjection3 {
91 /// Native curve parameter.
92 pub parameter: Scalar,
93 /// Evaluated point at `parameter`.
94 pub point: Point3,
95 /// Euclidean distance from the target.
96 pub distance: Scalar,
97 /// Newton updates used by the selected start.
98 pub iterations: u16,
99 /// Whether the selected parameter tuple touches its active domain boundary.
100 pub on_boundary: bool,
101 /// Local-solve outcome; not a global-optimum certificate.
102 pub status: ProjectionStatus,
103}
104
105#[derive(Debug, Clone, Copy, PartialEq)]
106/// Best surface projection candidate found within the supplied budget.
107pub struct SurfaceProjection {
108 /// Native first-axis surface parameter.
109 pub u: Scalar,
110 /// Native second-axis surface parameter.
111 pub v: Scalar,
112 /// Evaluated surface point at `(u, v)`.
113 pub point: Point3,
114 /// Euclidean distance from the target.
115 pub distance: Scalar,
116 /// Newton updates used by the selected start.
117 pub iterations: u16,
118 /// Whether the selected parameter tuple touches its active domain boundary.
119 pub on_boundary: bool,
120 /// Local-solve outcome; not a global-optimum certificate.
121 pub status: ProjectionStatus,
122}