src/bounds.rs

branch
dev
changeset 97
4e80fb049dca
child 124
6aa955ad8122
--- /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
+        }
+    }
+}

mercurial