use num_bigint::BigInt;
use num_rational::BigRational;
use num_traits::{One, Signed, Zero};
use std::cmp::Ordering;
use std::fmt;
use std::ops::{Add, Div, Mul, Neg, Sub};
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Bound {
NegInf,
Finite(BigRational),
PosInf,
}
impl Bound {
#[inline]
pub fn finite(r: BigRational) -> Self {
Self::Finite(r)
}
#[inline]
pub fn from_int(n: i64) -> Self {
Self::Finite(BigRational::from_integer(BigInt::from(n)))
}
#[inline]
pub fn is_finite(&self) -> bool {
matches!(self, Self::Finite(_))
}
#[inline]
pub fn is_infinite(&self) -> bool {
!self.is_finite()
}
#[inline]
pub fn is_neg_inf(&self) -> bool {
matches!(self, Self::NegInf)
}
#[inline]
pub fn is_pos_inf(&self) -> bool {
matches!(self, Self::PosInf)
}
pub fn as_finite(&self) -> Option<&BigRational> {
match self {
Self::Finite(r) => Some(r),
_ => None,
}
}
pub fn negate(&self) -> Bound {
match self {
Self::NegInf => Self::PosInf,
Self::PosInf => Self::NegInf,
Self::Finite(r) => Self::Finite(-r),
}
}
pub fn add(&self, other: &Bound) -> Bound {
match (self, other) {
(Self::NegInf, Self::PosInf) | (Self::PosInf, Self::NegInf) => {
panic!("Undefined: inf + (-inf)")
}
(Self::NegInf, _) | (_, Self::NegInf) => Self::NegInf,
(Self::PosInf, _) | (_, Self::PosInf) => Self::PosInf,
(Self::Finite(a), Self::Finite(b)) => Self::Finite(a + b),
}
}
pub fn sub(&self, other: &Bound) -> Bound {
self.add(&other.negate())
}
pub fn mul(&self, other: &Bound) -> Bound {
match (self, other) {
(Self::Finite(a), Self::Finite(b)) => Self::Finite(a * b),
(Self::Finite(a), inf) | (inf, Self::Finite(a)) => {
if a.is_zero() {
Self::Finite(BigRational::zero())
} else if a.is_positive() {
inf.clone()
} else {
inf.negate()
}
}
(Self::NegInf, Self::NegInf) | (Self::PosInf, Self::PosInf) => Self::PosInf,
(Self::NegInf, Self::PosInf) | (Self::PosInf, Self::NegInf) => Self::NegInf,
}
}
pub fn cmp_bound(&self, other: &Bound) -> Ordering {
match (self, other) {
(Self::NegInf, Self::NegInf) => Ordering::Equal,
(Self::NegInf, _) => Ordering::Less,
(_, Self::NegInf) => Ordering::Greater,
(Self::PosInf, Self::PosInf) => Ordering::Equal,
(Self::PosInf, _) => Ordering::Greater,
(_, Self::PosInf) => Ordering::Less,
(Self::Finite(a), Self::Finite(b)) => a.cmp(b),
}
}
pub fn min(&self, other: &Bound) -> Bound {
if self.cmp_bound(other) == Ordering::Less {
self.clone()
} else {
other.clone()
}
}
pub fn max(&self, other: &Bound) -> Bound {
if self.cmp_bound(other) == Ordering::Greater {
self.clone()
} else {
other.clone()
}
}
}
impl PartialOrd for Bound {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(std::cmp::Ord::cmp(self, other))
}
}
impl Ord for Bound {
fn cmp(&self, other: &Self) -> Ordering {
self.cmp_bound(other)
}
}
impl fmt::Display for Bound {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NegInf => write!(f, "-∞"),
Self::PosInf => write!(f, "+∞"),
Self::Finite(r) => write!(f, "{}", r),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Interval {
pub lo: Bound,
pub hi: Bound,
pub lo_open: bool,
pub hi_open: bool,
}
impl Interval {
pub fn empty() -> Self {
Self {
lo: Bound::PosInf,
hi: Bound::NegInf,
lo_open: true,
hi_open: true,
}
}
pub fn reals() -> Self {
Self {
lo: Bound::NegInf,
hi: Bound::PosInf,
lo_open: true,
hi_open: true,
}
}
pub fn point(a: BigRational) -> Self {
Self {
lo: Bound::Finite(a.clone()),
hi: Bound::Finite(a),
lo_open: false,
hi_open: false,
}
}
pub fn closed(a: BigRational, b: BigRational) -> Self {
Self {
lo: Bound::Finite(a),
hi: Bound::Finite(b),
lo_open: false,
hi_open: false,
}
}
pub fn open(a: BigRational, b: BigRational) -> Self {
Self {
lo: Bound::Finite(a),
hi: Bound::Finite(b),
lo_open: true,
hi_open: true,
}
}
pub fn half_open_right(a: BigRational, b: BigRational) -> Self {
Self {
lo: Bound::Finite(a),
hi: Bound::Finite(b),
lo_open: false,
hi_open: true,
}
}
pub fn half_open_left(a: BigRational, b: BigRational) -> Self {
Self {
lo: Bound::Finite(a),
hi: Bound::Finite(b),
lo_open: true,
hi_open: false,
}
}
pub fn at_most(b: BigRational) -> Self {
Self {
lo: Bound::NegInf,
hi: Bound::Finite(b),
lo_open: true,
hi_open: false,
}
}
pub fn less_than(b: BigRational) -> Self {
Self {
lo: Bound::NegInf,
hi: Bound::Finite(b),
lo_open: true,
hi_open: true,
}
}
pub fn at_least(a: BigRational) -> Self {
Self {
lo: Bound::Finite(a),
hi: Bound::PosInf,
lo_open: false,
hi_open: true,
}
}
pub fn greater_than(a: BigRational) -> Self {
Self {
lo: Bound::Finite(a),
hi: Bound::PosInf,
lo_open: true,
hi_open: true,
}
}
pub fn is_empty(&self) -> bool {
match self.lo.cmp_bound(&self.hi) {
Ordering::Greater => true,
Ordering::Equal => self.lo_open || self.hi_open,
Ordering::Less => false,
}
}
pub fn is_point(&self) -> bool {
!self.is_empty() && self.lo == self.hi && !self.lo_open && !self.hi_open
}
pub fn is_bounded(&self) -> bool {
self.lo.is_finite() && self.hi.is_finite()
}
pub fn contains(&self, x: &BigRational) -> bool {
let x_bound = Bound::Finite(x.clone());
let lo_ok = match self.lo.cmp_bound(&x_bound) {
Ordering::Less => true,
Ordering::Equal => !self.lo_open,
Ordering::Greater => false,
};
let hi_ok = match x_bound.cmp_bound(&self.hi) {
Ordering::Less => true,
Ordering::Equal => !self.hi_open,
Ordering::Greater => false,
};
lo_ok && hi_ok
}
pub fn contains_zero(&self) -> bool {
self.contains(&BigRational::zero())
}
pub fn is_positive(&self) -> bool {
match &self.lo {
Bound::NegInf => false,
Bound::PosInf => self.is_empty(),
Bound::Finite(r) => {
if r.is_positive() {
true
} else if r.is_zero() {
self.lo_open
} else {
false
}
}
}
}
pub fn is_negative(&self) -> bool {
match &self.hi {
Bound::PosInf => false,
Bound::NegInf => self.is_empty(),
Bound::Finite(r) => {
if r.is_negative() {
true
} else if r.is_zero() {
self.hi_open
} else {
false
}
}
}
}
pub fn is_non_negative(&self) -> bool {
match &self.lo {
Bound::NegInf => false,
Bound::PosInf => true,
Bound::Finite(r) => !r.is_negative(),
}
}
pub fn is_non_positive(&self) -> bool {
match &self.hi {
Bound::PosInf => false,
Bound::NegInf => true,
Bound::Finite(r) => !r.is_positive(),
}
}
pub fn sign(&self) -> Option<i8> {
if self.is_empty() {
return None;
}
if self.is_point()
&& let Bound::Finite(r) = &self.lo
{
return Some(if r.is_positive() {
1
} else if r.is_negative() {
-1
} else {
0
});
}
if self.is_positive() {
Some(1)
} else if self.is_negative() {
Some(-1)
} else {
None
}
}
pub fn negate(&self) -> Interval {
Interval {
lo: self.hi.negate(),
hi: self.lo.negate(),
lo_open: self.hi_open,
hi_open: self.lo_open,
}
}
pub fn add(&self, other: &Interval) -> Interval {
if self.is_empty() || other.is_empty() {
return Interval::empty();
}
Interval {
lo: self.lo.add(&other.lo),
hi: self.hi.add(&other.hi),
lo_open: self.lo_open || other.lo_open,
hi_open: self.hi_open || other.hi_open,
}
}
pub fn sub(&self, other: &Interval) -> Interval {
self.add(&other.negate())
}
pub fn mul(&self, other: &Interval) -> Interval {
if self.is_empty() || other.is_empty() {
return Interval::empty();
}
let products = [
(&self.lo, &other.lo, self.lo_open || other.lo_open),
(&self.lo, &other.hi, self.lo_open || other.hi_open),
(&self.hi, &other.lo, self.hi_open || other.lo_open),
(&self.hi, &other.hi, self.hi_open || other.hi_open),
];
let mut bounds: Vec<(Bound, bool)> = products
.iter()
.map(|(a, b, open)| (a.mul(b), *open))
.collect();
bounds.sort_by(|a, b| a.0.cmp_bound(&b.0));
let (lo, lo_open) = bounds.first().unwrap().clone();
let (hi, hi_open) = bounds.last().unwrap().clone();
Interval {
lo,
hi,
lo_open,
hi_open,
}
}
pub fn div(&self, other: &Interval) -> Option<Interval> {
if self.is_empty() || other.is_empty() {
return Some(Interval::empty());
}
if other.contains_zero() {
return None;
}
let recip_lo = match &other.hi {
Bound::NegInf | Bound::PosInf => Bound::Finite(BigRational::zero()),
Bound::Finite(r) => {
if r.is_zero() {
return None; }
Bound::Finite(BigRational::one() / r)
}
};
let recip_hi = match &other.lo {
Bound::NegInf | Bound::PosInf => Bound::Finite(BigRational::zero()),
Bound::Finite(r) => {
if r.is_zero() {
return None; }
Bound::Finite(BigRational::one() / r)
}
};
let recip = Interval {
lo: recip_lo,
hi: recip_hi,
lo_open: other.hi_open,
hi_open: other.lo_open,
};
Some(self.mul(&recip))
}
pub fn intersect(&self, other: &Interval) -> Interval {
if self.is_empty() || other.is_empty() {
return Interval::empty();
}
let (lo, lo_open) = match self.lo.cmp_bound(&other.lo) {
Ordering::Less => (other.lo.clone(), other.lo_open),
Ordering::Greater => (self.lo.clone(), self.lo_open),
Ordering::Equal => (self.lo.clone(), self.lo_open || other.lo_open),
};
let (hi, hi_open) = match self.hi.cmp_bound(&other.hi) {
Ordering::Less => (self.hi.clone(), self.hi_open),
Ordering::Greater => (other.hi.clone(), other.hi_open),
Ordering::Equal => (self.hi.clone(), self.hi_open || other.hi_open),
};
Interval {
lo,
hi,
lo_open,
hi_open,
}
}
pub fn union(&self, other: &Interval) -> Option<Interval> {
if self.is_empty() {
return Some(other.clone());
}
if other.is_empty() {
return Some(self.clone());
}
let intersects = !self.intersect(other).is_empty();
let adjacent_left = self.hi == other.lo && (!self.hi_open || !other.lo_open);
let adjacent_right = other.hi == self.lo && (!other.hi_open || !self.lo_open);
if !intersects && !adjacent_left && !adjacent_right {
return None;
}
let (lo, lo_open) = match self.lo.cmp_bound(&other.lo) {
Ordering::Less => (self.lo.clone(), self.lo_open),
Ordering::Greater => (other.lo.clone(), other.lo_open),
Ordering::Equal => (self.lo.clone(), self.lo_open && other.lo_open),
};
let (hi, hi_open) = match self.hi.cmp_bound(&other.hi) {
Ordering::Greater => (self.hi.clone(), self.hi_open),
Ordering::Less => (other.hi.clone(), other.hi_open),
Ordering::Equal => (self.hi.clone(), self.hi_open && other.hi_open),
};
Some(Interval {
lo,
hi,
lo_open,
hi_open,
})
}
pub fn pow(&self, n: u32) -> Interval {
if self.is_empty() {
return Interval::empty();
}
if n == 0 {
return Interval::point(BigRational::one());
}
if n == 1 {
return self.clone();
}
if n.is_multiple_of(2) {
if self.contains_zero() {
let lo_abs = match &self.lo {
Bound::Finite(r) => r.abs(),
Bound::NegInf => return Interval::at_least(BigRational::zero()),
Bound::PosInf => unreachable!(),
};
let hi_abs = match &self.hi {
Bound::Finite(r) => r.abs(),
Bound::PosInf => return Interval::at_least(BigRational::zero()),
Bound::NegInf => unreachable!(),
};
let max_abs = lo_abs.max(hi_abs);
Interval {
lo: Bound::Finite(BigRational::zero()),
hi: Bound::Finite(pow_rational(&max_abs, n)),
lo_open: false,
hi_open: self.lo_open && self.hi_open,
}
} else if self.is_positive() {
let lo_val = match &self.lo {
Bound::Finite(r) => pow_rational(r, n),
_ => return Interval::at_least(BigRational::zero()),
};
let hi_val = match &self.hi {
Bound::Finite(r) => pow_rational(r, n),
_ => return Interval::at_least(lo_val),
};
Interval {
lo: Bound::Finite(lo_val),
hi: Bound::Finite(hi_val),
lo_open: self.lo_open,
hi_open: self.hi_open,
}
} else {
let lo_val = match &self.hi {
Bound::Finite(r) => pow_rational(r, n),
_ => return Interval::at_least(BigRational::zero()),
};
let hi_val = match &self.lo {
Bound::Finite(r) => pow_rational(r, n),
_ => return Interval::at_least(lo_val),
};
Interval {
lo: Bound::Finite(lo_val),
hi: Bound::Finite(hi_val),
lo_open: self.hi_open,
hi_open: self.lo_open,
}
}
} else {
let lo_val = match &self.lo {
Bound::NegInf => Bound::NegInf,
Bound::PosInf => Bound::PosInf,
Bound::Finite(r) => Bound::Finite(pow_rational(r, n)),
};
let hi_val = match &self.hi {
Bound::NegInf => Bound::NegInf,
Bound::PosInf => Bound::PosInf,
Bound::Finite(r) => Bound::Finite(pow_rational(r, n)),
};
Interval {
lo: lo_val,
hi: hi_val,
lo_open: self.lo_open,
hi_open: self.hi_open,
}
}
}
pub fn midpoint(&self) -> Option<BigRational> {
match (&self.lo, &self.hi) {
(Bound::Finite(a), Bound::Finite(b)) => {
Some((a + b) / BigRational::from_integer(BigInt::from(2)))
}
_ => None,
}
}
pub fn width(&self) -> Option<BigRational> {
match (&self.lo, &self.hi) {
(Bound::Finite(a), Bound::Finite(b)) => Some(b - a),
_ => None,
}
}
pub fn split(&self, at: &BigRational) -> (Interval, Interval) {
if !self.contains(at) {
return (self.clone(), Interval::empty());
}
let left = Interval {
lo: self.lo.clone(),
hi: Bound::Finite(at.clone()),
lo_open: self.lo_open,
hi_open: false,
};
let right = Interval {
lo: Bound::Finite(at.clone()),
hi: self.hi.clone(),
lo_open: false,
hi_open: self.hi_open,
};
(left, right)
}
pub fn hull(&self, other: &Interval) -> Interval {
if self.is_empty() {
return other.clone();
}
if other.is_empty() {
return self.clone();
}
let (lo, lo_open) = match self.lo.cmp_bound(&other.lo) {
Ordering::Less => (self.lo.clone(), self.lo_open),
Ordering::Greater => (other.lo.clone(), other.lo_open),
Ordering::Equal => (self.lo.clone(), self.lo_open && other.lo_open),
};
let (hi, hi_open) = match self.hi.cmp_bound(&other.hi) {
Ordering::Greater => (self.hi.clone(), self.hi_open),
Ordering::Less => (other.hi.clone(), other.hi_open),
Ordering::Equal => (self.hi.clone(), self.hi_open && other.hi_open),
};
Interval {
lo,
hi,
lo_open,
hi_open,
}
}
pub fn tighten_lower(&self, new_lo: Bound, new_lo_open: bool) -> Interval {
if self.is_empty() {
return Interval::empty();
}
let (lo, lo_open) = match self.lo.cmp_bound(&new_lo) {
Ordering::Less => (new_lo, new_lo_open),
Ordering::Greater => (self.lo.clone(), self.lo_open),
Ordering::Equal => (self.lo.clone(), self.lo_open || new_lo_open),
};
Interval {
lo,
hi: self.hi.clone(),
lo_open,
hi_open: self.hi_open,
}
}
pub fn tighten_upper(&self, new_hi: Bound, new_hi_open: bool) -> Interval {
if self.is_empty() {
return Interval::empty();
}
let (hi, hi_open) = match self.hi.cmp_bound(&new_hi) {
Ordering::Greater => (new_hi, new_hi_open),
Ordering::Less => (self.hi.clone(), self.hi_open),
Ordering::Equal => (self.hi.clone(), self.hi_open || new_hi_open),
};
Interval {
lo: self.lo.clone(),
hi,
lo_open: self.lo_open,
hi_open,
}
}
pub fn propagate_add(x: &Interval, result: &Interval) -> Interval {
result.sub(x)
}
pub fn propagate_sub_left(x: &Interval, result: &Interval) -> Interval {
x.sub(result)
}
pub fn propagate_sub_right(y: &Interval, result: &Interval) -> Interval {
result.add(y)
}
pub fn propagate_mul(x: &Interval, result: &Interval) -> Option<Interval> {
Interval::div(result, x)
}
pub fn propagate_div_left(x: &Interval, result: &Interval) -> Option<Interval> {
Interval::div(x, result)
}
pub fn propagate_div_right(y: &Interval, result: &Interval) -> Interval {
result.mul(y)
}
pub fn is_subset_of(&self, other: &Interval) -> bool {
if self.is_empty() {
return true;
}
if other.is_empty() {
return false;
}
let lo_ok = match self.lo.cmp_bound(&other.lo) {
Ordering::Less => false,
Ordering::Greater => true,
Ordering::Equal => !self.lo_open || other.lo_open,
};
let hi_ok = match self.hi.cmp_bound(&other.hi) {
Ordering::Greater => false,
Ordering::Less => true,
Ordering::Equal => !self.hi_open || other.hi_open,
};
lo_ok && hi_ok
}
pub fn overlaps(&self, other: &Interval) -> bool {
!self.intersect(other).is_empty()
}
pub fn widen(&self, amount: &BigRational) -> Interval {
if self.is_empty() {
return Interval::empty();
}
let lo = match &self.lo {
Bound::Finite(r) => Bound::Finite(r - amount),
bound => bound.clone(),
};
let hi = match &self.hi {
Bound::Finite(r) => Bound::Finite(r + amount),
bound => bound.clone(),
};
Interval {
lo,
hi,
lo_open: self.lo_open,
hi_open: self.hi_open,
}
}
}
fn pow_rational(r: &BigRational, n: u32) -> BigRational {
if n == 0 {
return BigRational::one();
}
let mut result = BigRational::one();
let mut base = r.clone();
let mut exp = n;
while exp > 0 {
if exp & 1 == 1 {
result = &result * &base;
}
base = &base * &base;
exp >>= 1;
}
result
}
impl fmt::Display for Interval {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_empty() {
write!(f, "∅")
} else {
let lo_bracket = if self.lo_open { '(' } else { '[' };
let hi_bracket = if self.hi_open { ')' } else { ']' };
write!(f, "{}{}, {}{}", lo_bracket, self.lo, self.hi, hi_bracket)
}
}
}
impl Default for Interval {
fn default() -> Self {
Self::reals()
}
}
impl Neg for Interval {
type Output = Interval;
fn neg(self) -> Self::Output {
self.negate()
}
}
impl Neg for &Interval {
type Output = Interval;
fn neg(self) -> Self::Output {
self.negate()
}
}
impl Add for Interval {
type Output = Interval;
fn add(self, rhs: Self) -> Self::Output {
Interval::add(&self, &rhs)
}
}
impl Add<&Interval> for &Interval {
type Output = Interval;
fn add(self, rhs: &Interval) -> Self::Output {
Interval::add(self, rhs)
}
}
impl Sub for Interval {
type Output = Interval;
fn sub(self, rhs: Self) -> Self::Output {
Interval::sub(&self, &rhs)
}
}
impl Sub<&Interval> for &Interval {
type Output = Interval;
fn sub(self, rhs: &Interval) -> Self::Output {
Interval::sub(self, rhs)
}
}
impl Mul for Interval {
type Output = Interval;
fn mul(self, rhs: Self) -> Self::Output {
Interval::mul(&self, &rhs)
}
}
impl Mul<&Interval> for &Interval {
type Output = Interval;
fn mul(self, rhs: &Interval) -> Self::Output {
Interval::mul(self, rhs)
}
}
impl Div for Interval {
type Output = Option<Interval>;
fn div(self, rhs: Self) -> Self::Output {
Interval::div(&self, &rhs)
}
}
impl Div<&Interval> for &Interval {
type Output = Option<Interval>;
fn div(self, rhs: &Interval) -> Self::Output {
Interval::div(self, rhs)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn rat(n: i64) -> BigRational {
BigRational::from_integer(BigInt::from(n))
}
#[test]
fn test_interval_empty() {
let i = Interval::empty();
assert!(i.is_empty());
assert!(!i.contains(&rat(0)));
}
#[test]
fn test_interval_point() {
let i = Interval::point(rat(5));
assert!(i.is_point());
assert!(i.contains(&rat(5)));
assert!(!i.contains(&rat(4)));
assert!(i.is_positive());
}
#[test]
fn test_interval_closed() {
let i = Interval::closed(rat(1), rat(5));
assert!(i.contains(&rat(1)));
assert!(i.contains(&rat(3)));
assert!(i.contains(&rat(5)));
assert!(!i.contains(&rat(0)));
assert!(!i.contains(&rat(6)));
}
#[test]
fn test_interval_open() {
let i = Interval::open(rat(1), rat(5));
assert!(!i.contains(&rat(1)));
assert!(i.contains(&rat(3)));
assert!(!i.contains(&rat(5)));
}
#[test]
fn test_interval_add() {
let a = Interval::closed(rat(1), rat(3));
let b = Interval::closed(rat(2), rat(4));
let c = Interval::add(&a, &b);
assert!(c.contains(&rat(3)));
assert!(c.contains(&rat(7)));
assert!(!c.contains(&rat(2)));
assert!(!c.contains(&rat(8)));
}
#[test]
fn test_interval_mul() {
let a = Interval::closed(rat(2), rat(3));
let b = Interval::closed(rat(4), rat(5));
let c = Interval::mul(&a, &b);
assert!(c.contains(&rat(8)));
assert!(c.contains(&rat(15)));
assert!(!c.contains(&rat(7)));
assert!(!c.contains(&rat(16)));
}
#[test]
fn test_interval_mul_mixed_signs() {
let a = Interval::closed(rat(-2), rat(3));
let b = Interval::closed(rat(-1), rat(2));
let c = Interval::mul(&a, &b);
assert!(c.contains(&rat(-4)));
assert!(c.contains(&rat(6)));
assert!(c.contains(&rat(0)));
assert!(!c.contains(&rat(-5)));
assert!(!c.contains(&rat(7)));
}
#[test]
fn test_interval_negate() {
let a = Interval::closed(rat(1), rat(5));
let b = a.negate();
assert!(b.contains(&rat(-5)));
assert!(b.contains(&rat(-1)));
assert!(!b.contains(&rat(0)));
}
#[test]
fn test_interval_intersect() {
let a = Interval::closed(rat(1), rat(5));
let b = Interval::closed(rat(3), rat(7));
let c = a.intersect(&b);
assert!(c.contains(&rat(3)));
assert!(c.contains(&rat(5)));
assert!(!c.contains(&rat(2)));
assert!(!c.contains(&rat(6)));
}
#[test]
fn test_interval_pow_even() {
let a = Interval::closed(rat(-2), rat(3));
let b = a.pow(2);
assert!(b.contains(&rat(0)));
assert!(b.contains(&rat(9)));
assert!(!b.contains(&rat(-1)));
assert!(!b.contains(&rat(10)));
}
#[test]
fn test_interval_pow_odd() {
let a = Interval::closed(rat(-2), rat(3));
let b = a.pow(3);
assert!(b.contains(&rat(-8)));
assert!(b.contains(&rat(27)));
assert!(!b.contains(&rat(-9)));
assert!(!b.contains(&rat(28)));
}
#[test]
fn test_interval_sign() {
assert_eq!(Interval::closed(rat(1), rat(5)).sign(), Some(1));
assert_eq!(Interval::closed(rat(-5), rat(-1)).sign(), Some(-1));
assert_eq!(Interval::closed(rat(-1), rat(1)).sign(), None);
assert_eq!(Interval::point(rat(0)).sign(), Some(0));
}
#[test]
fn test_interval_unbounded() {
let a = Interval::at_least(rat(0));
assert!(a.is_non_negative());
assert!(a.contains(&rat(0)));
assert!(a.contains(&rat(1000)));
assert!(!a.contains(&rat(-1)));
let b = Interval::less_than(rat(0));
assert!(b.is_negative());
assert!(!b.contains(&rat(0)));
assert!(b.contains(&rat(-1)));
}
#[test]
fn test_interval_midpoint() {
let i = Interval::closed(rat(2), rat(8));
assert_eq!(i.midpoint(), Some(rat(5)));
}
#[test]
fn test_interval_div() {
let a = Interval::closed(rat(4), rat(12));
let b = Interval::closed(rat(2), rat(3));
let c = Interval::div(&a, &b).expect("Division should succeed");
assert!(c.contains(&BigRational::new(BigInt::from(4), BigInt::from(3))));
assert!(c.contains(&rat(6)));
}
#[test]
fn test_interval_div_by_zero() {
let a = Interval::closed(rat(1), rat(2));
let b = Interval::closed(rat(-1), rat(1)); assert!(Interval::div(&a, &b).is_none());
}
#[test]
fn test_interval_div_positive() {
let a = Interval::closed(rat(6), rat(12));
let b = Interval::closed(rat(2), rat(3));
let c = Interval::div(&a, &b).expect("Division should succeed");
assert!(c.contains(&rat(2)));
assert!(c.contains(&rat(6)));
}
#[test]
fn test_interval_hull() {
let a = Interval::closed(rat(1), rat(3));
let b = Interval::closed(rat(5), rat(7));
let hull = a.hull(&b);
assert_eq!(hull.lo, Bound::Finite(rat(1)));
assert_eq!(hull.hi, Bound::Finite(rat(7)));
assert!(hull.contains(&rat(1)));
assert!(hull.contains(&rat(4)));
assert!(hull.contains(&rat(7)));
}
#[test]
fn test_interval_tighten_lower() {
let i = Interval::closed(rat(1), rat(10));
let tightened = i.tighten_lower(Bound::Finite(rat(5)), false);
assert_eq!(tightened.lo, Bound::Finite(rat(5)));
assert_eq!(tightened.hi, Bound::Finite(rat(10)));
assert!(!tightened.contains(&rat(4)));
assert!(tightened.contains(&rat(5)));
}
#[test]
fn test_interval_tighten_upper() {
let i = Interval::closed(rat(1), rat(10));
let tightened = i.tighten_upper(Bound::Finite(rat(6)), false);
assert_eq!(tightened.lo, Bound::Finite(rat(1)));
assert_eq!(tightened.hi, Bound::Finite(rat(6)));
assert!(tightened.contains(&rat(6)));
assert!(!tightened.contains(&rat(7)));
}
#[test]
fn test_interval_propagate_add() {
let x = Interval::closed(rat(1), rat(2));
let result = Interval::closed(rat(5), rat(8));
let y = Interval::propagate_add(&x, &result);
assert!(y.contains(&rat(3)));
assert!(y.contains(&rat(7)));
assert!(!y.contains(&rat(2)));
assert!(!y.contains(&rat(8)));
}
#[test]
fn test_interval_propagate_sub() {
let x = Interval::closed(rat(10), rat(15));
let result = Interval::closed(rat(2), rat(5));
let y = Interval::propagate_sub_left(&x, &result);
assert!(y.contains(&rat(5)));
assert!(y.contains(&rat(13)));
}
#[test]
fn test_interval_propagate_mul() {
let x = Interval::closed(rat(2), rat(3));
let result = Interval::closed(rat(10), rat(18));
let y = Interval::propagate_mul(&x, &result).expect("Propagation should succeed");
assert!(y.contains(&rat(5)));
}
#[test]
fn test_interval_is_subset_of() {
let a = Interval::closed(rat(2), rat(5));
let b = Interval::closed(rat(1), rat(10));
assert!(a.is_subset_of(&b));
assert!(!b.is_subset_of(&a));
let c = Interval::closed(rat(1), rat(10));
assert!(c.is_subset_of(&c));
}
#[test]
fn test_interval_overlaps() {
let a = Interval::closed(rat(1), rat(5));
let b = Interval::closed(rat(3), rat(7));
assert!(a.overlaps(&b));
assert!(b.overlaps(&a));
let c = Interval::closed(rat(10), rat(15));
assert!(!a.overlaps(&c));
}
#[test]
fn test_interval_widen() {
let i = Interval::closed(rat(5), rat(10));
let widened = i.widen(&rat(2));
assert_eq!(widened.lo, Bound::Finite(rat(3)));
assert_eq!(widened.hi, Bound::Finite(rat(12)));
assert!(widened.contains(&rat(3)));
assert!(widened.contains(&rat(12)));
assert!(!widened.contains(&rat(2)));
assert!(!widened.contains(&rat(13)));
}
}