axiolid_nurbs/certified_projection.rs
1//! Public policy and certificate values for exhaustive projection.
2
3use axiolid_contracts::{GeomError, GeomResult};
4use axiolid_core::{Point2, Point3, Scalar, Tolerance};
5
6/// Closed native-parameter interval that may contain a global minimizer.
7#[derive(Debug, Clone, Copy, PartialEq)]
8pub struct ParameterInterval {
9 /// Inclusive lower parameter.
10 pub start: Scalar,
11 /// Inclusive upper parameter.
12 pub end: Scalar,
13}
14
15/// Explicit work and accuracy policy for globally certified projection.
16///
17/// Certification succeeds only when the returned global distance-bound gap is
18/// no larger than `tolerance.linear()`. `max_nodes` independently caps both
19/// pre-search refinement work and generated subdivision cells; pair queries
20/// share one refinement allowance across both input curves. One refinement
21/// work unit is one newly allocated homogeneous-control, expanded-knot, emitted
22/// control, or cell slot. Binary subdivision is also depth-bounded. There is
23/// deliberately no context-free `Default`.
24#[derive(Debug, Clone, Copy, PartialEq)]
25pub struct CertifiedProjectionOptions {
26 tolerance: Tolerance,
27 max_nodes: u32,
28 max_depth: u16,
29}
30
31impl CertifiedProjectionOptions {
32 /// Construct a non-vacuous certification policy.
33 pub fn new(tolerance: Tolerance, max_nodes: u32, max_depth: u16) -> GeomResult<Self> {
34 if max_nodes == 0 || max_depth == 0 {
35 return Err(GeomError::InvalidInput(
36 "certified projection budgets must be non-zero".to_owned(),
37 ));
38 }
39 Ok(Self {
40 tolerance,
41 max_nodes,
42 max_depth,
43 })
44 }
45
46 /// Required upper-minus-lower global distance gap.
47 pub const fn tolerance(self) -> Tolerance {
48 self.tolerance
49 }
50
51 /// Maximum refinement-allocation work units per query phase and maximum
52 /// number of generated search cells. Pair queries share the refinement
53 /// allowance across both curves.
54 pub const fn max_nodes(self) -> u32 {
55 self.max_nodes
56 }
57
58 /// Maximum binary subdivision depth of one Bézier segment.
59 pub const fn max_depth(self) -> u16 {
60 self.max_depth
61 }
62}
63
64/// Maximum accepted shared work budget for certified surface projection.
65pub const MAX_CERTIFIED_SURFACE_PROJECTION_WORK: u32 = 100_000;
66
67/// Maximum accepted binary subdivision depth for certified surface projection.
68pub const MAX_CERTIFIED_SURFACE_PROJECTION_DEPTH: u16 = 64;
69
70/// Explicit accuracy and work policy for globally certified surface projection.
71///
72/// `distance_tolerance` bounds the certified model-space distance gap.
73/// `parameter_tolerance` independently bounds both native parameter widths;
74/// native parameters are not assumed to have model-space units. `max_work`
75/// is one shared cap for Bézier conversion, generated search cells, root and
76/// child patch bounds, patch restriction, and representative enclosure.
77#[derive(Debug, Clone, Copy, PartialEq)]
78pub struct CertifiedSurfaceProjectionOptions {
79 distance_tolerance: Tolerance,
80 parameter_tolerance: Scalar,
81 max_work: u32,
82 max_depth: u16,
83}
84
85impl CertifiedSurfaceProjectionOptions {
86 /// Construct a non-vacuous, hard-capped certification policy.
87 pub fn new(
88 distance_tolerance: Tolerance,
89 parameter_tolerance: Scalar,
90 max_work: u32,
91 max_depth: u16,
92 ) -> GeomResult<Self> {
93 if !parameter_tolerance.is_finite() || parameter_tolerance <= 0.0 {
94 return Err(GeomError::InvalidInput(
95 "surface projection parameter tolerance must be positive and finite".to_owned(),
96 ));
97 }
98 if max_work == 0
99 || max_work > MAX_CERTIFIED_SURFACE_PROJECTION_WORK
100 || max_depth == 0
101 || max_depth > MAX_CERTIFIED_SURFACE_PROJECTION_DEPTH
102 {
103 return Err(GeomError::InvalidInput(format!(
104 "surface projection budgets must be in 1..={MAX_CERTIFIED_SURFACE_PROJECTION_WORK} work units and 1..={MAX_CERTIFIED_SURFACE_PROJECTION_DEPTH} depth"
105 )));
106 }
107 Ok(Self {
108 distance_tolerance,
109 parameter_tolerance,
110 max_work,
111 max_depth,
112 })
113 }
114
115 /// Required outward upper-minus-lower global distance gap.
116 pub const fn distance_tolerance(self) -> Tolerance {
117 self.distance_tolerance
118 }
119
120 /// Required maximum width of each retained native parameter interval.
121 pub const fn parameter_tolerance(self) -> Scalar {
122 self.parameter_tolerance
123 }
124
125 /// Shared conversion, search, restriction, and bound-construction work cap.
126 pub const fn max_work(self) -> u32 {
127 self.max_work
128 }
129
130 /// Maximum binary subdivision depth of one root Bézier patch.
131 pub const fn max_depth(self) -> u16 {
132 self.max_depth
133 }
134}
135
136/// Closed native `(u, v)` box that may contain a global minimizer.
137#[derive(Debug, Clone, Copy, PartialEq)]
138pub struct SurfaceParameterBox {
139 /// Closed native U interval.
140 pub u: ParameterInterval,
141 /// Closed native V interval.
142 pub v: ParameterInterval,
143}
144
145/// Why a valid surface projection remains unresolved without exhausting work.
146#[derive(Debug, Clone, Copy, PartialEq, Eq)]
147pub enum SurfaceProjectionUnresolvedReason {
148 /// At least one surviving candidate reached the configured depth cap.
149 DepthLimit,
150 /// Binary64 midpoint subdivision no longer advances on surviving candidates.
151 FloatingPointNoProgress,
152}
153
154/// Globally bounded closest-point certificate for a spatial surface.
155///
156/// The representative is a deterministic scalar-oracle evaluation. Its exact
157/// binary64 `(u, v)` parameters are also interval-evaluated, and that enclosure
158/// provides `distance_upper_bound`; the scalar `distance` is descriptive and
159/// is not substituted for the outward certificate bound.
160#[derive(Debug, Clone, PartialEq)]
161pub struct SurfaceProjectionCertificate3 {
162 /// Native U parameter of the attained representative.
163 pub u: Scalar,
164 /// Native V parameter of the attained representative.
165 pub v: Scalar,
166 /// Scalar-oracle approximation of the surface point at `(u, v)`.
167 pub point: Point3,
168 /// Euclidean distance of the scalar-oracle representative.
169 pub distance: Scalar,
170 /// Outward lower bound on the global minimum distance.
171 pub distance_lower_bound: Scalar,
172 /// Outward upper bound from evaluating the exact representative parameters.
173 pub distance_upper_bound: Scalar,
174 /// Closed native boxes whose union contains every global minimizer.
175 pub possible_minimizer_boxes: Vec<SurfaceParameterBox>,
176 /// Number of generated search cells, including root patches.
177 pub visited_nodes: u32,
178}
179
180impl SurfaceProjectionCertificate3 {
181 /// Outward-rounded certified global distance gap.
182 pub fn gap(&self) -> Scalar {
183 crate::certified_bezier::next_up(
184 (self.distance_upper_bound - self.distance_lower_bound).max(0.0),
185 )
186 }
187}
188
189/// Exhaustive bounded outcome for spatial surface projection.
190#[derive(Debug, Clone, PartialEq)]
191pub enum CertifiedSurfaceProjection3 {
192 /// All global-distance and native-parameter proof obligations were met.
193 Complete(SurfaceProjectionCertificate3),
194 /// Bounds and candidates remain sound, but the configured depth or floating
195 /// point parameter resolution prevented completion.
196 Unresolved {
197 /// Retained partial global certificate.
198 certificate: SurfaceProjectionCertificate3,
199 /// Exact reason completion stopped.
200 reason: SurfaceProjectionUnresolvedReason,
201 },
202}
203
204/// Globally bounded closest-point result for a planar curve.
205#[derive(Debug, Clone, PartialEq)]
206pub struct CurveProjectionCertificate2 {
207 /// Native parameter of the attained representative candidate.
208 pub parameter: Scalar,
209 /// Scalar-oracle evaluation at `parameter`.
210 pub point: Point2,
211 /// Euclidean distance of the representative scalar evaluation.
212 pub distance: Scalar,
213 /// Conservative lower bound on the global minimum distance.
214 pub distance_lower_bound: Scalar,
215 /// Conservative upper bound attained by the candidate enclosure.
216 pub distance_upper_bound: Scalar,
217 /// Parameter cells not excluded from containing another global minimizer.
218 pub possible_minimizer_intervals: Vec<ParameterInterval>,
219 /// Number of generated Bézier subdivision cells.
220 pub visited_nodes: u32,
221}
222
223impl CurveProjectionCertificate2 {
224 /// Certified global upper-minus-lower distance gap.
225 pub fn gap(&self) -> Scalar {
226 (self.distance_upper_bound - self.distance_lower_bound).max(0.0)
227 }
228}
229
230/// Globally bounded closest-point result for a spatial curve.
231#[derive(Debug, Clone, PartialEq)]
232pub struct CurveProjectionCertificate3 {
233 /// Native parameter of the attained representative candidate.
234 pub parameter: Scalar,
235 /// Scalar-oracle evaluation at `parameter`.
236 pub point: Point3,
237 /// Euclidean distance of the representative scalar evaluation.
238 pub distance: Scalar,
239 /// Conservative lower bound on the global minimum distance.
240 pub distance_lower_bound: Scalar,
241 /// Conservative upper bound attained by the candidate enclosure.
242 pub distance_upper_bound: Scalar,
243 /// Parameter cells not excluded from containing another global minimizer.
244 pub possible_minimizer_intervals: Vec<ParameterInterval>,
245 /// Number of generated Bézier subdivision cells.
246 pub visited_nodes: u32,
247}
248
249impl CurveProjectionCertificate3 {
250 /// Certified global upper-minus-lower distance gap.
251 pub fn gap(&self) -> Scalar {
252 (self.distance_upper_bound - self.distance_lower_bound).max(0.0)
253 }
254}
255
256/// Product-domain box that may contain a globally closest curve pair.
257#[derive(Debug, Clone, Copy, PartialEq)]
258pub struct CurvePairParameterBox {
259 /// Parameter interval on the first curve.
260 pub first: ParameterInterval,
261 /// Parameter interval on the second curve.
262 pub second: ParameterInterval,
263}
264
265/// Globally bounded minimum-distance result for two planar curves.
266#[derive(Debug, Clone, PartialEq)]
267pub struct CurveDistanceCertificate2 {
268 /// Representative parameter on the first curve.
269 pub first_parameter: Scalar,
270 /// Representative parameter on the second curve.
271 pub second_parameter: Scalar,
272 /// Scalar-oracle point on the first curve.
273 pub first_point: Point2,
274 /// Scalar-oracle point on the second curve.
275 pub second_point: Point2,
276 /// Euclidean distance between representative scalar evaluations.
277 pub distance: Scalar,
278 /// Conservative lower bound on the global minimum distance.
279 pub distance_lower_bound: Scalar,
280 /// Conservative upper bound attained by a candidate enclosure.
281 pub distance_upper_bound: Scalar,
282 /// Product cells not excluded from containing another global minimizer.
283 pub possible_minimizer_boxes: Vec<CurvePairParameterBox>,
284 /// Number of generated product cells.
285 pub visited_nodes: u32,
286}
287
288impl CurveDistanceCertificate2 {
289 /// Certified global upper-minus-lower distance gap.
290 pub fn gap(&self) -> Scalar {
291 (self.distance_upper_bound - self.distance_lower_bound).max(0.0)
292 }
293}
294
295/// Globally bounded minimum-distance result for two spatial curves.
296#[derive(Debug, Clone, PartialEq)]
297pub struct CurveDistanceCertificate3 {
298 /// Representative parameter on the first curve.
299 pub first_parameter: Scalar,
300 /// Representative parameter on the second curve.
301 pub second_parameter: Scalar,
302 /// Scalar-oracle point on the first curve.
303 pub first_point: Point3,
304 /// Scalar-oracle point on the second curve.
305 pub second_point: Point3,
306 /// Euclidean distance between representative scalar evaluations.
307 pub distance: Scalar,
308 /// Conservative lower bound on the global minimum distance.
309 pub distance_lower_bound: Scalar,
310 /// Conservative upper bound attained by a candidate enclosure.
311 pub distance_upper_bound: Scalar,
312 /// Product cells not excluded from containing another global minimizer.
313 pub possible_minimizer_boxes: Vec<CurvePairParameterBox>,
314 /// Number of generated product cells.
315 pub visited_nodes: u32,
316}
317
318impl CurveDistanceCertificate3 {
319 /// Certified global upper-minus-lower distance gap.
320 pub fn gap(&self) -> Scalar {
321 (self.distance_upper_bound - self.distance_lower_bound).max(0.0)
322 }
323}