axiolid_exact/certify.rs
1//! The two-tier driver: interval filter first, exact fallback second.
2
3use axiolid_guarantees::{Certified, Precision, Sign};
4
5use crate::arith::Arith;
6use crate::dyadic::Dyadic;
7use crate::interval::Interval;
8
9/// Why an exact computation was refused.
10#[non_exhaustive]
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum ExactError {
13 /// An input was NaN or infinite. Exact values exist only for finite
14 /// numbers.
15 NonFinite,
16 /// A line's two defining points coincide, so it has no direction.
17 DegenerateLine,
18 /// A circle's radius was negative.
19 NegativeRadius,
20 /// Two parameters were compared along different lines, where
21 /// parameters mean different things.
22 DifferentLines,
23 /// The expression has no real value in exact arithmetic (for example a
24 /// square root of a negative number). A construction that validated its
25 /// inputs never reports this.
26 Undefined,
27 /// More nested square roots than [`crate::tower::MAX_DEPTH`]: cost
28 /// grows exponentially with depth, so the tower refuses rather than
29 /// run unboundedly.
30 TooDeep,
31 /// A conic whose quadratic part is identically zero (it is a line), or
32 /// a conic pair no admissible shear could separate.
33 DegenerateConic,
34}
35
36/// Refuse NaN and infinities before any arithmetic runs.
37pub fn require_finite(values: &[f64]) -> Result<(), ExactError> {
38 if values.iter().all(|value| value.is_finite()) {
39 Ok(())
40 } else {
41 Err(ExactError::NonFinite)
42 }
43}
44
45/// A sign question, written once and evaluable in any [`Arith`].
46///
47/// Implementors build their polynomial from `T::from_f64` of the inputs and
48/// return `None` only when `T` cannot decide. The same code then runs as
49/// the interval filter and as the exact fallback.
50pub trait SignExpr {
51 /// The sign in arithmetic `T`, or `None` when `T` cannot decide.
52 fn sign_in<T: Arith>(&self) -> Option<Sign>;
53}
54
55/// The interval filter alone, so escalation can be observed and measured.
56///
57/// [`Certified::Uncertain`] means the exact tier is needed.
58pub fn filter<E: SignExpr>(expr: &E) -> Certified {
59 match expr.sign_in::<Interval>() {
60 Some(sign) => Certified::Certain {
61 sign,
62 precision: Precision::F64,
63 },
64 None => Certified::Uncertain {
65 attempted: Precision::F64,
66 },
67 }
68}
69
70/// The proven sign: filter, then exact arithmetic if the filter was
71/// undecided.
72///
73/// Callers must have checked their inputs are finite
74/// ([`require_finite`]); the exact tier has no value for NaN or infinity.
75pub fn certify<E: SignExpr>(expr: &E) -> Result<Sign, ExactError> {
76 if let Some(sign) = expr.sign_in::<Interval>() {
77 return Ok(sign);
78 }
79 expr.sign_in::<Dyadic>().ok_or(ExactError::Undefined)
80}