MinMaxMapping trait to allow alternatives to BTFN with relevant properties. dev

Sun, 27 Apr 2025 15:56:43 -0500

author
Tuomo Valkonen <tuomov@iki.fi>
date
Sun, 27 Apr 2025 15:56:43 -0500
branch
dev
changeset 97
4e80fb049dca
parent 96
962c8e346ab9
child 98
2e4517b55442

MinMaxMapping trait to allow alternatives to BTFN with relevant properties.

Cargo.lock file | annotate | diff | comparison | revisions
src/bisection_tree/aggregator.rs file | annotate | diff | comparison | revisions
src/bisection_tree/bt.rs file | annotate | diff | comparison | revisions
src/bisection_tree/btfn.rs file | annotate | diff | comparison | revisions
src/bisection_tree/support.rs file | annotate | diff | comparison | revisions
src/bounds.rs file | annotate | diff | comparison | revisions
src/lib.rs file | annotate | diff | comparison | revisions
--- a/Cargo.lock	Sun Apr 27 15:45:40 2025 -0500
+++ b/Cargo.lock	Sun Apr 27 15:56:43 2025 -0500
@@ -4,7 +4,7 @@
 
 [[package]]
 name = "alg_tools"
-version = "0.3.1"
+version = "0.4.0-dev"
 dependencies = [
  "anyhow",
  "colored",
--- a/src/bisection_tree/aggregator.rs	Sun Apr 27 15:45:40 2025 -0500
+++ b/src/bisection_tree/aggregator.rs	Sun Apr 27 15:56:43 2025 -0500
@@ -2,8 +2,7 @@
 Aggregation / summarisation of information in branches of bisection trees.
 */
 
-use crate::instance::Instance;
-use crate::sets::Set;
+pub use crate::bounds::Bounds;
 use crate::types::*;
 
 /// Trait for aggregating information about a branch of a [bisection tree][super::BT].
@@ -56,144 +55,6 @@
     }
 }
 
-/// Upper and lower bounds on an `F`-valued function.
-#[derive(Copy, Clone, Debug)]
-pub struct Bounds<F>(
-    /// Lower bound
-    pub F,
-    /// Upper bound
-    pub F,
-);
-
-impl<F: Copy> Bounds<F> {
-    /// Returns the lower bound
-    #[inline]
-    pub fn lower(&self) -> F {
-        self.0
-    }
-
-    /// Returns the upper bound
-    #[inline]
-    pub fn upper(&self) -> F {
-        self.1
-    }
-}
-
-impl<F: Float> Bounds<F> {
-    /// Returns a uniform bound.
-    ///
-    /// This is maximum over the absolute values of the upper and lower bound.
-    #[inline]
-    pub fn uniform(&self) -> F {
-        let &Bounds(lower, upper) = self;
-        lower.abs().max(upper.abs())
-    }
-
-    /// Construct a bounds, making sure `lower` bound is less than `upper`
-    #[inline]
-    pub fn corrected(lower: F, upper: F) -> Self {
-        if lower <= upper {
-            Bounds(lower, upper)
-        } else {
-            Bounds(upper, lower)
-        }
-    }
-
-    /// Refine the lower bound
-    #[inline]
-    pub fn refine_lower(&self, lower: F) -> Self {
-        let &Bounds(l, u) = self;
-        debug_assert!(l <= u);
-        Bounds(l.max(lower), u.max(lower))
-    }
-
-    /// Refine the lower bound
-    #[inline]
-    pub fn refine_upper(&self, upper: F) -> Self {
-        let &Bounds(l, u) = self;
-        debug_assert!(l <= u);
-        Bounds(l.min(upper), u.min(upper))
-    }
-}
-
-impl<'a, F: Float> std::ops::Add<Self> for Bounds<F> {
-    type Output = Self;
-    #[inline]
-    fn add(self, Bounds(l2, u2): Self) -> Self::Output {
-        let Bounds(l1, u1) = self;
-        debug_assert!(l1 <= u1 && l2 <= u2);
-        Bounds(l1 + l2, u1 + u2)
-    }
-}
-
-impl<'a, F: Float> std::ops::Mul<Self> for Bounds<F> {
-    type Output = Self;
-    #[inline]
-    fn mul(self, Bounds(l2, u2): Self) -> Self::Output {
-        let Bounds(l1, u1) = self;
-        debug_assert!(l1 <= u1 && l2 <= u2);
-        let a = l1 * l2;
-        let b = u1 * u2;
-        // The order may flip when negative numbers are involved, so need min/max
-        Bounds(a.min(b), a.max(b))
-    }
-}
-
-impl<F: Float> std::iter::Product for Bounds<F> {
-    #[inline]
-    fn product<I>(mut iter: I) -> Self
-    where
-        I: Iterator<Item = Self>,
-    {
-        match iter.next() {
-            None => Bounds(F::ZERO, F::ZERO),
-            Some(init) => iter.fold(init, |a, b| a * b),
-        }
-    }
-}
-
-impl<F: Float> Set<F> for Bounds<F> {
-    fn contains<I: Instance<F>>(&self, item: I) -> bool {
-        let v = item.own();
-        let &Bounds(l, u) = self;
-        debug_assert!(l <= u);
-        l <= v && v <= u
-    }
-}
-
-impl<F: Float> Bounds<F> {
-    /// Calculate a common bound (glb, lub) for two bounds.
-    #[inline]
-    pub fn common(&self, &Bounds(l2, u2): &Self) -> Self {
-        let &Bounds(l1, u1) = self;
-        debug_assert!(l1 <= u1 && l2 <= u2);
-        Bounds(l1.min(l2), u1.max(u2))
-    }
-
-    /// Indicates whether `Self` is a superset of the argument bound.
-    #[inline]
-    pub fn superset(&self, &Bounds(l2, u2): &Self) -> bool {
-        let &Bounds(l1, u1) = self;
-        debug_assert!(l1 <= u1 && l2 <= u2);
-        l1 <= l2 && u2 <= u1
-    }
-
-    /// Returns the greatest bound contained by both argument bounds, if one exists.
-    #[inline]
-    pub fn glb(&self, &Bounds(l2, u2): &Self) -> Option<Self> {
-        let &Bounds(l1, u1) = self;
-        debug_assert!(l1 <= u1 && l2 <= u2);
-        let l = l1.max(l2);
-        let u = u1.min(u2);
-        debug_assert!(l <= u);
-        if l < u {
-            Some(Bounds(l, u))
-        } else {
-            None
-        }
-    }
-}
-
 impl<F: Float> Aggregator for Bounds<F> {
     #[inline]
     fn aggregate<I>(&mut self, aggregates: I)
--- a/src/bisection_tree/bt.rs	Sun Apr 27 15:45:40 2025 -0500
+++ b/src/bisection_tree/bt.rs	Sun Apr 27 15:56:43 2025 -0500
@@ -246,7 +246,10 @@
 {
     /// Creates a new node branching structure, subdividing `domain` based on the
     /// [hint][Support::support_hint] of `support`.
-    pub(super) fn new_with<S: LocalAnalysis<F, A, N>>(domain: &Cube<F, N>, support: &S) -> Self {
+    pub(super) fn new_with<S: LocalAnalysis<F, A, N> + Support<F, N>>(
+        domain: &Cube<F, N>,
+        support: &S,
+    ) -> Self {
         let hint = support.bisection_hint(domain);
         let branch_at = map2(&hint, domain, |h, r| {
             h.unwrap_or_else(|| (r[0] + r[1]) / F::TWO)
@@ -329,7 +332,7 @@
     ///  * `support` is the [`Support`] that is used determine with which subcubes of `domain`
     ///     (at subdivision depth `new_leaf_depth`) the data `d` is to be associated with.
     ///
-    pub(super) fn insert<'refs, 'scope, M: Depth, S: LocalAnalysis<F, A, N>>(
+    pub(super) fn insert<'refs, 'scope, M: Depth, S: LocalAnalysis<F, A, N> + Support<F, N>>(
         &mut self,
         domain: &Cube<F, N>,
         d: D,
@@ -456,7 +459,7 @@
     /// If `self` is a [`NodeOption::Branches`], the data is passed to branches whose subcubes
     /// `support` intersects. If an [`NodeOption::Uninitialised`] node is encountered, a new leaf is
     /// created at a minimum depth of `new_leaf_depth`.
-    pub(super) fn insert<'refs, 'scope, M: Depth, S: LocalAnalysis<F, A, N>>(
+    pub(super) fn insert<'refs, 'scope, M: Depth, S: LocalAnalysis<F, A, N> + Support<F, N>>(
         &mut self,
         domain: &Cube<F, N>,
         d: D,
@@ -626,7 +629,11 @@
     ///
     /// Every leaf node of the tree that intersects the `support` will contain a copy of
     /// `d`.
-    fn insert<S: LocalAnalysis<F, Self::Agg, N>>(&mut self, d: Self::Data, support: &S);
+    fn insert<S: LocalAnalysis<F, Self::Agg, N> + Support<F, N>>(
+        &mut self,
+        d: Self::Data,
+        support: &S,
+    );
 
     /// Construct a new instance of the tree for a different aggregator
     ///
@@ -696,7 +703,7 @@
             type Agg = A;
             type Converted<ANew> = BT<M,F,D,ANew,$n> where ANew : Aggregator;
 
-            fn insert<S: LocalAnalysis<F, A, $n>>(
+            fn insert<S: LocalAnalysis<F, A, $n> + Support<F, $n>>(
                 &mut self,
                 d : D,
                 support : &S
--- a/src/bisection_tree/btfn.rs	Sun Apr 27 15:45:40 2025 -0500
+++ b/src/bisection_tree/btfn.rs	Sun Apr 27 15:56:43 2025 -0500
@@ -12,6 +12,7 @@
 use super::either::*;
 use super::refine::*;
 use super::support::*;
+use crate::bounds::MinMaxMapping;
 use crate::fe_model::base::RealLocalModel;
 use crate::fe_model::p2_local_model::*;
 use crate::loc::Loc;
@@ -837,18 +838,14 @@
 // there should be a result, or new nodes above the `glb` inserted into the queue. Then the waiting
 // threads can also continue processing. If, however, numerical inaccuracy destroyes the `glb`,
 // the queue may run out, and we get “Refiner failure”.
-impl<F: Float, G, BT, const N: usize> BTFN<F, G, BT, N>
+impl<F: Float, G, BT, const N: usize> MinMaxMapping<F, N> for BTFN<F, G, BT, N>
 where
     BT: BTSearch<F, N, Agg = Bounds<F>>,
     G: SupportGenerator<F, N, Id = BT::Data>,
     G::SupportType: Mapping<Loc<F, N>, Codomain = F> + LocalAnalysis<F, Bounds<F>, N>,
     Cube<F, N>: P2Minimise<Loc<F, N>, F>,
 {
-    /// Maximise the `BTFN` within stated value `tolerance`.
-    ///
-    /// At most `max_steps` refinement steps are taken.
-    /// Returns the approximate maximiser and the corresponding function value.
-    pub fn maximise(&mut self, tolerance: F, max_steps: usize) -> (Loc<F, N>, F) {
+    fn maximise(&mut self, tolerance: F, max_steps: usize) -> (Loc<F, N>, F) {
         let refiner = P2Refiner {
             tolerance,
             max_steps,
@@ -861,12 +858,7 @@
             .unwrap()
     }
 
-    /// Maximise the `BTFN` within stated value `tolerance` subject to a lower bound.
-    ///
-    /// At most `max_steps` refinement steps are taken.
-    /// Returns the approximate maximiser and the corresponding function value when one is found
-    /// above the `bound` threshold, otherwise `None`.
-    pub fn maximise_above(
+    fn maximise_above(
         &mut self,
         bound: F,
         tolerance: F,
@@ -883,11 +875,7 @@
             .expect("Refiner failure.")
     }
 
-    /// Minimise the `BTFN` within stated value `tolerance`.
-    ///
-    /// At most `max_steps` refinement steps are taken.
-    /// Returns the approximate minimiser and the corresponding function value.
-    pub fn minimise(&mut self, tolerance: F, max_steps: usize) -> (Loc<F, N>, F) {
+    fn minimise(&mut self, tolerance: F, max_steps: usize) -> (Loc<F, N>, F) {
         let refiner = P2Refiner {
             tolerance,
             max_steps,
@@ -900,12 +888,7 @@
             .unwrap()
     }
 
-    /// Minimise the `BTFN` within stated value `tolerance` subject to a lower bound.
-    ///
-    /// At most `max_steps` refinement steps are taken.
-    /// Returns the approximate minimiser and the corresponding function value when one is found
-    /// above the `bound` threshold, otherwise `None`.
-    pub fn minimise_below(
+    fn minimise_below(
         &mut self,
         bound: F,
         tolerance: F,
@@ -922,10 +905,7 @@
             .expect("Refiner failure.")
     }
 
-    /// Verify that the `BTFN` has a given upper `bound` within indicated `tolerance`.
-    ///
-    /// At most `max_steps` refinement steps are taken.
-    pub fn has_upper_bound(&mut self, bound: F, tolerance: F, max_steps: usize) -> bool {
+    fn has_upper_bound(&mut self, bound: F, tolerance: F, max_steps: usize) -> bool {
         let refiner = BoundRefiner {
             bound,
             tolerance,
@@ -937,10 +917,7 @@
             .expect("Refiner failure.")
     }
 
-    /// Verify that the `BTFN` has a given lower `bound` within indicated `tolerance`.
-    ///
-    /// At most `max_steps` refinement steps are taken.
-    pub fn has_lower_bound(&mut self, bound: F, tolerance: F, max_steps: usize) -> bool {
+    fn has_lower_bound(&mut self, bound: F, tolerance: F, max_steps: usize) -> bool {
         let refiner = BoundRefiner {
             bound,
             tolerance,
--- a/src/bisection_tree/support.rs	Sun Apr 27 15:45:40 2025 -0500
+++ b/src/bisection_tree/support.rs	Sun Apr 27 15:56:43 2025 -0500
@@ -2,6 +2,7 @@
 Traits for representing the support of a [`Mapping`], and analysing the mapping on a [`Cube`].
 */
 use super::aggregator::Bounds;
+pub use crate::bounds::{GlobalAnalysis, LocalAnalysis};
 use crate::loc::Loc;
 use crate::mapping::{DifferentiableImpl, DifferentiableMapping, Instance, Mapping, Space};
 use crate::maputil::map2;
@@ -52,56 +53,6 @@
     }
 }
 
-/// Trait for globally analysing a property `A` of a [`Mapping`].
-///
-/// Typically `A` is an [`Aggregator`][super::aggregator::Aggregator] such as
-/// [`Bounds`][super::aggregator::Bounds].
-pub trait GlobalAnalysis<F: Num, A> {
-    /// Perform global analysis of the property `A` of `Self`.
-    ///
-    /// As an example, in the case of `A` being [`Bounds`][super::aggregator::Bounds],
-    /// this function will return global upper and lower bounds for the mapping
-    /// represented by `self`.
-    fn global_analysis(&self) -> A;
-}
-
-// default impl<F, A, N, L> GlobalAnalysis<F, A, N> for L
-// where L : LocalAnalysis<F, A, N> {
-//     #[inline]
-//     fn global_analysis(&self) -> Bounds<F> {
-//         self.local_analysis(&self.support_hint())
-//     }
-// }
-
-/// Trait for locally analysing a property `A` of a [`Mapping`] (implementing [`Support`])
-/// within a [`Cube`].
-///
-/// Typically `A` is an [`Aggregator`][super::aggregator::Aggregator] such as
-/// [`Bounds`][super::aggregator::Bounds].
-pub trait LocalAnalysis<F: Num, A, const N: usize>: GlobalAnalysis<F, A> + Support<F, N> {
-    /// Perform local analysis of the property `A` of `Self`.
-    ///
-    /// As an example, in the case of `A` being [`Bounds`][super::aggregator::Bounds],
-    /// this function will return upper and lower bounds within `cube` for the mapping
-    /// represented by `self`.
-    fn local_analysis(&self, cube: &Cube<F, N>) -> A;
-}
-
-/// Trait for determining the upper and lower bounds of an float-valued [`Mapping`].
-///
-/// This is a blanket-implemented alias for [`GlobalAnalysis`]`<F, Bounds<F>>`
-/// [`Mapping`] is not a supertrait to allow flexibility in the implementation of either
-/// reference or non-reference arguments.
-pub trait Bounded<F: Float>: GlobalAnalysis<F, Bounds<F>> {
-    /// Return lower and upper bounds for the values of of `self`.
-    #[inline]
-    fn bounds(&self) -> Bounds<F> {
-        self.global_analysis()
-    }
-}
-
-impl<F: Float, T: GlobalAnalysis<F, Bounds<F>>> Bounded<F> for T {}
-
 /// Shift of [`Support`] and [`Mapping`]; output of [`Support::shift`].
 #[derive(Copy, Clone, Debug, Serialize)] // Serialize! but not implemented by Loc.
 pub struct Shift<T, F, const N: usize> {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/bounds.rs	Sun Apr 27 15:56:43 2025 -0500
@@ -0,0 +1,246 @@
+/*!
+Bounded and minimizable/maximizable mappings.
+*/
+
+use crate::instance::Instance;
+use crate::loc::Loc;
+use crate::mapping::RealMapping;
+use crate::sets::{Cube, Set};
+use crate::types::{Float, Num};
+
+/// Trait for globally analysing a property `A` of a [`Mapping`].
+///
+/// Typically `A` is an [`Aggregator`][super::aggregator::Aggregator] such as
+/// [`Bounds`][super::aggregator::Bounds].
+pub trait GlobalAnalysis<F: Num, A> {
+    /// Perform global analysis of the property `A` of `Self`.
+    ///
+    /// As an example, in the case of `A` being [`Bounds`][super::aggregator::Bounds],
+    /// this function will return global upper and lower bounds for the mapping
+    /// represented by `self`.
+    fn global_analysis(&self) -> A;
+}
+
+// default impl<F, A, N, L> GlobalAnalysis<F, A, N> for L
+// where L : LocalAnalysis<F, A, N> {
+//     #[inline]
+//     fn global_analysis(&self) -> Bounds<F> {
+//         self.local_analysis(&self.support_hint())
+//     }
+// }
+
+/// Trait for locally analysing a property `A` of a [`Mapping`] (implementing [`Support`])
+/// within a [`Cube`].
+///
+/// Typically `A` is an [`Aggregator`][super::aggregator::Aggregator] such as
+/// [`Bounds`][super::aggregator::Bounds].
+pub trait LocalAnalysis<F: Num, A, const N: usize>: GlobalAnalysis<F, A> {
+    /// Perform local analysis of the property `A` of `Self`.
+    ///
+    /// As an example, in the case of `A` being [`Bounds`][super::aggregator::Bounds],
+    /// this function will return upper and lower bounds within `cube` for the mapping
+    /// represented by `self`.
+    fn local_analysis(&self, cube: &Cube<F, N>) -> A;
+}
+
+/// Trait for determining the upper and lower bounds of an float-valued [`Mapping`].
+///
+/// This is a blanket-implemented alias for [`GlobalAnalysis`]`<F, Bounds<F>>`
+/// [`Mapping`] is not a supertrait to allow flexibility in the implementation of either
+/// reference or non-reference arguments.
+pub trait Bounded<F: Float>: GlobalAnalysis<F, Bounds<F>> {
+    /// Return lower and upper bounds for the values of of `self`.
+    #[inline]
+    fn bounds(&self) -> Bounds<F> {
+        self.global_analysis()
+    }
+}
+
+impl<F: Float, T: GlobalAnalysis<F, Bounds<F>>> Bounded<F> for T {}
+
+/// A [`RealMapping`] that provides rough bounds as well as minimisation and maximisation.
+pub trait MinMaxMapping<F: Float, const N: usize>: RealMapping<F, N> + Bounded<F> {
+    /// Maximise the mapping within stated value `tolerance`.
+    ///
+    /// At most `max_steps` refinement steps are taken.
+    /// Returns the approximate maximiser and the corresponding function value.
+    fn maximise(&mut self, tolerance: F, max_steps: usize) -> (Loc<F, N>, F);
+
+    /// Maximise the mapping within stated value `tolerance` subject to a lower bound.
+    ///
+    /// At most `max_steps` refinement steps are taken.
+    /// Returns the approximate maximiser and the corresponding function value when one is found
+    /// above the `bound` threshold, otherwise `None`.
+    fn maximise_above(
+        &mut self,
+        bound: F,
+        tolerance: F,
+        max_steps: usize,
+    ) -> Option<(Loc<F, N>, F)>;
+
+    /// Minimise the mapping within stated value `tolerance`.
+    ///
+    /// At most `max_steps` refinement steps are taken.
+    /// Returns the approximate minimiser and the corresponding function value.
+    fn minimise(&mut self, tolerance: F, max_steps: usize) -> (Loc<F, N>, F);
+
+    /// Minimise the mapping within stated value `tolerance` subject to a lower bound.
+    ///
+    /// At most `max_steps` refinement steps are taken.
+    /// Returns the approximate minimiser and the corresponding function value when one is found
+    /// above the `bound` threshold, otherwise `None`.
+    fn minimise_below(
+        &mut self,
+        bound: F,
+        tolerance: F,
+        max_steps: usize,
+    ) -> Option<(Loc<F, N>, F)>;
+
+    /// Verify that the mapping has a given upper `bound` within indicated `tolerance`.
+    ///
+    /// At most `max_steps` refinement steps are taken.
+    fn has_upper_bound(&mut self, bound: F, tolerance: F, max_steps: usize) -> bool;
+
+    /// Verify that the mapping has a given lower `bound` within indicated `tolerance`.
+    ///
+    /// At most `max_steps` refinement steps are taken.
+    fn has_lower_bound(&mut self, bound: F, tolerance: F, max_steps: usize) -> bool;
+}
+
+/// Upper and lower bounds on an `F`-valued function.
+#[derive(Copy, Clone, Debug)]
+pub struct Bounds<F>(
+    /// Lower bound
+    pub F,
+    /// Upper bound
+    pub F,
+);
+
+impl<F: Copy> Bounds<F> {
+    /// Returns the lower bound
+    #[inline]
+    pub fn lower(&self) -> F {
+        self.0
+    }
+
+    /// Returns the upper bound
+    #[inline]
+    pub fn upper(&self) -> F {
+        self.1
+    }
+}
+
+impl<F: Float> Bounds<F> {
+    /// Returns a uniform bound.
+    ///
+    /// This is maximum over the absolute values of the upper and lower bound.
+    #[inline]
+    pub fn uniform(&self) -> F {
+        let &Bounds(lower, upper) = self;
+        lower.abs().max(upper.abs())
+    }
+
+    /// Construct a bounds, making sure `lower` bound is less than `upper`
+    #[inline]
+    pub fn corrected(lower: F, upper: F) -> Self {
+        if lower <= upper {
+            Bounds(lower, upper)
+        } else {
+            Bounds(upper, lower)
+        }
+    }
+
+    /// Refine the lower bound
+    #[inline]
+    pub fn refine_lower(&self, lower: F) -> Self {
+        let &Bounds(l, u) = self;
+        debug_assert!(l <= u);
+        Bounds(l.max(lower), u.max(lower))
+    }
+
+    /// Refine the lower bound
+    #[inline]
+    pub fn refine_upper(&self, upper: F) -> Self {
+        let &Bounds(l, u) = self;
+        debug_assert!(l <= u);
+        Bounds(l.min(upper), u.min(upper))
+    }
+}
+
+impl<'a, F: Float> std::ops::Add<Self> for Bounds<F> {
+    type Output = Self;
+    #[inline]
+    fn add(self, Bounds(l2, u2): Self) -> Self::Output {
+        let Bounds(l1, u1) = self;
+        debug_assert!(l1 <= u1 && l2 <= u2);
+        Bounds(l1 + l2, u1 + u2)
+    }
+}
+
+impl<'a, F: Float> std::ops::Mul<Self> for Bounds<F> {
+    type Output = Self;
+    #[inline]
+    fn mul(self, Bounds(l2, u2): Self) -> Self::Output {
+        let Bounds(l1, u1) = self;
+        debug_assert!(l1 <= u1 && l2 <= u2);
+        let a = l1 * l2;
+        let b = u1 * u2;
+        // The order may flip when negative numbers are involved, so need min/max
+        Bounds(a.min(b), a.max(b))
+    }
+}
+
+impl<F: Float> std::iter::Product for Bounds<F> {
+    #[inline]
+    fn product<I>(mut iter: I) -> Self
+    where
+        I: Iterator<Item = Self>,
+    {
+        match iter.next() {
+            None => Bounds(F::ZERO, F::ZERO),
+            Some(init) => iter.fold(init, |a, b| a * b),
+        }
+    }
+}
+
+impl<F: Float> Set<F> for Bounds<F> {
+    fn contains<I: Instance<F>>(&self, item: I) -> bool {
+        let v = item.own();
+        let &Bounds(l, u) = self;
+        debug_assert!(l <= u);
+        l <= v && v <= u
+    }
+}
+
+impl<F: Float> Bounds<F> {
+    /// Calculate a common bound (glb, lub) for two bounds.
+    #[inline]
+    pub fn common(&self, &Bounds(l2, u2): &Self) -> Self {
+        let &Bounds(l1, u1) = self;
+        debug_assert!(l1 <= u1 && l2 <= u2);
+        Bounds(l1.min(l2), u1.max(u2))
+    }
+
+    /// Indicates whether `Self` is a superset of the argument bound.
+    #[inline]
+    pub fn superset(&self, &Bounds(l2, u2): &Self) -> bool {
+        let &Bounds(l1, u1) = self;
+        debug_assert!(l1 <= u1 && l2 <= u2);
+        l1 <= l2 && u2 <= u1
+    }
+
+    /// Returns the greatest bound contained by both argument bounds, if one exists.
+    #[inline]
+    pub fn glb(&self, &Bounds(l2, u2): &Self) -> Option<Self> {
+        let &Bounds(l1, u1) = self;
+        debug_assert!(l1 <= u1 && l2 <= u2);
+        let l = l1.max(l2);
+        let u = u1.min(u2);
+        debug_assert!(l <= u);
+        if l < u {
+            Some(Bounds(l, u))
+        } else {
+            None
+        }
+    }
+}
--- a/src/lib.rs	Sun Apr 27 15:45:40 2025 -0500
+++ b/src/lib.rs	Sun Apr 27 15:56:43 2025 -0500
@@ -1,48 +1,48 @@
 // The main documentation is in the README.
 #![doc = include_str!("../README.md")]
-
 // We use unicode. We would like to use much more of it than Rust allows.
 // Live with it. Embrace it.
 #![allow(uncommon_codepoints)]
 #![allow(mixed_script_confusables)]
 #![allow(confusable_idents)]
-
-#![cfg_attr(feature = "nightly",
-    feature(maybe_uninit_array_assume_init,maybe_uninit_slice),
+#![cfg_attr(
+    feature = "nightly",
+    feature(maybe_uninit_array_assume_init, maybe_uninit_slice),
     feature(float_minimum_maximum),
     feature(get_mut_unchecked),
-    feature(cow_is_borrowed),
+    feature(cow_is_borrowed)
 )]
 
-pub mod types;
-pub mod instance;
 pub mod collection;
-pub mod nanleast;
 pub mod error;
+pub mod euclidean;
+pub mod instance;
+pub mod maputil;
+pub mod nanleast;
+pub mod norms;
 pub mod parallelism;
-pub mod maputil;
 pub mod tuple;
-pub mod euclidean;
-pub mod norms;
+pub mod types;
 #[macro_use]
 pub mod loc;
+pub mod bisection_tree;
+pub mod bounds;
+pub mod coefficients;
+pub mod convex;
+pub mod direct_product;
+pub mod discrete_gradient;
+pub mod fe_model;
 pub mod iter;
-pub mod linops;
 pub mod iterate;
-pub mod tabledump;
-pub mod logger;
+pub mod lingrid;
+pub mod linops;
 pub mod linsolve;
-pub mod lingrid;
-pub mod sets;
+pub mod logger;
 pub mod mapping;
-pub mod coefficients;
-pub mod fe_model;
-pub mod bisection_tree;
+pub(crate) mod metaprogramming;
 pub mod nalgebra_support;
-pub(crate) mod metaprogramming;
-pub mod direct_product;
-pub mod convex;
-pub mod discrete_gradient;
 pub mod operator_arithmetic;
+pub mod sets;
+pub mod tabledump;
 
 pub use types::*;

mercurial