Tue, 09 Sep 2025 08:24:27 -0500
Remove Norm trait bound from BoundedLinear
/*! Bounded and minimizable/maximizable mappings. */ use crate::instance::{Instance, Space}; use crate::mapping::Mapping; 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<N, F>) -> 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 real-valued [`Mapping`] that provides rough bounds as well as minimisation and maximisation. pub trait MinMaxMapping<Domain: Space, F: Float = f64>: Mapping<Domain, Codomain = F> + 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) -> (Domain, 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<(Domain, F)> { let res @ (_, v) = self.maximise(tolerance, max_steps); (v > bound).then_some(res) } /// 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) -> (Domain, 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 /// below the `bound` threshold, otherwise `None`. fn minimise_below(&mut self, bound: F, tolerance: F, max_steps: usize) -> Option<(Domain, F)> { let res @ (_, v) = self.minimise(tolerance, max_steps); (v < bound).then_some(res) } /// 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 { match self.maximise_above(bound, tolerance, max_steps) { None => true, Some((_, v)) => v <= bound, } } /// 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 { match self.minimise_below(bound, tolerance, max_steps) { None => true, Some((_, v)) => v >= bound, } } } /// 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 } } }