src/bounds.rs

Mon, 01 Sep 2025 00:04:16 -0500

author
Tuomo Valkonen <tuomov@iki.fi>
date
Mon, 01 Sep 2025 00:04:16 -0500
branch
dev
changeset 148
26ef556870fd
parent 126
0ccad3ee8e95
child 197
1f301affeae3
permissions
-rw-r--r--

AXPY for nalgebra matrices, not just vectors

/*!
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
        }
    }
}

mercurial