Skip to content

Commit

Permalink
test: add main structure coverage (#6)
Browse files Browse the repository at this point in the history
* add test file skeleton

* write the first basic tests

* complete missing parameters test

* derive additional traits for params

* add inconsistent parameters testing

* add all possibility for A_x_x_Rectangle

* reorganize tests

* replace ClosureExplicit by a macro

* replace other tests with macro & rewrite ValuesUniform

* add trapezoid tests for integral A

rectangles are more precise than trapezoid?

* update lint exceptions for tests
  • Loading branch information
imrn99 authored May 9, 2024
1 parent 5272b52 commit 5ea1b5d
Show file tree
Hide file tree
Showing 3 changed files with 299 additions and 3 deletions.
2 changes: 2 additions & 0 deletions src/parameters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
/// Currently, the supported integration domain can only be one-dimensionnal, described using
/// `f64` values (i.e. the type used for further computations). In the future, adding support
/// for higher dimension & generic value type can be considered.
#[derive(Debug, Clone)]
pub enum DomainDescriptor<'a> {
/// List of values taken by the variable on which we integrate.
Explicit(&'a [f64]),
Expand Down Expand Up @@ -35,6 +36,7 @@ pub enum FunctionDescriptor {
}

/// Numerical integration method enum
#[derive(Debug, Clone, Copy)]
pub enum ComputeMethod {
/// Rectangle method -- [reference](https://en.wikipedia.org/wiki/Riemann_sum)
Rectangle,
Expand Down
2 changes: 1 addition & 1 deletion src/structure/definitions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::{ComputeMethod, DomainDescriptor, FunctionDescriptor};
// ------ CONTENT

/// Integral error
#[derive(Debug)]
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum IntegraalError {
/// One or more parameters are missing.
MissingParameters(&'static str),
Expand Down
298 changes: 296 additions & 2 deletions src/structure/tests.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,298 @@
//! main structure tests
// ------ IMPORTS

use crate::{ComputeMethod, DomainDescriptor, FunctionDescriptor, Integraal, IntegraalError};

// ------ CONTENT

// test utils

const RECTANGLE_TOLERANCE: f64 = 1e-5;
const TRAPEZOID_TOLERANCE: f64 = 1e-5;
const STEP: f64 = 0.001;

macro_rules! almost_equal {
($v1: expr, $v2: expr, $tol: ident) => {
($v1 - $v2).abs() < $tol
};
}

macro_rules! generate_sample_descriptors {
($f: ident, $d: ident, $c: ident) => {
let $f = FunctionDescriptor::Closure(Box::new(|x| x));
let $d = DomainDescriptor::Explicit(&[]);
let $c = ComputeMethod::Rectangle;
};
}

macro_rules! generate_missing {
($a: ident, $b: ident) => {
let mut integral = Integraal::default();
integral.$a($a).$b($b);
assert_eq!(
integral.compute(),
Err(IntegraalError::MissingParameters(
"one or more parameter is missing"
))
);
};
}

// incorrect usages

#[allow(unused_variables)]
#[test]
fn missing_parameters() {
// missing function descriptor
generate_sample_descriptors!(function, domain, method);
generate_missing!(domain, method);

// missing domain descriptor
generate_sample_descriptors!(function, domain, method);
generate_missing!(function, method);

// missing compute method
generate_sample_descriptors!(function, domain, method);
generate_missing!(function, domain);

// missing all but one
let mut integral = Integraal::default();
integral.method(method);
assert_eq!(
integral.compute(),
Err(IntegraalError::MissingParameters(
"one or more parameter is missing"
))
);
}

#[test]
fn inconsistent_parameters() {
let method = ComputeMethod::Rectangle;
let function = FunctionDescriptor::Values(vec![1., 1., 1., 1., 1., 1.]);
let domain = vec![0.0, 0.1, 0.2, 0.3, 0.4]; // missing the last x value
let domain = DomainDescriptor::Explicit(&domain);

let mut integral = Integraal::default();
integral.method(method).function(function).domain(domain);
assert_eq!(
integral.compute(),
Err(IntegraalError::InconsistentParameters(
"provided function and domain value slices have different lengthes"
))
);

// this is equivalent to the first domain
let domain = DomainDescriptor::Uniform {
start: 0.,
step: 0.1,
n_step: 5,
};
let function = FunctionDescriptor::Values(vec![1., 1., 1., 1., 1., 1.]);

let mut integral = Integraal::default();
integral.method(method).function(function).domain(domain);
assert_eq!(
integral.compute(),
Err(IntegraalError::InconsistentParameters(
"provided function and domain value slices have different lengthes"
))
);
}

// correct usages

// test are groups per module according to the integral & the computation method
// test names follow this pattern:
// <FunctionDescriptorEnum><DomainDescriptorEnum>

// integral A
// y = f(x) = sin(x) from 0 to PI

macro_rules! generate_test {
($name: ident, $dm: stmt, $fnd: expr, $dmd: expr, $met: expr, $tol: ident) => {
#[allow(non_snake_case)]
#[test]
fn $name() {
$dm

let functiond = $fnd;
let domaind = $dmd;
let computem = $met;
let mut integraal = Integraal::default();
let res = integraal
.function(functiond)
.domain(domaind)
.method(computem)
.compute();
assert!(res.is_ok());
assert!(
almost_equal!(res.unwrap(), 2.0, $tol),
"left: {} \nright: 2.0",
res.unwrap()
);
}
};
($name: ident, $fnd: expr, $dmd: expr, $met: expr, $tol: ident) => {
#[allow(non_snake_case)]
#[test]
fn $name() {
let functiond = $fnd;
let domaind = $dmd;
let computem = $met;
let mut integraal = Integraal::default();
let res = integraal
.function(functiond)
.domain(domaind)
.method(computem)
.compute();
assert!(res.is_ok());
assert!(
almost_equal!(res.unwrap(), 2.0, $tol),
"left: {} \nright: 2.0",
res.unwrap()
);
}
};
}

#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
mod a_rectangle {
use super::*;

generate_test!(
ClosureExplicit,
let domain: Vec<f64> = (0..(std::f64::consts::PI * 1000.) as usize)
.map(|step_id| step_id as f64 * STEP)
.collect(),
FunctionDescriptor::Closure(Box::new(f64::sin)),
DomainDescriptor::Explicit(&domain),
ComputeMethod::Rectangle,
RECTANGLE_TOLERANCE
);

generate_test!(
ClosureUniform,
FunctionDescriptor::Closure(Box::new(f64::sin)),
DomainDescriptor::Uniform {
start: 0.,
step: STEP,
n_step: (1000. * std::f64::consts::PI) as usize,
},
ComputeMethod::Rectangle,
RECTANGLE_TOLERANCE
);

generate_test!(
ValuesExplicit,
let domain: Vec<f64> = (0..(std::f64::consts::PI * 1000.) as usize)
.map(|step_id| step_id as f64 * STEP)
.collect(),
FunctionDescriptor::Values(domain.iter().copied().map(f64::sin).collect()),
DomainDescriptor::Explicit(&domain),
ComputeMethod::Rectangle,
RECTANGLE_TOLERANCE
);

generate_test!(
ValuesUniform,
FunctionDescriptor::Values(
(0..(1000. * std::f64::consts::PI) as usize)
.map(|step_id| (step_id as f64 * STEP).sin())
.collect()
),
DomainDescriptor::Uniform {
start: 0.,
step: STEP,
n_step: (1000. * std::f64::consts::PI) as usize,
},
ComputeMethod::Rectangle,
RECTANGLE_TOLERANCE
);
}

#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
mod a_trapezoid {
use super::*;

generate_test!(
ClosureExplicit,
let domain: Vec<f64> = (0..(std::f64::consts::PI * 1000.) as usize)
.map(|step_id| step_id as f64 * STEP)
.collect(),
FunctionDescriptor::Closure(Box::new(f64::sin)),
DomainDescriptor::Explicit(&domain),
ComputeMethod::Trapezoid,
TRAPEZOID_TOLERANCE
);

generate_test!(
ClosureUniform,
FunctionDescriptor::Closure(Box::new(f64::sin)),
DomainDescriptor::Uniform {
start: 0.,
step: STEP,
n_step: (1000. * std::f64::consts::PI) as usize,
},
ComputeMethod::Trapezoid,
TRAPEZOID_TOLERANCE
);

generate_test!(
ValuesExplicit,
let domain: Vec<f64> = (0..(std::f64::consts::PI * 1000.) as usize)
.map(|step_id| step_id as f64 * STEP)
.collect(),
FunctionDescriptor::Values(domain.iter().copied().map(f64::sin).collect()),
DomainDescriptor::Explicit(&domain),
ComputeMethod::Trapezoid,
TRAPEZOID_TOLERANCE
);

generate_test!(
ValuesUniform,
FunctionDescriptor::Values(
(0..(1000. * std::f64::consts::PI) as usize)
.map(|step_id| (step_id as f64 * STEP).sin())
.collect()
),
DomainDescriptor::Uniform {
start: 0.,
step: STEP,
n_step: (1000. * std::f64::consts::PI) as usize,
},
ComputeMethod::Trapezoid,
TRAPEZOID_TOLERANCE
);
}

// integral B
// y = f(x) = x from -1 to 1

#[allow(non_snake_case)]
#[test]
fn basic() {
assert_eq!(1 + 1, 2);
fn B_Closure_Explicit_Rectangle() {
let functiond = FunctionDescriptor::Closure(Box::new(|x| x));
// -1 to 1, with .001 steps
// FIXME
// currently requires one more value because of
// the inconsistent sampling policy
let domain: Vec<f64> = (0..=2001)
.map(|step_id| -1. + f64::from(step_id) * STEP)
.collect();
let domaind = DomainDescriptor::Explicit(&domain);
let computem = ComputeMethod::Rectangle;
let mut integraal = Integraal::default();
let res = integraal
.function(functiond)
.domain(domaind)
.method(computem)
.compute();
assert!(res.is_ok());
assert!(
almost_equal!(res.unwrap(), 0.0, RECTANGLE_TOLERANCE),
"left: {} \nright: 0.0",
res.unwrap()
);
}

0 comments on commit 5ea1b5d

Please sign in to comment.