Replace HasDomain to enable multi-argument edge case and domain tests
This also allows reusing the same generator logic between logspace tests and extensive tests, so comes with a nice bit of cleanup. Changes: * Make the generator part of `CheckCtx` since a `Generator` and `CheckCtx` are almost always passed together. * Rename `domain_logspace` to `spaced` since this no longer only operates within a domain and we may want to handle integer spacing. * Domain is now calculated at runtime rather than using traits, which is much easier to work with. * With the above, domains for multidimensional functions are added. * The extensive test generator code tests has been combined with the domain_logspace generator code. With this, the domain tests have just become a subset of extensive tests. These were renamed to "quickspace" since, technically, the extensive tests are also "domain" or "domain logspace" tests. * Edge case generators now handle functions with multiple inputs. * The test runners can be significantly cleaned up and deduplicated.
This commit is contained in:
parent
20cd1e7257
commit
2d857e1c21
12 changed files with 539 additions and 520 deletions
|
|
@ -4,7 +4,7 @@ use std::time::Duration;
|
|||
use criterion::{Criterion, criterion_main};
|
||||
use libm_test::gen::random;
|
||||
use libm_test::gen::random::RandomInput;
|
||||
use libm_test::{CheckBasis, CheckCtx, MathOp, TupleCall};
|
||||
use libm_test::{CheckBasis, CheckCtx, GeneratorKind, MathOp, TupleCall};
|
||||
|
||||
/// Benchmark with this many items to get a variety
|
||||
const BENCH_ITER_ITEMS: usize = if cfg!(feature = "short-benchmarks") { 50 } else { 500 };
|
||||
|
|
@ -52,7 +52,7 @@ where
|
|||
{
|
||||
let name = Op::NAME;
|
||||
|
||||
let ctx = CheckCtx::new(Op::IDENTIFIER, CheckBasis::Musl);
|
||||
let ctx = CheckCtx::new(Op::IDENTIFIER, CheckBasis::Musl, GeneratorKind::Random);
|
||||
let benchvec: Vec<_> =
|
||||
random::get_test_cases::<Op::RustArgs>(&ctx).take(BENCH_ITER_ITEMS).collect();
|
||||
|
||||
|
|
|
|||
|
|
@ -12,9 +12,9 @@ use std::path::Path;
|
|||
use std::process::Command;
|
||||
use std::{env, fs};
|
||||
|
||||
use libm_test::domain::HasDomain;
|
||||
use libm_test::gen::{domain_logspace, edge_cases};
|
||||
use libm_test::{CheckBasis, CheckCtx, MathOp, op};
|
||||
use libm_test::gen::spaced::SpacedInput;
|
||||
use libm_test::gen::{edge_cases, spaced};
|
||||
use libm_test::{CheckBasis, CheckCtx, GeneratorKind, MathOp, op};
|
||||
|
||||
const JL_PLOT: &str = "examples/plot_file.jl";
|
||||
|
||||
|
|
@ -52,23 +52,13 @@ fn main() {
|
|||
/// Run multiple generators for a single operator.
|
||||
fn plot_one_operator<Op>(out_dir: &Path, config: &mut String)
|
||||
where
|
||||
Op: MathOp<FTy = f32> + HasDomain<f32>,
|
||||
Op: MathOp<FTy = f32, RustArgs = (f32,)>,
|
||||
Op::RustArgs: SpacedInput<Op>,
|
||||
{
|
||||
let ctx = CheckCtx::new(Op::IDENTIFIER, CheckBasis::Mpfr);
|
||||
plot_one_generator(
|
||||
out_dir,
|
||||
&ctx,
|
||||
"logspace",
|
||||
config,
|
||||
domain_logspace::get_test_cases::<Op>(&ctx),
|
||||
);
|
||||
plot_one_generator(
|
||||
out_dir,
|
||||
&ctx,
|
||||
"edge_cases",
|
||||
config,
|
||||
edge_cases::get_test_cases::<Op, _>(&ctx),
|
||||
);
|
||||
let mut ctx = CheckCtx::new(Op::IDENTIFIER, CheckBasis::Mpfr, GeneratorKind::QuickSpaced);
|
||||
plot_one_generator(out_dir, &ctx, "logspace", config, spaced::get_test_cases::<Op>(&ctx).0);
|
||||
ctx.gen_kind = GeneratorKind::EdgeCases;
|
||||
plot_one_generator(out_dir, &ctx, "edge_cases", config, edge_cases::get_test_cases::<Op>(&ctx));
|
||||
}
|
||||
|
||||
/// Plot the output of a single generator.
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
//! Traits and operations related to bounds of a function.
|
||||
|
||||
use std::fmt;
|
||||
use std::ops::{self, Bound};
|
||||
use std::ops::Bound;
|
||||
|
||||
use crate::{Float, FloatExt};
|
||||
use libm::support::Int;
|
||||
|
||||
/// Representation of a function's domain.
|
||||
use crate::{BaseName, Float, FloatExt, Identifier};
|
||||
|
||||
/// Representation of a single dimension of a function's domain.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Domain<T> {
|
||||
/// Start of the region for which a function is defined (ignoring poles).
|
||||
|
|
@ -39,56 +41,131 @@ impl<F: FloatExt> Domain<F> {
|
|||
}
|
||||
}
|
||||
|
||||
/// A value that may be any float type or any integer type.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum EitherPrim<F, I> {
|
||||
Float(F),
|
||||
Int(I),
|
||||
}
|
||||
|
||||
impl<F: fmt::Debug, I: fmt::Debug> EitherPrim<F, I> {
|
||||
pub fn unwrap_float(self) -> F {
|
||||
match self {
|
||||
EitherPrim::Float(f) => f,
|
||||
EitherPrim::Int(_) => panic!("expected float; got {self:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unwrap_int(self) -> I {
|
||||
match self {
|
||||
EitherPrim::Float(_) => panic!("expected int; got {self:?}"),
|
||||
EitherPrim::Int(i) => i,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenience 1-dimensional float domains.
|
||||
impl<F: Float> Domain<F> {
|
||||
/// x ∈ ℝ
|
||||
pub const UNBOUNDED: Self =
|
||||
const UNBOUNDED: Self =
|
||||
Self { start: Bound::Unbounded, end: Bound::Unbounded, check_points: None };
|
||||
|
||||
/// x ∈ ℝ >= 0
|
||||
pub const POSITIVE: Self =
|
||||
const POSITIVE: Self =
|
||||
Self { start: Bound::Included(F::ZERO), end: Bound::Unbounded, check_points: None };
|
||||
|
||||
/// x ∈ ℝ > 0
|
||||
pub const STRICTLY_POSITIVE: Self =
|
||||
const STRICTLY_POSITIVE: Self =
|
||||
Self { start: Bound::Excluded(F::ZERO), end: Bound::Unbounded, check_points: None };
|
||||
|
||||
/// Wrap in the float variant of [`EitherPrim`].
|
||||
const fn into_prim_float<I>(self) -> EitherPrim<Self, Domain<I>> {
|
||||
EitherPrim::Float(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenience 1-dimensional integer domains.
|
||||
impl<I: Int> Domain<I> {
|
||||
/// x ∈ ℝ
|
||||
const UNBOUNDED_INT: Self =
|
||||
Self { start: Bound::Unbounded, end: Bound::Unbounded, check_points: None };
|
||||
|
||||
/// Wrap in the int variant of [`EitherPrim`].
|
||||
const fn into_prim_int<F>(self) -> EitherPrim<Domain<F>, Self> {
|
||||
EitherPrim::Int(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Multidimensional domains, represented as an array of 1-D domains.
|
||||
impl<F: Float, I: Int> EitherPrim<Domain<F>, Domain<I>> {
|
||||
/// x ∈ ℝ
|
||||
const UNBOUNDED1: [Self; 1] =
|
||||
[Domain { start: Bound::Unbounded, end: Bound::Unbounded, check_points: None }
|
||||
.into_prim_float()];
|
||||
|
||||
/// {x1, x2} ∈ ℝ
|
||||
const UNBOUNDED2: [Self; 2] =
|
||||
[Domain::UNBOUNDED.into_prim_float(), Domain::UNBOUNDED.into_prim_float()];
|
||||
|
||||
/// {x1, x2, x3} ∈ ℝ
|
||||
const UNBOUNDED3: [Self; 3] = [
|
||||
Domain::UNBOUNDED.into_prim_float(),
|
||||
Domain::UNBOUNDED.into_prim_float(),
|
||||
Domain::UNBOUNDED.into_prim_float(),
|
||||
];
|
||||
|
||||
/// {x1, x2} ∈ ℝ, one float and one int
|
||||
const UNBOUNDED_F_I: [Self; 2] =
|
||||
[Domain::UNBOUNDED.into_prim_float(), Domain::UNBOUNDED_INT.into_prim_int()];
|
||||
|
||||
/// x ∈ ℝ >= 0
|
||||
const POSITIVE: [Self; 1] = [Domain::POSITIVE.into_prim_float()];
|
||||
|
||||
/// x ∈ ℝ > 0
|
||||
const STRICTLY_POSITIVE: [Self; 1] = [Domain::STRICTLY_POSITIVE.into_prim_float()];
|
||||
|
||||
/// Used for versions of `asin` and `acos`.
|
||||
pub const INVERSE_TRIG_PERIODIC: Self = Self {
|
||||
const INVERSE_TRIG_PERIODIC: [Self; 1] = [Domain {
|
||||
start: Bound::Included(F::NEG_ONE),
|
||||
end: Bound::Included(F::ONE),
|
||||
check_points: None,
|
||||
};
|
||||
}
|
||||
.into_prim_float()];
|
||||
|
||||
/// Domain for `acosh`
|
||||
pub const ACOSH: Self =
|
||||
Self { start: Bound::Included(F::ONE), end: Bound::Unbounded, check_points: None };
|
||||
const ACOSH: [Self; 1] =
|
||||
[Domain { start: Bound::Included(F::ONE), end: Bound::Unbounded, check_points: None }
|
||||
.into_prim_float()];
|
||||
|
||||
/// Domain for `atanh`
|
||||
pub const ATANH: Self = Self {
|
||||
const ATANH: [Self; 1] = [Domain {
|
||||
start: Bound::Excluded(F::NEG_ONE),
|
||||
end: Bound::Excluded(F::ONE),
|
||||
check_points: None,
|
||||
};
|
||||
}
|
||||
.into_prim_float()];
|
||||
|
||||
/// Domain for `sin`, `cos`, and `tan`
|
||||
pub const TRIG: Self = Self {
|
||||
// TODO
|
||||
const TRIG: [Self; 1] = [Domain {
|
||||
// Trig functions have special behavior at fractions of π.
|
||||
check_points: Some(|| Box::new([-F::PI, -F::FRAC_PI_2, F::FRAC_PI_2, F::PI].into_iter())),
|
||||
..Self::UNBOUNDED
|
||||
};
|
||||
..Domain::UNBOUNDED
|
||||
}
|
||||
.into_prim_float()];
|
||||
|
||||
/// Domain for `log` in various bases
|
||||
pub const LOG: Self = Self::STRICTLY_POSITIVE;
|
||||
const LOG: [Self; 1] = Self::STRICTLY_POSITIVE;
|
||||
|
||||
/// Domain for `log1p` i.e. `log(1 + x)`
|
||||
pub const LOG1P: Self =
|
||||
Self { start: Bound::Excluded(F::NEG_ONE), end: Bound::Unbounded, check_points: None };
|
||||
const LOG1P: [Self; 1] =
|
||||
[Domain { start: Bound::Excluded(F::NEG_ONE), end: Bound::Unbounded, check_points: None }
|
||||
.into_prim_float()];
|
||||
|
||||
/// Domain for `sqrt`
|
||||
pub const SQRT: Self = Self::POSITIVE;
|
||||
const SQRT: [Self; 1] = Self::POSITIVE;
|
||||
|
||||
/// Domain for `gamma`
|
||||
pub const GAMMA: Self = Self {
|
||||
const GAMMA: [Self; 1] = [Domain {
|
||||
check_points: Some(|| {
|
||||
// Negative integers are asymptotes
|
||||
Box::new((0..u8::MAX).map(|scale| {
|
||||
|
|
@ -100,122 +177,84 @@ impl<F: Float> Domain<F> {
|
|||
}))
|
||||
}),
|
||||
// Whether or not gamma is defined for negative numbers is implementation dependent
|
||||
..Self::UNBOUNDED
|
||||
};
|
||||
..Domain::UNBOUNDED
|
||||
}
|
||||
.into_prim_float()];
|
||||
|
||||
/// Domain for `loggamma`
|
||||
pub const LGAMMA: Self = Self::STRICTLY_POSITIVE;
|
||||
const LGAMMA: [Self; 1] = Self::STRICTLY_POSITIVE;
|
||||
|
||||
/// Domain for `jn` and `yn`.
|
||||
// FIXME: the domain should provide some sort of "reasonable range" so we don't actually test
|
||||
// the entire system unbounded.
|
||||
const BESSEL_N: [Self; 2] =
|
||||
[Domain::UNBOUNDED_INT.into_prim_int(), Domain::UNBOUNDED.into_prim_float()];
|
||||
}
|
||||
|
||||
/// Implement on `op::*` types to indicate how they are bounded.
|
||||
pub trait HasDomain<T>
|
||||
where
|
||||
T: Copy + fmt::Debug + ops::Add<Output = T> + ops::Sub<Output = T> + PartialOrd + 'static,
|
||||
{
|
||||
const DOMAIN: Domain<T>;
|
||||
}
|
||||
|
||||
/// Implement [`HasDomain`] for both the `f32` and `f64` variants of a function.
|
||||
macro_rules! impl_has_domain {
|
||||
($($fn_name:ident => $domain:expr;)*) => {
|
||||
paste::paste! {
|
||||
$(
|
||||
// Implement for f64 functions
|
||||
impl HasDomain<f64> for $crate::op::$fn_name::Routine {
|
||||
const DOMAIN: Domain<f64> = Domain::<f64>::$domain;
|
||||
}
|
||||
|
||||
// Implement for f32 functions
|
||||
impl HasDomain<f32> for $crate::op::[< $fn_name f >]::Routine {
|
||||
const DOMAIN: Domain<f32> = Domain::<f32>::$domain;
|
||||
}
|
||||
)*
|
||||
}
|
||||
/// Get the domain for a given function.
|
||||
pub fn get_domain<F: Float, I: Int>(
|
||||
id: Identifier,
|
||||
argnum: usize,
|
||||
) -> EitherPrim<Domain<F>, Domain<I>> {
|
||||
let x = match id.base_name() {
|
||||
BaseName::Acos => &EitherPrim::INVERSE_TRIG_PERIODIC[..],
|
||||
BaseName::Acosh => &EitherPrim::ACOSH[..],
|
||||
BaseName::Asin => &EitherPrim::INVERSE_TRIG_PERIODIC[..],
|
||||
BaseName::Asinh => &EitherPrim::UNBOUNDED1[..],
|
||||
BaseName::Atan => &EitherPrim::UNBOUNDED1[..],
|
||||
BaseName::Atan2 => &EitherPrim::UNBOUNDED2[..],
|
||||
BaseName::Cbrt => &EitherPrim::UNBOUNDED1[..],
|
||||
BaseName::Atanh => &EitherPrim::ATANH[..],
|
||||
BaseName::Ceil => &EitherPrim::UNBOUNDED1[..],
|
||||
BaseName::Cosh => &EitherPrim::UNBOUNDED1[..],
|
||||
BaseName::Copysign => &EitherPrim::UNBOUNDED2[..],
|
||||
BaseName::Cos => &EitherPrim::TRIG[..],
|
||||
BaseName::Exp => &EitherPrim::UNBOUNDED1[..],
|
||||
BaseName::Erf => &EitherPrim::UNBOUNDED1[..],
|
||||
BaseName::Erfc => &EitherPrim::UNBOUNDED1[..],
|
||||
BaseName::Expm1 => &EitherPrim::UNBOUNDED1[..],
|
||||
BaseName::Exp10 => &EitherPrim::UNBOUNDED1[..],
|
||||
BaseName::Exp2 => &EitherPrim::UNBOUNDED1[..],
|
||||
BaseName::Frexp => &EitherPrim::UNBOUNDED1[..],
|
||||
BaseName::Fabs => &EitherPrim::UNBOUNDED1[..],
|
||||
BaseName::Fdim => &EitherPrim::UNBOUNDED2[..],
|
||||
BaseName::Floor => &EitherPrim::UNBOUNDED1[..],
|
||||
BaseName::Fma => &EitherPrim::UNBOUNDED3[..],
|
||||
BaseName::Fmax => &EitherPrim::UNBOUNDED2[..],
|
||||
BaseName::Fmin => &EitherPrim::UNBOUNDED2[..],
|
||||
BaseName::Fmod => &EitherPrim::UNBOUNDED2[..],
|
||||
BaseName::Hypot => &EitherPrim::UNBOUNDED2[..],
|
||||
BaseName::Ilogb => &EitherPrim::UNBOUNDED1[..],
|
||||
BaseName::J0 => &EitherPrim::UNBOUNDED1[..],
|
||||
BaseName::J1 => &EitherPrim::UNBOUNDED1[..],
|
||||
BaseName::Jn => &EitherPrim::BESSEL_N[..],
|
||||
BaseName::Ldexp => &EitherPrim::UNBOUNDED_F_I[..],
|
||||
BaseName::Lgamma => &EitherPrim::LGAMMA[..],
|
||||
BaseName::LgammaR => &EitherPrim::LGAMMA[..],
|
||||
BaseName::Log => &EitherPrim::LOG[..],
|
||||
BaseName::Log10 => &EitherPrim::LOG[..],
|
||||
BaseName::Log1p => &EitherPrim::LOG1P[..],
|
||||
BaseName::Log2 => &EitherPrim::LOG[..],
|
||||
BaseName::Modf => &EitherPrim::UNBOUNDED1[..],
|
||||
BaseName::Nextafter => &EitherPrim::UNBOUNDED2[..],
|
||||
BaseName::Pow => &EitherPrim::UNBOUNDED2[..],
|
||||
BaseName::Remainder => &EitherPrim::UNBOUNDED2[..],
|
||||
BaseName::Remquo => &EitherPrim::UNBOUNDED2[..],
|
||||
BaseName::Rint => &EitherPrim::UNBOUNDED1[..],
|
||||
BaseName::Round => &EitherPrim::UNBOUNDED1[..],
|
||||
BaseName::Scalbn => &EitherPrim::UNBOUNDED_F_I[..],
|
||||
BaseName::Sin => &EitherPrim::TRIG[..],
|
||||
BaseName::Sincos => &EitherPrim::TRIG[..],
|
||||
BaseName::Sinh => &EitherPrim::UNBOUNDED1[..],
|
||||
BaseName::Sqrt => &EitherPrim::SQRT[..],
|
||||
BaseName::Tan => &EitherPrim::TRIG[..],
|
||||
BaseName::Tanh => &EitherPrim::UNBOUNDED1[..],
|
||||
BaseName::Tgamma => &EitherPrim::GAMMA[..],
|
||||
BaseName::Trunc => &EitherPrim::UNBOUNDED1[..],
|
||||
BaseName::Y0 => &EitherPrim::UNBOUNDED1[..],
|
||||
BaseName::Y1 => &EitherPrim::UNBOUNDED1[..],
|
||||
BaseName::Yn => &EitherPrim::BESSEL_N[..],
|
||||
};
|
||||
}
|
||||
|
||||
// Tie functions together with their domains.
|
||||
impl_has_domain! {
|
||||
acos => INVERSE_TRIG_PERIODIC;
|
||||
acosh => ACOSH;
|
||||
asin => INVERSE_TRIG_PERIODIC;
|
||||
asinh => UNBOUNDED;
|
||||
atan => UNBOUNDED;
|
||||
atanh => ATANH;
|
||||
cbrt => UNBOUNDED;
|
||||
ceil => UNBOUNDED;
|
||||
cos => TRIG;
|
||||
cosh => UNBOUNDED;
|
||||
erf => UNBOUNDED;
|
||||
erfc => UNBOUNDED;
|
||||
exp => UNBOUNDED;
|
||||
exp10 => UNBOUNDED;
|
||||
exp2 => UNBOUNDED;
|
||||
expm1 => UNBOUNDED;
|
||||
fabs => UNBOUNDED;
|
||||
floor => UNBOUNDED;
|
||||
frexp => UNBOUNDED;
|
||||
ilogb => UNBOUNDED;
|
||||
j0 => UNBOUNDED;
|
||||
j1 => UNBOUNDED;
|
||||
lgamma => LGAMMA;
|
||||
log => LOG;
|
||||
log10 => LOG;
|
||||
log1p => LOG1P;
|
||||
log2 => LOG;
|
||||
modf => UNBOUNDED;
|
||||
rint => UNBOUNDED;
|
||||
round => UNBOUNDED;
|
||||
sin => TRIG;
|
||||
sincos => TRIG;
|
||||
sinh => UNBOUNDED;
|
||||
sqrt => SQRT;
|
||||
tan => TRIG;
|
||||
tanh => UNBOUNDED;
|
||||
tgamma => GAMMA;
|
||||
trunc => UNBOUNDED;
|
||||
y0 => UNBOUNDED;
|
||||
y1 => UNBOUNDED;
|
||||
}
|
||||
|
||||
/* Manual implementations, these functions don't follow `foo`->`foof` naming */
|
||||
|
||||
impl HasDomain<f32> for crate::op::lgammaf_r::Routine {
|
||||
const DOMAIN: Domain<f32> = Domain::<f32>::LGAMMA;
|
||||
}
|
||||
|
||||
impl HasDomain<f64> for crate::op::lgamma_r::Routine {
|
||||
const DOMAIN: Domain<f64> = Domain::<f64>::LGAMMA;
|
||||
}
|
||||
|
||||
/* Not all `f16` and `f128` functions exist yet so we can't easily use the macros. */
|
||||
|
||||
#[cfg(f16_enabled)]
|
||||
impl HasDomain<f16> for crate::op::fabsf16::Routine {
|
||||
const DOMAIN: Domain<f16> = Domain::<f16>::UNBOUNDED;
|
||||
}
|
||||
|
||||
#[cfg(f128_enabled)]
|
||||
impl HasDomain<f128> for crate::op::fabsf128::Routine {
|
||||
const DOMAIN: Domain<f128> = Domain::<f128>::UNBOUNDED;
|
||||
}
|
||||
|
||||
#[cfg(f16_enabled)]
|
||||
impl HasDomain<f16> for crate::op::fdimf16::Routine {
|
||||
const DOMAIN: Domain<f16> = Domain::<f16>::UNBOUNDED;
|
||||
}
|
||||
|
||||
#[cfg(f128_enabled)]
|
||||
impl HasDomain<f128> for crate::op::fdimf128::Routine {
|
||||
const DOMAIN: Domain<f128> = Domain::<f128>::UNBOUNDED;
|
||||
}
|
||||
|
||||
#[cfg(f16_enabled)]
|
||||
impl HasDomain<f16> for crate::op::truncf16::Routine {
|
||||
const DOMAIN: Domain<f16> = Domain::<f16>::UNBOUNDED;
|
||||
}
|
||||
|
||||
#[cfg(f128_enabled)]
|
||||
impl HasDomain<f128> for crate::op::truncf128::Routine {
|
||||
const DOMAIN: Domain<f128> = Domain::<f128>::UNBOUNDED;
|
||||
|
||||
x[argnum].clone()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
//! Different generators that can create random or systematic bit patterns.
|
||||
|
||||
pub mod domain_logspace;
|
||||
pub mod edge_cases;
|
||||
pub mod extensive;
|
||||
pub mod random;
|
||||
pub mod spaced;
|
||||
|
||||
/// A wrapper to turn any iterator into an `ExactSizeIterator`. Asserts the final result to ensure
|
||||
/// the provided size was correct.
|
||||
|
|
|
|||
|
|
@ -1,31 +0,0 @@
|
|||
//! A generator that produces logarithmically spaced values within domain bounds.
|
||||
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
use libm::support::{IntTy, MinInt};
|
||||
|
||||
use crate::domain::HasDomain;
|
||||
use crate::op::OpITy;
|
||||
use crate::run_cfg::{GeneratorKind, iteration_count};
|
||||
use crate::{CheckCtx, MathOp, logspace};
|
||||
|
||||
/// Create a range of logarithmically spaced inputs within a function's domain.
|
||||
///
|
||||
/// This allows us to get reasonably thorough coverage without wasting time on values that are
|
||||
/// NaN or out of range. Random tests will still cover values that are excluded here.
|
||||
pub fn get_test_cases<Op>(ctx: &CheckCtx) -> impl Iterator<Item = (Op::FTy,)>
|
||||
where
|
||||
Op: MathOp + HasDomain<Op::FTy>,
|
||||
IntTy<Op::FTy>: TryFrom<u64>,
|
||||
RangeInclusive<IntTy<Op::FTy>>: Iterator,
|
||||
{
|
||||
let domain = Op::DOMAIN;
|
||||
let ntests = iteration_count(ctx, GeneratorKind::Domain, 0);
|
||||
|
||||
// We generate logspaced inputs within a specific range, excluding values that are out of
|
||||
// range in order to make iterations useful (random tests still cover the full range).
|
||||
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).0.map(|v| (v,))
|
||||
}
|
||||
|
|
@ -1,20 +1,28 @@
|
|||
//! A generator that checks a handful of cases near infinities, zeros, asymptotes, and NaNs.
|
||||
|
||||
use libm::support::Float;
|
||||
use libm::support::{Float, Int};
|
||||
|
||||
use crate::domain::HasDomain;
|
||||
use crate::domain::get_domain;
|
||||
use crate::gen::KnownSize;
|
||||
use crate::run_cfg::{check_near_count, check_point_count};
|
||||
use crate::{CheckCtx, FloatExt, MathOp};
|
||||
use crate::{CheckCtx, FloatExt, MathOp, test_log};
|
||||
|
||||
/// Generate a sequence of edge cases, e.g. numbers near zeroes and infiniteis.
|
||||
pub trait EdgeCaseInput<Op> {
|
||||
fn get_cases(ctx: &CheckCtx) -> impl ExactSizeIterator<Item = Self> + Send;
|
||||
}
|
||||
|
||||
/// Create a list of values around interesting points (infinities, zeroes, NaNs).
|
||||
pub fn get_test_cases<Op, F>(ctx: &CheckCtx) -> impl Iterator<Item = (F,)>
|
||||
fn float_edge_cases<Op>(
|
||||
ctx: &CheckCtx,
|
||||
argnum: usize,
|
||||
) -> (impl Iterator<Item = Op::FTy> + Clone, u64)
|
||||
where
|
||||
Op: MathOp<FTy = F> + HasDomain<F>,
|
||||
F: Float,
|
||||
Op: MathOp,
|
||||
{
|
||||
let mut ret = Vec::new();
|
||||
let values = &mut ret;
|
||||
let domain = Op::DOMAIN;
|
||||
let domain = get_domain::<_, i8>(ctx.fn_ident, argnum).unwrap_float();
|
||||
let domain_start = domain.range_start();
|
||||
let domain_end = domain.range_end();
|
||||
|
||||
|
|
@ -22,17 +30,17 @@ where
|
|||
let near_points = check_near_count(ctx);
|
||||
|
||||
// Check near some notable constants
|
||||
count_up(F::ONE, near_points, values);
|
||||
count_up(F::ZERO, near_points, values);
|
||||
count_up(F::NEG_ONE, near_points, values);
|
||||
count_down(F::ONE, near_points, values);
|
||||
count_down(F::ZERO, near_points, values);
|
||||
count_down(F::NEG_ONE, near_points, values);
|
||||
values.push(F::NEG_ZERO);
|
||||
count_up(Op::FTy::ONE, near_points, values);
|
||||
count_up(Op::FTy::ZERO, near_points, values);
|
||||
count_up(Op::FTy::NEG_ONE, near_points, values);
|
||||
count_down(Op::FTy::ONE, near_points, values);
|
||||
count_down(Op::FTy::ZERO, near_points, values);
|
||||
count_down(Op::FTy::NEG_ONE, near_points, values);
|
||||
values.push(Op::FTy::NEG_ZERO);
|
||||
|
||||
// Check values near the extremes
|
||||
count_up(F::NEG_INFINITY, near_points, values);
|
||||
count_down(F::INFINITY, near_points, values);
|
||||
count_up(Op::FTy::NEG_INFINITY, near_points, values);
|
||||
count_down(Op::FTy::INFINITY, near_points, values);
|
||||
count_down(domain_end, near_points, values);
|
||||
count_up(domain_start, near_points, values);
|
||||
count_down(domain_start, near_points, values);
|
||||
|
|
@ -40,8 +48,8 @@ where
|
|||
count_down(domain_end, near_points, values);
|
||||
|
||||
// Check some special values that aren't included in the above ranges
|
||||
values.push(F::NAN);
|
||||
values.extend(F::consts().iter());
|
||||
values.push(Op::FTy::NAN);
|
||||
values.extend(Op::FTy::consts().iter());
|
||||
|
||||
// Check around asymptotes
|
||||
if let Some(f) = domain.check_points {
|
||||
|
|
@ -56,7 +64,18 @@ where
|
|||
values.sort_by_key(|x| x.to_bits());
|
||||
values.dedup_by_key(|x| x.to_bits());
|
||||
|
||||
ret.into_iter().map(|v| (v,))
|
||||
let count = ret.len().try_into().unwrap();
|
||||
|
||||
test_log(&format!(
|
||||
"{gen_kind:?} {basis:?} {fn_ident} arg {arg}/{args}: {count} edge cases",
|
||||
gen_kind = ctx.gen_kind,
|
||||
basis = ctx.basis,
|
||||
fn_ident = ctx.fn_ident,
|
||||
arg = argnum + 1,
|
||||
args = ctx.input_count(),
|
||||
));
|
||||
|
||||
(ret.into_iter(), count)
|
||||
}
|
||||
|
||||
/// Add `AROUND` values starting at and including `x` and counting up. Uses the smallest possible
|
||||
|
|
@ -84,3 +103,131 @@ fn count_down<F: Float>(mut x: F, points: u64, values: &mut Vec<F>) {
|
|||
count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a list of values around interesting integer points (min, zero, max).
|
||||
pub fn int_edge_cases<I: Int>(
|
||||
ctx: &CheckCtx,
|
||||
_argnum: usize,
|
||||
) -> (impl Iterator<Item = I> + Clone, u64) {
|
||||
let mut values = Vec::new();
|
||||
let near_points = check_near_count(ctx);
|
||||
|
||||
for up_from in [I::MIN, I::ZERO] {
|
||||
let mut x = up_from;
|
||||
for _ in 0..near_points {
|
||||
values.push(x);
|
||||
x += I::ONE;
|
||||
}
|
||||
}
|
||||
|
||||
for down_from in [I::ZERO, I::MAX] {
|
||||
let mut x = down_from;
|
||||
for _ in 0..near_points {
|
||||
values.push(x);
|
||||
x -= I::ONE;
|
||||
}
|
||||
}
|
||||
|
||||
values.sort();
|
||||
values.dedup();
|
||||
let len = values.len().try_into().unwrap();
|
||||
(values.into_iter(), len)
|
||||
}
|
||||
|
||||
macro_rules! impl_edge_case_input {
|
||||
($fty:ty) => {
|
||||
impl<Op> EdgeCaseInput<Op> for ($fty,)
|
||||
where
|
||||
Op: MathOp<RustArgs = Self, FTy = $fty>,
|
||||
{
|
||||
fn get_cases(ctx: &CheckCtx) -> impl ExactSizeIterator<Item = Self> {
|
||||
let (iter0, steps0) = float_edge_cases::<Op>(ctx, 0);
|
||||
let iter0 = iter0.map(|v| (v,));
|
||||
KnownSize::new(iter0, steps0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Op> EdgeCaseInput<Op> for ($fty, $fty)
|
||||
where
|
||||
Op: MathOp<RustArgs = Self, FTy = $fty>,
|
||||
{
|
||||
fn get_cases(ctx: &CheckCtx) -> impl ExactSizeIterator<Item = Self> {
|
||||
let (iter0, steps0) = float_edge_cases::<Op>(ctx, 0);
|
||||
let (iter1, steps1) = float_edge_cases::<Op>(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)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Op> EdgeCaseInput<Op> for ($fty, $fty, $fty)
|
||||
where
|
||||
Op: MathOp<RustArgs = Self, FTy = $fty>,
|
||||
{
|
||||
fn get_cases(ctx: &CheckCtx) -> impl ExactSizeIterator<Item = Self> {
|
||||
let (iter0, steps0) = float_edge_cases::<Op>(ctx, 0);
|
||||
let (iter1, steps1) = float_edge_cases::<Op>(ctx, 1);
|
||||
let (iter2, steps2) = float_edge_cases::<Op>(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)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Op> EdgeCaseInput<Op> for (i32, $fty)
|
||||
where
|
||||
Op: MathOp<RustArgs = Self, FTy = $fty>,
|
||||
{
|
||||
fn get_cases(ctx: &CheckCtx) -> impl ExactSizeIterator<Item = Self> {
|
||||
let (iter0, steps0) = int_edge_cases(ctx, 0);
|
||||
let (iter1, steps1) = float_edge_cases::<Op>(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)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Op> EdgeCaseInput<Op> for ($fty, i32)
|
||||
where
|
||||
Op: MathOp<RustArgs = Self, FTy = $fty>,
|
||||
{
|
||||
fn get_cases(ctx: &CheckCtx) -> impl ExactSizeIterator<Item = Self> {
|
||||
let (iter0, steps0) = float_edge_cases::<Op>(ctx, 0);
|
||||
let (iter1, steps1) = int_edge_cases(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)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(f16_enabled)]
|
||||
impl_edge_case_input!(f16);
|
||||
impl_edge_case_input!(f32);
|
||||
impl_edge_case_input!(f64);
|
||||
#[cfg(f128_enabled)]
|
||||
impl_edge_case_input!(f128);
|
||||
|
||||
pub fn get_test_cases<Op>(
|
||||
ctx: &CheckCtx,
|
||||
) -> impl ExactSizeIterator<Item = Op::RustArgs> + use<'_, Op>
|
||||
where
|
||||
Op: MathOp,
|
||||
Op::RustArgs: EdgeCaseInput<Op>,
|
||||
{
|
||||
Op::RustArgs::get_cases(ctx)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ use rand::{Rng, SeedableRng};
|
|||
use rand_chacha::ChaCha8Rng;
|
||||
|
||||
use super::KnownSize;
|
||||
use crate::CheckCtx;
|
||||
use crate::run_cfg::{int_range, iteration_count};
|
||||
use crate::{CheckCtx, GeneratorKind};
|
||||
|
||||
pub(crate) const SEED_ENV: &str = "LIBM_SEED";
|
||||
|
||||
|
|
@ -52,7 +52,7 @@ macro_rules! impl_random_input {
|
|||
($fty:ty) => {
|
||||
impl RandomInput for ($fty,) {
|
||||
fn get_cases(ctx: &CheckCtx) -> impl ExactSizeIterator<Item = Self> {
|
||||
let count = iteration_count(ctx, GeneratorKind::Random, 0);
|
||||
let count = iteration_count(ctx, 0);
|
||||
let iter = random_floats(count).map(|f: $fty| (f,));
|
||||
KnownSize::new(iter, count)
|
||||
}
|
||||
|
|
@ -60,8 +60,8 @@ macro_rules! impl_random_input {
|
|||
|
||||
impl RandomInput for ($fty, $fty) {
|
||||
fn get_cases(ctx: &CheckCtx) -> impl ExactSizeIterator<Item = Self> {
|
||||
let count0 = iteration_count(ctx, GeneratorKind::Random, 0);
|
||||
let count1 = iteration_count(ctx, GeneratorKind::Random, 1);
|
||||
let count0 = iteration_count(ctx, 0);
|
||||
let count1 = iteration_count(ctx, 1);
|
||||
let iter = random_floats(count0)
|
||||
.flat_map(move |f1: $fty| random_floats(count1).map(move |f2: $fty| (f1, f2)));
|
||||
KnownSize::new(iter, count0 * count1)
|
||||
|
|
@ -70,9 +70,9 @@ macro_rules! impl_random_input {
|
|||
|
||||
impl RandomInput for ($fty, $fty, $fty) {
|
||||
fn get_cases(ctx: &CheckCtx) -> impl ExactSizeIterator<Item = Self> {
|
||||
let count0 = iteration_count(ctx, GeneratorKind::Random, 0);
|
||||
let count1 = iteration_count(ctx, GeneratorKind::Random, 1);
|
||||
let count2 = iteration_count(ctx, GeneratorKind::Random, 2);
|
||||
let count0 = iteration_count(ctx, 0);
|
||||
let count1 = iteration_count(ctx, 1);
|
||||
let count2 = iteration_count(ctx, 2);
|
||||
let iter = random_floats(count0).flat_map(move |f1: $fty| {
|
||||
random_floats(count1).flat_map(move |f2: $fty| {
|
||||
random_floats(count2).map(move |f3: $fty| (f1, f2, f3))
|
||||
|
|
@ -84,9 +84,9 @@ macro_rules! impl_random_input {
|
|||
|
||||
impl RandomInput for (i32, $fty) {
|
||||
fn get_cases(ctx: &CheckCtx) -> impl ExactSizeIterator<Item = Self> {
|
||||
let count0 = iteration_count(ctx, GeneratorKind::Random, 0);
|
||||
let count1 = iteration_count(ctx, GeneratorKind::Random, 1);
|
||||
let range0 = int_range(ctx, GeneratorKind::Random, 0);
|
||||
let count0 = iteration_count(ctx, 0);
|
||||
let count1 = iteration_count(ctx, 1);
|
||||
let range0 = int_range(ctx, 0);
|
||||
let iter = random_ints(count0, range0)
|
||||
.flat_map(move |f1: i32| random_floats(count1).map(move |f2: $fty| (f1, f2)));
|
||||
KnownSize::new(iter, count0 * count1)
|
||||
|
|
@ -95,9 +95,9 @@ macro_rules! impl_random_input {
|
|||
|
||||
impl RandomInput for ($fty, i32) {
|
||||
fn get_cases(ctx: &CheckCtx) -> impl ExactSizeIterator<Item = Self> {
|
||||
let count0 = iteration_count(ctx, GeneratorKind::Random, 0);
|
||||
let count1 = iteration_count(ctx, GeneratorKind::Random, 1);
|
||||
let range1 = int_range(ctx, GeneratorKind::Random, 1);
|
||||
let count0 = iteration_count(ctx, 0);
|
||||
let count1 = iteration_count(ctx, 1);
|
||||
let range1 = int_range(ctx, 1);
|
||||
let iter = random_floats(count0).flat_map(move |f1: $fty| {
|
||||
random_ints(count1, range1.clone()).map(move |f2: i32| (f1, f2))
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,23 +3,23 @@ use std::ops::RangeInclusive;
|
|||
|
||||
use libm::support::{Float, MinInt};
|
||||
|
||||
use crate::domain::HasDomain;
|
||||
use crate::domain::get_domain;
|
||||
use crate::op::OpITy;
|
||||
use crate::run_cfg::{int_range, iteration_count};
|
||||
use crate::{CheckCtx, GeneratorKind, MathOp, linear_ints, logspace};
|
||||
use crate::{CheckCtx, MathOp, linear_ints, logspace};
|
||||
|
||||
/// Generate a sequence of inputs that either cover the domain in completeness (for smaller float
|
||||
/// Generate a sequence of inputs that eiher 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> {
|
||||
pub trait SpacedInput<Op> {
|
||||
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
|
||||
|
|
@ -28,6 +28,11 @@ where
|
|||
u64: TryFrom<OpITy<Op>, Error: fmt::Debug>,
|
||||
RangeInclusive<OpITy<Op>>: Iterator,
|
||||
{
|
||||
// i8 is a dummy type here, it can be any integer.
|
||||
let domain = get_domain::<Op::FTy, i8>(ctx.fn_ident, argnum).unwrap_float();
|
||||
let start = domain.range_start();
|
||||
let end = domain.range_end();
|
||||
|
||||
let max_steps = OpITy::<Op>::try_from(max_steps).unwrap_or(OpITy::<Op>::MAX);
|
||||
let (iter, steps) = logspace(start, end, max_steps);
|
||||
|
||||
|
|
@ -76,15 +81,14 @@ where
|
|||
(F::Int::MIN..=F::Int::MAX).map(|bits| F::from_bits(bits))
|
||||
}
|
||||
|
||||
macro_rules! impl_extensive_input {
|
||||
macro_rules! impl_spaced_input {
|
||||
($fty:ty) => {
|
||||
impl<Op> ExtensiveInput<Op> for ($fty,)
|
||||
impl<Op> SpacedInput<Op> for ($fty,)
|
||||
where
|
||||
Op: MathOp<RustArgs = Self, FTy = $fty>,
|
||||
Op: HasDomain<Op::FTy>,
|
||||
{
|
||||
fn get_cases(ctx: &CheckCtx) -> (impl Iterator<Item = Self>, u64) {
|
||||
let max_steps0 = iteration_count(ctx, GeneratorKind::Extensive, 0);
|
||||
let max_steps0 = iteration_count(ctx, 0);
|
||||
// `f16` and `f32` can have exhaustive tests.
|
||||
match value_count::<Op::FTy>() {
|
||||
Some(steps0) if steps0 <= max_steps0 => {
|
||||
|
|
@ -93,9 +97,7 @@ macro_rules! impl_extensive_input {
|
|||
(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, steps0) = logspace_steps::<Op>(ctx, 0, max_steps0);
|
||||
let iter0 = iter0.map(|v| (v,));
|
||||
(EitherIter::B(iter0), steps0)
|
||||
}
|
||||
|
|
@ -103,13 +105,13 @@ macro_rules! impl_extensive_input {
|
|||
}
|
||||
}
|
||||
|
||||
impl<Op> ExtensiveInput<Op> for ($fty, $fty)
|
||||
impl<Op> SpacedInput<Op> for ($fty, $fty)
|
||||
where
|
||||
Op: MathOp<RustArgs = Self, FTy = $fty>,
|
||||
{
|
||||
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_steps0 = iteration_count(ctx, 0);
|
||||
let max_steps1 = iteration_count(ctx, 1);
|
||||
// `f16` can have exhaustive tests.
|
||||
match value_count::<Op::FTy>() {
|
||||
Some(count) if count <= max_steps0 && count <= max_steps1 => {
|
||||
|
|
@ -118,10 +120,8 @@ macro_rules! impl_extensive_input {
|
|||
(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 (iter0, steps0) = logspace_steps::<Op>(ctx, 0, max_steps0);
|
||||
let (iter1, steps1) = logspace_steps::<Op>(ctx, 1, max_steps1);
|
||||
let iter = iter0.flat_map(move |first| {
|
||||
iter1.clone().map(move |second| (first, second))
|
||||
});
|
||||
|
|
@ -132,14 +132,14 @@ macro_rules! impl_extensive_input {
|
|||
}
|
||||
}
|
||||
|
||||
impl<Op> ExtensiveInput<Op> for ($fty, $fty, $fty)
|
||||
impl<Op> SpacedInput<Op> for ($fty, $fty, $fty)
|
||||
where
|
||||
Op: MathOp<RustArgs = Self, FTy = $fty>,
|
||||
{
|
||||
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);
|
||||
let max_steps0 = iteration_count(ctx, 0);
|
||||
let max_steps1 = iteration_count(ctx, 1);
|
||||
let max_steps2 = iteration_count(ctx, 2);
|
||||
// `f16` can be exhaustive tested if `LIBM_EXTENSIVE_TESTS` is incresed.
|
||||
match value_count::<Op::FTy>() {
|
||||
Some(count)
|
||||
|
|
@ -153,12 +153,9 @@ macro_rules! impl_extensive_input {
|
|||
(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 (iter0, steps0) = logspace_steps::<Op>(ctx, 0, max_steps0);
|
||||
let (iter1, steps1) = logspace_steps::<Op>(ctx, 1, max_steps1);
|
||||
let (iter2, steps2) = logspace_steps::<Op>(ctx, 2, max_steps2);
|
||||
|
||||
let iter = iter0
|
||||
.flat_map(move |first| iter1.clone().map(move |second| (first, second)))
|
||||
|
|
@ -174,14 +171,14 @@ macro_rules! impl_extensive_input {
|
|||
}
|
||||
}
|
||||
|
||||
impl<Op> ExtensiveInput<Op> for (i32, $fty)
|
||||
impl<Op> SpacedInput<Op> for (i32, $fty)
|
||||
where
|
||||
Op: MathOp<RustArgs = Self, FTy = $fty>,
|
||||
{
|
||||
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);
|
||||
let range0 = int_range(ctx, 0);
|
||||
let max_steps0 = iteration_count(ctx, 0);
|
||||
let max_steps1 = iteration_count(ctx, 1);
|
||||
match value_count::<Op::FTy>() {
|
||||
Some(count1) if count1 <= max_steps1 => {
|
||||
let (iter0, steps0) = linear_ints(range0, max_steps0);
|
||||
|
|
@ -190,11 +187,8 @@ macro_rules! impl_extensive_input {
|
|||
(EitherIter::A(iter), steps0.checked_mul(count1).unwrap())
|
||||
}
|
||||
_ => {
|
||||
let start = <$fty>::NEG_INFINITY;
|
||||
let end = <$fty>::INFINITY;
|
||||
|
||||
let (iter0, steps0) = linear_ints(range0, max_steps0);
|
||||
let (iter1, steps1) = logspace_steps::<Op>(start, end, max_steps1);
|
||||
let (iter1, steps1) = logspace_steps::<Op>(ctx, 1, max_steps1);
|
||||
|
||||
let iter = iter0.flat_map(move |first| {
|
||||
iter1.clone().map(move |second| (first, second))
|
||||
|
|
@ -207,14 +201,14 @@ macro_rules! impl_extensive_input {
|
|||
}
|
||||
}
|
||||
|
||||
impl<Op> ExtensiveInput<Op> for ($fty, i32)
|
||||
impl<Op> SpacedInput<Op> for ($fty, i32)
|
||||
where
|
||||
Op: MathOp<RustArgs = Self, FTy = $fty>,
|
||||
{
|
||||
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);
|
||||
let max_steps0 = iteration_count(ctx, 0);
|
||||
let range1 = int_range(ctx, 1);
|
||||
let max_steps1 = iteration_count(ctx, 1);
|
||||
match value_count::<Op::FTy>() {
|
||||
Some(count0) if count0 <= max_steps0 => {
|
||||
let (iter1, steps1) = linear_ints(range1, max_steps1);
|
||||
|
|
@ -224,10 +218,7 @@ macro_rules! impl_extensive_input {
|
|||
(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, max_steps0);
|
||||
let (iter0, steps0) = logspace_steps::<Op>(ctx, 0, max_steps0);
|
||||
let (iter1, steps1) = linear_ints(range1, max_steps1);
|
||||
|
||||
let iter = iter0.flat_map(move |first| {
|
||||
|
|
@ -244,11 +235,11 @@ macro_rules! impl_extensive_input {
|
|||
}
|
||||
|
||||
#[cfg(f16_enabled)]
|
||||
impl_extensive_input!(f16);
|
||||
impl_extensive_input!(f32);
|
||||
impl_extensive_input!(f64);
|
||||
impl_spaced_input!(f16);
|
||||
impl_spaced_input!(f32);
|
||||
impl_spaced_input!(f64);
|
||||
#[cfg(f128_enabled)]
|
||||
impl_extensive_input!(f128);
|
||||
impl_spaced_input!(f128);
|
||||
|
||||
/// Create a test case iterator for extensive inputs. Also returns the total test case count.
|
||||
pub fn get_test_cases<Op>(
|
||||
|
|
@ -256,7 +247,7 @@ pub fn get_test_cases<Op>(
|
|||
) -> (impl Iterator<Item = Op::RustArgs> + Send + use<'_, Op>, u64)
|
||||
where
|
||||
Op: MathOp,
|
||||
Op::RustArgs: ExtensiveInput<Op>,
|
||||
Op::RustArgs: SpacedInput<Op>,
|
||||
{
|
||||
Op::RustArgs::get_cases(ctx)
|
||||
}
|
||||
|
|
@ -39,11 +39,12 @@ pub struct CheckCtx {
|
|||
pub base_name_str: &'static str,
|
||||
/// Source of truth for tests.
|
||||
pub basis: CheckBasis,
|
||||
pub gen_kind: GeneratorKind,
|
||||
}
|
||||
|
||||
impl CheckCtx {
|
||||
/// Create a new check context, using the default ULP for the function.
|
||||
pub fn new(fn_ident: Identifier, basis: CheckBasis) -> Self {
|
||||
pub fn new(fn_ident: Identifier, basis: CheckBasis, gen_kind: GeneratorKind) -> Self {
|
||||
let mut ret = Self {
|
||||
ulp: 0,
|
||||
fn_ident,
|
||||
|
|
@ -51,10 +52,16 @@ impl CheckCtx {
|
|||
base_name: fn_ident.base_name(),
|
||||
base_name_str: fn_ident.base_name().as_str(),
|
||||
basis,
|
||||
gen_kind,
|
||||
};
|
||||
ret.ulp = crate::default_ulp(&ret);
|
||||
ret
|
||||
}
|
||||
|
||||
/// The number of input arguments for this function.
|
||||
pub fn input_count(&self) -> usize {
|
||||
self.fn_ident.math_op().rust_sig.args.len()
|
||||
}
|
||||
}
|
||||
|
||||
/// Possible items to test against
|
||||
|
|
@ -66,11 +73,13 @@ pub enum CheckBasis {
|
|||
Mpfr,
|
||||
}
|
||||
|
||||
/// The different kinds of generators that provide test input.
|
||||
/// The different kinds of generators that provide test input, which account for input pattern
|
||||
/// and quantity.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum GeneratorKind {
|
||||
Domain,
|
||||
EdgeCases,
|
||||
Extensive,
|
||||
QuickSpaced,
|
||||
Random,
|
||||
}
|
||||
|
||||
|
|
@ -155,7 +164,7 @@ impl TestEnv {
|
|||
}
|
||||
|
||||
/// The number of iterations to run for a given test.
|
||||
pub fn iteration_count(ctx: &CheckCtx, gen_kind: GeneratorKind, argnum: usize) -> u64 {
|
||||
pub fn iteration_count(ctx: &CheckCtx, argnum: usize) -> u64 {
|
||||
let t_env = TestEnv::from_env(ctx);
|
||||
|
||||
// Ideally run 5M tests
|
||||
|
|
@ -185,10 +194,13 @@ pub fn iteration_count(ctx: &CheckCtx, gen_kind: GeneratorKind, argnum: usize) -
|
|||
// Run fewer random tests than domain tests.
|
||||
let random_iter_count = domain_iter_count / 100;
|
||||
|
||||
let mut total_iterations = match gen_kind {
|
||||
GeneratorKind::Domain => domain_iter_count,
|
||||
let mut total_iterations = match ctx.gen_kind {
|
||||
GeneratorKind::QuickSpaced => domain_iter_count,
|
||||
GeneratorKind::Random => random_iter_count,
|
||||
GeneratorKind::Extensive => *EXTENSIVE_MAX_ITERATIONS,
|
||||
GeneratorKind::EdgeCases => {
|
||||
unimplemented!("edge case tests shoudn't need `iteration_count`")
|
||||
}
|
||||
};
|
||||
|
||||
// FMA has a huge domain but is reasonably fast to run, so increase iterations.
|
||||
|
|
@ -213,16 +225,18 @@ pub fn iteration_count(ctx: &CheckCtx, gen_kind: GeneratorKind, argnum: usize) -
|
|||
};
|
||||
let total = ntests.pow(t_env.input_count.try_into().unwrap());
|
||||
|
||||
let seed_msg = match gen_kind {
|
||||
GeneratorKind::Domain | GeneratorKind::Extensive => String::new(),
|
||||
let seed_msg = match ctx.gen_kind {
|
||||
GeneratorKind::QuickSpaced | GeneratorKind::Extensive => String::new(),
|
||||
GeneratorKind::Random => {
|
||||
format!(" using `{SEED_ENV}={}`", str::from_utf8(SEED.as_slice()).unwrap())
|
||||
}
|
||||
GeneratorKind::EdgeCases => unreachable!(),
|
||||
};
|
||||
|
||||
test_log(&format!(
|
||||
"{gen_kind:?} {basis:?} {fn_ident} arg {arg}/{args}: {ntests} iterations \
|
||||
({total} total){seed_msg}",
|
||||
gen_kind = ctx.gen_kind,
|
||||
basis = ctx.basis,
|
||||
fn_ident = ctx.fn_ident,
|
||||
arg = argnum + 1,
|
||||
|
|
@ -233,7 +247,7 @@ pub fn iteration_count(ctx: &CheckCtx, gen_kind: GeneratorKind, argnum: usize) -
|
|||
}
|
||||
|
||||
/// Some tests require that an integer be kept within reasonable limits; generate that here.
|
||||
pub fn int_range(ctx: &CheckCtx, gen_kind: GeneratorKind, argnum: usize) -> RangeInclusive<i32> {
|
||||
pub fn int_range(ctx: &CheckCtx, argnum: usize) -> RangeInclusive<i32> {
|
||||
let t_env = TestEnv::from_env(ctx);
|
||||
|
||||
if !matches!(ctx.base_name, BaseName::Jn | BaseName::Yn) {
|
||||
|
|
@ -252,22 +266,42 @@ pub fn int_range(ctx: &CheckCtx, gen_kind: GeneratorKind, argnum: usize) -> Rang
|
|||
|
||||
let extensive_range = (-0xfff)..=0xfffff;
|
||||
|
||||
match gen_kind {
|
||||
match ctx.gen_kind {
|
||||
GeneratorKind::Extensive => extensive_range,
|
||||
GeneratorKind::Domain | GeneratorKind::Random => non_extensive_range,
|
||||
GeneratorKind::QuickSpaced | GeneratorKind::Random => non_extensive_range,
|
||||
GeneratorKind::EdgeCases => extensive_range,
|
||||
}
|
||||
}
|
||||
|
||||
/// For domain tests, limit how many asymptotes or specified check points we test.
|
||||
pub fn check_point_count(ctx: &CheckCtx) -> usize {
|
||||
assert_eq!(
|
||||
ctx.gen_kind,
|
||||
GeneratorKind::EdgeCases,
|
||||
"check_point_count is intended for edge case tests"
|
||||
);
|
||||
let t_env = TestEnv::from_env(ctx);
|
||||
if t_env.slow_platform || !cfg!(optimizations_enabled) { 4 } else { 10 }
|
||||
}
|
||||
|
||||
/// When validating points of interest (e.g. asymptotes, inflection points, extremes), also check
|
||||
/// this many surrounding values.
|
||||
pub fn check_near_count(_ctx: &CheckCtx) -> u64 {
|
||||
if cfg!(optimizations_enabled) { 100 } else { 10 }
|
||||
pub fn check_near_count(ctx: &CheckCtx) -> u64 {
|
||||
assert_eq!(
|
||||
ctx.gen_kind,
|
||||
GeneratorKind::EdgeCases,
|
||||
"check_near_count is intended for edge case tests"
|
||||
);
|
||||
if cfg!(optimizations_enabled) {
|
||||
// Taper based on the number of inputs.
|
||||
match ctx.input_count() {
|
||||
1 | 2 => 100,
|
||||
3 => 50,
|
||||
x => panic!("unexpected argument count {x}"),
|
||||
}
|
||||
} else {
|
||||
10
|
||||
}
|
||||
}
|
||||
|
||||
/// Check whether extensive actions should be run or skipped.
|
||||
|
|
|
|||
|
|
@ -9,12 +9,26 @@
|
|||
// There are some targets we can't build musl for
|
||||
#![cfg(feature = "build-musl")]
|
||||
|
||||
use libm_test::domain::HasDomain;
|
||||
use libm_test::gen::random::RandomInput;
|
||||
use libm_test::gen::{domain_logspace, edge_cases, random};
|
||||
use libm_test::{CheckBasis, CheckCtx, CheckOutput, MathOp, TupleCall};
|
||||
use libm_test::gen::{edge_cases, random, spaced};
|
||||
use libm_test::{CheckBasis, CheckCtx, CheckOutput, GeneratorKind, MathOp, TupleCall};
|
||||
|
||||
macro_rules! musl_rand_tests {
|
||||
const BASIS: CheckBasis = CheckBasis::Musl;
|
||||
|
||||
fn musl_runner<Op: MathOp>(
|
||||
ctx: &CheckCtx,
|
||||
cases: impl Iterator<Item = Op::RustArgs>,
|
||||
musl_fn: Op::CFn,
|
||||
) {
|
||||
for input in cases {
|
||||
let musl_res = input.call(musl_fn);
|
||||
let crate_res = input.call(Op::ROUTINE);
|
||||
|
||||
crate_res.validate(musl_res, input, ctx).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
/// Test against musl with generators from a domain.
|
||||
macro_rules! musl_tests {
|
||||
(
|
||||
fn_name: $fn_name:ident,
|
||||
attrs: [$($attr:meta),*],
|
||||
|
|
@ -23,136 +37,50 @@ macro_rules! musl_rand_tests {
|
|||
#[test]
|
||||
$(#[$attr])*
|
||||
fn [< musl_random_ $fn_name >]() {
|
||||
test_one_random::<libm_test::op::$fn_name::Routine>(musl_math_sys::$fn_name);
|
||||
type Op = libm_test::op::$fn_name::Routine;
|
||||
let ctx = CheckCtx::new(Op::IDENTIFIER, BASIS, GeneratorKind::Random);
|
||||
let cases = random::get_test_cases::<<Op as MathOp>::RustArgs>(&ctx);
|
||||
musl_runner::<Op>(&ctx, cases, musl_math_sys::$fn_name);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn test_one_random<Op>(musl_fn: Op::CFn)
|
||||
where
|
||||
Op: MathOp,
|
||||
Op::RustArgs: RandomInput,
|
||||
{
|
||||
let ctx = CheckCtx::new(Op::IDENTIFIER, CheckBasis::Musl);
|
||||
let cases = random::get_test_cases::<Op::RustArgs>(&ctx);
|
||||
|
||||
for input in cases {
|
||||
let musl_res = input.call(musl_fn);
|
||||
let crate_res = input.call(Op::ROUTINE);
|
||||
|
||||
crate_res.validate(musl_res, input, &ctx).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
libm_macros::for_each_function! {
|
||||
callback: musl_rand_tests,
|
||||
// Musl does not support `f16` and `f128` on all platforms.
|
||||
skip: [
|
||||
copysignf128,
|
||||
copysignf16,
|
||||
fabsf128,
|
||||
fabsf16,
|
||||
fdimf128,
|
||||
fdimf16,
|
||||
truncf128,
|
||||
truncf16,
|
||||
],
|
||||
attributes: [
|
||||
#[cfg_attr(x86_no_sse, ignore)] // FIXME(correctness): wrong result on i586
|
||||
[exp10, exp10f, exp2, exp2f, rint]
|
||||
],
|
||||
}
|
||||
|
||||
/// Test against musl with generators from a domain.
|
||||
macro_rules! musl_domain_tests {
|
||||
(
|
||||
fn_name: $fn_name:ident,
|
||||
attrs: [$($attr:meta),*],
|
||||
) => {
|
||||
paste::paste! {
|
||||
#[test]
|
||||
$(#[$attr])*
|
||||
fn [< musl_edge_case_ $fn_name >]() {
|
||||
type Op = libm_test::op::$fn_name::Routine;
|
||||
domain_test_runner::<Op, _>(
|
||||
edge_cases::get_test_cases::<Op, _>,
|
||||
musl_math_sys::$fn_name,
|
||||
);
|
||||
let ctx = CheckCtx::new(Op::IDENTIFIER, BASIS, GeneratorKind::EdgeCases);
|
||||
let cases = edge_cases::get_test_cases::<Op>(&ctx);
|
||||
musl_runner::<Op>(&ctx, cases, musl_math_sys::$fn_name);
|
||||
}
|
||||
|
||||
#[test]
|
||||
$(#[$attr])*
|
||||
fn [< musl_logspace_ $fn_name >]() {
|
||||
fn [< musl_quickspace_ $fn_name >]() {
|
||||
type Op = libm_test::op::$fn_name::Routine;
|
||||
domain_test_runner::<Op, _>(
|
||||
domain_logspace::get_test_cases::<Op>,
|
||||
musl_math_sys::$fn_name,
|
||||
);
|
||||
let ctx = CheckCtx::new(Op::IDENTIFIER, BASIS, GeneratorKind::QuickSpaced);
|
||||
let cases = spaced::get_test_cases::<Op>(&ctx).0;
|
||||
musl_runner::<Op>(&ctx, cases, musl_math_sys::$fn_name);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Test a single routine against domaine-aware inputs.
|
||||
fn domain_test_runner<Op, I>(gen: impl FnOnce(&CheckCtx) -> I, musl_fn: Op::CFn)
|
||||
where
|
||||
Op: MathOp,
|
||||
Op: HasDomain<Op::FTy>,
|
||||
I: Iterator<Item = Op::RustArgs>,
|
||||
{
|
||||
let ctx = CheckCtx::new(Op::IDENTIFIER, CheckBasis::Musl);
|
||||
let cases = gen(&ctx);
|
||||
|
||||
for input in cases {
|
||||
let musl_res = input.call(musl_fn);
|
||||
let crate_res = input.call(Op::ROUTINE);
|
||||
|
||||
crate_res.validate(musl_res, input, &ctx).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
libm_macros::for_each_function! {
|
||||
callback: musl_domain_tests,
|
||||
callback: musl_tests,
|
||||
attributes: [],
|
||||
skip: [
|
||||
// Functions with multiple inputs
|
||||
atan2,
|
||||
atan2f,
|
||||
copysign,
|
||||
copysignf,
|
||||
copysignf16,
|
||||
copysignf128,
|
||||
fdim,
|
||||
fdimf,
|
||||
fma,
|
||||
fmaf,
|
||||
fmax,
|
||||
fmaxf,
|
||||
fmin,
|
||||
fminf,
|
||||
fmod,
|
||||
fmodf,
|
||||
hypot,
|
||||
hypotf,
|
||||
// TODO integer inputs
|
||||
jn,
|
||||
jnf,
|
||||
ldexp,
|
||||
ldexpf,
|
||||
nextafter,
|
||||
nextafterf,
|
||||
pow,
|
||||
powf,
|
||||
remainder,
|
||||
remainderf,
|
||||
remquo,
|
||||
remquof,
|
||||
scalbn,
|
||||
scalbnf,
|
||||
yn,
|
||||
ynf,
|
||||
|
||||
// Not provided by musl
|
||||
copysignf128,
|
||||
copysignf16,
|
||||
fabsf128,
|
||||
fabsf16,
|
||||
fdimf128,
|
||||
|
|
|
|||
|
|
@ -2,14 +2,23 @@
|
|||
|
||||
#![cfg(feature = "build-mpfr")]
|
||||
|
||||
use libm_test::domain::HasDomain;
|
||||
use libm_test::gen::random::RandomInput;
|
||||
use libm_test::gen::{domain_logspace, edge_cases, random};
|
||||
use libm_test::gen::{edge_cases, random, spaced};
|
||||
use libm_test::mpfloat::MpOp;
|
||||
use libm_test::{CheckBasis, CheckCtx, CheckOutput, MathOp, OpFTy, OpRustFn, OpRustRet, TupleCall};
|
||||
use libm_test::{CheckBasis, CheckCtx, CheckOutput, GeneratorKind, MathOp, TupleCall};
|
||||
|
||||
/// Test against MPFR with random inputs.
|
||||
macro_rules! mp_rand_tests {
|
||||
const BASIS: CheckBasis = CheckBasis::Mpfr;
|
||||
|
||||
fn mp_runner<Op: MathOp + MpOp>(ctx: &CheckCtx, cases: impl Iterator<Item = Op::RustArgs>) {
|
||||
let mut mp_vals = Op::new_mp();
|
||||
for input in cases {
|
||||
let mp_res = Op::run(&mut mp_vals, input);
|
||||
let crate_res = input.call(Op::ROUTINE);
|
||||
|
||||
crate_res.validate(mp_res, input, ctx).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! mp_tests {
|
||||
(
|
||||
fn_name: $fn_name:ident,
|
||||
attrs: [$($attr:meta),*],
|
||||
|
|
@ -18,32 +27,35 @@ macro_rules! mp_rand_tests {
|
|||
#[test]
|
||||
$(#[$attr])*
|
||||
fn [< mp_random_ $fn_name >]() {
|
||||
test_one_random::<libm_test::op::$fn_name::Routine>();
|
||||
type Op = libm_test::op::$fn_name::Routine;
|
||||
let ctx = CheckCtx::new(Op::IDENTIFIER, BASIS, GeneratorKind::Random);
|
||||
let cases = random::get_test_cases::<<Op as MathOp>::RustArgs>(&ctx);
|
||||
mp_runner::<Op>(&ctx, cases);
|
||||
}
|
||||
|
||||
#[test]
|
||||
$(#[$attr])*
|
||||
fn [< mp_edge_case_ $fn_name >]() {
|
||||
type Op = libm_test::op::$fn_name::Routine;
|
||||
let ctx = CheckCtx::new(Op::IDENTIFIER, BASIS, GeneratorKind::EdgeCases);
|
||||
let cases = edge_cases::get_test_cases::<Op>(&ctx);
|
||||
mp_runner::<Op>(&ctx, cases);
|
||||
}
|
||||
|
||||
#[test]
|
||||
$(#[$attr])*
|
||||
fn [< mp_quickspace_ $fn_name >]() {
|
||||
type Op = libm_test::op::$fn_name::Routine;
|
||||
let ctx = CheckCtx::new(Op::IDENTIFIER, BASIS, GeneratorKind::QuickSpaced);
|
||||
let cases = spaced::get_test_cases::<Op>(&ctx).0;
|
||||
mp_runner::<Op>(&ctx, cases);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Test a single routine with random inputs
|
||||
fn test_one_random<Op>()
|
||||
where
|
||||
Op: MathOp + MpOp,
|
||||
Op::RustArgs: RandomInput,
|
||||
{
|
||||
let mut mp_vals = Op::new_mp();
|
||||
let ctx = CheckCtx::new(Op::IDENTIFIER, CheckBasis::Mpfr);
|
||||
let cases = random::get_test_cases::<Op::RustArgs>(&ctx);
|
||||
|
||||
for input in cases {
|
||||
let mp_res = Op::run(&mut mp_vals, input);
|
||||
let crate_res = input.call(Op::ROUTINE);
|
||||
|
||||
crate_res.validate(mp_res, input, &ctx).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
libm_macros::for_each_function! {
|
||||
callback: mp_rand_tests,
|
||||
callback: mp_tests,
|
||||
attributes: [
|
||||
// Also an assertion failure on i686: at `MPFR_ASSERTN (! mpfr_erangeflag_p ())`
|
||||
#[ignore = "large values are infeasible in MPFR"]
|
||||
|
|
@ -56,97 +68,3 @@ libm_macros::for_each_function! {
|
|||
nextafterf,
|
||||
],
|
||||
}
|
||||
|
||||
/// Test against MPFR with generators from a domain.
|
||||
macro_rules! mp_domain_tests {
|
||||
(
|
||||
fn_name: $fn_name:ident,
|
||||
attrs: [$($attr:meta),*],
|
||||
) => {
|
||||
paste::paste! {
|
||||
#[test]
|
||||
$(#[$attr])*
|
||||
fn [< mp_edge_case_ $fn_name >]() {
|
||||
type Op = libm_test::op::$fn_name::Routine;
|
||||
domain_test_runner::<Op, _>(edge_cases::get_test_cases::<Op, _>);
|
||||
}
|
||||
|
||||
#[test]
|
||||
$(#[$attr])*
|
||||
fn [< mp_logspace_ $fn_name >]() {
|
||||
type Op = libm_test::op::$fn_name::Routine;
|
||||
domain_test_runner::<Op, _>(domain_logspace::get_test_cases::<Op>);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Test a single routine against domaine-aware inputs.
|
||||
fn domain_test_runner<Op, I>(gen: impl FnOnce(&CheckCtx) -> I)
|
||||
where
|
||||
// Complicated generics...
|
||||
// The operation must take a single float argument (unary only)
|
||||
Op: MathOp<RustArgs = (<Op as MathOp>::FTy,)>,
|
||||
// It must also support multiprecision operations
|
||||
Op: MpOp,
|
||||
// And it must have a domain specified
|
||||
Op: HasDomain<Op::FTy>,
|
||||
// The single float argument tuple must be able to call the `RustFn` and return `RustRet`
|
||||
(OpFTy<Op>,): TupleCall<OpRustFn<Op>, Output = OpRustRet<Op>>,
|
||||
I: Iterator<Item = (Op::FTy,)>,
|
||||
{
|
||||
let mut mp_vals = Op::new_mp();
|
||||
let ctx = CheckCtx::new(Op::IDENTIFIER, CheckBasis::Mpfr);
|
||||
let cases = gen(&ctx);
|
||||
|
||||
for input in cases {
|
||||
let mp_res = Op::run(&mut mp_vals, input);
|
||||
let crate_res = input.call(Op::ROUTINE);
|
||||
|
||||
crate_res.validate(mp_res, input, &ctx).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
libm_macros::for_each_function! {
|
||||
callback: mp_domain_tests,
|
||||
attributes: [],
|
||||
skip: [
|
||||
// Functions with multiple inputs
|
||||
atan2,
|
||||
atan2f,
|
||||
copysign,
|
||||
copysignf,
|
||||
copysignf16,
|
||||
copysignf128,
|
||||
fdim,
|
||||
fdimf,
|
||||
fdimf16,
|
||||
fdimf128,
|
||||
fma,
|
||||
fmaf,
|
||||
fmax,
|
||||
fmaxf,
|
||||
fmin,
|
||||
fminf,
|
||||
fmod,
|
||||
fmodf,
|
||||
hypot,
|
||||
hypotf,
|
||||
jn,
|
||||
jnf,
|
||||
ldexp,
|
||||
ldexpf,
|
||||
nextafter,
|
||||
nextafterf,
|
||||
pow,
|
||||
powf,
|
||||
remainder,
|
||||
remainderf,
|
||||
remquo,
|
||||
remquof,
|
||||
scalbn,
|
||||
scalbnf,
|
||||
yn,
|
||||
ynf,
|
||||
],
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,13 +6,18 @@ use std::sync::atomic::{AtomicU64, Ordering};
|
|||
use std::time::Duration;
|
||||
|
||||
use indicatif::{ProgressBar, ProgressStyle};
|
||||
use libm_test::gen::extensive::{self, ExtensiveInput};
|
||||
use libm_test::gen::spaced;
|
||||
use libm_test::mpfloat::MpOp;
|
||||
use libm_test::{
|
||||
CheckBasis, CheckCtx, CheckOutput, MathOp, TestResult, TupleCall, skip_extensive_test,
|
||||
CheckBasis, CheckCtx, CheckOutput, GeneratorKind, MathOp, TestResult, TupleCall,
|
||||
skip_extensive_test,
|
||||
};
|
||||
use libtest_mimic::{Arguments, Trial};
|
||||
use rayon::prelude::*;
|
||||
use spaced::SpacedInput;
|
||||
|
||||
const BASIS: CheckBasis = CheckBasis::Mpfr;
|
||||
const GEN_KIND: GeneratorKind = GeneratorKind::Extensive;
|
||||
|
||||
/// Run the extensive test suite.
|
||||
pub fn run() {
|
||||
|
|
@ -62,10 +67,10 @@ fn register_all_tests() -> Vec<Trial> {
|
|||
fn register_single_test<Op>(all: &mut Vec<Trial>)
|
||||
where
|
||||
Op: MathOp + MpOp,
|
||||
Op::RustArgs: ExtensiveInput<Op> + Send,
|
||||
Op::RustArgs: SpacedInput<Op> + Send,
|
||||
{
|
||||
let test_name = format!("mp_extensive_{}", Op::NAME);
|
||||
let ctx = CheckCtx::new(Op::IDENTIFIER, CheckBasis::Mpfr);
|
||||
let ctx = CheckCtx::new(Op::IDENTIFIER, BASIS, GEN_KIND);
|
||||
let skip = skip_extensive_test(&ctx);
|
||||
|
||||
let runner = move || {
|
||||
|
|
@ -73,7 +78,7 @@ where
|
|||
panic!("extensive tests should be run with --release");
|
||||
}
|
||||
|
||||
let res = run_single_test::<Op>();
|
||||
let res = run_single_test::<Op>(&ctx);
|
||||
let e = match res {
|
||||
Ok(()) => return Ok(()),
|
||||
Err(e) => e,
|
||||
|
|
@ -91,18 +96,17 @@ where
|
|||
}
|
||||
|
||||
/// Test runner for a signle routine.
|
||||
fn run_single_test<Op>() -> TestResult
|
||||
fn run_single_test<Op>(ctx: &CheckCtx) -> TestResult
|
||||
where
|
||||
Op: MathOp + MpOp,
|
||||
Op::RustArgs: ExtensiveInput<Op> + Send,
|
||||
Op::RustArgs: SpacedInput<Op> + Send,
|
||||
{
|
||||
// Small delay before printing anything so other output from the runner has a chance to flush.
|
||||
std::thread::sleep(Duration::from_millis(500));
|
||||
eprintln!();
|
||||
|
||||
let completed = AtomicU64::new(0);
|
||||
let ctx = CheckCtx::new(Op::IDENTIFIER, CheckBasis::Mpfr);
|
||||
let (ref mut cases, total) = extensive::get_test_cases::<Op>(&ctx);
|
||||
let (ref mut cases, total) = spaced::get_test_cases::<Op>(ctx);
|
||||
let pb = Progress::new(Op::NAME, total);
|
||||
|
||||
let test_single_chunk = |mp_vals: &mut Op::MpTy, input_vec: Vec<Op::RustArgs>| -> TestResult {
|
||||
|
|
@ -110,7 +114,7 @@ where
|
|||
// Test the input.
|
||||
let mp_res = Op::run(mp_vals, input);
|
||||
let crate_res = input.call(Op::ROUTINE);
|
||||
crate_res.validate(mp_res, input, &ctx)?;
|
||||
crate_res.validate(mp_res, input, ctx)?;
|
||||
|
||||
let completed = completed.fetch_add(1, Ordering::Relaxed) + 1;
|
||||
pb.update(completed, input);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue