#![expect(missing_docs)]
use ebcc::{ebcc_decode_into, ebcc_encode, EBCCConfig, EBCCResult};
use ndarray::Array;
use ::{ebcc_sys as _, thiserror as _};
#[test]
fn test_basic_compression_roundtrip() -> EBCCResult<()> {
let data = Array::from_shape_simple_fn((1, 32, 32), || 1.0);
let config = EBCCConfig::new();
let compressed = ebcc_encode(data.view(), &config)?;
let mut decompressed = Array::zeros(data.dim());
ebcc_decode_into(&compressed, decompressed.view_mut())?;
let original_size = data.len() * std::mem::size_of::<f32>();
assert!(
compressed.len() < original_size,
"Compressed size ({}) should be less than original size ({})",
compressed.len(),
original_size
);
Ok(())
}
#[test]
fn test_jpeg2000_only_compression() -> EBCCResult<()> {
let mut i: i16 = 0;
let data = Array::from_shape_simple_fn((1, 32, 32), || {
let x = f32::from(i) * 0.1;
i += 1;
x
});
let config = EBCCConfig::jpeg2000_only(10.0);
let compressed = ebcc_encode(data.view(), &config)?;
let mut decompressed = Array::zeros(data.dim());
ebcc_decode_into(&compressed, decompressed.view_mut())?;
let max_error = data
.iter()
.zip(decompressed.iter())
.map(|(&orig, &decomp)| (orig - decomp).abs())
.fold(0.0f32, f32::max);
let data_range = data.iter().fold(f32::NEG_INFINITY, |a, &b| a.max(b))
- data.iter().fold(f32::INFINITY, |a, &b| a.min(b));
assert!(
max_error < data_range * 0.1,
"Max error {max_error} exceeds 10% of data range {data_range}",
);
Ok(())
}
#[test]
fn test_max_error_bounded_compression() -> EBCCResult<()> {
let mut i: i16 = 0;
let data = Array::from_shape_simple_fn((1, 32, 32), || {
let x = f32::from(i) * 0.1;
i += 1;
x
});
let config_error = 0.1;
let config = EBCCConfig::max_absolute_error_bounded(15.0, config_error);
let compressed = ebcc_encode(data.view(), &config)?;
let mut decompressed = Array::zeros(data.dim());
ebcc_decode_into(&compressed, decompressed.view_mut())?;
let max_error = data
.iter()
.zip(decompressed.iter())
.map(|(&orig, &decomp)| (orig - decomp).abs())
.fold(0.0f32, f32::max);
assert!(
max_error <= (config_error + 1e-6),
"Max error {max_error} exceeds error bound {config_error}",
);
Ok(())
}
#[test]
fn test_relative_error_bounded_compression() -> EBCCResult<()> {
let mut i: i16 = 0;
let data = Array::from_shape_simple_fn((1, 32, 32), || {
let x = f32::from(i) * 0.1;
i += 1;
x
});
let config_error = 0.001;
let config = EBCCConfig::relative_error_bounded(15.0, config_error);
let compressed = ebcc_encode(data.view(), &config)?;
let mut decompressed = Array::zeros(data.dim());
ebcc_decode_into(&compressed, decompressed.view_mut())?;
let max_error = data
.iter()
.zip(decompressed.iter())
.map(|(&orig, &decomp)| (orig - decomp).abs())
.fold(0.0f32, f32::max);
let data_range = data.iter().fold(f32::NEG_INFINITY, |a, &b| a.max(b))
- data.iter().fold(f32::INFINITY, |a, &b| a.min(b));
assert!(
max_error < data_range * 0.1,
"Max error {max_error} exceeds 10% of data range {data_range}",
);
Ok(())
}
#[test]
fn test_constant_field() -> EBCCResult<()> {
let data = Array::from_shape_simple_fn((1, 32, 32), || 42.0);
let config = EBCCConfig::new();
let compressed = ebcc_encode(data.view(), &config)?;
let mut decompressed = Array::zeros(data.dim());
ebcc_decode_into(&compressed, decompressed.view_mut())?;
for (&orig, &decomp) in data.iter().zip(decompressed.iter()) {
assert!(
(orig - decomp).abs() < 1e-6,
"Constant field not preserved: {orig} vs {decomp}",
);
}
let original_size = data.len() * std::mem::size_of::<f32>();
#[expect(clippy::cast_precision_loss)]
let compression_ratio = original_size as f64 / compressed.len() as f64;
println!(
"Original size: {original_size} bytes, Compressed size: {} bytes, Ratio: {compression_ratio:.2}:1",
compressed.len(),
);
assert!(
compression_ratio >= 2.0,
"Constant field should compress to at least 2:1 ratio, got {compression_ratio:.2}:1",
);
Ok(())
}
#[test]
fn test_large_array() -> EBCCResult<()> {
let height = 721; let width = 1440;
let frames = 1;
#[expect(clippy::suboptimal_flops, clippy::cast_precision_loss)]
let data = Array::from_shape_fn((frames, height, width), |(_k, i, j)| {
let lat = -90.0 + (i as f32 / height as f32) * 180.0;
let lon = -180.0 + (j as f32 / width as f32) * 360.0;
#[allow(clippy::let_and_return)]
let temp = 273.15 + 30.0 * (1.0 - lat.abs() / 90.0) + 5.0 * (lon / 180.0).sin();
temp
});
let config_error = 0.1;
let config = EBCCConfig::max_absolute_error_bounded(20.0, config_error);
let compressed = ebcc_encode(data.view(), &config)?;
let mut decompressed = Array::zeros(data.dim());
ebcc_decode_into(&compressed, decompressed.view_mut())?;
let original_size = data.len() * std::mem::size_of::<f32>();
#[expect(clippy::cast_precision_loss)]
let compression_ratio = original_size as f64 / compressed.len() as f64;
assert!(
compression_ratio > 5.0,
"Compression ratio {compression_ratio} should be at least 5:1",
);
let max_error = data
.iter()
.zip(decompressed.iter())
.map(|(&orig, &decomp)| (orig - decomp).abs())
.fold(0.0f32, f32::max);
assert!(
max_error <= (config_error + 1e-6),
"Max error {max_error} exceeds error bound {config_error}",
);
Ok(())
}
#[test]
fn test_error_bounds() -> EBCCResult<()> {
let mut i: i16 = 0;
let data = Array::from_shape_simple_fn((1, 32, 32), || {
let x = (f32::from(i) * 0.1).sin() * 100.0;
i += 1;
x
});
let error_bounds = [0.01, 0.1, 1.0, 5.0];
for error_bound in error_bounds {
let config = EBCCConfig::max_absolute_error_bounded(15.0, error_bound);
let compressed = ebcc_encode(data.view(), &config)?;
let mut decompressed = Array::zeros(data.dim());
ebcc_decode_into(&compressed, decompressed.view_mut())?;
let max_error = data
.iter()
.zip(decompressed.iter())
.map(|(&orig, &decomp)| (orig - decomp).abs())
.fold(0.0f32, f32::max);
let tolerance = error_bound * (1.0 + 1e-4);
assert!(
max_error <= error_bound + tolerance,
"Max error {max_error} exceeds bound {error_bound} + tolerance {tolerance}",
);
}
Ok(())
}
#[test]
#[expect(clippy::indexing_slicing)]
fn test_invalid_inputs() {
let mut data_with_nan = Array::from_shape_simple_fn((1, 32, 32), || 1.0);
data_with_nan[(0, 0, 1)] = f32::NAN;
let config = EBCCConfig::new();
let result = ebcc_encode(data_with_nan.view(), &config);
assert!(result.is_err());
let mut data_with_inf = Array::from_shape_simple_fn((1, 32, 32), || 1.0);
data_with_inf[(0, 0, 1)] = f32::INFINITY;
let result = ebcc_encode(data_with_inf.view(), &config);
assert!(result.is_err());
let result = ebcc_decode_into(&[], Array::zeros((0, 0, 0)).view_mut());
assert!(result.is_err());
}
#[test]
fn test_config_validation() {
let valid_config = EBCCConfig::new();
assert!(valid_config.validate().is_ok());
let mut invalid_config = EBCCConfig::new();
invalid_config.base_cr = -1.0; assert!(invalid_config.validate().is_err());
invalid_config = EBCCConfig::max_absolute_error_bounded(10.0, -0.1); assert!(invalid_config.validate().is_err());
invalid_config = EBCCConfig::new(); assert!(ebcc_encode(Array::zeros((0, 32, 32)).view(), &invalid_config).is_err());
}