1use crate::services::reviewable_exact_evidence;
7use crate::{MetricPoint, MobilityProfile, ThresholdVerdict};
8use axioval_ir::{Evidence, ObjectId};
9use std::sync::Arc;
10use thiserror::Error;
11
12#[derive(Clone, Debug, Error, PartialEq, Eq)]
13pub enum FreeSpaceError {
14 #[error("free-area interval is invalid")]
15 InvalidAreaInterval,
16 #[error("metric direction is zero or non-finite")]
17 InvalidMetricDirection,
18 #[error("metric frame axes are not mutually perpendicular")]
19 InvalidMetricFrame,
20 #[error("clearance shape dimensions must be positive and finite")]
21 InvalidClearanceShape,
22 #[error("placement offset interval is non-finite or reversed")]
23 InvalidOffsetInterval,
24 #[error("placement support gap must be finite and non-negative")]
25 InvalidSupportGap,
26 #[error("clearance evidence is incomplete")]
27 IncompleteClearanceEvidence,
28 #[error("obstruction evidence has no blocking objects")]
29 EmptyObstructionEvidence,
30 #[error("obstruction evidence names an object outside the request candidate set")]
31 UnexpectedObstacleEvidence,
32 #[error("obstruction provenance is not exact and reviewable")]
33 InexactObstructionEvidence,
34 #[error("free-area evidence is not exact and reviewable")]
35 InexactAreaEvidence,
36 #[error("placement evidence is not exact and reviewable")]
37 InexactPlacementEvidence,
38 #[error("support evidence is not exact and reviewable")]
39 InexactSupportEvidence,
40 #[error("supported placement has no complete support evidence")]
41 MissingSupportEvidence,
42 #[error("support evidence does not match the requested support or found frame")]
43 SupportEvidenceMismatch,
44 #[error("placement frame is not grounded in the requested scope")]
45 PlacementScopeMismatch,
46 #[error("placement witness falls outside its requested search domain")]
47 PlacementDomainMismatch,
48 #[error("free-space backend returned evidence for another request")]
49 ResponseRequestMismatch,
50 #[error("free-space geometry is unavailable for `{0}`")]
51 MissingGeometry(Box<ObjectId>),
52 #[error("free-space query unavailable: {0}")]
53 Unavailable(String),
54}
55
56#[derive(Clone, Copy, Debug, PartialEq)]
57pub struct AreaInterval {
58 lower_square_metres: f64,
59 upper_square_metres: f64,
60}
61impl AreaInterval {
62 pub fn try_new(lower: f64, upper: f64) -> Result<Self, FreeSpaceError> {
63 if !valid_non_negative(lower) || !valid_non_negative(upper) || lower > upper {
64 return Err(FreeSpaceError::InvalidAreaInterval);
65 }
66 Ok(Self {
67 lower_square_metres: lower,
68 upper_square_metres: upper,
69 })
70 }
71 pub fn exact(square_metres: f64) -> Result<Self, FreeSpaceError> {
72 Self::try_new(square_metres, square_metres)
73 }
74 pub fn lower_square_metres(&self) -> f64 {
75 self.lower_square_metres
76 }
77 pub fn upper_square_metres(&self) -> f64 {
78 self.upper_square_metres
79 }
80 pub fn compare_minimum(&self, minimum: f64) -> Result<ThresholdVerdict, FreeSpaceError> {
81 if !valid_non_negative(minimum) {
82 return Err(FreeSpaceError::InvalidAreaInterval);
83 }
84 if self.lower_square_metres >= minimum {
85 Ok(ThresholdVerdict::Satisfied)
86 } else if self.upper_square_metres < minimum {
87 Ok(ThresholdVerdict::Violated)
88 } else {
89 Ok(ThresholdVerdict::Indeterminate)
90 }
91 }
92}
93
94#[derive(Clone, Copy, Debug, PartialEq)]
95pub struct MetricDirection([f64; 3]);
96impl MetricDirection {
97 pub fn try_new(vector: [f64; 3]) -> Result<Self, FreeSpaceError> {
98 if !vector.iter().all(|v| v.is_finite()) {
99 return Err(FreeSpaceError::InvalidMetricDirection);
100 }
101 let norm = vector.iter().map(|v| v * v).sum::<f64>().sqrt();
102 if norm <= f64::EPSILON {
103 return Err(FreeSpaceError::InvalidMetricDirection);
104 }
105 Ok(Self([vector[0] / norm, vector[1] / norm, vector[2] / norm]))
106 }
107 pub fn components(&self) -> [f64; 3] {
108 self.0
109 }
110 fn dot(self, other: Self) -> f64 {
111 self.0[0] * other.0[0] + self.0[1] * other.0[1] + self.0[2] * other.0[2]
112 }
113}
114
115#[derive(Clone, Debug, PartialEq)]
116pub struct MetricFrame {
117 origin: MetricPoint,
118 right: MetricDirection,
119 forward: MetricDirection,
120 up: MetricDirection,
121}
122impl MetricFrame {
123 pub fn try_new(
124 origin: MetricPoint,
125 right: MetricDirection,
126 forward: MetricDirection,
127 up: MetricDirection,
128 ) -> Result<Self, FreeSpaceError> {
129 const ORTHOGONAL_TOLERANCE: f64 = 1.0e-9;
130 let [rx, ry, rz] = right.components();
131 let [fx, fy, fz] = forward.components();
132 let [ux, uy, uz] = up.components();
133 let handedness =
134 (ry * fz - rz * fy) * ux + (rz * fx - rx * fz) * uy + (rx * fy - ry * fx) * uz;
135 if right.dot(forward).abs() > ORTHOGONAL_TOLERANCE
136 || right.dot(up).abs() > ORTHOGONAL_TOLERANCE
137 || forward.dot(up).abs() > ORTHOGONAL_TOLERANCE
138 || handedness < 1.0 - ORTHOGONAL_TOLERANCE
139 {
140 return Err(FreeSpaceError::InvalidMetricFrame);
141 }
142 Ok(Self {
143 origin,
144 right,
145 forward,
146 up,
147 })
148 }
149 pub fn origin(&self) -> &MetricPoint {
150 &self.origin
151 }
152 pub fn right(&self) -> MetricDirection {
153 self.right
154 }
155 pub fn forward(&self) -> MetricDirection {
156 self.forward
157 }
158 pub fn up(&self) -> MetricDirection {
159 self.up
160 }
161}
162
163#[derive(Clone, Copy, Debug, PartialEq)]
164pub struct BoxClearance {
165 width: f64,
166 depth: f64,
167 height: f64,
168}
169impl BoxClearance {
170 pub fn try_new(width: f64, depth: f64, height: f64) -> Result<Self, FreeSpaceError> {
171 if !valid_positive(width) || !valid_positive(depth) || !valid_positive(height) {
172 return Err(FreeSpaceError::InvalidClearanceShape);
173 }
174 Ok(Self {
175 width,
176 depth,
177 height,
178 })
179 }
180 pub fn width_metres(&self) -> f64 {
181 self.width
182 }
183 pub fn depth_metres(&self) -> f64 {
184 self.depth
185 }
186 pub fn height_metres(&self) -> f64 {
187 self.height
188 }
189}
190
191#[derive(Clone, Copy, Debug, PartialEq)]
192pub struct CylinderClearance {
193 radius: f64,
194 height: f64,
195}
196impl CylinderClearance {
197 pub fn try_new(radius: f64, height: f64) -> Result<Self, FreeSpaceError> {
198 if !valid_positive(radius) || !valid_positive(height) {
199 return Err(FreeSpaceError::InvalidClearanceShape);
200 }
201 Ok(Self { radius, height })
202 }
203 pub fn radius_metres(&self) -> f64 {
204 self.radius
205 }
206 pub fn height_metres(&self) -> f64 {
207 self.height
208 }
209}
210
211#[derive(Clone, Copy, Debug, PartialEq)]
212pub enum ClearanceShape {
213 Box(BoxClearance),
214 Cylinder(CylinderClearance),
215}
216
217#[derive(Clone, Debug, PartialEq)]
218pub struct ClearanceRequest {
219 frame: MetricFrame,
220 shape: ClearanceShape,
221 obstacles: Vec<ObjectId>,
222}
223impl ClearanceRequest {
224 pub fn new(frame: MetricFrame, shape: ClearanceShape, mut obstacles: Vec<ObjectId>) -> Self {
225 obstacles.sort();
226 obstacles.dedup();
227 Self {
228 frame,
229 shape,
230 obstacles,
231 }
232 }
233 pub fn frame(&self) -> &MetricFrame {
234 &self.frame
235 }
236 pub fn shape(&self) -> ClearanceShape {
237 self.shape
238 }
239 pub fn obstacles(&self) -> &[ObjectId] {
240 &self.obstacles
241 }
242}
243
244#[derive(Clone, Debug, PartialEq)]
245pub struct FreeAreaRequest {
246 scope: ObjectId,
247 mobility: MobilityProfile,
248 obstacles: Vec<ObjectId>,
249}
250impl FreeAreaRequest {
251 pub fn new(scope: ObjectId, mobility: MobilityProfile, mut obstacles: Vec<ObjectId>) -> Self {
252 obstacles.sort();
253 obstacles.dedup();
254 Self {
255 scope,
256 mobility,
257 obstacles,
258 }
259 }
260 pub fn scope(&self) -> &ObjectId {
261 &self.scope
262 }
263 pub fn mobility(&self) -> MobilityProfile {
264 self.mobility
265 }
266 pub fn obstacles(&self) -> &[ObjectId] {
267 &self.obstacles
268 }
269}
270
271#[derive(Clone, Copy, Debug, PartialEq)]
273pub struct SignedDistanceInterval {
274 lower_metres: f64,
275 upper_metres: f64,
276}
277impl SignedDistanceInterval {
278 pub fn try_new(lower_metres: f64, upper_metres: f64) -> Result<Self, FreeSpaceError> {
279 if !lower_metres.is_finite() || !upper_metres.is_finite() || lower_metres > upper_metres {
280 return Err(FreeSpaceError::InvalidOffsetInterval);
281 }
282 Ok(Self {
283 lower_metres,
284 upper_metres,
285 })
286 }
287 pub fn exact(metres: f64) -> Result<Self, FreeSpaceError> {
288 Self::try_new(metres, metres)
289 }
290 pub fn lower_metres(&self) -> f64 {
291 self.lower_metres
292 }
293 pub fn upper_metres(&self) -> f64 {
294 self.upper_metres
295 }
296 fn contains(self, value: f64) -> bool {
297 value >= self.lower_metres && value <= self.upper_metres
298 }
299}
300
301#[derive(Clone, Debug, PartialEq)]
303pub struct SupportedPlacement {
304 support: ObjectId,
305 maximum_gap_metres: f64,
306}
307impl SupportedPlacement {
308 pub fn try_new(support: ObjectId, maximum_gap_metres: f64) -> Result<Self, FreeSpaceError> {
309 if !valid_non_negative(maximum_gap_metres) {
310 return Err(FreeSpaceError::InvalidSupportGap);
311 }
312 Ok(Self {
313 support,
314 maximum_gap_metres,
315 })
316 }
317 pub fn support(&self) -> &ObjectId {
318 &self.support
319 }
320 pub fn maximum_gap_metres(&self) -> f64 {
321 self.maximum_gap_metres
322 }
323}
324
325#[derive(Clone, Debug, PartialEq)]
327pub struct FrameOffsetPlacement {
328 anchor: MetricFrame,
329 right: SignedDistanceInterval,
330 forward: SignedDistanceInterval,
331 up: SignedDistanceInterval,
332}
333impl FrameOffsetPlacement {
334 pub fn new(
335 anchor: MetricFrame,
336 right: SignedDistanceInterval,
337 forward: SignedDistanceInterval,
338 up: SignedDistanceInterval,
339 ) -> Self {
340 Self {
341 anchor,
342 right,
343 forward,
344 up,
345 }
346 }
347 pub fn anchor(&self) -> &MetricFrame {
348 &self.anchor
349 }
350 pub fn right(&self) -> SignedDistanceInterval {
351 self.right
352 }
353 pub fn forward(&self) -> SignedDistanceInterval {
354 self.forward
355 }
356 pub fn up(&self) -> SignedDistanceInterval {
357 self.up
358 }
359 fn contains_frame(&self, frame: &MetricFrame) -> bool {
360 let aligned = self.anchor.right() == frame.right()
361 && self.anchor.forward() == frame.forward()
362 && self.anchor.up() == frame.up();
363 if !aligned {
364 return false;
365 }
366 let anchor = self.anchor.origin().coordinates_metres();
367 let found = frame.origin().coordinates_metres();
368 let delta = [
369 found[0] - anchor[0],
370 found[1] - anchor[1],
371 found[2] - anchor[2],
372 ];
373 let project = |axis: MetricDirection| {
374 axis.components()
375 .into_iter()
376 .zip(delta)
377 .map(|(a, b)| a * b)
378 .sum()
379 };
380 self.right.contains(project(self.anchor.right()))
381 && self.forward.contains(project(self.anchor.forward()))
382 && self.up.contains(project(self.anchor.up()))
383 }
384}
385
386#[derive(Clone, Debug, PartialEq)]
388pub enum PlacementDomain {
389 Unconstrained,
390 Supported(SupportedPlacement),
391 FrameOffsets(FrameOffsetPlacement),
392 SupportedFrameOffsets {
393 support: SupportedPlacement,
394 offsets: FrameOffsetPlacement,
395 },
396}
397
398fn requested_support(domain: &PlacementDomain) -> Option<&SupportedPlacement> {
399 match domain {
400 PlacementDomain::Supported(support)
401 | PlacementDomain::SupportedFrameOffsets { support, .. } => Some(support),
402 _ => None,
403 }
404}
405
406#[derive(Clone, Debug, PartialEq)]
408pub struct PlacementRequest {
409 scope: ObjectId,
410 shape: ClearanceShape,
411 obstacles: Vec<ObjectId>,
412 domain: PlacementDomain,
413}
414impl PlacementRequest {
415 pub fn new(scope: ObjectId, shape: ClearanceShape, mut obstacles: Vec<ObjectId>) -> Self {
416 obstacles.sort();
417 obstacles.dedup();
418 Self {
419 scope,
420 shape,
421 obstacles,
422 domain: PlacementDomain::Unconstrained,
423 }
424 }
425 pub fn new_in_domain(
426 scope: ObjectId,
427 shape: ClearanceShape,
428 mut obstacles: Vec<ObjectId>,
429 domain: PlacementDomain,
430 ) -> Result<Self, FreeSpaceError> {
431 let offsets = match &domain {
432 PlacementDomain::FrameOffsets(offsets)
433 | PlacementDomain::SupportedFrameOffsets { offsets, .. } => Some(offsets),
434 _ => None,
435 };
436 if offsets.is_some_and(|offsets| offsets.anchor().origin().subject() != &scope) {
437 return Err(FreeSpaceError::PlacementScopeMismatch);
438 }
439 obstacles.sort();
440 obstacles.dedup();
441 Ok(Self {
442 scope,
443 shape,
444 obstacles,
445 domain,
446 })
447 }
448 pub fn scope(&self) -> &ObjectId {
449 &self.scope
450 }
451 pub fn shape(&self) -> ClearanceShape {
452 self.shape
453 }
454 pub fn obstacles(&self) -> &[ObjectId] {
455 &self.obstacles
456 }
457 pub fn domain(&self) -> &PlacementDomain {
458 &self.domain
459 }
460}
461
462#[derive(Clone, Debug, PartialEq)]
464pub struct CompleteSupportEvidence {
465 support: ObjectId,
466 frame: MetricFrame,
467 maximum_gap_metres: f64,
468 evidence: Evidence,
469}
470
471impl CompleteSupportEvidence {
472 pub fn try_new(
473 support: ObjectId,
474 frame: MetricFrame,
475 maximum_gap_metres: f64,
476 evidence: Evidence,
477 ) -> Result<Self, FreeSpaceError> {
478 if !valid_non_negative(maximum_gap_metres) {
479 return Err(FreeSpaceError::InvalidSupportGap);
480 }
481 if !reviewable_exact_evidence(&evidence) {
482 return Err(FreeSpaceError::InexactSupportEvidence);
483 }
484 Ok(Self {
485 support,
486 frame,
487 maximum_gap_metres,
488 evidence,
489 })
490 }
491 pub fn support(&self) -> &ObjectId {
492 &self.support
493 }
494 pub fn frame(&self) -> &MetricFrame {
495 &self.frame
496 }
497 pub fn maximum_gap_metres(&self) -> f64 {
498 self.maximum_gap_metres
499 }
500 pub fn evidence(&self) -> &Evidence {
501 &self.evidence
502 }
503}
504
505#[derive(Clone, Debug, PartialEq)]
506pub struct CompleteClearanceEvidence {
507 request: ClearanceRequest,
508 evidence: Evidence,
509}
510impl CompleteClearanceEvidence {
511 pub fn try_new(request: ClearanceRequest, evidence: Evidence) -> Result<Self, FreeSpaceError> {
512 if !reviewable_exact_evidence(&evidence) {
513 return Err(FreeSpaceError::IncompleteClearanceEvidence);
514 }
515 Ok(Self { request, evidence })
516 }
517 pub fn request(&self) -> &ClearanceRequest {
518 &self.request
519 }
520 pub fn evidence(&self) -> &Evidence {
521 &self.evidence
522 }
523}
524
525#[derive(Clone, Debug, PartialEq)]
526pub struct ObstructionEvidence {
527 request: ClearanceRequest,
528 blockers: Vec<ObjectId>,
529 evidence: Evidence,
530}
531impl ObstructionEvidence {
532 pub fn try_new(
533 request: ClearanceRequest,
534 mut blockers: Vec<ObjectId>,
535 evidence: Evidence,
536 ) -> Result<Self, FreeSpaceError> {
537 if blockers.is_empty() {
538 return Err(FreeSpaceError::EmptyObstructionEvidence);
539 }
540 if !reviewable_exact_evidence(&evidence) {
541 return Err(FreeSpaceError::InexactObstructionEvidence);
542 }
543 blockers.sort();
544 blockers.dedup();
545 if blockers
546 .iter()
547 .any(|blocker| request.obstacles().binary_search(blocker).is_err())
548 {
549 return Err(FreeSpaceError::UnexpectedObstacleEvidence);
550 }
551 Ok(Self {
552 request,
553 blockers,
554 evidence,
555 })
556 }
557 pub fn request(&self) -> &ClearanceRequest {
558 &self.request
559 }
560 pub fn blockers(&self) -> &[ObjectId] {
561 &self.blockers
562 }
563 pub fn evidence(&self) -> &Evidence {
564 &self.evidence
565 }
566}
567
568#[derive(Clone, Debug, PartialEq)]
569pub enum ClearanceOutcome {
570 Clear(CompleteClearanceEvidence),
571 Obstructed(ObstructionEvidence),
572}
573
574#[derive(Clone, Debug, PartialEq)]
576pub struct ClearancePlacementEvidence {
577 request: PlacementRequest,
578 frame: MetricFrame,
579 support_evidence: Option<Box<CompleteSupportEvidence>>,
580 evidence: Evidence,
581}
582fn validate_placement_witness(
583 request: &PlacementRequest,
584 frame: &MetricFrame,
585 evidence: &Evidence,
586) -> Result<(), FreeSpaceError> {
587 if frame.origin().subject() != request.scope() {
588 return Err(FreeSpaceError::PlacementScopeMismatch);
589 }
590 let offsets = match request.domain() {
591 PlacementDomain::FrameOffsets(offsets)
592 | PlacementDomain::SupportedFrameOffsets { offsets, .. } => Some(offsets),
593 _ => None,
594 };
595 if offsets.is_some_and(|offsets| !offsets.contains_frame(frame)) {
596 return Err(FreeSpaceError::PlacementDomainMismatch);
597 }
598 if !reviewable_exact_evidence(evidence) {
599 return Err(FreeSpaceError::InexactPlacementEvidence);
600 }
601 Ok(())
602}
603
604impl ClearancePlacementEvidence {
605 pub fn try_new(
606 request: PlacementRequest,
607 frame: MetricFrame,
608 evidence: Evidence,
609 ) -> Result<Self, FreeSpaceError> {
610 if requested_support(request.domain()).is_some() {
611 return Err(FreeSpaceError::MissingSupportEvidence);
612 }
613 validate_placement_witness(&request, &frame, &evidence)?;
614 Ok(Self {
615 request,
616 frame,
617 support_evidence: None,
618 evidence,
619 })
620 }
621 pub fn try_new_supported(
622 request: PlacementRequest,
623 frame: MetricFrame,
624 support_evidence: CompleteSupportEvidence,
625 evidence: Evidence,
626 ) -> Result<Self, FreeSpaceError> {
627 validate_placement_witness(&request, &frame, &evidence)?;
628 let required =
629 requested_support(request.domain()).ok_or(FreeSpaceError::SupportEvidenceMismatch)?;
630 if support_evidence.support() != required.support()
631 || support_evidence.frame() != &frame
632 || support_evidence.maximum_gap_metres() > required.maximum_gap_metres()
633 {
634 return Err(FreeSpaceError::SupportEvidenceMismatch);
635 }
636 Ok(Self {
637 request,
638 frame,
639 support_evidence: Some(Box::new(support_evidence)),
640 evidence,
641 })
642 }
643 pub fn request(&self) -> &PlacementRequest {
644 &self.request
645 }
646 pub fn frame(&self) -> &MetricFrame {
647 &self.frame
648 }
649 pub fn support_evidence(&self) -> Option<&CompleteSupportEvidence> {
650 self.support_evidence.as_deref()
651 }
652 pub fn evidence(&self) -> &Evidence {
653 &self.evidence
654 }
655}
656
657#[derive(Clone, Debug, PartialEq)]
659pub struct CompletePlacementEvidence {
660 request: PlacementRequest,
661 evidence: Evidence,
662}
663impl CompletePlacementEvidence {
664 pub fn try_new(request: PlacementRequest, evidence: Evidence) -> Result<Self, FreeSpaceError> {
665 if !reviewable_exact_evidence(&evidence) {
666 return Err(FreeSpaceError::IncompleteClearanceEvidence);
667 }
668 Ok(Self { request, evidence })
669 }
670 pub fn request(&self) -> &PlacementRequest {
671 &self.request
672 }
673 pub fn evidence(&self) -> &Evidence {
674 &self.evidence
675 }
676}
677
678#[derive(Clone, Debug, PartialEq)]
679pub enum PlacementOutcome {
680 Found(ClearancePlacementEvidence),
681 NoPlacement(CompletePlacementEvidence),
682}
683
684#[derive(Clone, Debug, PartialEq)]
685pub struct FreeAreaEvidence {
686 request: FreeAreaRequest,
687 available_area: AreaInterval,
688 evidence: Evidence,
689}
690impl FreeAreaEvidence {
691 pub fn try_new(
692 request: FreeAreaRequest,
693 available_area: AreaInterval,
694 evidence: Evidence,
695 ) -> Result<Self, FreeSpaceError> {
696 if !reviewable_exact_evidence(&evidence) {
697 return Err(FreeSpaceError::InexactAreaEvidence);
698 }
699 Ok(Self {
700 request,
701 available_area,
702 evidence,
703 })
704 }
705 pub fn request(&self) -> &FreeAreaRequest {
706 &self.request
707 }
708 pub fn available_area(&self) -> &AreaInterval {
709 &self.available_area
710 }
711 pub fn evidence(&self) -> &Evidence {
712 &self.evidence
713 }
714}
715
716pub trait FreeSpaceService: Send + Sync + 'static {
717 fn assess_clearance(
718 &self,
719 request: &ClearanceRequest,
720 ) -> Result<ClearanceOutcome, FreeSpaceError>;
721 fn find_placement(
722 &self,
723 request: &PlacementRequest,
724 ) -> Result<PlacementOutcome, FreeSpaceError>;
725 fn measure_free_area(
726 &self,
727 request: &FreeAreaRequest,
728 ) -> Result<FreeAreaEvidence, FreeSpaceError>;
729}
730
731#[derive(Clone)]
732pub struct FreeSpaceServiceHandle(Arc<dyn FreeSpaceService>);
733impl FreeSpaceServiceHandle {
734 pub fn new(service: Arc<dyn FreeSpaceService>) -> Self {
735 Self(service)
736 }
737 pub fn assess_clearance(
738 &self,
739 request: &ClearanceRequest,
740 ) -> Result<ClearanceOutcome, FreeSpaceError> {
741 let outcome = self.0.assess_clearance(request)?;
742 let actual = match &outcome {
743 ClearanceOutcome::Clear(value) => value.request(),
744 ClearanceOutcome::Obstructed(value) => value.request(),
745 };
746 if actual != request {
747 return Err(FreeSpaceError::ResponseRequestMismatch);
748 }
749 Ok(outcome)
750 }
751 pub fn find_placement(
752 &self,
753 request: &PlacementRequest,
754 ) -> Result<PlacementOutcome, FreeSpaceError> {
755 let outcome = self.0.find_placement(request)?;
756 let actual = match &outcome {
757 PlacementOutcome::Found(value) => value.request(),
758 PlacementOutcome::NoPlacement(value) => value.request(),
759 };
760 if actual != request {
761 return Err(FreeSpaceError::ResponseRequestMismatch);
762 }
763 Ok(outcome)
764 }
765 pub fn measure_free_area(
766 &self,
767 request: &FreeAreaRequest,
768 ) -> Result<FreeAreaEvidence, FreeSpaceError> {
769 let evidence = self.0.measure_free_area(request)?;
770 if evidence.request() != request {
771 return Err(FreeSpaceError::ResponseRequestMismatch);
772 }
773 Ok(evidence)
774 }
775}
776
777fn valid_non_negative(value: f64) -> bool {
778 value.is_finite() && value >= 0.0
779}
780fn valid_positive(value: f64) -> bool {
781 value.is_finite() && value > 0.0
782}