diff -r 962c8e346ab9 -r 4e80fb049dca src/bounds.rs --- /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 { + /// 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 GlobalAnalysis for L +// where L : LocalAnalysis { +// #[inline] +// fn global_analysis(&self) -> Bounds { +// 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: GlobalAnalysis { + /// 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) -> A; +} + +/// Trait for determining the upper and lower bounds of an float-valued [`Mapping`]. +/// +/// This is a blanket-implemented alias for [`GlobalAnalysis`]`>` +/// [`Mapping`] is not a supertrait to allow flexibility in the implementation of either +/// reference or non-reference arguments. +pub trait Bounded: GlobalAnalysis> { + /// Return lower and upper bounds for the values of of `self`. + #[inline] + fn bounds(&self) -> Bounds { + self.global_analysis() + } +} + +impl>> Bounded for T {} + +/// A [`RealMapping`] that provides rough bounds as well as minimisation and maximisation. +pub trait MinMaxMapping: RealMapping + Bounded { + /// 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); + + /// 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)>; + + /// 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); + + /// 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)>; + + /// 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( + /// Lower bound + pub F, + /// Upper bound + pub F, +); + +impl Bounds { + /// 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 Bounds { + /// 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 for Bounds { + 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 for Bounds { + 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 std::iter::Product for Bounds { + #[inline] + fn product(mut iter: I) -> Self + where + I: Iterator, + { + match iter.next() { + None => Bounds(F::ZERO, F::ZERO), + Some(init) => iter.fold(init, |a, b| a * b), + } + } +} + +impl Set for Bounds { + fn contains>(&self, item: I) -> bool { + let v = item.own(); + let &Bounds(l, u) = self; + debug_assert!(l <= u); + l <= v && v <= u + } +} + +impl Bounds { + /// 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 { + 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 + } + } +}