rint 0.1.0

A pure Rust library for the numerical integration of real or complex valued functions of real variables in multiple dimensions.
Documentation
use crate::{InitialisationError, InitialisationErrorKind};

/// Integration limits for an integration axis.
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub struct Limits {
    lower: f64,
    upper: f64,
}

impl Limits {
    /// Generate a new set of integration [`Limits`].
    ///
    /// # Errors
    ///
    /// Returns an [`InitialisationError`] if `(upper-lower).abs()<f64::MAX` or
    /// `(upper+lower).abs()<f64::MAX` is _not_ satisfied.
    pub const fn new(lower: f64, upper: f64) -> Result<Self, InitialisationError> {
        if (upper - lower).abs() > f64::MAX || (upper + lower).abs() > f64::MAX {
            let kind = InitialisationErrorKind::IntegrationRangeTooLarge { lower, upper };
            let err = InitialisationError::new(kind);
            Err(err)
        } else {
            let out = Self { lower, upper };
            Ok(out)
        }
    }

    pub(crate) const fn new_unchecked(lower: f64, upper: f64) -> Self {
        Self { lower, upper }
    }

    /// Return the lower integration limit.
    #[must_use]
    pub const fn lower(&self) -> f64 {
        self.lower
    }

    /// Return the upper integration limit.
    #[must_use]
    pub const fn upper(&self) -> f64 {
        self.upper
    }

    /// Return the centre of the integration region.
    pub(crate) const fn centre(&self) -> f64 {
        // Overflow guard
        (self.upper).midpoint(self.lower)
    }

    /// Return the width of the integration region.
    pub(crate) const fn width(&self) -> f64 {
        2.0 * self.half_width()
    }

    /// Return the half width of the integration region.
    pub(crate) const fn half_width(&self) -> f64 {
        (self.upper).midpoint(-self.lower)
    }

    /// Bisect the integration region into two new regions.
    #[must_use]
    pub const fn bisect(&self) -> [Self; 2] {
        let upper = self.upper();
        let lower = self.lower();
        let midpoint = self.centre();

        [
            Self::new_unchecked(lower, midpoint),
            Self::new_unchecked(midpoint, upper),
        ]
    }

    /// Determine if a subinterval is too small.
    ///
    /// If an integral has a singularity or other area of difficulty, it is possible for a
    /// subregion to become too small upon further bisection. This function does a guard check of
    /// this condition and returns true iff the subregion can be further subdivided.
    #[inline]
    pub(crate) fn subinterval_too_small(&self) -> bool {
        let lower = self.lower();
        let upper = self.upper();
        let midpoint = self.centre();

        let eps = f64::EPSILON;
        let min = f64::MIN_POSITIVE;

        let tmp = (1.0 + 100.0 * eps) * (midpoint.abs() + 1000.0 * min);

        lower.abs() <= tmp && upper.abs() <= tmp
    }

    /// Scale the limits by a constant factor.
    ///
    /// # Errors
    /// Returns an [`InitialisationError`] if upon rescalling the new bounds `lower` and `upper` do
    /// not satisfy `(upper-lower).abs()<f64::MAX` or `(upper+lower).abs()<f64::MAX` .
    pub fn scale(self, scale: f64) -> Result<Self, InitialisationError> {
        let lower = self.lower() * scale;
        let upper = self.upper() * scale;

        Limits::new(lower, upper)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_bisection() {
        let limit = Limits::new(0.0, 1.0).unwrap();
        let [lower, upper] = limit.bisect();

        assert_eq!(lower, Limits::new(0.0, 0.5).unwrap());
        assert_eq!(upper, Limits::new(0.5, 1.0).unwrap());
    }
}