Skip to main content

Crate intrval

Crate intrval 

Source
Expand description

§intrvals

Build Status Latest version MSRV Documentation codecov

intrvals is a generic intervals library with basic arithmetic (+, -, *) and sets (union, intersect, complement) operations support.

Its main structure is an Interval which represents a single-dimensioned (possibly unbounded) span of values instantiated by any type T.

§Continuous intervals

If the underlying type T is discrete like one of the integer types, the Interval is not really continuous, but we still consider it contains every value between the given bounds.

§macro syntax

Allows to simplify the definitions of an interval in common inequality and ranges terms.

For the half-open intervals, the inclusive bound is marked with = symbol; for the closed interval both [a, b] and (=a, =b) definitions are possible.


let i0: Interval<i16> = interval!(0);
assert_eq!(i0, Interval::Empty);

let igt2: Interval<i16> = interval!(> 2);
assert_eq!(igt2, Interval::GreaterThan(2));

let igt10: Interval<i16> = interval!(> 10);
assert_eq!(igt10, Interval::GreaterThan(10));

let i_2to10_incl: Interval<i16> = interval!([-2, 10]);
assert_eq!(i_2to10_incl, Interval::Closed((-2, 10)));

let i5to20_excl: Interval<i16> = interval!((5, =20));
assert_eq!(i5to20_excl, Interval::LeftOpen((5, 20)));

let iuni: Interval<i16> = interval!(U);
assert_eq!(iuni, Interval::Full);

§common functions

Functions not falling into arithmetic or set categories, but still common for all intervals.


assert!(interval!(0: i32).is_empty());
assert!(interval!((1, 0)).is_empty());
assert!(interval!((0, 0)).is_empty());
assert!(!(interval!([0, 0]).is_empty()));
assert!(interval!(U: i32).is_full());

let igt10 = interval!(> 10);
assert!(igt10.contains(&11));
assert!(!(igt10.contains(&10)));
assert!(!(interval!(0).contains(&0)));

assert_eq!(interval!(0: i8).len(), Size::Empty);
assert_eq!(interval!((1, 0)).len(), Size::Empty);
assert_eq!(interval!([1, 1]).len(), Size::SinglePoint);
assert_eq!(interval!((-10, =10)).len(), Size::Finite(20));
assert_eq!(interval!(> 2).len(), Size::Infinite);

let i_left_open = interval!((2, =5));
assert_eq!(i_left_open.clamp(10).unwrap(), (Ordering::Equal, 5));
assert_eq!(i_left_open.clamp(3).unwrap(), (Ordering::Equal, 3));
assert_eq!(i_left_open.clamp(2).unwrap(), (Ordering::Greater, 2));
assert_eq!(i_left_open.clamp(0).unwrap(), (Ordering::Greater, 2));

§scalar arithmetic

Add, subtract or multiply the interval bounds with a scalar value of type U if the underlying type T: {Add, Sub, Mul}<U>.


// negation changes the sign and flips the bounds
assert_eq!(-interval!(> 2), interval!(< -2));
assert_eq!(-interval!([-2, 10]), interval!([-10, 2]));

// full and empy does not change with scalars
assert_eq!(interval!(0) + 5, interval!(0));
assert_eq!(interval!(0: i32) * 2, interval!(0));
assert_eq!(interval!(U) - 100, interval!(U));
assert_eq!(interval!(U: i32) * -5, interval!(U));
// however, multiplying by 0 is different
assert_eq!(interval!(U: i32) * 0, interval!([0, 0]));

assert_eq!(interval!(> 2) + 3, interval!(> 5));
assert_eq!(interval!(> 10) - 5, interval!(> 5));
assert_eq!(interval!(> 2) * 5, interval!(> 10));
assert_eq!(interval!((5, =20)) / 5, Interval::LeftOpen((1, 4)));
// multiplying/dividing by negative flips the bounds
assert_eq!(interval!([-2, 10]) * -4, interval!([-40, 8]));
assert_eq!(interval!([16, 79]) / -8, Interval::Closed((-9, -2)));

§interval arithmetic

Add, subtract or multiply an Interval<T> with an Interval<U> to produce another Interval<Z> if the underlying type T: {Add, Sub, Mul}<U, Output=Z>.


let i0 = interval!(0: i32);
let igt10 = interval!(> 10);
let i_2to10_incl = interval!([-2, 10]);
let i5to20_excl = interval!((5, =20));
let iuni = interval!(U: i32);

assert_eq!(igt10 + i_2to10_incl, interval!(> 8));
// adding an empty interval one does not change the proper one
assert_eq!(igt10 + interval!((1, 0)), igt10);
assert_eq!(interval!((1, 0)) + igt10, igt10);

assert_eq!(i_2to10_incl - i5to20_excl, interval!((=-22, 5)));
// subtracting an empty interval does not change the proper one
assert_eq!(igt10 - interval!((1, 0)), igt10);
// subtracting _from_ an empty interval negates the proper one
assert_eq!(interval!((2, 0)) - i_2to10_incl, -i_2to10_incl);

// Interval::Empty is neutral over multiplication
assert_eq!(i0 * i_2to10_incl, i0);
// positive (+inf) times positive is positive
assert_eq!(interval!(> 2) * igt10, interval!(> 20));
// positive (+inf) times (negative and positive) is (-inf, +inf)
assert_eq!(igt10 * i_2to10_incl, interval!(U));
assert_eq!(igt10 * i5to20_excl, interval!(> 50));
// Interval::Full is neutral over multiplication
assert_eq!(i5to20_excl * iuni, iuni);

§set operations

Representation of an Interval-s as a sets of points allows to apply various set operations on them.


let igt2 = interval!(> 2);
let igt_e2 = interval!(>= 2);
let igt10 = interval!(> 10);

assert!(igt_e2.is_super(&igt2));
assert!(igt2.is_sub(&igt_e2));
assert!(!igt2.is_super(&igt_e2));
assert!(!igt_e2.is_sub(&igt2));
assert!(igt2.is_super(&igt10));
assert!(igt10.is_sub(&igt2));
assert!(igt2.is_disjoint(&interval!(< 0)));
assert!(igt2.is_disjoint(&interval!(< 2)));
assert!(!igt2.is_disjoint(&interval!(<= 2)));

assert_eq!(igt2.complement().into_single().unwrap(), interval!(<= 2));
// `.complement` is aliased with `!`
assert_eq!(
    (!interval!([-2, 10])).into_pair().unwrap(),
    (interval!(< -2), igt10)
);

assert_eq!(
    igt2.difference(igt10).unwrap().into_single().unwrap(),
    interval!((2, =10)),
);
assert_eq!(
    igt2.symmetric_difference(interval!(<= 5))
        .unwrap()
        .into_pair()
        .unwrap(),
    (interval!(<= 2), interval!(> 5)),
);
// `.symmetric_difference` is aliased with `^` (falling back to Interval::Empty)
assert_eq!(
    (interval!(<= 5) ^ interval!((0, 5))).into_pair().unwrap(),
    (interval!(<= 0), interval!(== 5)),
);

assert_eq!(igt2.intersect(igt10).unwrap(), igt10);
// `.intersect` is aliased with `&` (falling back to Interval::Empty)
assert!((igt10 & interval!([-2, 10])).is_empty());
assert_eq!(
    interval!([-2, 10]) & interval!((5, =20)),
    interval!((5, =10))
);

assert_eq!(
    igt10.union(interval!([-2, 10])).into_single().unwrap(),
    interval!(>= -2)
);
// `.union` is aliased with `|`
assert_eq!(
    (interval!([-2, 10]) | igt2).into_single().unwrap(),
    interval!(>= -2)
);
assert_eq!(
    (interval!((5, 10)) | interval!([3, 4]))
        .into_pair()
        .unwrap(),
    // reorders the input intervals in left-to-right order if they do not intersect
    (interval!([3, 4]), interval!((5, 10)))
);
assert_eq!((interval!(0) | igt2).into_single().unwrap(), igt2);
assert_eq!((igt2 | interval!(0)).into_single().unwrap(), igt2);

assert_eq!(interval!([-2, 10]).enclosure(igt10 * 2), interval!(>= -2));

§Discrete intervals

LinearSpace is an Interval combined with the notion of step to represent discrete space of points.

§constructors (injectors) and getters (projectors)

Create and deconstruct discrete intervals.


let space = LinearSpace::try_bounded(interval!([-2, 10]), 3_i8).unwrap();
assert_eq!(space.bounds(), &interval!([-2, 10]));
assert_eq!(space.step(), &3);
assert_eq!(space.into_parts(), (interval!([-2, 10]), 3));
assert_eq!(
    space.map(|x| x * 2).unwrap().into_parts(),
    (interval!([-4, 20]), 6)
);

let space = LinearSpace::try_new(10_i8).unwrap();
assert_eq!(space.bounds(), &interval!(U));
assert_eq!(space.step(), &10);
assert_eq!(space.into_parts(), (interval!(U), 10));
assert_eq!(
    space.map(|x| x + 2).unwrap().into_parts(),
    (interval!(U), 12)
);
assert!(space.map(|x| x - 10).is_none());

§arithmetic operations

Modify the space by doing some simple operations using underlying Interval implementations.


let space = LinearSpace::try_bounded(interval!((20, =142)), 10_u16).unwrap();

// `Shl` and `Shr` (forwarded to underlying `Interval`, without changing the `step`)
assert_eq!((space >> 5).into_parts(), (interval!((25, =147)), 10));
assert_eq!((space << 20).into_parts(), (interval!((0, =122)), 10));

// `Mul`/`Div` by scalar to extend/shrink the `Interval` along with the `step`
assert_eq!(
    (space * 10).unwrap().into_parts(),
    (interval!((200, =1420)), 100)
);
assert_eq!((space / 3).unwrap().into_parts(), (interval!((6, =47)), 3));

// `Mul` by another `LinearSpace`
let space2 = LinearSpace::try_bounded(interval!([5, 10]), 3).unwrap();
assert_eq!((space * space2).into_parts(), (interval!((100, =1420)), 30));

§iterators

Convert a space into forward and backward iterators.


let space = LinearSpace::try_bounded(interval!((20, =80)), 10_u16).unwrap();
assert_eq!(
    space.try_into_forward_iter().unwrap().collect::<Vec<_>>(),
    [30, 40, 50, 60, 70, 80]
);
assert_eq!(
    space.into_forward_iter_from(65).collect::<Vec<_>>(),
    [70, 80]
);

let space_unbounded_lower = LinearSpace::try_bounded(interval!(<= 20), 5_u8).unwrap();
let _err = space_unbounded_lower.try_into_forward_iter().unwrap_err();
assert_eq!(
    space_unbounded_lower
        .into_forward_iter_from(8)
        .collect::<Vec<_>>(),
    [10, 15, 20]
);

assert_eq!(
    space.try_into_backward_iter().unwrap().collect::<Vec<_>>(),
    [80, 70, 60, 50, 40, 30]
);
assert_eq!(
    space.into_backward_iter_up_to(42).collect::<Vec<_>>(),
    [40, 30]
);

let space_unbounded_upper = LinearSpace::try_bounded(interval!(> 100), 5_u8).unwrap();
let _err = space_unbounded_upper.try_into_backward_iter().unwrap_err();
assert_eq!(
    space_unbounded_upper
        .into_backward_iter_up_to(127)
        .collect::<Vec<_>>(),
    [125, 120, 115, 110, 105]
);

§rounding

Round (using Roundable trait) a point: T to the values of the space.


let space = LinearSpace::try_bounded(interval!(> 100), 4_u8).unwrap();

assert_eq!(space.round(&102, DirectedMode::UP).unwrap(), 104);
assert_eq!(space.round(&117, DirectedMode::DOWN).unwrap(), 116);
assert_eq!(
    space.round(&253, DirectedMode::AwayFromZero).unwrap_err(),
    RoundError::InvalidDirection {
        rounded: 252,
        direction: DirectedMode::AwayFromZero
    }
);
assert_eq!(
    space
        .round(&142, NearestMode(DirectedMode::DOWN))
        .unwrap(),
    140,
);

#[cfg(feature = "random")]
{
    use intrval::discrete::rounding::StochasticMode;
    let rounded = space
        .round_with_rng(
            &141,
            StochasticMode,
            // you should provide an optional RNG for the stable results,
            // see the caveats of using fallback RNG in docs for `round_with_rng` method.
            None,
        )
        .unwrap();
    assert!([140, 144].contains(&rounded));
}

§Features

By default, all the features below are disabled to ensure minimalistic no-dependency library.

§serde

Enables the support of serde::{Serialize, Deserialize} for Interval<T>.

§arbitrary

Enables the proptest::Arbitrary for Interval<T> along with the property tests (could be invoked with cargo test prop_test --features=arbitrary).

§random

Enables the ability to use stochastic rounding (discrete::rounding::Mode::Stochastic) and random tie-breaking (in the discrete::rounding::Mode::Nearest).

This feature also enables the Roundable::round_with_rng to be able to provide a custom RNG (random number generator implementing rand::RngCore). If you do not provide an RNG (either calling Roundable::round_with_rng with None or calling Roundable::round) using any of the modes mentioned above, be aware that the default very simple RNG (rand::SmallRng) will be used. Its seed can be provided as a CONST_RANDOM_SEED environment variable at compile time (during cargo build or cargo run).

§singleton

This feature is useful if you want to have a dedicated Interval::Singleton variant (otherwise represented as Interval::Closed). This allows to create an Interval from a single value without cloning it. However, to get the bounds of an Interval, you have to meet the requirement of T: Clone again.

The table below summarizes these limitations for the Interval<T>

use casesingleton featureno singleton feature
create an Interval from a single point
(Singleton::singleton)
-T: Clone
convert an Interval into a pair of Endpoint-s
(Bounded::into_bounds)
T: Clone-

There are two helper traits used to implement the desired feature-gated behaviour:

  • trait Singleton to create a point-sized Interval from a single value (when the feature is disabled, create an Interval::Closed((x.clone(), x)) instead);
  • trait SingletonBounds to convert a single value into a pair of Endpoint-s (when the feature is disabled, this trait has no methods and implemented for an Interval<T> unconditionally).

Modules§

discrete
Implementation of discrete intervals, i.e. intervals over types with discrete values (e.g. integers).

Macros§

impl_int_div
Helper macro to implement IntDiv for numeric types which ratio could be (fallibly) converted to/from core numeric types that already implement IntDiv.
interval
Create an Interval using a concise syntax.

Structs§

EmptyIntervalError
The operation error indicating that the interval is empty.

Enums§

Endpoint
The bound of an interval.
Interval
Represent the subset of domain values for some T: PartialOrd.
OneOrPair
Either a single value or a pair of values.
Size
Represent a length of the arbitrary interval.
ValOrInf
Extend a type T by adding a notion of possible infinity to it.

Traits§

Bounded
Extend the IntoBounds trait to allow creating a value from a pair of Endpoint-s.
IntDiv
Extend a self-divisible type with integer division.
IntoBounds
Used to convert an interval-like value into its endpoints.
Linear
Helper trait combining the four basic arithmetic linear operations:
Measure
The ability to have a distance between two values of the type.
MonotonicLinear
Extension of linear types with monotonic addition and subtraction.
SetOps
Provides a bunch of set operations for Bounded types.
Singleton
Create a representation of a singleton sets from the given value.
SingletonBounds
Special trait to convert a value into Endpoint-s representing a singleton interval.
Zero
The trait to define scalar (single-dimension) types with a dedicated origin (zero) point.