pub mod fft;
pub mod memory;
pub mod optimize;
pub mod stats;
pub mod types;
#[cfg(feature = "linalg")]
pub mod linalg;
pub use self::fft::*;
pub use self::memory::*;
pub use self::optimize::*;
pub use self::stats::*;
pub use self::types::*;
#[cfg(feature = "linalg")]
pub use self::linalg::*;
#[no_mangle]
pub extern "C" fn sci_version() -> *const std::os::raw::c_char {
static VERSION: &[u8] = concat!(env!("CARGO_PKG_VERSION"), "\0").as_bytes();
VERSION.as_ptr() as *const std::os::raw::c_char
}
#[cfg(test)]
mod tests {
use super::*;
use std::ptr;
#[test]
fn test_vector_new_and_free() {
let mut v = SciVector {
data: ptr::null_mut(),
len: 0,
};
let r = unsafe { sci_vector_new(10, &mut v) };
assert!(r.success);
assert_eq!(v.len, 10);
assert!(!v.data.is_null());
let slice = unsafe { std::slice::from_raw_parts(v.data, v.len) };
for &val in slice {
assert_eq!(val, 0.0);
}
unsafe { sci_vector_free(&mut v) };
assert!(v.data.is_null());
assert_eq!(v.len, 0);
}
#[test]
fn test_vector_from_data() {
let src = [1.0, 2.0, 3.0, 4.0, 5.0];
let mut v = SciVector {
data: ptr::null_mut(),
len: 0,
};
let r = unsafe { sci_vector_from_data(src.as_ptr(), src.len(), &mut v) };
assert!(r.success);
assert_eq!(v.len, 5);
let slice = unsafe { std::slice::from_raw_parts(v.data, v.len) };
assert_eq!(slice, &[1.0, 2.0, 3.0, 4.0, 5.0]);
unsafe { sci_vector_free(&mut v) };
}
#[test]
fn test_vector_new_null_out() {
let r = unsafe { sci_vector_new(10, ptr::null_mut()) };
assert!(!r.success);
assert!(!r.error_msg.is_null());
unsafe { sci_free_error(r.error_msg) };
}
#[test]
fn test_matrix_new_and_free() {
let mut m = SciMatrix {
data: ptr::null_mut(),
rows: 0,
cols: 0,
};
let r = unsafe { sci_matrix_new(3, 4, &mut m) };
assert!(r.success);
assert_eq!(m.rows, 3);
assert_eq!(m.cols, 4);
assert!(!m.data.is_null());
let slice = unsafe { std::slice::from_raw_parts(m.data, m.rows * m.cols) };
for &val in slice {
assert_eq!(val, 0.0);
}
unsafe { sci_matrix_free(&mut m) };
assert!(m.data.is_null());
}
#[test]
fn test_matrix_from_data() {
let src = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
let mut m = SciMatrix {
data: ptr::null_mut(),
rows: 0,
cols: 0,
};
let r = unsafe { sci_matrix_from_data(src.as_ptr(), 2, 3, &mut m) };
assert!(r.success);
assert_eq!(m.rows, 2);
assert_eq!(m.cols, 3);
let slice = unsafe { std::slice::from_raw_parts(m.data, m.rows * m.cols) };
assert_eq!(slice, &[1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
unsafe { sci_matrix_free(&mut m) };
}
#[test]
fn test_mean() {
let data = [1.0, 2.0, 3.0, 4.0, 5.0];
let v = SciVector {
data: data.as_ptr() as *mut f64,
len: data.len(),
};
let mut result = 0.0;
let r = unsafe { sci_mean(&v, &mut result) };
assert!(r.success);
assert!((result - 3.0).abs() < 1e-10);
}
#[test]
fn test_mean_empty() {
let v = SciVector {
data: ptr::null_mut(),
len: 0,
};
let mut result = 0.0;
let r = unsafe { sci_mean(&v, &mut result) };
assert!(!r.success);
unsafe { sci_free_error(r.error_msg) };
}
#[test]
fn test_std_sample() {
let data = [2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0];
let v = SciVector {
data: data.as_ptr() as *mut f64,
len: data.len(),
};
let mut result = 0.0;
let r = unsafe { sci_std(&v, 1, &mut result) };
assert!(r.success);
let expected = (32.0_f64 / 7.0_f64).sqrt();
assert!(
(result - expected).abs() < 1e-10,
"result={result}, expected={expected}"
);
}
#[test]
fn test_std_population() {
let data = [2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0];
let v = SciVector {
data: data.as_ptr() as *mut f64,
len: data.len(),
};
let mut result = 0.0;
let r = unsafe { sci_std(&v, 0, &mut result) };
assert!(r.success);
assert!((result - 2.0).abs() < 1e-10);
}
#[test]
fn test_percentile() {
let data = [1.0, 2.0, 3.0, 4.0, 5.0];
let v = SciVector {
data: data.as_ptr() as *mut f64,
len: data.len(),
};
let mut result = 0.0;
let r = unsafe { sci_percentile(&v, 0.0, &mut result) };
assert!(r.success);
assert!((result - 1.0).abs() < 1e-10);
let r = unsafe { sci_percentile(&v, 50.0, &mut result) };
assert!(r.success);
assert!((result - 3.0).abs() < 1e-10);
let r = unsafe { sci_percentile(&v, 100.0, &mut result) };
assert!(r.success);
assert!((result - 5.0).abs() < 1e-10);
}
#[test]
fn test_percentile_interpolation() {
let data = [10.0, 20.0, 30.0, 40.0];
let v = SciVector {
data: data.as_ptr() as *mut f64,
len: data.len(),
};
let mut result = 0.0;
let r = unsafe { sci_percentile(&v, 25.0, &mut result) };
assert!(r.success);
assert!((result - 17.5).abs() < 1e-10);
}
#[test]
fn test_percentile_invalid_q() {
let data = [1.0, 2.0, 3.0];
let v = SciVector {
data: data.as_ptr() as *mut f64,
len: data.len(),
};
let mut result = 0.0;
let r = unsafe { sci_percentile(&v, -1.0, &mut result) };
assert!(!r.success);
unsafe { sci_free_error(r.error_msg) };
let r = unsafe { sci_percentile(&v, 101.0, &mut result) };
assert!(!r.success);
unsafe { sci_free_error(r.error_msg) };
}
#[test]
fn test_correlation() {
let x_data = [1.0, 2.0, 3.0, 4.0, 5.0];
let y_data = [2.0, 4.0, 6.0, 8.0, 10.0];
let x = SciVector {
data: x_data.as_ptr() as *mut f64,
len: x_data.len(),
};
let y = SciVector {
data: y_data.as_ptr() as *mut f64,
len: y_data.len(),
};
let mut result = 0.0;
let r = unsafe { sci_correlation(&x, &y, &mut result) };
assert!(r.success);
assert!((result - 1.0).abs() < 1e-10);
let y_neg = [10.0, 8.0, 6.0, 4.0, 2.0];
let yn = SciVector {
data: y_neg.as_ptr() as *mut f64,
len: y_neg.len(),
};
let r = unsafe { sci_correlation(&x, &yn, &mut result) };
assert!(r.success);
assert!((result - (-1.0)).abs() < 1e-10);
}
#[test]
fn test_correlation_length_mismatch() {
let x_data = [1.0, 2.0, 3.0];
let y_data = [1.0, 2.0];
let x = SciVector {
data: x_data.as_ptr() as *mut f64,
len: x_data.len(),
};
let y = SciVector {
data: y_data.as_ptr() as *mut f64,
len: y_data.len(),
};
let mut result = 0.0;
let r = unsafe { sci_correlation(&x, &y, &mut result) };
assert!(!r.success);
unsafe { sci_free_error(r.error_msg) };
}
#[test]
fn test_variance() {
let data = [2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0];
let v = SciVector {
data: data.as_ptr() as *mut f64,
len: data.len(),
};
let mut result = 0.0;
let r = unsafe { sci_variance(&v, 0, &mut result) };
assert!(r.success);
assert!((result - 4.0).abs() < 1e-10);
let r = unsafe { sci_variance(&v, 1, &mut result) };
assert!(r.success);
assert!((result - 32.0 / 7.0).abs() < 1e-10);
}
#[test]
fn test_median() {
let data = [3.0, 1.0, 2.0, 5.0, 4.0];
let v = SciVector {
data: data.as_ptr() as *mut f64,
len: data.len(),
};
let mut result = 0.0;
let r = unsafe { sci_median(&v, &mut result) };
assert!(r.success);
assert!((result - 3.0).abs() < 1e-10);
let data2 = [3.0, 1.0, 2.0, 4.0];
let v2 = SciVector {
data: data2.as_ptr() as *mut f64,
len: data2.len(),
};
let r = unsafe { sci_median(&v2, &mut result) };
assert!(r.success);
assert!((result - 2.5).abs() < 1e-10);
}
#[test]
fn test_fft_roundtrip() {
let real = [1.0, 2.0, 3.0, 4.0];
let imag = [0.0, 0.0, 0.0, 0.0];
let mut fwd = SciComplexVector {
real: ptr::null_mut(),
imag: ptr::null_mut(),
len: 0,
};
let r = unsafe { sci_fft_forward(real.as_ptr(), imag.as_ptr(), 4, &mut fwd) };
assert!(r.success);
assert_eq!(fwd.len, 4);
let mut inv = SciComplexVector {
real: ptr::null_mut(),
imag: ptr::null_mut(),
len: 0,
};
let r = unsafe { sci_fft_inverse(fwd.real, fwd.imag, fwd.len, &mut inv) };
assert!(r.success);
assert_eq!(inv.len, 4);
let inv_real = unsafe { std::slice::from_raw_parts(inv.real, inv.len) };
let inv_imag = unsafe { std::slice::from_raw_parts(inv.imag, inv.len) };
for i in 0..4 {
assert!(
(inv_real[i] - real[i]).abs() < 1e-10,
"real[{}]: expected {}, got {}",
i,
real[i],
inv_real[i]
);
assert!(
inv_imag[i].abs() < 1e-10,
"imag[{}]: expected 0, got {}",
i,
inv_imag[i]
);
}
unsafe {
sci_complex_vector_free(&mut fwd);
sci_complex_vector_free(&mut inv);
}
}
#[test]
fn test_fft_known_values() {
let real = [1.0, 0.0, 0.0, 0.0];
let mut fwd = SciComplexVector {
real: ptr::null_mut(),
imag: ptr::null_mut(),
len: 0,
};
let r = unsafe { sci_fft_forward(real.as_ptr(), ptr::null(), 4, &mut fwd) };
assert!(r.success);
let fwd_real = unsafe { std::slice::from_raw_parts(fwd.real, fwd.len) };
let fwd_imag = unsafe { std::slice::from_raw_parts(fwd.imag, fwd.len) };
for i in 0..4 {
assert!(
(fwd_real[i] - 1.0).abs() < 1e-10,
"FFT real[{}] = {}, expected 1.0",
i,
fwd_real[i]
);
assert!(
fwd_imag[i].abs() < 1e-10,
"FFT imag[{}] = {}, expected 0.0",
i,
fwd_imag[i]
);
}
unsafe { sci_complex_vector_free(&mut fwd) };
}
#[test]
fn test_fft_non_power_of_2() {
let real = [1.0, 2.0, 3.0, 4.0, 5.0];
let mut fwd = SciComplexVector {
real: ptr::null_mut(),
imag: ptr::null_mut(),
len: 0,
};
let r = unsafe { sci_fft_forward(real.as_ptr(), ptr::null(), 5, &mut fwd) };
assert!(r.success);
assert_eq!(fwd.len, 5);
let mut inv = SciComplexVector {
real: ptr::null_mut(),
imag: ptr::null_mut(),
len: 0,
};
let r = unsafe { sci_fft_inverse(fwd.real, fwd.imag, fwd.len, &mut inv) };
assert!(r.success);
let inv_real = unsafe { std::slice::from_raw_parts(inv.real, inv.len) };
for i in 0..5 {
assert!(
(inv_real[i] - real[i]).abs() < 1e-9,
"roundtrip real[{}]: expected {}, got {}",
i,
real[i],
inv_real[i]
);
}
unsafe {
sci_complex_vector_free(&mut fwd);
sci_complex_vector_free(&mut inv);
}
}
#[test]
fn test_rfft() {
let real = [1.0, 2.0, 3.0, 4.0];
let mut out = SciComplexVector {
real: ptr::null_mut(),
imag: ptr::null_mut(),
len: 0,
};
let r = unsafe { sci_rfft(real.as_ptr(), 4, &mut out) };
assert!(r.success);
assert_eq!(out.len, 4);
let out_real = unsafe { std::slice::from_raw_parts(out.real, out.len) };
assert!((out_real[0] - 10.0).abs() < 1e-10);
unsafe { sci_complex_vector_free(&mut out) };
}
unsafe extern "C" fn quadratic_fn(x: f64, _user_data: *mut std::ffi::c_void) -> f64 {
(x - 2.0) * (x - 2.0)
}
#[test]
fn test_minimize_scalar_golden_section() {
let mut x_min = 0.0;
let mut f_min = 0.0;
let r = unsafe {
sci_minimize_scalar(
quadratic_fn,
ptr::null_mut(),
-10.0,
10.0,
1e-8,
0, &mut x_min,
&mut f_min,
)
};
assert!(r.success);
assert!((x_min - 2.0).abs() < 1e-6, "x_min = {}", x_min);
assert!(f_min.abs() < 1e-10, "f_min = {}", f_min);
}
#[test]
fn test_minimize_brent() {
let mut x_min = 0.0;
let mut f_min = 0.0;
let r = unsafe {
sci_minimize_brent(
quadratic_fn,
ptr::null_mut(),
-10.0,
10.0,
1e-8,
0,
&mut x_min,
&mut f_min,
)
};
assert!(r.success);
assert!((x_min - 2.0).abs() < 1e-6, "x_min = {}", x_min);
assert!(f_min.abs() < 1e-10, "f_min = {}", f_min);
}
unsafe extern "C" fn root_fn(x: f64, _user_data: *mut std::ffi::c_void) -> f64 {
x * x - 4.0
}
#[test]
fn test_root_find_positive() {
let mut root = 0.0;
let r = unsafe { sci_root_find(root_fn, ptr::null_mut(), 0.0, 10.0, 1e-12, 0, &mut root) };
assert!(r.success);
assert!((root - 2.0).abs() < 1e-10, "root = {}", root);
}
#[test]
fn test_root_find_negative() {
let mut root = 0.0;
let r = unsafe { sci_root_find(root_fn, ptr::null_mut(), -10.0, 0.0, 1e-12, 0, &mut root) };
assert!(r.success);
assert!((root - (-2.0)).abs() < 1e-10, "root = {}", root);
}
#[test]
fn test_root_find_no_sign_change() {
let mut root = 0.0;
let r = unsafe { sci_root_find(root_fn, ptr::null_mut(), 0.0, 1.0, 1e-12, 0, &mut root) };
assert!(!r.success);
unsafe { sci_free_error(r.error_msg) };
}
unsafe extern "C" fn shifted_fn(x: f64, user_data: *mut std::ffi::c_void) -> f64 {
let shift = if user_data.is_null() {
0.0
} else {
unsafe { *(user_data as *const f64) }
};
(x - shift) * (x - shift)
}
#[test]
fn test_minimize_with_user_data() {
let shift: f64 = 5.0;
let mut x_min = 0.0;
let mut f_min = 0.0;
let r = unsafe {
sci_minimize_scalar(
shifted_fn,
&shift as *const f64 as *mut std::ffi::c_void,
0.0,
10.0,
1e-8,
0,
&mut x_min,
&mut f_min,
)
};
assert!(r.success);
assert!((x_min - 5.0).abs() < 1e-6, "x_min = {}", x_min);
}
#[test]
fn test_version() {
let v = sci_version();
assert!(!v.is_null());
let c_str = unsafe { std::ffi::CStr::from_ptr(v) };
let version = c_str.to_str().expect("version is valid UTF-8");
assert!(!version.is_empty());
}
#[cfg(feature = "linalg")]
mod linalg_tests {
use super::*;
#[test]
fn test_det_identity() {
let data = [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0];
let m = SciMatrix {
data: data.as_ptr() as *mut f64,
rows: 3,
cols: 3,
};
let mut det = 0.0;
let r = unsafe { sci_det(&m, &mut det) };
assert!(r.success);
assert!((det - 1.0).abs() < 1e-10, "det = {}", det);
}
#[test]
fn test_det_2x2() {
let data = [1.0, 2.0, 3.0, 4.0];
let m = SciMatrix {
data: data.as_ptr() as *mut f64,
rows: 2,
cols: 2,
};
let mut det = 0.0;
let r = unsafe { sci_det(&m, &mut det) };
assert!(r.success);
assert!((det - (-2.0)).abs() < 1e-10, "det = {}", det);
}
#[test]
fn test_det_non_square() {
let data = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
let m = SciMatrix {
data: data.as_ptr() as *mut f64,
rows: 2,
cols: 3,
};
let mut det = 0.0;
let r = unsafe { sci_det(&m, &mut det) };
assert!(!r.success);
unsafe { sci_free_error(r.error_msg) };
}
#[test]
fn test_inv_2x2() {
let data = [4.0, 7.0, 2.0, 6.0];
let m = SciMatrix {
data: data.as_ptr() as *mut f64,
rows: 2,
cols: 2,
};
let mut inv_m = SciMatrix {
data: ptr::null_mut(),
rows: 0,
cols: 0,
};
let r = unsafe { sci_inv(&m, &mut inv_m) };
assert!(r.success);
assert_eq!(inv_m.rows, 2);
assert_eq!(inv_m.cols, 2);
let inv_data = unsafe { std::slice::from_raw_parts(inv_m.data, 4) };
assert!((inv_data[0] - 0.6).abs() < 1e-10);
assert!((inv_data[1] - (-0.7)).abs() < 1e-10);
assert!((inv_data[2] - (-0.2)).abs() < 1e-10);
assert!((inv_data[3] - 0.4).abs() < 1e-10);
unsafe { sci_matrix_free(&mut inv_m) };
}
#[test]
fn test_solve_2x2() {
let a_data = [2.0, 1.0, 1.0, 3.0];
let b_data = [5.0, 7.0];
let a = SciMatrix {
data: a_data.as_ptr() as *mut f64,
rows: 2,
cols: 2,
};
let b = SciVector {
data: b_data.as_ptr() as *mut f64,
len: 2,
};
let mut x = SciVector {
data: ptr::null_mut(),
len: 0,
};
let r = unsafe { sci_solve(&a, &b, &mut x) };
assert!(r.success);
assert_eq!(x.len, 2);
let x_data = unsafe { std::slice::from_raw_parts(x.data, x.len) };
assert!((x_data[0] - 1.6).abs() < 1e-10, "x[0] = {}", x_data[0]);
assert!((x_data[1] - 1.8).abs() < 1e-10, "x[1] = {}", x_data[1]);
unsafe { sci_vector_free(&mut x) };
}
#[test]
fn test_svd_identity() {
let data = [1.0, 0.0, 0.0, 1.0];
let m = SciMatrix {
data: data.as_ptr() as *mut f64,
rows: 2,
cols: 2,
};
let mut svd_result = SciSvdResult {
u: SciMatrix {
data: ptr::null_mut(),
rows: 0,
cols: 0,
},
s: SciVector {
data: ptr::null_mut(),
len: 0,
},
vt: SciMatrix {
data: ptr::null_mut(),
rows: 0,
cols: 0,
},
};
let r = unsafe { sci_svd(&m, &mut svd_result) };
assert!(r.success);
assert_eq!(svd_result.s.len, 2);
let s = unsafe { std::slice::from_raw_parts(svd_result.s.data, svd_result.s.len) };
assert!((s[0] - 1.0).abs() < 1e-10);
assert!((s[1] - 1.0).abs() < 1e-10);
unsafe { sci_svd_result_free(&mut svd_result) };
}
#[test]
fn test_eig_diagonal() {
let data = [3.0, 0.0, 0.0, 5.0];
let m = SciMatrix {
data: data.as_ptr() as *mut f64,
rows: 2,
cols: 2,
};
let mut eig_result = SciEigResult {
eigenvalues: SciComplexVector {
real: ptr::null_mut(),
imag: ptr::null_mut(),
len: 0,
},
eigenvectors: SciMatrix {
data: ptr::null_mut(),
rows: 0,
cols: 0,
},
};
let r = unsafe { sci_eig(&m, &mut eig_result) };
assert!(r.success);
assert_eq!(eig_result.eigenvalues.len, 2);
let real = unsafe { std::slice::from_raw_parts(eig_result.eigenvalues.real, 2) };
let imag = unsafe { std::slice::from_raw_parts(eig_result.eigenvalues.imag, 2) };
let mut evals: Vec<f64> = real.to_vec();
evals.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
assert!((evals[0] - 3.0).abs() < 1e-10, "eval[0] = {}", evals[0]);
assert!((evals[1] - 5.0).abs() < 1e-10, "eval[1] = {}", evals[1]);
for &im in imag {
assert!(im.abs() < 1e-10);
}
unsafe { sci_eig_result_free(&mut eig_result) };
}
}
}