use crate::*;
use scirs2_core::ndarray::{Array2, Array3, ArrayView2, ArrayView3, ArrayViewMut1};
pub mod simd_image {
use super::*;
pub fn simd_reconstruct_from_patches_2d(
patches: &ArrayView3<Float>,
image_size: (usize, usize),
) -> SklResult<Array2<Float>> {
let (height, width) = image_size;
let n_patches = patches.shape()[0];
if n_patches == 0 {
return Ok(Array2::zeros((height, width)));
}
let patch_height = patches.shape()[1];
let patch_width = patches.shape()[2];
let max_row = if height >= patch_height {
height - patch_height + 1
} else {
return Err(SklearsError::InvalidInput(format!(
"Patch height {} is larger than image height {}",
patch_height, height
)));
};
let max_col = if width >= patch_width {
width - patch_width + 1
} else {
return Err(SklearsError::InvalidInput(format!(
"Patch width {} is larger than image width {}",
patch_width, width
)));
};
let mut accumulation: Array2<Float> = Array2::zeros((height, width));
let mut counts: Array2<Float> = Array2::zeros((height, width));
let mut patch_idx = 0;
'outer: for row in 0..max_row {
for col in 0..max_col {
if patch_idx >= n_patches {
break 'outer;
}
for i in 0..patch_height {
for j in 0..patch_width {
let img_row = row + i;
let img_col = col + j;
accumulation[[img_row, img_col]] += patches[[patch_idx, i, j]];
counts[[img_row, img_col]] += 1.0;
}
}
patch_idx += 1;
}
}
let mut reconstructed = Array2::zeros((height, width));
for i in 0..height {
for j in 0..width {
if counts[[i, j]] > 0.0 {
reconstructed[[i, j]] = accumulation[[i, j]] / counts[[i, j]];
}
}
}
Ok(reconstructed)
}
pub fn simd_array_subtraction(
a: &ArrayView2<Float>,
b: &ArrayView2<Float>,
) -> SklResult<Array2<Float>> {
Ok(a - b)
}
pub fn simd_detect_extrema(
below: &ArrayView2<Float>,
center: &ArrayView2<Float>,
above: &ArrayView2<Float>,
threshold: Float,
) -> Vec<(usize, usize, bool)> {
let (height, width) = center.dim();
let mut extrema = Vec::new();
if below.dim() != center.dim() || above.dim() != center.dim() {
return extrema;
}
for i in 1..(height - 1) {
for j in 1..(width - 1) {
let val = center[[i, j]];
if val.abs() < threshold {
continue;
}
let mut is_max = true;
let mut is_min = true;
for di in -1..=1 {
for dj in -1..=1 {
if di == 0 && dj == 0 {
continue;
}
let ni = (i as isize + di) as usize;
let nj = (j as isize + dj) as usize;
let neighbor = center[[ni, nj]];
if val <= neighbor {
is_max = false;
}
if val >= neighbor {
is_min = false;
}
}
}
for di in -1..=1 {
for dj in -1..=1 {
let ni = (i as isize + di) as usize;
let nj = (j as isize + dj) as usize;
let neighbor = below[[ni, nj]];
if val <= neighbor {
is_max = false;
}
if val >= neighbor {
is_min = false;
}
}
}
for di in -1..=1 {
for dj in -1..=1 {
let ni = (i as isize + di) as usize;
let nj = (j as isize + dj) as usize;
let neighbor = above[[ni, nj]];
if val <= neighbor {
is_max = false;
}
if val >= neighbor {
is_min = false;
}
}
}
if is_max {
extrema.push((j, i, true)); } else if is_min {
extrema.push((j, i, false)); }
}
}
extrema
}
pub fn simd_normalize_descriptor(descriptor: &mut ArrayViewMut1<Float>, threshold: Float) {
let n = descriptor.len();
if n == 0 {
return;
}
let mut norm_sq = 0.0;
for i in 0..n {
norm_sq += descriptor[i] * descriptor[i];
}
let norm = norm_sq.sqrt();
if norm > 1e-10 {
for i in 0..n {
descriptor[i] /= norm;
}
}
for i in 0..n {
if descriptor[i] > threshold {
descriptor[i] = threshold;
} else if descriptor[i] < -threshold {
descriptor[i] = -threshold;
}
}
let mut norm_sq = 0.0;
for i in 0..n {
norm_sq += descriptor[i] * descriptor[i];
}
let norm = norm_sq.sqrt();
if norm > 1e-10 {
for i in 0..n {
descriptor[i] /= norm;
}
}
}
pub fn simd_gaussian_blur(
image: &ArrayView2<Float>,
kernel: &[Float],
_sigma: Float,
) -> Array2<Float> {
let (height, width) = image.dim();
if kernel.is_empty() {
return image.to_owned();
}
let radius = kernel.len() / 2;
let mut temp = Array2::zeros((height, width));
#[allow(clippy::needless_range_loop)]
for i in 0..height {
for j in 0..width {
let mut sum = 0.0;
for k in 0..kernel.len() {
let offset = k as isize - radius as isize;
let col = (j as isize + offset).max(0).min((width - 1) as isize) as usize;
sum += image[[i, col]] * kernel[k];
}
temp[[i, j]] = sum;
}
}
let mut result = Array2::zeros((height, width));
#[allow(clippy::needless_range_loop)]
for i in 0..height {
for j in 0..width {
let mut sum = 0.0;
for k in 0..kernel.len() {
let offset = k as isize - radius as isize;
let row = (i as isize + offset).max(0).min((height - 1) as isize) as usize;
sum += temp[[row, j]] * kernel[k];
}
result[[i, j]] = sum;
}
}
result
}
pub fn simd_compute_integral_image(image: &ArrayView2<Float>) -> Array2<Float> {
let (height, width) = image.dim();
let mut integral = Array2::zeros((height, width));
for i in 0..height {
for j in 0..width {
let mut sum = image[[i, j]];
if i > 0 {
sum += integral[[i - 1, j]];
}
if j > 0 {
sum += integral[[i, j - 1]];
}
if i > 0 && j > 0 {
sum -= integral[[i - 1, j - 1]];
}
integral[[i, j]] = sum;
}
}
integral
}
pub fn simd_haar_x_response(
integral: &ArrayView2<Float>,
x: usize,
y: usize,
size: usize,
) -> Float {
let (height, width) = integral.dim();
let half_size = size / 2;
if x < half_size || y < half_size || x + half_size >= width || y + half_size >= height {
return 0.0;
}
let top = y.saturating_sub(half_size);
let bottom = (y + half_size - 1).min(height - 1);
let left_col = x.saturating_sub(half_size);
let left_end = if x > 0 { x - 1 } else { 0 };
let right_col = x;
let right_end = (x + half_size - 1).min(width - 1);
let left_sum = if x > 0 && left_end >= left_col {
get_rect_sum(integral, top, left_col, bottom, left_end)
} else {
0.0
};
let right_sum = if right_end >= right_col {
get_rect_sum(integral, top, right_col, bottom, right_end)
} else {
0.0
};
right_sum - left_sum
}
pub fn simd_haar_y_response(
integral: &ArrayView2<Float>,
x: usize,
y: usize,
size: usize,
) -> Float {
let (height, width) = integral.dim();
let half_size = size / 2;
if x < half_size || y < half_size || x + half_size >= width || y + half_size >= height {
return 0.0;
}
let left = x.saturating_sub(half_size);
let right = (x + half_size - 1).min(width - 1);
let top_row = y.saturating_sub(half_size);
let top_end = if y > 0 { y - 1 } else { 0 };
let bottom_row = y;
let bottom_end = (y + half_size - 1).min(height - 1);
let top_sum = if y > 0 && top_end >= top_row {
get_rect_sum(integral, top_row, left, top_end, right)
} else {
0.0
};
let bottom_sum = if bottom_end >= bottom_row {
get_rect_sum(integral, bottom_row, left, bottom_end, right)
} else {
0.0
};
bottom_sum - top_sum
}
fn get_rect_sum(
integral: &ArrayView2<Float>,
top: usize,
left: usize,
bottom: usize,
right: usize,
) -> Float {
let (height, width) = integral.dim();
let top = top.min(height - 1);
let left = left.min(width - 1);
let bottom = bottom.min(height - 1);
let right = right.min(width - 1);
let br = integral[[bottom, right]];
let tr = if top > 0 {
integral[[top - 1, right]]
} else {
0.0
};
let bl = if left > 0 {
integral[[bottom, left - 1]]
} else {
0.0
};
let tl = if top > 0 && left > 0 {
integral[[top - 1, left - 1]]
} else {
0.0
};
br - tr - bl + tl
}
pub fn simd_compute_kurtosis(values: &[Float], mean: Float, std_dev: Float) -> Float {
super::compute_kurtosis_fallback(values, mean, std_dev)
}
}
pub fn extract_patches_fallback(
image: &ArrayView2<Float>,
patch_size: (usize, usize),
positions: &[(usize, usize)],
) -> SklResult<Array3<Float>> {
let (patch_height, patch_width) = patch_size;
let mut patches = Array3::zeros((positions.len(), patch_height, patch_width));
for (patch_idx, &(row, col)) in positions.iter().enumerate() {
for i in 0..patch_height {
for j in 0..patch_width {
if row + i < image.shape()[0] && col + j < image.shape()[1] {
patches[[patch_idx, i, j]] = image[[row + i, col + j]];
}
}
}
}
Ok(patches)
}
pub fn compute_entropy_fallback(probabilities: &[f64]) -> f64 {
let mut entropy = 0.0;
for &prob in probabilities {
if prob > 0.0 {
entropy -= prob * prob.log2();
}
}
entropy
}
pub fn compute_skewness_fallback(values: &[f64], mean: f64, std_dev: f64) -> f64 {
if std_dev == 0.0 {
return 0.0;
}
let n = values.len() as f64;
let sum: f64 = values.iter().map(|&x| ((x - mean) / std_dev).powi(3)).sum();
sum / n
}
pub fn compute_kurtosis_fallback(values: &[f64], mean: f64, std_dev: f64) -> f64 {
if std_dev == 0.0 {
return 0.0;
}
let n = values.len() as f64;
let sum: f64 = values.iter().map(|&x| ((x - mean) / std_dev).powi(4)).sum();
(sum / n) - 3.0 }
pub fn compute_image_statistics(image: &ArrayView2<Float>) -> (Float, Float, Float, Float) {
let values: Vec<Float> = image.iter().cloned().collect();
let n = values.len() as Float;
let mean = values.iter().sum::<Float>() / n;
let variance = values.iter().map(|&x| (x - mean).powi(2)).sum::<Float>() / n;
let std_dev = variance.sqrt();
let values_f64: Vec<f64> = values.to_vec();
let skewness = compute_skewness_fallback(&values_f64, mean, std_dev) as Float;
let kurtosis = compute_kurtosis_fallback(&values_f64, mean, std_dev) as Float;
(mean, std_dev, skewness, kurtosis)
}
pub fn rgb_to_grayscale(rgb_image: &ArrayView3<Float>) -> SklResult<Array2<Float>> {
let (height, width, channels) = rgb_image.dim();
if channels != 3 {
return Err(SklearsError::InvalidInput(
"Input must be an RGB image with 3 channels".to_string(),
));
}
let mut grayscale = Array2::zeros((height, width));
let r_weight = 0.299;
let g_weight = 0.587;
let b_weight = 0.114;
for i in 0..height {
for j in 0..width {
let r = rgb_image[[i, j, 0]];
let g = rgb_image[[i, j, 1]];
let b = rgb_image[[i, j, 2]];
grayscale[[i, j]] = r_weight * r + g_weight * g + b_weight * b;
}
}
Ok(grayscale)
}
pub fn normalize_image(image: &ArrayView2<Float>) -> Array2<Float> {
let min_val = image.iter().fold(Float::INFINITY, |a, &b| a.min(b));
let max_val = image.iter().fold(Float::NEG_INFINITY, |a, &b| a.max(b));
if (max_val - min_val).abs() < Float::EPSILON {
return Array2::zeros(image.dim());
}
let range = max_val - min_val;
image.mapv(|x| (x - min_val) / range)
}
#[allow(non_snake_case)]
#[cfg(test)]
mod tests {
use super::*;
use scirs2_core::ndarray::Array2;
#[test]
fn test_compute_entropy_fallback() {
let probs = vec![0.5, 0.3, 0.2];
let entropy = compute_entropy_fallback(&probs);
let expected = -(0.5 * 0.5_f64.log2() + 0.3 * 0.3_f64.log2() + 0.2 * 0.2_f64.log2());
assert!((entropy - expected).abs() < 1e-10);
}
#[test]
fn test_compute_skewness_fallback() {
let values = vec![1.0, 2.0, 3.0, 4.0, 5.0];
let mean = 3.0;
let std_dev = (10.0_f64 / 5.0).sqrt();
let skewness = compute_skewness_fallback(&values, mean, std_dev);
assert!(skewness.abs() < 1e-10); }
#[test]
fn test_compute_kurtosis_fallback() {
let values = vec![1.0, 2.0, 3.0, 4.0, 5.0];
let mean = 3.0;
let std_dev = (10.0_f64 / 5.0).sqrt();
let kurtosis = compute_kurtosis_fallback(&values, mean, std_dev);
assert!(kurtosis < 0.0);
}
#[test]
fn test_compute_image_statistics() {
let image = Array2::from_shape_vec((2, 2), vec![1.0, 2.0, 3.0, 4.0])
.expect("operation should succeed");
let (mean, std_dev, skewness, kurtosis) = compute_image_statistics(&image.view());
assert!((mean - 2.5).abs() < 1e-6);
assert!(std_dev > 0.0);
assert!(skewness.abs() < 1e-6); assert!(kurtosis < 0.0); }
#[test]
fn test_normalize_image() {
let image = Array2::from_shape_vec((2, 2), vec![10.0, 20.0, 30.0, 40.0])
.expect("operation should succeed");
let normalized = normalize_image(&image.view());
assert!((normalized[[0, 0]] - 0.0).abs() < 1e-6);
assert!((normalized[[1, 1]] - 1.0).abs() < 1e-6);
assert!((normalized[[0, 1]] - 1.0 / 3.0).abs() < 1e-6);
assert!((normalized[[1, 0]] - 2.0 / 3.0).abs() < 1e-6);
}
#[test]
fn test_extract_patches_fallback() {
let image = Array2::from_shape_vec((4, 4), (0..16).map(|x| x as f64).collect())
.expect("operation should succeed");
let positions = vec![(0, 0), (1, 1), (2, 2)];
let patches = extract_patches_fallback(&image.view(), (2, 2), &positions)
.expect("operation should succeed");
assert_eq!(patches.shape(), &[3, 2, 2]);
assert_eq!(patches[[0, 0, 0]], 0.0);
assert_eq!(patches[[1, 0, 0]], 5.0);
assert_eq!(patches[[2, 0, 0]], 10.0);
}
#[test]
fn test_simd_gaussian_blur_basic() {
use scirs2_core::ndarray::array;
let image = array![[0.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 0.0],];
let kernel = vec![0.25, 0.5, 0.25];
let blurred = simd_image::simd_gaussian_blur(&image.view(), &kernel, 1.0);
assert!(
blurred[[1, 1]] < 1.0,
"Center value should be reduced by blur"
);
assert!(
blurred[[1, 1]] > 0.0,
"Center value should still be positive"
);
assert!(
blurred[[0, 1]] > 0.0,
"Neighbor should receive blurred value"
);
assert!(
blurred[[1, 0]] > 0.0,
"Neighbor should receive blurred value"
);
}
#[test]
fn test_simd_gaussian_blur_empty_kernel() {
use scirs2_core::ndarray::array;
let image = array![[1.0, 2.0], [3.0, 4.0]];
let kernel = vec![];
let result = simd_image::simd_gaussian_blur(&image.view(), &kernel, 1.0);
assert_eq!(result, image);
}
#[test]
fn test_simd_detect_extrema_maximum() {
use scirs2_core::ndarray::Array2;
let below = Array2::from_elem((5, 5), 0.0);
let mut center = Array2::from_elem((5, 5), 0.0);
center[[2, 2]] = 10.0; let above = Array2::from_elem((5, 5), 0.0);
let extrema =
simd_image::simd_detect_extrema(&below.view(), ¢er.view(), &above.view(), 1.0);
assert_eq!(extrema.len(), 1, "Should detect exactly one extremum");
assert_eq!(extrema[0], (2, 2, true), "Should detect maximum at center");
}
#[test]
fn test_simd_detect_extrema_minimum() {
use scirs2_core::ndarray::Array2;
let below = Array2::from_elem((5, 5), 10.0);
let mut center = Array2::from_elem((5, 5), 10.0);
center[[2, 2]] = -5.0; let above = Array2::from_elem((5, 5), 10.0);
let extrema =
simd_image::simd_detect_extrema(&below.view(), ¢er.view(), &above.view(), 1.0);
assert_eq!(extrema.len(), 1, "Should detect exactly one extremum");
assert_eq!(extrema[0], (2, 2, false), "Should detect minimum at center");
}
#[test]
fn test_simd_detect_extrema_threshold() {
use scirs2_core::ndarray::Array2;
let below = Array2::from_elem((5, 5), 0.0);
let mut center = Array2::from_elem((5, 5), 0.0);
center[[2, 2]] = 0.5; let above = Array2::from_elem((5, 5), 0.0);
let extrema =
simd_image::simd_detect_extrema(&below.view(), ¢er.view(), &above.view(), 1.0);
assert_eq!(
extrema.len(),
0,
"Should not detect extremum below threshold"
);
}
#[test]
fn test_simd_detect_extrema_boundaries() {
use scirs2_core::ndarray::Array2;
let below = Array2::from_elem((5, 5), 0.0);
let mut center = Array2::from_elem((5, 5), 0.0);
center[[0, 0]] = 10.0; center[[0, 2]] = 10.0; let above = Array2::from_elem((5, 5), 0.0);
let extrema =
simd_image::simd_detect_extrema(&below.view(), ¢er.view(), &above.view(), 1.0);
assert_eq!(extrema.len(), 0, "Should not detect extrema at boundaries");
}
#[test]
fn test_simd_normalize_descriptor_l2() {
use scirs2_core::ndarray::Array1;
let mut descriptor = Array1::from_vec(vec![3.0, 4.0, 0.0]);
simd_image::simd_normalize_descriptor(&mut descriptor.view_mut(), 0.5);
let norm_sq: f64 = descriptor.iter().map(|&x| x * x).sum();
let norm = norm_sq.sqrt();
assert!(
(norm - 1.0).abs() < 1e-6,
"Descriptor should be L2 normalized"
);
}
#[test]
fn test_simd_normalize_descriptor_clamping() {
use scirs2_core::ndarray::Array1;
let mut descriptor = Array1::from_vec(vec![10.0, 10.0, 1.0, 1.0]);
let original = descriptor.clone();
simd_image::simd_normalize_descriptor(&mut descriptor.view_mut(), 0.2);
let mut diff_sq = 0.0;
for i in 0..original.len() {
let orig_norm_sq: f64 = original.iter().map(|&x| x * x).sum();
let orig_normalized = original[i] / orig_norm_sq.sqrt();
diff_sq += (descriptor[i] - orig_normalized).powi(2);
}
assert!(
diff_sq > 0.01,
"Clamping should modify the descriptor distribution"
);
let norm_sq: f64 = descriptor.iter().map(|&x| x * x).sum();
let norm = norm_sq.sqrt();
assert!(
(norm - 1.0).abs() < 1e-6,
"Should be L2 normalized after clamping"
);
}
#[test]
fn test_simd_normalize_descriptor_empty() {
use scirs2_core::ndarray::Array1;
let mut descriptor = Array1::from_vec(vec![]);
simd_image::simd_normalize_descriptor(&mut descriptor.view_mut(), 0.2);
}
#[test]
fn test_simd_normalize_descriptor_symmetric() {
use scirs2_core::ndarray::Array1;
let mut descriptor = Array1::from_vec(vec![1.0, -1.0, 1.0, -1.0]);
simd_image::simd_normalize_descriptor(&mut descriptor.view_mut(), 0.3);
let norm_sq: f64 = descriptor.iter().map(|&x| x * x).sum();
let norm = norm_sq.sqrt();
assert!((norm - 1.0).abs() < 1e-6, "Should be L2 normalized");
assert!(
(descriptor[0].abs() - descriptor[1].abs()).abs() < 1e-6,
"Symmetry should be preserved"
);
assert!(
(descriptor[2].abs() - descriptor[3].abs()).abs() < 1e-6,
"Symmetry should be preserved"
);
}
}
#[test]
fn test_haar_x_response_vertical_edge() {
let image = Array2::from_shape_vec(
(4, 4),
vec![
0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, 1.0,
],
)
.expect("operation should succeed");
let integral = simd_image::simd_compute_integral_image(&image.view());
let response = simd_image::simd_haar_x_response(&integral.view(), 2, 2, 2);
assert!(
response > 0.0,
"Should detect right edge (positive response)"
);
assert!(
(response - 2.0).abs() < 0.1,
"Response should be ~2.0, got {}",
response
);
}
#[test]
fn test_haar_y_response_horizontal_edge() {
let image = Array2::from_shape_vec(
(4, 4),
vec![
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
],
)
.expect("operation should succeed");
let integral = simd_image::simd_compute_integral_image(&image.view());
let response = simd_image::simd_haar_y_response(&integral.view(), 2, 2, 2);
assert!(
response > 0.0,
"Should detect bottom edge (positive response)"
);
assert!(
(response - 2.0).abs() < 0.1,
"Response magnitude should be ~2.0"
);
}
#[test]
fn test_haar_x_response_uniform() {
let image = Array2::from_elem((6, 6), 1.0);
let integral = simd_image::simd_compute_integral_image(&image.view());
let response = simd_image::simd_haar_x_response(&integral.view(), 3, 3, 4);
assert_eq!(
response, 0.0,
"Uniform image should have zero Haar response"
);
}
#[test]
fn test_haar_y_response_uniform() {
let image = Array2::from_elem((6, 6), 1.0);
let integral = simd_image::simd_compute_integral_image(&image.view());
let response = simd_image::simd_haar_y_response(&integral.view(), 3, 3, 4);
assert_eq!(
response, 0.0,
"Uniform image should have zero Haar response"
);
}
#[test]
fn test_haar_responses_bounds_checking() {
let image = Array2::from_elem((4, 4), 1.0);
let integral = simd_image::simd_compute_integral_image(&image.view());
let response_x = simd_image::simd_haar_x_response(&integral.view(), 0, 0, 4);
let response_y = simd_image::simd_haar_y_response(&integral.view(), 0, 0, 4);
assert_eq!(response_x, 0.0, "Out of bounds should return 0");
assert_eq!(response_y, 0.0, "Out of bounds should return 0");
}
#[test]
fn test_integral_image_computation() {
let image = Array2::from_shape_vec((3, 3), vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0])
.expect("operation should succeed");
let integral = simd_image::simd_compute_integral_image(&image.view());
assert_eq!(integral[[0, 0]], 1.0);
assert_eq!(integral[[1, 1]], 12.0);
assert_eq!(integral[[2, 2]], 45.0);
}
#[test]
fn test_haar_x_response_negative_edge() {
let image = Array2::from_shape_vec(
(4, 4),
vec![
1.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0,
],
)
.expect("operation should succeed");
let integral = simd_image::simd_compute_integral_image(&image.view());
let response = simd_image::simd_haar_x_response(&integral.view(), 2, 2, 2);
assert!(
response < 0.0,
"Should detect left edge (negative response)"
);
assert!(
(response + 2.0).abs() < 0.1,
"Response magnitude should be ~-2.0"
);
}
#[test]
fn test_haar_y_response_negative_edge() {
let image = Array2::from_shape_vec(
(4, 4),
vec![
1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
],
)
.expect("operation should succeed");
let integral = simd_image::simd_compute_integral_image(&image.view());
let response = simd_image::simd_haar_y_response(&integral.view(), 2, 2, 2);
assert!(response < 0.0, "Should detect top edge (negative response)");
assert!(
(response + 2.0).abs() < 0.1,
"Response magnitude should be ~-2.0"
);
}