Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make extensive tests exhaustive if there are enough iterations available #416

Merged
merged 1 commit into from
Jan 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/libm-test/src/gen/domain_logspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,5 @@ where
let start = domain.range_start();
let end = domain.range_end();
let steps = OpITy::<Op>::try_from(ntests).unwrap_or(OpITy::<Op>::MAX);
logspace(start, end, steps).map(|v| (v,))
logspace(start, end, steps).0.map(|v| (v,))
}
237 changes: 171 additions & 66 deletions crates/libm-test/src/gen/extensive.rs
Original file line number Diff line number Diff line change
@@ -1,44 +1,79 @@
use std::fmt;
use std::ops::RangeInclusive;

use libm::support::MinInt;
use libm::support::{Float, MinInt};

use crate::domain::HasDomain;
use crate::gen::KnownSize;
use crate::op::OpITy;
use crate::run_cfg::{int_range, iteration_count};
use crate::{CheckCtx, GeneratorKind, MathOp, logspace};
use crate::{CheckCtx, GeneratorKind, MathOp, linear_ints, logspace};

/// Generate a sequence of inputs that either cover the domain in completeness (for smaller float
/// types and single argument functions) or provide evenly spaced inputs across the domain with
/// approximately `u32::MAX` total iterations.
pub trait ExtensiveInput<Op> {
fn get_cases(ctx: &CheckCtx) -> impl ExactSizeIterator<Item = Self> + Send;
fn get_cases(ctx: &CheckCtx) -> (impl Iterator<Item = Self> + Send, u64);
}

/// Construct an iterator from `logspace` and also calculate the total number of steps expected
/// for that iterator.
fn logspace_steps<Op>(
start: Op::FTy,
end: Op::FTy,
ctx: &CheckCtx,
argnum: usize,
max_steps: u64,
) -> (impl Iterator<Item = Op::FTy> + Clone, u64)
where
Op: MathOp,
OpITy<Op>: TryFrom<u64, Error: fmt::Debug>,
u64: TryFrom<OpITy<Op>, Error: fmt::Debug>,
RangeInclusive<OpITy<Op>>: Iterator,
{
let max_steps = iteration_count(ctx, GeneratorKind::Extensive, argnum);
let max_steps = OpITy::<Op>::try_from(max_steps).unwrap_or(OpITy::<Op>::MAX);
let iter = logspace(start, end, max_steps);
let (iter, steps) = logspace(start, end, max_steps);

// `steps` will be <= the original `max_steps`, which is a `u64`.
(iter, steps.try_into().unwrap())
}

/// Represents the iterator in either `Left` or `Right`.
enum EitherIter<A, B> {
A(A),
B(B),
}

// `logspace` can't implement `ExactSizeIterator` because of the range, but its size hint
// should be accurate (assuming <= usize::MAX iterations).
let size_hint = iter.size_hint();
assert_eq!(size_hint.0, size_hint.1.unwrap());
impl<T, A: Iterator<Item = T>, B: Iterator<Item = T>> Iterator for EitherIter<A, B> {
type Item = T;

(iter, size_hint.0.try_into().unwrap())
fn next(&mut self) -> Option<Self::Item> {
match self {
Self::A(iter) => iter.next(),
Self::B(iter) => iter.next(),
}
}

fn size_hint(&self) -> (usize, Option<usize>) {
match self {
Self::A(iter) => iter.size_hint(),
Self::B(iter) => iter.size_hint(),
}
}
}

/// Gets the total number of possible values, returning `None` if that number doesn't fit in a
/// `u64`.
fn value_count<F: Float>() -> Option<u64>
where
u64: TryFrom<F::Int>,
{
u64::try_from(F::Int::MAX).ok().and_then(|max| max.checked_add(1))
}

/// Returns an iterator of every possible value of type `F`.
fn all_values<F: Float>() -> impl Iterator<Item = F>
where
RangeInclusive<F::Int>: Iterator<Item = F::Int>,
{
(F::Int::MIN..=F::Int::MAX).map(|bits| F::from_bits(bits))
}

macro_rules! impl_extensive_input {
Expand All @@ -48,91 +83,161 @@ macro_rules! impl_extensive_input {
Op: MathOp<RustArgs = Self, FTy = $fty>,
Op: HasDomain<Op::FTy>,
{
fn get_cases(ctx: &CheckCtx) -> impl ExactSizeIterator<Item = Self> {
let start = Op::DOMAIN.range_start();
let end = Op::DOMAIN.range_end();
let (iter0, steps0) = logspace_steps::<Op>(start, end, ctx, 0);
let iter0 = iter0.map(|v| (v,));
KnownSize::new(iter0, steps0)
fn get_cases(ctx: &CheckCtx) -> (impl Iterator<Item = Self>, u64) {
let max_steps0 = iteration_count(ctx, GeneratorKind::Extensive, 0);
// `f16` and `f32` can have exhaustive tests.
match value_count::<Op::FTy>() {
Some(steps0) if steps0 <= max_steps0 => {
let iter0 = all_values();
let iter0 = iter0.map(|v| (v,));
(EitherIter::A(iter0), steps0)
}
_ => {
let start = Op::DOMAIN.range_start();
let end = Op::DOMAIN.range_end();
let (iter0, steps0) = logspace_steps::<Op>(start, end, max_steps0);
let iter0 = iter0.map(|v| (v,));
(EitherIter::B(iter0), steps0)
}
}
}
}

impl<Op> ExtensiveInput<Op> for ($fty, $fty)
where
Op: MathOp<RustArgs = Self, FTy = $fty>,
{
fn get_cases(ctx: &CheckCtx) -> impl ExactSizeIterator<Item = Self> {
let start = <$fty>::NEG_INFINITY;
let end = <$fty>::INFINITY;
let (iter0, steps0) = logspace_steps::<Op>(start, end, ctx, 0);
let (iter1, steps1) = logspace_steps::<Op>(start, end, ctx, 1);
let iter =
iter0.flat_map(move |first| iter1.clone().map(move |second| (first, second)));
let count = steps0.checked_mul(steps1).unwrap();
KnownSize::new(iter, count)
fn get_cases(ctx: &CheckCtx) -> (impl Iterator<Item = Self>, u64) {
let max_steps0 = iteration_count(ctx, GeneratorKind::Extensive, 0);
let max_steps1 = iteration_count(ctx, GeneratorKind::Extensive, 1);
// `f16` can have exhaustive tests.
match value_count::<Op::FTy>() {
Some(count) if count <= max_steps0 && count <= max_steps1 => {
let iter = all_values()
.flat_map(|first| all_values().map(move |second| (first, second)));
(EitherIter::A(iter), count.checked_mul(count).unwrap())
}
_ => {
let start = <$fty>::NEG_INFINITY;
let end = <$fty>::INFINITY;
let (iter0, steps0) = logspace_steps::<Op>(start, end, max_steps0);
let (iter1, steps1) = logspace_steps::<Op>(start, end, max_steps1);
let iter = iter0.flat_map(move |first| {
iter1.clone().map(move |second| (first, second))
});
let count = steps0.checked_mul(steps1).unwrap();
(EitherIter::B(iter), count)
}
}
}
}

impl<Op> ExtensiveInput<Op> for ($fty, $fty, $fty)
where
Op: MathOp<RustArgs = Self, FTy = $fty>,
{
fn get_cases(ctx: &CheckCtx) -> impl ExactSizeIterator<Item = Self> {
let start = <$fty>::NEG_INFINITY;
let end = <$fty>::INFINITY;

let (iter0, steps0) = logspace_steps::<Op>(start, end, ctx, 0);
let (iter1, steps1) = logspace_steps::<Op>(start, end, ctx, 1);
let (iter2, steps2) = logspace_steps::<Op>(start, end, ctx, 2);

let iter = iter0
.flat_map(move |first| iter1.clone().map(move |second| (first, second)))
.flat_map(move |(first, second)| {
iter2.clone().map(move |third| (first, second, third))
});
let count = steps0.checked_mul(steps1).unwrap().checked_mul(steps2).unwrap();

KnownSize::new(iter, count)
fn get_cases(ctx: &CheckCtx) -> (impl Iterator<Item = Self>, u64) {
let max_steps0 = iteration_count(ctx, GeneratorKind::Extensive, 0);
let max_steps1 = iteration_count(ctx, GeneratorKind::Extensive, 1);
let max_steps2 = iteration_count(ctx, GeneratorKind::Extensive, 2);
// `f16` can be exhaustive tested if `LIBM_EXTENSIVE_TESTS` is incresed.
match value_count::<Op::FTy>() {
Some(count)
if count <= max_steps0 && count <= max_steps1 && count <= max_steps2 =>
{
let iter = all_values().flat_map(|first| {
all_values().flat_map(move |second| {
all_values().map(move |third| (first, second, third))
})
});
(EitherIter::A(iter), count.checked_pow(3).unwrap())
}
_ => {
let start = <$fty>::NEG_INFINITY;
let end = <$fty>::INFINITY;

let (iter0, steps0) = logspace_steps::<Op>(start, end, max_steps0);
let (iter1, steps1) = logspace_steps::<Op>(start, end, max_steps1);
let (iter2, steps2) = logspace_steps::<Op>(start, end, max_steps2);

let iter = iter0
.flat_map(move |first| iter1.clone().map(move |second| (first, second)))
.flat_map(move |(first, second)| {
iter2.clone().map(move |third| (first, second, third))
});
let count =
steps0.checked_mul(steps1).unwrap().checked_mul(steps2).unwrap();

(EitherIter::B(iter), count)
}
}
}
}

impl<Op> ExtensiveInput<Op> for (i32, $fty)
where
Op: MathOp<RustArgs = Self, FTy = $fty>,
{
fn get_cases(ctx: &CheckCtx) -> impl ExactSizeIterator<Item = Self> {
let start = <$fty>::NEG_INFINITY;
let end = <$fty>::INFINITY;
fn get_cases(ctx: &CheckCtx) -> (impl Iterator<Item = Self>, u64) {
let range0 = int_range(ctx, GeneratorKind::Extensive, 0);
let max_steps0 = iteration_count(ctx, GeneratorKind::Extensive, 0);
let max_steps1 = iteration_count(ctx, GeneratorKind::Extensive, 1);
match value_count::<Op::FTy>() {
Some(count1) if count1 <= max_steps1 => {
let (iter0, steps0) = linear_ints(range0, max_steps0);
let iter = iter0
.flat_map(move |first| all_values().map(move |second| (first, second)));
(EitherIter::A(iter), steps0.checked_mul(count1).unwrap())
}
_ => {
let start = <$fty>::NEG_INFINITY;
let end = <$fty>::INFINITY;

let iter0 = int_range(ctx, GeneratorKind::Extensive, 0);
let steps0 = iteration_count(ctx, GeneratorKind::Extensive, 0);
let (iter1, steps1) = logspace_steps::<Op>(start, end, ctx, 1);
let (iter0, steps0) = linear_ints(range0, max_steps0);
let (iter1, steps1) = logspace_steps::<Op>(start, end, max_steps1);

let iter =
iter0.flat_map(move |first| iter1.clone().map(move |second| (first, second)));
let count = steps0.checked_mul(steps1).unwrap();
let iter = iter0.flat_map(move |first| {
iter1.clone().map(move |second| (first, second))
});
let count = steps0.checked_mul(steps1).unwrap();

KnownSize::new(iter, count)
(EitherIter::B(iter), count)
}
}
}
}

impl<Op> ExtensiveInput<Op> for ($fty, i32)
where
Op: MathOp<RustArgs = Self, FTy = $fty>,
{
fn get_cases(ctx: &CheckCtx) -> impl ExactSizeIterator<Item = Self> {
let start = <$fty>::NEG_INFINITY;
let end = <$fty>::INFINITY;
fn get_cases(ctx: &CheckCtx) -> (impl Iterator<Item = Self>, u64) {
let max_steps0 = iteration_count(ctx, GeneratorKind::Extensive, 0);
let range1 = int_range(ctx, GeneratorKind::Extensive, 1);
let max_steps1 = iteration_count(ctx, GeneratorKind::Extensive, 1);
match value_count::<Op::FTy>() {
Some(count0) if count0 <= max_steps0 => {
let (iter1, steps1) = linear_ints(range1, max_steps1);
let iter = all_values().flat_map(move |first| {
iter1.clone().map(move |second| (first, second))
});
(EitherIter::A(iter), count0.checked_mul(steps1).unwrap())
}
_ => {
let start = <$fty>::NEG_INFINITY;
let end = <$fty>::INFINITY;

let (iter0, steps0) = logspace_steps::<Op>(start, end, ctx, 0);
let iter1 = int_range(ctx, GeneratorKind::Extensive, 0);
let steps1 = iteration_count(ctx, GeneratorKind::Extensive, 0);
let (iter0, steps0) = logspace_steps::<Op>(start, end, max_steps0);
let (iter1, steps1) = linear_ints(range1, max_steps1);

let iter =
iter0.flat_map(move |first| iter1.clone().map(move |second| (first, second)));
let count = steps0.checked_mul(steps1).unwrap();
let iter = iter0.flat_map(move |first| {
iter1.clone().map(move |second| (first, second))
});
let count = steps0.checked_mul(steps1).unwrap();

KnownSize::new(iter, count)
(EitherIter::B(iter), count)
}
}
}
}
};
Expand All @@ -145,10 +250,10 @@ impl_extensive_input!(f64);
#[cfg(f128_enabled)]
impl_extensive_input!(f128);

/// Create a test case iterator for extensive inputs.
/// Create a test case iterator for extensive inputs. Also returns the total test case count.
pub fn get_test_cases<Op>(
ctx: &CheckCtx,
) -> impl ExactSizeIterator<Item = Op::RustArgs> + Send + use<'_, Op>
) -> (impl Iterator<Item = Op::RustArgs> + Send + use<'_, Op>, u64)
where
Op: MathOp,
Op::RustArgs: ExtensiveInput<Op>,
Expand Down
2 changes: 1 addition & 1 deletion crates/libm-test/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use std::time::SystemTime;

pub use f8_impl::f8;
pub use libm::support::{Float, Int, IntTy, MinInt};
pub use num::{FloatExt, logspace};
pub use num::{FloatExt, linear_ints, logspace};
pub use op::{
BaseName, FloatTy, Identifier, MathOp, OpCFn, OpCRet, OpFTy, OpRustFn, OpRustRet, Ty,
};
Expand Down
Loading