maryada 0.1.2

No-std binary64 real interval arithmetic conforming to IEEE 1788.1
Documentation
#![cfg(feature = "num-complex")]

use maryada::{ComplexBox, DecoratedInterval, Decoration, Interval, decoration_part, set_dec};
use num_complex::Complex64;

fn assert_contains(interval: Interval, value: f64) {
    assert!(interval.inf() <= value && value <= interval.sup());
}

fn assert_contains_complex(value: ComplexBox<Interval>, reference: Complex64) {
    assert_contains(value.re, reference.re);
    assert_contains(value.im, reference.im);
}

#[test]
fn arithmetic_and_elementary_functions_enclose_singleton_references() {
    let x = ComplexBox::<Interval>::new(1.0.into(), 2.0.into());
    let y = ComplexBox::<Interval>::new(3.0.into(), 4.0.into());
    let z = ComplexBox::<Interval>::new(5.0.into(), 6.0.into());
    let fused = x.mul_add(y, z);
    assert_eq!(fused.re.bounds(), (0.0, 0.0));
    assert_eq!(fused.im.bounds(), (16.0, 16.0));

    let square = ComplexBox::<Interval>::new(3.0.into(), 4.0.into()).sqr();
    assert_eq!(square.re.bounds(), (-7.0, -7.0));
    assert_eq!(square.im.bounds(), (24.0, 24.0));

    for (result, reference) in [
        (ComplexBox::<Interval>::from(0.5).atan(), 0.5_f64.atan()),
        (ComplexBox::<Interval>::from(0.5).atanh(), 0.5_f64.atanh()),
    ] {
        assert_contains(result.re, reference);
        assert_contains(result.im, 0.0);
    }
    let acosh = ComplexBox::<Interval>::from(2.0).acosh();
    assert_contains(acosh.re, 2.0_f64.acosh());
    assert_contains(acosh.im, 0.0);
}

#[test]
fn rectangular_geometry_and_set_relations_are_componentwise() {
    let inner = ComplexBox::<Interval>::new(Interval::new(1.0, 2.0), Interval::new(1.0, 2.0));
    let outer = ComplexBox::<Interval>::new(Interval::new(0.0, 3.0), Interval::new(0.0, 3.0));
    let apart = ComplexBox::<Interval>::new(Interval::new(4.0, 5.0), Interval::new(1.0, 2.0));

    assert!(inner.subset(outer));
    assert!(inner.interior(outer));
    assert!(inner.disjoint(apart));
    assert_eq!(inner.intersection(outer), inner);
    assert_eq!(inner.convex_hull(outer), outer);

    let value = ComplexBox::<Interval>::new(Interval::new(0.0, 6.0), Interval::new(0.0, 8.0));
    assert_eq!(value.wid_box(), Complex64::new(6.0, 8.0));
    assert!(value.diameter() >= 10.0);
    assert!(value.rad() >= 5.0);
    assert_eq!(value.lower_corner(), Complex64::new(0.0, 0.0));
    assert_eq!(value.upper_corner(), Complex64::new(6.0, 8.0));
}

#[test]
fn decorations_conversions_and_constants_preserve_semantics() {
    let decorated = ComplexBox::new(
        set_dec(Interval::from(3.0), Decoration::Def),
        DecoratedInterval::from(4.0),
    );
    let abs = decorated.abs();
    assert_contains(abs.into(), 5.0);
    assert_eq!(decoration_part(abs), Decoration::Def);

    let converted = ComplexBox::<Interval>::from(Complex64::new(2.0, 3.0));
    assert!(converted.contains(Complex64::new(2.0, 3.0)));
    assert!(!converted.contains(Complex64::new(f64::NAN, 3.0)));
    let point = Complex64::new(2.0, 3.0);
    assert_eq!(ComplexBox::<Interval>::from(&point), converted);
    let real = Interval::from(2.0);
    assert_eq!(ComplexBox::<Interval>::from(&real), ComplexBox::from(2.0));
    let scalar = 2.0;
    assert_eq!(ComplexBox::<Interval>::from(&scalar), ComplexBox::from(2.0));

    let decorated_owned = ComplexBox::<DecoratedInterval>::from(converted);
    assert_eq!(ComplexBox::<Interval>::from(decorated_owned), converted);
    assert!(ComplexBox::<Interval>::ZERO.is_zero());
    assert!(ComplexBox::<Interval>::ONE.is_real());
    assert_eq!(ComplexBox::<Interval>::I, ComplexBox::i());
}

#[test]
fn arithmetic_operators_polar_forms_and_powers_enclose_reference_values() {
    let z = ComplexBox::<Interval>::from(Complex64::new(2.0, 1.0));
    let w = ComplexBox::<Interval>::from(Complex64::new(-0.5, 0.25));

    for (result, reference) in [
        (z + w, Complex64::new(1.5, 1.25)),
        (z - w, Complex64::new(2.5, 0.75)),
        (z * w, Complex64::new(-1.25, 0.0)),
        (z / w, Complex64::new(-2.4, -3.2)),
        (-z, Complex64::new(-2.0, -1.0)),
        (z + 2.0, Complex64::new(4.0, 1.0)),
        (2.0 - z, Complex64::new(0.0, -1.0)),
        (z * 2.0, Complex64::new(4.0, 2.0)),
        (2.0 / z, Complex64::new(0.8, -0.4)),
        (z.add_real(1.0.into()), Complex64::new(3.0, 1.0)),
        (z.sub_real(1.0.into()), Complex64::new(1.0, 1.0)),
        (z.scale(2.0.into()), Complex64::new(4.0, 2.0)),
        (z.div_real(2.0.into()), Complex64::new(1.0, 0.5)),
        (z.conj(), Complex64::new(2.0, -1.0)),
        (z.recip(), Complex64::new(0.4, -0.2)),
        (z.pown(0), Complex64::new(1.0, 0.0)),
        (z.pown(2), Complex64::new(3.0, 4.0)),
        (z.powi(3), Complex64::new(2.0, 11.0)),
        (z.pown(-1), Complex64::new(0.4, -0.2)),
    ] {
        assert_contains_complex(result, reference);
    }

    assert_contains(z.norm_sqr(), 5.0);
    assert_contains(z.norm(), 5.0_f64.sqrt());
    let (magnitude, argument) = z.to_polar();
    assert_contains(magnitude, 5.0_f64.sqrt());
    assert_contains(argument, 1.0_f64.atan2(2.0));

    let theta = Interval::from(0.25);
    assert_contains_complex(
        ComplexBox::cis(theta),
        Complex64::new(0.25_f64.cos(), 0.25_f64.sin()),
    );
    assert_contains_complex(
        ComplexBox::from_polar(Interval::from(2.0), theta),
        Complex64::new(2.0 * 0.25_f64.cos(), 2.0 * 0.25_f64.sin()),
    );
    assert_contains_complex(
        ComplexBox::<Interval>::from(2.0).pow(ComplexBox::from(3.0)),
        Complex64::new(8.0, 0.0),
    );
}

#[test]
fn complex_scalar_operators_cover_interval_and_box_combinations() {
    type BareBox = ComplexBox<Interval>;
    type DecoratedBox = ComplexBox<DecoratedInterval>;

    fn bare(_: BareBox) {}
    fn decorated(_: DecoratedBox) {}

    macro_rules! check_op {
        ($op:tt) => {{
            let scalar = Complex64::new(2.0, 1.0);
            let bare_box = BareBox::from(3.0);
            let decorated_box = DecoratedBox::from(4.0);
            let bare_interval = Interval::from(5.0);
            let decorated_interval = DecoratedInterval::from(6.0);

            bare(bare_box $op scalar);
            bare(scalar $op bare_box);
            decorated(decorated_box $op scalar);
            decorated(scalar $op decorated_box);
            bare(bare_interval $op scalar);
            bare(scalar $op bare_interval);
            decorated(decorated_interval $op scalar);
            decorated(scalar $op decorated_interval);
        }};
    }

    check_op!(+);
    check_op!(-);
    check_op!(*);
    check_op!(/);
}

#[test]
fn analytic_families_enclose_real_and_nonreal_reference_formulas() {
    let real = ComplexBox::<Interval>::from(0.5);
    for (result, reference) in [
        (real.sqrt(), 0.5_f64.sqrt()),
        (real.exp(), 0.5_f64.exp()),
        (real.exp2(), 0.5_f64.exp2()),
        (real.exp10(), 10.0_f64.powf(0.5)),
        (real.sin(), 0.5_f64.sin()),
        (real.cos(), 0.5_f64.cos()),
        (real.tan(), 0.5_f64.tan()),
        (real.asin(), 0.5_f64.asin()),
        (real.acos(), 0.5_f64.acos()),
        (real.sinh(), 0.5_f64.sinh()),
        (real.cosh(), 0.5_f64.cosh()),
        (real.tanh(), 0.5_f64.tanh()),
        (real.asinh(), 0.5_f64.asinh()),
    ] {
        assert_contains_complex(result, Complex64::new(reference, 0.0));
    }

    let positive = ComplexBox::<Interval>::from(2.0);
    for (result, reference) in [
        (positive.log(), 2.0_f64.ln()),
        (positive.log2(), 2.0_f64.log2()),
        (positive.log10(), 2.0_f64.log10()),
        (positive.acosh(), 2.0_f64.acosh()),
    ] {
        assert_contains_complex(result, Complex64::new(reference, 0.0));
    }

    let a = 0.25_f64;
    let b = 0.5_f64;
    let z = ComplexBox::<Interval>::from(Complex64::new(a, b));
    assert_contains_complex(
        z.exp(),
        Complex64::new(a.exp() * b.cos(), a.exp() * b.sin()),
    );
    assert_contains_complex(
        z.sin(),
        Complex64::new(a.sin() * b.cosh(), a.cos() * b.sinh()),
    );
    assert_contains_complex(
        z.cos(),
        Complex64::new(a.cos() * b.cosh(), -a.sin() * b.sinh()),
    );
    assert_contains_complex(
        z.sinh(),
        Complex64::new(a.sinh() * b.cos(), a.cosh() * b.sin()),
    );
    assert_contains_complex(
        z.cosh(),
        Complex64::new(a.cosh() * b.cos(), a.sinh() * b.sin()),
    );

    for result in [
        z.tan(),
        z.tanh(),
        z.atan(),
        z.asin(),
        z.acos(),
        z.asinh(),
        z.atanh(),
    ] {
        assert!(!result.is_empty());
        assert!(result.is_bounded());
    }

    let decorated = ComplexBox::new(
        set_dec(Interval::from(0.5), Decoration::Def),
        DecoratedInterval::ZERO,
    );
    for result in [
        decorated.recip(),
        decorated.sqrt(),
        decorated.exp(),
        decorated.log(),
        decorated.sin(),
        decorated.cos(),
        decorated.tan(),
        decorated.sinh(),
        decorated.cosh(),
        decorated.tanh(),
        decorated.asin(),
        decorated.acos(),
        decorated.atan(),
        decorated.asinh(),
        decorated.atanh(),
    ] {
        assert!(!result.is_nai());
        assert_eq!(result.decoration(), Decoration::Def);
    }
}

#[test]
fn geometry_and_set_relations_handle_empty_entire_and_nai_boxes() {
    let value = ComplexBox::<Interval>::new(Interval::new(0.0, 2.0), Interval::new(-2.0, 4.0));
    assert_eq!(value.mid(), Complex64::new(1.0, 1.0));
    assert_eq!(value.wid(), value.wid_box());
    assert_eq!(value.rad_box(), Complex64::new(1.0, 3.0));
    assert_eq!(value.mid_rad(), (value.mid(), value.rad_box()));
    assert_eq!(
        value.corners(),
        [
            Complex64::new(0.0, -2.0),
            Complex64::new(2.0, -2.0),
            Complex64::new(2.0, 4.0),
            Complex64::new(0.0, 4.0),
        ]
    );
    assert!(value.mag() >= value.mig());
    assert!(value.is_bounded());
    assert!(!value.is_singleton());
    assert!(!value.is_real());
    assert!(value.intersects(ComplexBox::from(Complex64::new(1.0, 0.0))));

    let empty = ComplexBox::<Interval>::new(Interval::EMPTY, Interval::ONE);
    assert!(empty.is_empty());
    assert!(empty.rad().is_nan());
    assert!(empty.diameter().is_nan());
    assert!(empty.subset(value));
    assert!(empty.interior(value));
    assert!(!value.subset(empty));
    assert!(!value.interior(empty));
    assert_eq!(empty.convex_hull(value), value);
    assert_eq!(value.convex_hull(empty), value);
    assert!(value.intersection(ComplexBox::from(10.0)).is_empty());
    assert!(empty.pown(3).is_empty());

    assert!(ComplexBox::<Interval>::ENTIRE.is_entire());
    assert!(!ComplexBox::<Interval>::ENTIRE.is_bounded());
    assert_eq!(
        ComplexBox::<Interval>::default(),
        ComplexBox::<Interval>::ZERO
    );
    assert!(ComplexBox::<Interval>::ONE.is_singleton());
    assert!(ComplexBox::<Interval>::ONE.is_real());
    assert!(ComplexBox::<Interval>::ZERO.is_zero());
    assert!(ComplexBox::<Interval>::ONE.to_string().contains("i"));

    let nai = ComplexBox::new(DecoratedInterval::NAI, DecoratedInterval::ZERO);
    let ordinary = ComplexBox::<DecoratedInterval>::ONE;
    assert!(nai.is_nai());
    assert!(!nai.subset(ordinary));
    assert!(!ordinary.subset(nai));
    assert!(!nai.interior(ordinary));
    assert!(!ordinary.interior(nai));

    let decorated = value.decorate(&mut ());
    assert_eq!(ComplexBox::<Interval>::from(&decorated), value);
    assert_eq!(ComplexBox::<DecoratedInterval>::from(&value), decorated);
}