use num_complex::{Complex, ComplexFloat as _};
use pyo3::{
types::{PyAnyMethods as _, PyList, PyModule},
PyErr, Python,
};
const ALLOWED_ERROR: f64 = 1e-8;
const TEST_VALUES: [f64; 13] = [
1.0, 1e-3, 1e-5, 1e-8, 1e-15, 1e+3, 1e+5, 1e+8, 1e+15, 3.0, -1.0, -1e-8, -1e+8,
];
#[test]
fn exponential() {
for max_fn_evals in 1..500 {
compare_values(
&TEST_VALUES,
|x| (-x).exp(),
"exponential",
|s| (s + 1.0).recip(),
max_fn_evals,
);
}
}
#[test]
fn sine() {
for max_fn_evals in 1..500 {
compare_values(
&TEST_VALUES,
|x| x.sin(),
"sine",
|s| (1.0 + s.powi(2)).recip(),
max_fn_evals,
);
}
}
#[test]
fn squarewave() {
for max_fn_evals in 1..500 {
compare_values(
&[1.0, 2.0, 2.5, 3.0, 4.1, 5.01],
|x| x.floor() % 2.0,
"squarewave",
|s| s.recip() * (1.0 + s.exp()).recip(),
max_fn_evals,
);
}
}
#[test]
fn staircase() {
for max_fn_evals in 1..500 {
compare_values(
&[1.0, 2.0, 2.5, 3.0, 4.1, 5.01],
|x| x.floor(),
"staircase",
|s| s.recip() * (s.exp() - 1.0).recip(),
max_fn_evals,
);
}
}
fn compare_values(
x_values: &[f64],
expected_fn: impl Fn(f64) -> f64,
python_func_name: &str,
rust_func: impl Fn(Complex<f64>) -> Complex<f64>,
max_fn_evals: usize,
) {
let python_values = run_python(python_func_name, x_values, max_fn_evals);
let rust_values = run_rust(rust_func, x_values, max_fn_evals);
x_values
.iter()
.zip(python_values)
.zip(rust_values)
.for_each(|((x, python), rust)| {
let real = expected_fn(*x);
let python_dist = (real - python).abs();
let rust_dist = (real - rust).abs();
assert!(rust_dist <= python_dist + ALLOWED_ERROR, "Rust result is worse than original Python result:\n\tPython Delta: {python_dist}\n\tRust Delta : {rust_dist}\n\tPython : {python}\n\tRust : {rust}\n\tExpected : {real}\n\tFn evals : {max_fn_evals}\n\tX : {x}");
})
}
fn run_rust(
lt_func: impl Fn(Complex<f64>) -> Complex<f64>,
test_values: &[f64],
max_fn_evals: usize,
) -> Vec<f64> {
test_values
.iter()
.map(|test_value| iltcme::laplace_inversion(<_func, *test_value, max_fn_evals))
.collect()
}
fn run_python(lt_func: &str, test_values: &[f64], max_fn_evals: usize) -> Vec<f64> {
Python::initialize();
Python::attach(|py| {
let py_mod = PyModule::from_code(
py,
pyo3::ffi::c_str!(include_str!("./iltcme.py")),
pyo3::ffi::c_str!("iltcme.py"),
pyo3::ffi::c_str!("iltcme"),
)
.map_err(|e: PyErr| e.print_and_set_sys_last_vars(py))
.unwrap();
let py_fun = py_mod
.getattr("ilt")
.map_err(|e| e.print_and_set_sys_last_vars(py))
.unwrap();
py_fun
.call1((
lt_func,
PyList::new(py, test_values.iter()).unwrap(),
max_fn_evals,
include_str!("../iltcme.json"),
))
.map_err(|e: PyErr| e.print_and_set_sys_last_vars(py))
.unwrap()
.extract()
.map_err(|e: PyErr| e.print_and_set_sys_last_vars(py))
.unwrap()
})
}