Skip to main content

oxiphysics_core/interval/
functions.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use super::types::{Interval, IntervalNewtonResult};
6
7/// Interval sqrt: `sqrt([lo, hi]) = [sqrt(lo), sqrt(hi)]`.
8///
9/// Panics if `lo < 0`.
10pub fn sqrt_interval(a: Interval) -> Interval {
11    assert!(a.lo >= 0.0, "sqrt_interval: lo must be ≥ 0, got {}", a.lo);
12    Interval {
13        lo: a.lo.sqrt(),
14        hi: a.hi.sqrt(),
15    }
16}
17/// Interval absolute value.
18///
19/// - If `lo >= 0`: `[lo, hi]`
20/// - If `hi <= 0`: `[-hi, -lo]`
21/// - Otherwise (0 ∈ interval): `[0, max(|lo|, |hi|)]`
22pub fn abs_interval(a: Interval) -> Interval {
23    if a.lo >= 0.0 {
24        a
25    } else if a.hi <= 0.0 {
26        Interval {
27            lo: -a.hi,
28            hi: -a.lo,
29        }
30    } else {
31        Interval {
32            lo: 0.0,
33            hi: (-a.lo).max(a.hi),
34        }
35    }
36}
37/// Tighter squaring of an interval (tighter than `a * a`).
38///
39/// - If `0 ∈ a`: `[0, max(lo², hi²)]`
40/// - Otherwise: `[min(lo², hi²), max(lo², hi²)]`
41pub fn square(a: Interval) -> Interval {
42    let lo_sq = a.lo * a.lo;
43    let hi_sq = a.hi * a.hi;
44    if a.lo <= 0.0 && a.hi >= 0.0 {
45        Interval {
46            lo: 0.0,
47            hi: lo_sq.max(hi_sq),
48        }
49    } else {
50        Interval {
51            lo: lo_sq.min(hi_sq),
52            hi: lo_sq.max(hi_sq),
53        }
54    }
55}
56/// Interval power: compute `a^n` for non-negative integer `n`.
57pub fn pow_interval(a: Interval, n: u32) -> Interval {
58    match n {
59        0 => Interval::point(1.0),
60        1 => a,
61        2 => square(a),
62        _ => {
63            if n.is_multiple_of(2) {
64                let abs_a = abs_interval(a);
65                Interval {
66                    lo: abs_a.lo.powi(n as i32),
67                    hi: abs_a.hi.powi(n as i32),
68                }
69            } else {
70                Interval {
71                    lo: a.lo.powi(n as i32),
72                    hi: a.hi.powi(n as i32),
73                }
74            }
75        }
76    }
77}
78/// Interval sin: conservative enclosure of sin over `[lo, hi]`.
79pub fn sin_interval(a: Interval) -> Interval {
80    let sin_lo = a.lo.sin();
81    let sin_hi = a.hi.sin();
82    let mut lo = sin_lo.min(sin_hi);
83    let mut hi = sin_lo.max(sin_hi);
84    let two_pi = 2.0 * std::f64::consts::PI;
85    let half_pi = std::f64::consts::FRAC_PI_2;
86    let k_start = ((a.lo - half_pi) / two_pi).ceil() as i64;
87    let k_end = ((a.hi - half_pi) / two_pi).floor() as i64;
88    if k_start <= k_end {
89        hi = 1.0;
90    }
91    let k_start2 = ((a.lo + half_pi) / two_pi).ceil() as i64;
92    let k_end2 = ((a.hi + half_pi) / two_pi).floor() as i64;
93    if k_start2 <= k_end2 {
94        lo = -1.0;
95    }
96    Interval { lo, hi }
97}
98/// Interval cos: conservative enclosure of cos over `[lo, hi]`.
99pub fn cos_interval(a: Interval) -> Interval {
100    let shifted = Interval::new(
101        a.lo + std::f64::consts::FRAC_PI_2,
102        a.hi + std::f64::consts::FRAC_PI_2,
103    );
104    sin_interval(shifted)
105}
106/// Interval exp: `exp([lo, hi]) = [exp(lo), exp(hi)]`.
107pub fn exp_interval(a: Interval) -> Interval {
108    Interval {
109        lo: a.lo.exp(),
110        hi: a.hi.exp(),
111    }
112}
113/// Interval ln: `ln([lo, hi]) = [ln(lo), ln(hi)]`. Panics if `lo <= 0`.
114pub fn ln_interval(a: Interval) -> Interval {
115    assert!(a.lo > 0.0, "ln_interval: lo must be > 0, got {}", a.lo);
116    Interval {
117        lo: a.lo.ln(),
118        hi: a.hi.ln(),
119    }
120}
121/// Perform interval Newton iteration on a function `f` with derivative `f'`.
122///
123/// Given `x_k`, the next iterate is:
124///   N(x_k) = mid(x_k) - f(mid(x_k)) / F'(x_k)
125/// intersected with x_k.
126///
127/// Uses an explicit work-stack to avoid recursive monomorphization issues.
128///
129/// - `f`: function evaluated at a point
130/// - `f_interval`: function evaluated over an interval (inclusion function)
131/// - `df_interval`: derivative evaluated over an interval
132/// - `initial`: starting interval
133/// - `tol`: width tolerance for convergence
134/// - `max_iter`: maximum iterations per sub-interval
135pub fn interval_newton(
136    f: &dyn Fn(f64) -> f64,
137    _f_interval: &dyn Fn(Interval) -> Interval,
138    df_interval: &dyn Fn(Interval) -> Interval,
139    initial: Interval,
140    tol: f64,
141    max_iter: usize,
142) -> IntervalNewtonResult {
143    let mut stack: Vec<(Interval, usize)> = vec![(initial, max_iter)];
144    let mut results: Vec<Interval> = Vec::new();
145    while let Some((mut x, iters_left)) = stack.pop() {
146        let mut converged = false;
147        for _ in 0..iters_left {
148            if x.width() < tol {
149                results.push(x);
150                converged = true;
151                break;
152            }
153            let m = x.midpoint();
154            let fm = f(m);
155            let df = df_interval(x);
156            if df.contains(0.0) {
157                let (left, right) = x.bisect();
158                let sub_iter = iters_left / 2;
159                if sub_iter > 0 {
160                    stack.push((left, sub_iter));
161                    stack.push((right, sub_iter));
162                }
163                converged = true;
164                break;
165            }
166            let correction = Interval::point(fm) / df;
167            let newton_step = Interval::point(m) - correction;
168            match Interval::intersection(x, newton_step) {
169                Some(new_x) => {
170                    if new_x.width() >= x.width() * 0.99 {
171                        let (left, right) = x.bisect();
172                        let sub_iter = iters_left / 2;
173                        if sub_iter > 0 {
174                            stack.push((left, sub_iter));
175                            stack.push((right, sub_iter));
176                        }
177                        converged = true;
178                        break;
179                    }
180                    x = new_x;
181                }
182                None => {
183                    converged = true;
184                    break;
185                }
186            }
187        }
188        if !converged {
189            results.push(x);
190        }
191    }
192    if results.is_empty() {
193        IntervalNewtonResult::NoRoot
194    } else if results.len() == 1 {
195        IntervalNewtonResult::Root(
196            results
197                .into_iter()
198                .next()
199                .expect("results has exactly one element"),
200        )
201    } else {
202        IntervalNewtonResult::Multiple(results)
203    }
204}
205/// Find all root-containing sub-intervals using bisection with an inclusion function.
206///
207/// Subdivides `initial` into pieces of width ≤ `tol`, keeping only those
208/// sub-intervals where `f_interval` contains zero.
209pub fn interval_bisection<F>(
210    f_interval: F,
211    initial: Interval,
212    tol: f64,
213    max_depth: usize,
214) -> Vec<Interval>
215where
216    F: Fn(Interval) -> Interval,
217{
218    let mut stack = vec![(initial, 0usize)];
219    let mut results = Vec::new();
220    while let Some((iv, depth)) = stack.pop() {
221        let fiv = f_interval(iv);
222        if !fiv.contains(0.0) {
223            continue;
224        }
225        if iv.width() <= tol || depth >= max_depth {
226            results.push(iv);
227            continue;
228        }
229        let (left, right) = iv.bisect();
230        stack.push((left, depth + 1));
231        stack.push((right, depth + 1));
232    }
233    results
234}
235/// Global counter for noise symbol IDs.
236pub(super) static NOISE_COUNTER: std::sync::atomic::AtomicUsize =
237    std::sync::atomic::AtomicUsize::new(0);
238/// Allocate a new noise symbol ID.
239pub(super) fn new_noise_id() -> usize {
240    NOISE_COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
241}
242/// Simple constraint propagation over interval boxes.
243///
244/// An `IntervalConstraint` narrows an interval box by propagating
245/// known relationships between variables.
246pub trait IntervalConstraint {
247    /// Try to narrow the given interval box. Returns `true` if narrowing occurred.
248    fn propagate(&self, box_: &mut [Interval]) -> bool;
249}
250/// Run constraint propagation to a fixed point (or max iterations).
251pub fn propagate_constraints(
252    constraints: &[&dyn IntervalConstraint],
253    box_: &mut [Interval],
254    max_iter: usize,
255) -> usize {
256    let mut total_iters = 0;
257    for _ in 0..max_iter {
258        let mut any_changed = false;
259        for c in constraints {
260            if c.propagate(box_) {
261                any_changed = true;
262            }
263        }
264        total_iters += 1;
265        if !any_changed {
266            break;
267        }
268    }
269    total_iters
270}
271/// Conservative interval bound for `tan(a)`.
272///
273/// Returns `[-∞, +∞]` if the interval contains any odd multiple of `π/2`
274/// (where tan is discontinuous), otherwise evaluates endpoint bounds.
275pub fn tan_interval(a: Interval) -> Interval {
276    use std::f64::consts::FRAC_PI_2;
277    let lo_k = (a.lo / std::f64::consts::PI - 0.5).ceil() as i64;
278    let hi_k = (a.hi / std::f64::consts::PI - 0.5).floor() as i64;
279    if lo_k <= hi_k {
280        return Interval {
281            lo: f64::NEG_INFINITY,
282            hi: f64::INFINITY,
283        };
284    }
285    let t_lo = a.lo.tan();
286    let t_hi = a.hi.tan();
287    let _ = FRAC_PI_2;
288    Interval {
289        lo: t_lo.min(t_hi),
290        hi: t_lo.max(t_hi),
291    }
292}
293/// Conservative interval for `atan(a)`.
294///
295/// `atan` is monotone on all of ℝ so `[atan(lo), atan(hi)]`.
296pub fn atan_interval(a: Interval) -> Interval {
297    Interval {
298        lo: a.lo.atan(),
299        hi: a.hi.atan(),
300    }
301}
302/// Interval min: `min([a.lo,a.hi], [b.lo,b.hi]) = [min(a.lo,b.lo), min(a.hi,b.hi)]`.
303pub fn interval_min(a: Interval, b: Interval) -> Interval {
304    Interval {
305        lo: a.lo.min(b.lo),
306        hi: a.hi.min(b.hi),
307    }
308}
309/// Interval max: `max([a.lo,a.hi], [b.lo,b.hi]) = [max(a.lo,b.lo), max(a.hi,b.hi)]`.
310pub fn interval_max(a: Interval, b: Interval) -> Interval {
311    Interval {
312        lo: a.lo.max(b.lo),
313        hi: a.hi.max(b.hi),
314    }
315}
316/// Returns a tighter interval bound for `f` on `[a.lo, a.hi]` by evaluating
317/// at `n` equally-spaced sample points and taking their hull.
318///
319/// This is a purely rigorous-*sampling* approach — it gives an inner bound
320/// (the true enclosure is at least as wide), but is useful for comparison or
321/// for functions where monotonicity is hard to determine analytically.
322pub fn sample_enclosure(f: impl Fn(f64) -> f64, a: Interval, n: usize) -> Interval {
323    assert!(n >= 2, "need at least 2 sample points");
324    let step = a.width() / (n - 1) as f64;
325    let mut lo = f64::INFINITY;
326    let mut hi = f64::NEG_INFINITY;
327    for i in 0..n {
328        let x = a.lo + i as f64 * step;
329        let y = f(x);
330        if y < lo {
331            lo = y;
332        }
333        if y > hi {
334            hi = y;
335        }
336    }
337    Interval { lo, hi }
338}
339/// Recursively splits `iv` until all sub-intervals have width ≤ `max_width`.
340///
341/// Returns the list of sub-intervals.
342pub fn subdivide(iv: Interval, max_width: f64) -> Vec<Interval> {
343    if iv.width() <= max_width {
344        return vec![iv];
345    }
346    let (left, right) = iv.bisect();
347    let mut result = subdivide(left, max_width);
348    result.extend(subdivide(right, max_width));
349    result
350}
351/// Evaluates `f` on each sub-interval and returns the hull of all results.
352///
353/// Achieves tighter enclosure than evaluating `f` on the original interval
354/// when `f` suffers from the dependency problem.
355pub fn range_by_subdivision(
356    f: impl Fn(Interval) -> Interval,
357    iv: Interval,
358    max_width: f64,
359) -> Interval {
360    let parts = subdivide(iv, max_width);
361    parts.iter().fold(Interval::empty(), |acc, &sub| {
362        let fv = f(sub);
363        if acc.is_empty() {
364            fv
365        } else {
366            Interval::hull(acc, fv)
367        }
368    })
369}
370/// First-order Taylor enclosure of `f` around the midpoint of `iv`.
371///
372/// `f_mid` = `f(midpoint(iv))` (a point), `df_iv` = bound on `f'` over `iv`.
373///
374/// Returns `f_mid + df_iv * (iv - midpoint(iv))`.
375pub fn taylor_enclosure(f_mid: f64, df_iv: Interval, iv: Interval) -> Interval {
376    let m = iv.midpoint();
377    let delta = iv - Interval::point(m);
378    Interval::point(f_mid) + df_iv * delta
379}
380/// Krawczyk operator for root verification.
381///
382/// Given `f : ℝ → ℝ`, approximate Jacobian inverse `y ≈ 1/f'(m)`, and
383/// interval `iv`, computes `K = m - y*f(m) + (I - y*f'(iv))*(iv - m)`.
384///
385/// If `K ⊆ iv` then `iv` contains exactly one root.  If `K ∩ iv = ∅`
386/// then `iv` contains no root.
387pub fn krawczyk_operator(f_mid: f64, y: f64, df_iv: Interval, iv: Interval) -> Interval {
388    let m = iv.midpoint();
389    let radius = iv - Interval::point(m);
390    let coeff = Interval::point(1.0) - Interval::point(y) * df_iv;
391    Interval::point(m - y * f_mid) + coeff * radius
392}
393#[cfg(test)]
394mod tests {
395    use super::*;
396    use crate::interval::AffineForm;
397    use crate::interval::BoundConstraint;
398    use crate::interval::Interval3;
399    use crate::interval::IntervalMatrix;
400    use crate::interval::IntervalMatrix2;
401    use crate::interval::IntervalMatrix3;
402    use crate::interval::IntervalVec;
403    use crate::interval::LinearConstraint;
404    use crate::interval::TaylorModel;
405    #[test]
406    fn test_add() {
407        let a = Interval::new(1.0, 2.0);
408        let b = Interval::new(3.0, 4.0);
409        assert_eq!(a + b, Interval::new(4.0, 6.0));
410    }
411    #[test]
412    fn test_mul_positive() {
413        let a = Interval::new(1.0, 2.0);
414        let b = Interval::new(3.0, 4.0);
415        assert_eq!(a * b, Interval::new(3.0, 8.0));
416    }
417    #[test]
418    fn test_mul_mixed_signs() {
419        let a = Interval::new(-2.0, 1.0);
420        let b = Interval::new(-2.0, 1.0);
421        let result = a * b;
422        assert_eq!(result, Interval::new(-2.0, 4.0));
423    }
424    #[test]
425    fn test_sqrt_interval() {
426        let a = Interval::new(4.0, 9.0);
427        assert_eq!(sqrt_interval(a), Interval::new(2.0, 3.0));
428    }
429    #[test]
430    fn test_overlaps_true() {
431        let a = Interval::new(1.0, 3.0);
432        let b = Interval::new(2.0, 4.0);
433        assert!(a.overlaps(&b));
434    }
435    #[test]
436    fn test_overlaps_false() {
437        let a = Interval::new(1.0, 2.0);
438        let b = Interval::new(3.0, 4.0);
439        assert!(!a.overlaps(&b));
440    }
441    #[test]
442    fn test_interval3_overlap() {
443        let a = Interval3::from_aabb([0.0, 0.0, 0.0], [2.0, 2.0, 2.0]);
444        let b = Interval3::from_aabb([1.0, 1.0, 1.0], [3.0, 3.0, 3.0]);
445        assert!(a.overlaps_3d(&b));
446        let c = Interval3::from_aabb([3.0, 0.0, 0.0], [5.0, 2.0, 2.0]);
447        assert!(!a.overlaps_3d(&c));
448    }
449    #[test]
450    fn test_sub() {
451        let a = Interval::new(3.0, 5.0);
452        let b = Interval::new(1.0, 2.0);
453        assert_eq!(a - b, Interval::new(1.0, 4.0));
454    }
455    #[test]
456    fn test_neg() {
457        let a = Interval::new(1.0, 3.0);
458        assert_eq!(-a, Interval::new(-3.0, -1.0));
459    }
460    #[test]
461    fn test_square() {
462        let a = Interval::new(-2.0, 1.0);
463        assert_eq!(square(a), Interval::new(0.0, 4.0));
464        let b = Interval::new(1.0, 2.0);
465        assert_eq!(square(b), Interval::new(1.0, 4.0));
466        let c = Interval::new(-2.0, -1.0);
467        assert_eq!(square(c), Interval::new(1.0, 4.0));
468    }
469    #[test]
470    fn test_abs_interval() {
471        assert_eq!(
472            abs_interval(Interval::new(1.0, 3.0)),
473            Interval::new(1.0, 3.0)
474        );
475        assert_eq!(
476            abs_interval(Interval::new(-3.0, -1.0)),
477            Interval::new(1.0, 3.0)
478        );
479        assert_eq!(
480            abs_interval(Interval::new(-2.0, 3.0)),
481            Interval::new(0.0, 3.0)
482        );
483    }
484    #[test]
485    fn test_hull() {
486        let a = Interval::new(1.0, 3.0);
487        let b = Interval::new(2.0, 5.0);
488        assert_eq!(Interval::hull(a, b), Interval::new(1.0, 5.0));
489    }
490    #[test]
491    fn test_width_midpoint_contains() {
492        let a = Interval::new(2.0, 6.0);
493        assert_eq!(a.width(), 4.0);
494        assert_eq!(a.midpoint(), 4.0);
495        assert!(a.contains(3.0));
496        assert!(!a.contains(7.0));
497    }
498    #[test]
499    fn test_length_sq_bounds() {
500        let iv = Interval3::from_aabb([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]);
501        let bounds = iv.length_sq_bounds();
502        assert_eq!(bounds.lo, 0.0);
503        assert_eq!(bounds.hi, 3.0);
504    }
505    #[test]
506    fn test_from_to_aabb() {
507        let min = [1.0, 2.0, 3.0];
508        let max = [4.0, 5.0, 6.0];
509        let iv = Interval3::from_aabb(min, max);
510        let (out_min, out_max) = iv.to_aabb();
511        assert_eq!(out_min, min);
512        assert_eq!(out_max, max);
513    }
514    #[test]
515    fn test_div() {
516        let a = Interval::new(2.0, 6.0);
517        let b = Interval::new(1.0, 2.0);
518        let r = a / b;
519        assert_eq!(r, Interval::new(1.0, 6.0));
520    }
521    #[test]
522    fn test_scalar_mul() {
523        let a = Interval::new(1.0, 3.0);
524        assert_eq!(a * 2.0, Interval::new(2.0, 6.0));
525        assert_eq!(a * (-1.0), Interval::new(-3.0, -1.0));
526        assert_eq!(a * 0.0, Interval::new(0.0, 0.0));
527    }
528    #[test]
529    fn test_reciprocal() {
530        let a = Interval::new(2.0, 4.0);
531        let r = a.reciprocal();
532        assert!((r.lo - 0.25).abs() < 1e-15);
533        assert!((r.hi - 0.5).abs() < 1e-15);
534    }
535    #[test]
536    fn test_intersection() {
537        let a = Interval::new(1.0, 4.0);
538        let b = Interval::new(2.0, 5.0);
539        let c = Interval::intersection(a, b).unwrap();
540        assert_eq!(c, Interval::new(2.0, 4.0));
541        let d = Interval::new(5.0, 6.0);
542        assert!(Interval::intersection(a, d).is_none());
543    }
544    #[test]
545    fn test_bisect() {
546        let a = Interval::new(0.0, 4.0);
547        let (left, right) = a.bisect();
548        assert_eq!(left, Interval::new(0.0, 2.0));
549        assert_eq!(right, Interval::new(2.0, 4.0));
550    }
551    #[test]
552    fn test_split_at() {
553        let a = Interval::new(0.0, 10.0);
554        let (l, r) = a.split_at(3.0);
555        assert_eq!(l, Interval::new(0.0, 3.0));
556        assert_eq!(r, Interval::new(3.0, 10.0));
557    }
558    #[test]
559    fn test_inflate() {
560        let a = Interval::new(1.0, 3.0);
561        let b = a.inflate(0.5);
562        assert_eq!(b, Interval::new(0.5, 3.5));
563    }
564    #[test]
565    fn test_radius() {
566        let a = Interval::new(1.0, 5.0);
567        assert!((a.radius() - 2.0).abs() < 1e-15);
568    }
569    #[test]
570    fn test_contains_interval() {
571        let a = Interval::new(0.0, 10.0);
572        let b = Interval::new(2.0, 8.0);
573        assert!(a.contains_interval(&b));
574        assert!(!b.contains_interval(&a));
575    }
576    #[test]
577    fn test_hull_many() {
578        let intervals = vec![
579            Interval::new(1.0, 3.0),
580            Interval::new(5.0, 7.0),
581            Interval::new(-1.0, 2.0),
582        ];
583        let h = Interval::hull_many(&intervals);
584        assert_eq!(h, Interval::new(-1.0, 7.0));
585    }
586    #[test]
587    fn test_empty_interval() {
588        let e = Interval::empty();
589        assert!(e.is_empty());
590        let a = Interval::new(1.0, 2.0);
591        assert!(!a.is_empty());
592    }
593    #[test]
594    fn test_pow_interval() {
595        let a = Interval::new(-2.0, 3.0);
596        assert_eq!(pow_interval(a, 0), Interval::point(1.0));
597        assert_eq!(pow_interval(a, 1), a);
598        let sq = pow_interval(a, 2);
599        assert!((sq.lo - 0.0).abs() < 1e-15);
600        assert!((sq.hi - 9.0).abs() < 1e-15);
601    }
602    #[test]
603    fn test_exp_interval() {
604        let a = Interval::new(0.0, 1.0);
605        let e = exp_interval(a);
606        assert!((e.lo - 1.0).abs() < 1e-10);
607        assert!((e.hi - std::f64::consts::E).abs() < 1e-10);
608    }
609    #[test]
610    fn test_ln_interval() {
611        let a = Interval::new(1.0, std::f64::consts::E);
612        let l = ln_interval(a);
613        assert!((l.lo - 0.0).abs() < 1e-10);
614        assert!((l.hi - 1.0).abs() < 1e-10);
615    }
616    #[test]
617    fn test_sin_interval_full_period() {
618        let a = Interval::new(0.0, 2.0 * std::f64::consts::PI);
619        let s = sin_interval(a);
620        assert!((s.lo - (-1.0)).abs() < 1e-10);
621        assert!((s.hi - 1.0).abs() < 1e-10);
622    }
623    #[test]
624    fn test_sin_interval_small() {
625        let a = Interval::new(0.0, std::f64::consts::FRAC_PI_6);
626        let s = sin_interval(a);
627        assert!(s.lo <= 0.0 + 1e-10);
628        assert!((s.hi - 0.5).abs() < 1e-10);
629    }
630    #[test]
631    fn test_cos_interval() {
632        let a = Interval::new(0.0, std::f64::consts::PI);
633        let c = cos_interval(a);
634        assert!((c.lo - (-1.0)).abs() < 1e-10);
635        assert!((c.hi - 1.0).abs() < 1e-10);
636    }
637    #[test]
638    fn test_interval_bisection_finds_root() {
639        let f_iv = |iv: Interval| square(iv) - Interval::point(2.0);
640        let roots = interval_bisection(f_iv, Interval::new(0.0, 2.0), 0.01, 20);
641        assert!(!roots.is_empty());
642        let sqrt2 = 2.0_f64.sqrt();
643        let found = roots.iter().any(|r| r.contains(sqrt2));
644        assert!(found, "sqrt(2) not found in any root interval: {:?}", roots);
645    }
646    #[test]
647    fn test_interval_bisection_no_root() {
648        let f_iv = |iv: Interval| square(iv) + Interval::point(1.0);
649        let roots = interval_bisection(f_iv, Interval::new(-5.0, 5.0), 0.01, 20);
650        assert!(roots.is_empty(), "should find no roots, got {:?}", roots);
651    }
652    #[test]
653    fn test_interval_newton_basic() {
654        let f = |x: f64| x * x - 4.0;
655        let f_iv = |iv: Interval| square(iv) - Interval::point(4.0);
656        let df_iv = |iv: Interval| iv * 2.0;
657        let result = interval_newton(&f, &f_iv, &df_iv, Interval::new(1.0, 3.0), 1e-10, 50);
658        match result {
659            IntervalNewtonResult::Root(r) => {
660                assert!(r.contains(2.0), "root interval {:?} should contain 2.0", r);
661                assert!(
662                    r.width() < 1e-8,
663                    "root should be tight, width={}",
664                    r.width()
665                );
666            }
667            other => panic!("expected Root, got {:?}", other),
668        }
669    }
670    #[test]
671    fn test_interval_vec_operations() {
672        let a = IntervalVec::from_slice(&[Interval::new(1.0, 2.0), Interval::new(3.0, 4.0)]);
673        let b = IntervalVec::from_slice(&[Interval::new(0.5, 1.5), Interval::new(2.0, 3.0)]);
674        let sum = a.add(&b);
675        assert_eq!(sum.data[0], Interval::new(1.5, 3.5));
676        assert_eq!(sum.data[1], Interval::new(5.0, 7.0));
677        let diff = a.sub(&b);
678        assert_eq!(diff.data[0], Interval::new(-0.5, 1.5));
679    }
680    #[test]
681    fn test_interval_vec_dot() {
682        let a = IntervalVec::from_slice(&[Interval::point(1.0), Interval::point(2.0)]);
683        let b = IntervalVec::from_slice(&[Interval::point(3.0), Interval::point(4.0)]);
684        let d = a.dot(&b);
685        assert!((d.lo - 11.0).abs() < 1e-15);
686        assert!((d.hi - 11.0).abs() < 1e-15);
687    }
688    #[test]
689    fn test_interval_vec_hull() {
690        let a = IntervalVec::from_slice(&[Interval::new(1.0, 3.0)]);
691        let b = IntervalVec::from_slice(&[Interval::new(2.0, 5.0)]);
692        let h = a.hull(&b);
693        assert_eq!(h.data[0], Interval::new(1.0, 5.0));
694    }
695    #[test]
696    fn test_interval_vec_max_width() {
697        let v = IntervalVec::from_slice(&[
698            Interval::new(0.0, 1.0),
699            Interval::new(0.0, 3.0),
700            Interval::new(0.0, 2.0),
701        ]);
702        assert!((v.max_width() - 3.0).abs() < 1e-15);
703    }
704    #[test]
705    fn test_interval_vec_midpoint() {
706        let v = IntervalVec::from_slice(&[Interval::new(0.0, 4.0), Interval::new(2.0, 6.0)]);
707        let mid = v.midpoint();
708        assert!((mid[0] - 2.0).abs() < 1e-15);
709        assert!((mid[1] - 4.0).abs() < 1e-15);
710    }
711    #[test]
712    fn test_interval_matrix_identity() {
713        let id = IntervalMatrix::identity(3);
714        assert_eq!(id.get(0, 0), Interval::point(1.0));
715        assert_eq!(id.get(0, 1), Interval::point(0.0));
716        assert_eq!(id.get(1, 1), Interval::point(1.0));
717    }
718    #[test]
719    fn test_interval_matrix_mul_vec() {
720        let mut m = IntervalMatrix::zeros(2, 2);
721        m.set(0, 0, Interval::point(1.0));
722        m.set(0, 1, Interval::point(2.0));
723        m.set(1, 0, Interval::point(3.0));
724        m.set(1, 1, Interval::point(4.0));
725        let v = IntervalVec::from_slice(&[Interval::point(1.0), Interval::point(1.0)]);
726        let r = m.mul_vec(&v);
727        assert!((r.data[0].lo - 3.0).abs() < 1e-15);
728        assert!((r.data[1].lo - 7.0).abs() < 1e-15);
729    }
730    #[test]
731    fn test_interval_matrix_mul_mat() {
732        let id = IntervalMatrix::identity(2);
733        let mut m = IntervalMatrix::zeros(2, 2);
734        m.set(0, 0, Interval::point(5.0));
735        m.set(0, 1, Interval::point(6.0));
736        m.set(1, 0, Interval::point(7.0));
737        m.set(1, 1, Interval::point(8.0));
738        let r = id.mul_mat(&m);
739        assert_eq!(r.get(0, 0), Interval::point(5.0));
740        assert_eq!(r.get(1, 1), Interval::point(8.0));
741    }
742    #[test]
743    fn test_interval_matrix_transpose() {
744        let mut m = IntervalMatrix::zeros(2, 3);
745        m.set(0, 2, Interval::point(42.0));
746        let t = m.transpose();
747        assert_eq!(t.rows, 3);
748        assert_eq!(t.cols, 2);
749        assert_eq!(t.get(2, 0), Interval::point(42.0));
750    }
751    #[test]
752    fn test_affine_form_constant() {
753        let a = AffineForm::constant(3.0);
754        let iv = a.to_interval();
755        assert!((iv.lo - 3.0).abs() < 1e-15);
756        assert!((iv.hi - 3.0).abs() < 1e-15);
757    }
758    #[test]
759    fn test_affine_from_interval() {
760        let iv = Interval::new(1.0, 5.0);
761        let a = AffineForm::from_interval(iv);
762        let back = a.to_interval();
763        assert!((back.lo - 1.0).abs() < 1e-10);
764        assert!((back.hi - 5.0).abs() < 1e-10);
765    }
766    #[test]
767    fn test_affine_add_sub() {
768        let iv1 = Interval::new(1.0, 3.0);
769        let iv2 = Interval::new(2.0, 4.0);
770        let a = AffineForm::from_interval(iv1);
771        let b = AffineForm::from_interval(iv2);
772        let sum = a.add(&b);
773        let sum_iv = sum.to_interval();
774        assert!(sum_iv.lo <= 3.0 + 1e-10);
775        assert!(sum_iv.hi >= 7.0 - 1e-10);
776        let diff = a.sub(&b);
777        let diff_iv = diff.to_interval();
778        assert!(diff_iv.lo <= -3.0 + 1e-10);
779        assert!(diff_iv.hi >= 1.0 - 1e-10);
780    }
781    #[test]
782    fn test_affine_correlation_tighter() {
783        let iv = Interval::new(1.0, 3.0);
784        let a = AffineForm::from_interval(iv);
785        let diff = a.sub(&a);
786        let diff_iv = diff.to_interval();
787        assert!(
788            diff_iv.width() < 1e-10,
789            "x - x should be tight, got {:?}",
790            diff_iv
791        );
792    }
793    #[test]
794    fn test_affine_scale() {
795        let a = AffineForm::from_interval(Interval::new(1.0, 3.0));
796        let scaled = a.scale(2.0);
797        let iv = scaled.to_interval();
798        assert!((iv.lo - 2.0).abs() < 1e-10);
799        assert!((iv.hi - 6.0).abs() < 1e-10);
800    }
801    #[test]
802    fn test_affine_mul() {
803        let a = AffineForm::from_interval(Interval::new(1.0, 3.0));
804        let b = AffineForm::from_interval(Interval::new(2.0, 4.0));
805        let prod = a.mul(&b);
806        let iv = prod.to_interval();
807        assert!(iv.lo <= 2.0 + 1e-10, "lo={} should be <= 2", iv.lo);
808        assert!(iv.hi >= 12.0 - 1e-10, "hi={} should be >= 12", iv.hi);
809    }
810    #[test]
811    fn test_constraint_propagation_bound() {
812        let mut box_ = vec![Interval::new(-10.0, 10.0), Interval::new(-10.0, 10.0)];
813        let c = BoundConstraint {
814            idx: 0,
815            bounds: Interval::new(0.0, 5.0),
816        };
817        let changed = c.propagate(&mut box_);
818        assert!(changed);
819        assert_eq!(box_[0], Interval::new(0.0, 5.0));
820        assert_eq!(box_[1], Interval::new(-10.0, 10.0));
821    }
822    #[test]
823    fn test_constraint_propagation_linear() {
824        let mut box_ = vec![Interval::new(0.0, 10.0), Interval::new(0.0, 10.0)];
825        let c = LinearConstraint {
826            coeffs: vec![(0, 1.0), (1, 1.0)],
827            rhs: Interval::new(3.0, 3.0),
828        };
829        c.propagate(&mut box_);
830        assert!(box_[0].lo >= -0.01);
831        assert!(box_[0].hi <= 3.01);
832    }
833    #[test]
834    fn test_propagate_constraints_fixed_point() {
835        let mut box_ = vec![Interval::new(-10.0, 10.0)];
836        let c1 = BoundConstraint {
837            idx: 0,
838            bounds: Interval::new(0.0, 5.0),
839        };
840        let c2 = BoundConstraint {
841            idx: 0,
842            bounds: Interval::new(2.0, 8.0),
843        };
844        let constraints: Vec<&dyn IntervalConstraint> = vec![&c1, &c2];
845        let iters = propagate_constraints(&constraints, &mut box_, 100);
846        assert!(iters <= 3);
847        assert!((box_[0].lo - 2.0).abs() < 1e-10);
848        assert!((box_[0].hi - 5.0).abs() < 1e-10);
849    }
850    #[test]
851    fn test_interval3_hull() {
852        let a = Interval3::from_aabb([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]);
853        let b = Interval3::from_aabb([2.0, 2.0, 2.0], [3.0, 3.0, 3.0]);
854        let h = Interval3::hull_3d(a, b);
855        let (min, max) = h.to_aabb();
856        assert_eq!(min, [0.0, 0.0, 0.0]);
857        assert_eq!(max, [3.0, 3.0, 3.0]);
858    }
859    #[test]
860    fn test_interval3_intersection() {
861        let a = Interval3::from_aabb([0.0, 0.0, 0.0], [2.0, 2.0, 2.0]);
862        let b = Interval3::from_aabb([1.0, 1.0, 1.0], [3.0, 3.0, 3.0]);
863        let c = Interval3::intersection_3d(a, b).unwrap();
864        let (min, max) = c.to_aabb();
865        assert_eq!(min, [1.0, 1.0, 1.0]);
866        assert_eq!(max, [2.0, 2.0, 2.0]);
867        let d = Interval3::from_aabb([5.0, 5.0, 5.0], [6.0, 6.0, 6.0]);
868        assert!(Interval3::intersection_3d(a, d).is_none());
869    }
870    #[test]
871    fn test_interval3_volume_surface_area() {
872        let iv = Interval3::from_aabb([0.0, 0.0, 0.0], [2.0, 3.0, 4.0]);
873        assert!((iv.volume() - 24.0).abs() < 1e-15);
874        assert!((iv.surface_area() - 52.0).abs() < 1e-15);
875    }
876    #[test]
877    fn test_interval3_center() {
878        let iv = Interval3::from_aabb([1.0, 2.0, 3.0], [5.0, 6.0, 7.0]);
879        let c = iv.center();
880        assert_eq!(c, [3.0, 4.0, 5.0]);
881    }
882    #[test]
883    fn test_interval3_inflate() {
884        let iv = Interval3::from_aabb([1.0, 1.0, 1.0], [2.0, 2.0, 2.0]);
885        let inf = iv.inflate(0.5);
886        let (min, max) = inf.to_aabb();
887        assert_eq!(min, [0.5, 0.5, 0.5]);
888        assert_eq!(max, [2.5, 2.5, 2.5]);
889    }
890    #[test]
891    fn test_interval3_contains_point() {
892        let iv = Interval3::from_aabb([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]);
893        assert!(iv.contains_point([0.5, 0.5, 0.5]));
894        assert!(!iv.contains_point([1.5, 0.5, 0.5]));
895    }
896    #[test]
897    fn test_tan_interval_monotone() {
898        let a = Interval::new(0.0, std::f64::consts::FRAC_PI_4);
899        let t = tan_interval(a);
900        assert!(t.lo <= 0.0 + 1e-10);
901        assert!((t.hi - 1.0).abs() < 1e-10);
902    }
903    #[test]
904    fn test_tan_interval_with_pole() {
905        let a = Interval::new(0.0, std::f64::consts::PI);
906        let t = tan_interval(a);
907        assert_eq!(t.lo, f64::NEG_INFINITY);
908        assert_eq!(t.hi, f64::INFINITY);
909    }
910    #[test]
911    fn test_atan_interval_monotone() {
912        let a = Interval::new(-1.0, 1.0);
913        let t = atan_interval(a);
914        let expected_lo = (-1.0_f64).atan();
915        let expected_hi = 1.0_f64.atan();
916        assert!((t.lo - expected_lo).abs() < 1e-12);
917        assert!((t.hi - expected_hi).abs() < 1e-12);
918    }
919    #[test]
920    fn test_interval_min() {
921        let a = Interval::new(1.0, 5.0);
922        let b = Interval::new(3.0, 7.0);
923        let m = interval_min(a, b);
924        assert_eq!(m, Interval::new(1.0, 5.0));
925    }
926    #[test]
927    fn test_interval_max() {
928        let a = Interval::new(1.0, 5.0);
929        let b = Interval::new(3.0, 7.0);
930        let m = interval_max(a, b);
931        assert_eq!(m, Interval::new(3.0, 7.0));
932    }
933    #[test]
934    fn test_interval_matrix2_identity_mul_vec() {
935        let id = IntervalMatrix2::identity();
936        let v = [Interval::point(3.0), Interval::point(5.0)];
937        let r = id.mul_vec2(v);
938        assert!((r[0].lo - 3.0).abs() < 1e-15);
939        assert!((r[1].lo - 5.0).abs() < 1e-15);
940    }
941    #[test]
942    fn test_interval_matrix2_mul_mat_identity() {
943        let id = IntervalMatrix2::identity();
944        let m = IntervalMatrix2::from_f64(1.0, 2.0, 3.0, 4.0);
945        let r = id.mul_mat2(&m);
946        assert!((r.row0[0].lo - 1.0).abs() < 1e-15);
947        assert!((r.row0[1].lo - 2.0).abs() < 1e-15);
948        assert!((r.row1[0].lo - 3.0).abs() < 1e-15);
949        assert!((r.row1[1].lo - 4.0).abs() < 1e-15);
950    }
951    #[test]
952    fn test_interval_matrix2_mul_vec_general() {
953        let m = IntervalMatrix2::from_f64(2.0, 0.0, 0.0, 3.0);
954        let v = [Interval::point(1.0), Interval::point(2.0)];
955        let r = m.mul_vec2(v);
956        assert!((r[0].lo - 2.0).abs() < 1e-15);
957        assert!((r[1].lo - 6.0).abs() < 1e-15);
958    }
959    #[test]
960    fn test_interval_matrix2_transpose() {
961        let m = IntervalMatrix2::from_f64(1.0, 2.0, 3.0, 4.0);
962        let t = m.transpose2();
963        assert!((t.row0[1].lo - 3.0).abs() < 1e-15);
964        assert!((t.row1[0].lo - 2.0).abs() < 1e-15);
965    }
966    #[test]
967    fn test_interval_matrix3_identity_mul_vec() {
968        let id = IntervalMatrix3::identity();
969        let v = [
970            Interval::point(1.0),
971            Interval::point(2.0),
972            Interval::point(3.0),
973        ];
974        let r = id.mul_vec3(v);
975        assert!((r[0].lo - 1.0).abs() < 1e-15);
976        assert!((r[1].lo - 2.0).abs() < 1e-15);
977        assert!((r[2].lo - 3.0).abs() < 1e-15);
978    }
979    #[test]
980    fn test_interval_matrix3_mul_mat_identity() {
981        let id = IntervalMatrix3::identity();
982        let r = id.mul_mat3(&id);
983        for i in 0..3 {
984            for j in 0..3 {
985                let expected = if i == j { 1.0 } else { 0.0 };
986                assert!(
987                    (r.rows[i][j].lo - expected).abs() < 1e-12,
988                    "r[{i}][{j}].lo={}",
989                    r.rows[i][j].lo
990                );
991            }
992        }
993    }
994    #[test]
995    fn test_interval_matrix3_transpose() {
996        let id = IntervalMatrix3::identity();
997        let t = id.transpose3();
998        for i in 0..3 {
999            for j in 0..3 {
1000                assert!((t.rows[i][j].lo - id.rows[i][j].lo).abs() < 1e-12);
1001            }
1002        }
1003    }
1004    #[test]
1005    fn test_subdivide_width_limit() {
1006        let iv = Interval::new(0.0, 1.0);
1007        let parts = subdivide(iv, 0.25);
1008        for p in &parts {
1009            assert!(p.width() <= 0.25 + 1e-12, "width={}", p.width());
1010        }
1011        let hull = parts.iter().fold(Interval::empty(), |acc, &p| {
1012            if acc.is_empty() {
1013                p
1014            } else {
1015                Interval::hull(acc, p)
1016            }
1017        });
1018        assert!((hull.lo - 0.0).abs() < 1e-12);
1019        assert!((hull.hi - 1.0).abs() < 1e-12);
1020    }
1021    #[test]
1022    fn test_range_by_subdivision_tighter_than_naive() {
1023        let iv = Interval::new(0.0, 1.0);
1024        let f_iv = |x: Interval| x * (Interval::point(1.0) - x);
1025        let naive = f_iv(iv);
1026        let tight = range_by_subdivision(f_iv, iv, 0.1);
1027        assert!(
1028            tight.hi <= naive.hi + 1e-10,
1029            "tight.hi={} naive.hi={}",
1030            tight.hi,
1031            naive.hi
1032        );
1033    }
1034    #[test]
1035    fn test_taylor_enclosure_linear_exact() {
1036        let iv = Interval::new(1.0, 3.0);
1037        let enc = taylor_enclosure(4.0, Interval::point(2.0), iv);
1038        assert!((enc.lo - 2.0).abs() < 1e-12);
1039        assert!((enc.hi - 6.0).abs() < 1e-12);
1040    }
1041    #[test]
1042    fn test_taylor_enclosure_contains_true_values() {
1043        let iv = Interval::new(1.0, 3.0);
1044        let df_iv = Interval::new(2.0, 6.0);
1045        let enc = taylor_enclosure(4.0, df_iv, iv);
1046        assert!(enc.lo <= 1.0, "enc.lo={} should be ≤ 1", enc.lo);
1047        assert!(enc.hi >= 9.0, "enc.hi={} should be ≥ 9", enc.hi);
1048    }
1049    #[test]
1050    fn test_krawczyk_contains_root() {
1051        let iv = Interval::new(1.5, 2.5);
1052        let m = iv.midpoint();
1053        let f_mid = m * m - 4.0;
1054        let y = 1.0 / (2.0 * m);
1055        let df_iv = Interval::new(2.0 * 1.5, 2.0 * 2.5);
1056        let k = krawczyk_operator(f_mid, y, df_iv, iv);
1057        assert!(
1058            k.lo <= 2.0 + 1e-10 && k.hi >= 2.0 - 1e-10,
1059            "Krawczyk interval {:?} should contain root 2",
1060            k
1061        );
1062    }
1063    #[test]
1064    fn test_krawczyk_subset_of_iv() {
1065        let iv = Interval::new(1.5, 2.5);
1066        let m = iv.midpoint();
1067        let f_mid = m * m - 4.0;
1068        let y = 1.0 / (2.0 * m);
1069        let df_iv = Interval::new(2.0 * 1.5, 2.0 * 2.5);
1070        let k = krawczyk_operator(f_mid, y, df_iv, iv);
1071        assert!(
1072            k.lo >= iv.lo - 1e-10 && k.hi <= iv.hi + 1e-10,
1073            "K={:?} should be ⊆ iv={:?}",
1074            k,
1075            iv
1076        );
1077    }
1078    #[test]
1079    fn test_sample_enclosure_monotone() {
1080        let iv = Interval::new(0.0, 1.0);
1081        let enc = sample_enclosure(|x| x, iv, 10);
1082        assert!((enc.lo - 0.0).abs() < 1e-10);
1083        assert!((enc.hi - 1.0).abs() < 1e-10);
1084    }
1085    #[test]
1086    fn test_sample_enclosure_parabola() {
1087        let iv = Interval::new(0.0, 3.0);
1088        let enc = sample_enclosure(|x| x * x, iv, 100);
1089        assert!(enc.lo <= 0.01);
1090        assert!((enc.hi - 9.0).abs() < 0.1);
1091    }
1092    #[test]
1093    fn test_interval_add_basic() {
1094        let a = Interval::new(1.0, 2.0);
1095        let b = Interval::new(3.0, 4.0);
1096        let c = a + b;
1097        assert!((c.lo - 4.0).abs() < 1e-12);
1098        assert!((c.hi - 6.0).abs() < 1e-12);
1099    }
1100    #[test]
1101    fn test_interval_sub_basic() {
1102        let a = Interval::new(5.0, 7.0);
1103        let b = Interval::new(1.0, 2.0);
1104        let c = a - b;
1105        assert!((c.lo - 3.0).abs() < 1e-12);
1106        assert!((c.hi - 6.0).abs() < 1e-12);
1107    }
1108    #[test]
1109    fn test_interval_mul_positive() {
1110        let a = Interval::new(2.0, 3.0);
1111        let b = Interval::new(4.0, 5.0);
1112        let c = a * b;
1113        assert!((c.lo - 8.0).abs() < 1e-12);
1114        assert!((c.hi - 15.0).abs() < 1e-12);
1115    }
1116    #[test]
1117    fn test_interval_mul_mixed_sign() {
1118        let a = Interval::new(-1.0, 2.0);
1119        let b = Interval::new(-1.0, 3.0);
1120        let c = a * b;
1121        assert!(
1122            c.lo <= -2.0 - 1e-12 || (c.lo + 3.0).abs() < 1e-12,
1123            "lo should cover -3, got {}",
1124            c.lo
1125        );
1126        assert!(c.hi >= 6.0 - 1e-12, "hi should be >= 6, got {}", c.hi);
1127    }
1128    #[test]
1129    fn test_interval_div_basic() {
1130        let a = Interval::new(6.0, 8.0);
1131        let b = Interval::new(2.0, 4.0);
1132        let c = a / b;
1133        assert!(c.lo <= 1.5 + 1e-12);
1134        assert!(c.hi >= 4.0 - 1e-12);
1135    }
1136    #[test]
1137    fn test_interval_neg() {
1138        let a = Interval::new(-1.0, 3.0);
1139        let b = -a;
1140        assert!((b.lo - (-3.0)).abs() < 1e-12);
1141        assert!((b.hi - 1.0).abs() < 1e-12);
1142    }
1143    #[test]
1144    fn test_interval_contains_point() {
1145        let a = Interval::new(1.0, 5.0);
1146        assert!(a.contains(3.0));
1147        assert!(!a.contains(6.0));
1148        assert!(a.contains(1.0));
1149        assert!(a.contains(5.0));
1150    }
1151    #[test]
1152    fn test_interval_overlaps() {
1153        let a = Interval::new(1.0, 3.0);
1154        let b = Interval::new(2.0, 5.0);
1155        let c = Interval::new(4.0, 6.0);
1156        assert!(a.overlaps(&b));
1157        assert!(!a.overlaps(&c));
1158    }
1159    #[test]
1160    fn test_interval_hull() {
1161        let a = Interval::new(1.0, 3.0);
1162        let b = Interval::new(5.0, 7.0);
1163        let h = Interval::hull(a, b);
1164        assert!((h.lo - 1.0).abs() < 1e-12);
1165        assert!((h.hi - 7.0).abs() < 1e-12);
1166    }
1167    #[test]
1168    fn test_interval_intersection_overlapping() {
1169        let a = Interval::new(1.0, 5.0);
1170        let b = Interval::new(3.0, 7.0);
1171        let c = Interval::intersection(a, b).expect("should intersect");
1172        assert!((c.lo - 3.0).abs() < 1e-12);
1173        assert!((c.hi - 5.0).abs() < 1e-12);
1174    }
1175    #[test]
1176    fn test_interval_intersection_disjoint() {
1177        let a = Interval::new(1.0, 2.0);
1178        let b = Interval::new(3.0, 4.0);
1179        assert!(Interval::intersection(a, b).is_none());
1180    }
1181    #[test]
1182    fn test_interval_powi_even() {
1183        let a = Interval::new(-2.0, 3.0);
1184        let p = a.powi(2);
1185        assert!(p.lo <= 1e-10, "lo={}", p.lo);
1186        assert!((p.hi - 9.0).abs() < 1e-10, "hi={}", p.hi);
1187    }
1188    #[test]
1189    fn test_interval_powi_odd() {
1190        let a = Interval::new(-1.0, 2.0);
1191        let p = a.powi(3);
1192        assert!((p.lo - (-1.0)).abs() < 1e-10);
1193        assert!((p.hi - 8.0).abs() < 1e-10);
1194    }
1195    #[test]
1196    fn test_interval_sqrt() {
1197        let a = Interval::new(4.0, 9.0);
1198        let s = a.sqrt();
1199        assert!((s.lo - 2.0).abs() < 1e-10);
1200        assert!((s.hi - 3.0).abs() < 1e-10);
1201    }
1202    #[test]
1203    fn test_interval_abs() {
1204        let a = Interval::new(-3.0, 2.0);
1205        let b = a.abs();
1206        assert!(b.lo >= 0.0, "abs lo should be >= 0");
1207        assert!(b.hi >= 2.0, "abs hi should be >= 2.0, got {}", b.hi);
1208    }
1209    #[test]
1210    fn test_interval_midpoint() {
1211        let a = Interval::new(2.0, 6.0);
1212        assert!((a.midpoint() - 4.0).abs() < 1e-12);
1213    }
1214    #[test]
1215    fn test_interval_radius() {
1216        let a = Interval::new(2.0, 6.0);
1217        assert!((a.radius() - 2.0).abs() < 1e-12);
1218    }
1219    #[test]
1220    fn test_interval_bisect() {
1221        let a = Interval::new(0.0, 4.0);
1222        let (left, right) = a.bisect();
1223        assert!((left.lo - 0.0).abs() < 1e-12);
1224        assert!((left.hi - 2.0).abs() < 1e-12);
1225        assert!((right.lo - 2.0).abs() < 1e-12);
1226        assert!((right.hi - 4.0).abs() < 1e-12);
1227    }
1228    #[test]
1229    fn test_interval_inflate() {
1230        let a = Interval::new(1.0, 3.0);
1231        let b = a.inflate(0.5);
1232        assert!((b.lo - 0.5).abs() < 1e-12);
1233        assert!((b.hi - 3.5).abs() < 1e-12);
1234    }
1235    #[test]
1236    fn test_cos_interval_at_zero() {
1237        let a = Interval::new(0.0, 0.0);
1238        let c = cos_interval(a);
1239        assert!((c.lo - 1.0).abs() < 1e-10);
1240        assert!((c.hi - 1.0).abs() < 1e-10);
1241    }
1242    #[test]
1243    fn test_exp_interval_basic() {
1244        let a = Interval::new(0.0, 1.0);
1245        let e = exp_interval(a);
1246        assert!((e.lo - 1.0).abs() < 1e-10);
1247        assert!((e.hi - std::f64::consts::E).abs() < 1e-8);
1248    }
1249    #[test]
1250    fn test_ln_interval_basic() {
1251        let a = Interval::new(1.0, std::f64::consts::E);
1252        let l = ln_interval(a);
1253        assert!((l.lo - 0.0).abs() < 1e-10);
1254        assert!((l.hi - 1.0).abs() < 1e-8);
1255    }
1256    #[test]
1257    fn test_interval_newton_x_squared_minus_2() {
1258        let result = interval_newton(
1259            &|x| x * x - 2.0,
1260            &|iv: Interval| iv * iv - Interval::point(2.0),
1261            &|iv: Interval| Interval::point(2.0) * iv,
1262            Interval::new(1.0, 2.0),
1263            1e-8,
1264            50,
1265        );
1266        match result {
1267            IntervalNewtonResult::Root(r) => {
1268                assert!(
1269                    r.contains(2.0_f64.sqrt()),
1270                    "root interval should contain sqrt(2)"
1271                );
1272            }
1273            IntervalNewtonResult::Multiple(v) => {
1274                let any_contains = v.iter().any(|iv| iv.contains(2.0_f64.sqrt()));
1275                assert!(any_contains, "one interval should contain sqrt(2)");
1276            }
1277            IntervalNewtonResult::NoRoot => panic!("expected a root"),
1278        }
1279    }
1280    #[test]
1281    fn test_interval3_overlaps() {
1282        let a = Interval3::from_aabb([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]);
1283        let b = Interval3::from_aabb([0.5, 0.5, 0.5], [2.0, 2.0, 2.0]);
1284        assert!(a.overlaps_3d(&b));
1285    }
1286    #[test]
1287    fn test_interval3_no_overlap() {
1288        let a = Interval3::from_aabb([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]);
1289        let b = Interval3::from_aabb([2.0, 0.0, 0.0], [3.0, 1.0, 1.0]);
1290        assert!(!a.overlaps_3d(&b));
1291    }
1292    #[test]
1293    fn test_interval3_volume_exact() {
1294        let a = Interval3::from_aabb([0.0, 0.0, 0.0], [2.0, 3.0, 4.0]);
1295        assert!((a.volume() - 24.0).abs() < 1e-12, "volume={}", a.volume());
1296    }
1297    #[test]
1298    fn test_taylor_model_evaluate() {
1299        let tm = TaylorModel::new(vec![1.0, 1.0], Interval::new(-0.01, 0.01));
1300        let ev = tm.evaluate(Interval::point(0.0));
1301        assert!(ev.contains(1.0), "Taylor model at 0 should contain 1");
1302    }
1303    #[test]
1304    fn test_taylor_model_add() {
1305        let a = TaylorModel::new(vec![1.0, 2.0], Interval::new(-0.01, 0.01));
1306        let b = TaylorModel::new(vec![3.0, 1.0], Interval::new(-0.01, 0.01));
1307        let c = a.add(&b);
1308        assert!((c.poly[0] - 4.0).abs() < 1e-12, "constant term 1+3=4");
1309        assert!((c.poly[1] - 3.0).abs() < 1e-12, "linear term 2+1=3");
1310    }
1311    #[test]
1312    fn test_taylor_model_scale() {
1313        let tm = TaylorModel::new(vec![2.0, 4.0], Interval::new(-0.1, 0.1));
1314        let scaled = tm.scale(3.0);
1315        assert!((scaled.poly[0] - 6.0).abs() < 1e-12);
1316        assert!((scaled.poly[1] - 12.0).abs() < 1e-12);
1317    }
1318    #[test]
1319    fn test_interval_bisection_x_squared_minus_1() {
1320        let roots = interval_bisection(
1321            |iv: Interval| iv * iv - Interval::point(1.0),
1322            Interval::new(-2.0, 0.0),
1323            0.01,
1324            20,
1325        );
1326        assert!(!roots.is_empty(), "should find root of x^2-1 near -1");
1327        let any_contains = roots.iter().any(|iv| iv.contains(-1.0));
1328        assert!(any_contains, "a root interval should contain -1");
1329    }
1330    #[test]
1331    fn test_affine_form_from_interval_roundtrip() {
1332        let iv = Interval::new(1.0, 3.0);
1333        let af = AffineForm::from_interval(iv);
1334        let back = af.to_interval();
1335        assert!(back.lo <= 1.0 + 1e-10, "lo={}", back.lo);
1336        assert!(back.hi >= 3.0 - 1e-10, "hi={}", back.hi);
1337    }
1338    #[test]
1339    fn test_affine_form_add_constants() {
1340        let a = AffineForm::constant(2.0);
1341        let b = AffineForm::constant(3.0);
1342        let c = a.add(&b);
1343        let iv = c.to_interval();
1344        assert!((iv.lo - 5.0).abs() < 1e-12);
1345        assert!((iv.hi - 5.0).abs() < 1e-12);
1346    }
1347    #[test]
1348    fn test_affine_form_scale_constant() {
1349        let a = AffineForm::constant(4.0);
1350        let b = a.scale(2.5);
1351        let iv = b.to_interval();
1352        assert!((iv.lo - 10.0).abs() < 1e-12);
1353        assert!((iv.hi - 10.0).abs() < 1e-12);
1354    }
1355    #[test]
1356    fn test_interval_matrix2_diagonal_mul_mat() {
1357        let a = IntervalMatrix2::from_f64(2.0, 0.0, 0.0, 3.0);
1358        let b = IntervalMatrix2::from_f64(1.0, 0.0, 0.0, 1.0);
1359        let c = a.mul_mat2(&b);
1360        assert!((c.row0[0].lo - 2.0).abs() < 1e-12);
1361        assert!((c.row1[1].lo - 3.0).abs() < 1e-12);
1362    }
1363    #[test]
1364    fn test_interval_matrix3_identity_vec3_passthrough() {
1365        let m = IntervalMatrix3::identity();
1366        let v = [
1367            Interval::new(1.0, 2.0),
1368            Interval::new(3.0, 4.0),
1369            Interval::new(5.0, 6.0),
1370        ];
1371        let result = m.mul_vec3(v);
1372        for i in 0..3 {
1373            assert!((result[i].lo - v[i].lo).abs() < 1e-12);
1374            assert!((result[i].hi - v[i].hi).abs() < 1e-12);
1375        }
1376    }
1377}