#[cfg(not(feature = "std"))]
extern crate alloc;
#[cfg(not(feature = "std"))]
use alloc::{format, vec, vec::Vec};
use crate::convert::TryToUsize;
use crate::error::FormatError;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ZfpElementType {
F32,
F64,
I32,
I64,
}
const BLOCK_SIZE: usize = 4;
const EBITS_F32: u32 = 8;
const EBITS_F64: u32 = 11;
const NBMASK_U32: u32 = 0xAAAA_AAAA;
const NBMASK_U64: u64 = 0xAAAA_AAAA_AAAA_AAAA;
struct BitWriter {
buf: Vec<u8>,
word: u64,
bits: u32,
}
impl BitWriter {
fn new() -> Self {
Self {
buf: Vec::new(),
word: 0,
bits: 0,
}
}
fn with_capacity(bytes: usize) -> Self {
Self {
buf: Vec::with_capacity(bytes),
word: 0,
bits: 0,
}
}
#[inline]
fn write(&mut self, n: u32, value: u64) {
debug_assert!(n <= 64);
if n == 0 {
return;
}
let masked = if n == 64 {
value
} else {
value & ((1u64 << n) - 1)
};
self.word |= masked << self.bits;
self.bits += n;
if self.bits >= 64 {
self.buf.extend_from_slice(&self.word.to_le_bytes());
self.bits -= 64;
self.word = if self.bits > 0 {
masked >> (n - self.bits)
} else {
0
};
}
}
#[inline]
fn write_bit(&mut self, bit: bool) {
self.word |= (bit as u64) << self.bits;
self.bits += 1;
if self.bits == 64 {
self.buf.extend_from_slice(&self.word.to_le_bytes());
self.word = 0;
self.bits = 0;
}
}
fn finish(mut self) -> Vec<u8> {
if self.bits > 0 {
self.buf.extend_from_slice(&self.word.to_le_bytes());
}
self.buf
}
fn position(&self) -> usize {
self.buf.len() * 8 + self.bits as usize
}
}
struct BitReader<'a> {
data: &'a [u8],
byte_pos: usize,
word: u64,
bits: u32,
}
impl<'a> BitReader<'a> {
fn new(data: &'a [u8]) -> Self {
let mut r = Self {
data,
byte_pos: 0,
word: 0,
bits: 0,
};
r.refill();
r
}
fn refill(&mut self) {
if self.byte_pos + 8 <= self.data.len() {
let bytes: [u8; 8] = self.data[self.byte_pos..self.byte_pos + 8]
.try_into()
.unwrap();
self.word = u64::from_le_bytes(bytes);
self.byte_pos += 8;
self.bits = 64;
} else {
let mut bytes = [0u8; 8];
let avail = self.data.len().saturating_sub(self.byte_pos);
bytes[..avail].copy_from_slice(&self.data[self.byte_pos..self.byte_pos + avail]);
self.word = u64::from_le_bytes(bytes);
self.byte_pos += 8;
self.bits = 64;
}
}
#[inline]
fn read(&mut self, n: u32) -> u64 {
debug_assert!(n <= 64);
if n == 0 {
return 0;
}
if n <= self.bits {
let mask = if n == 64 { u64::MAX } else { (1u64 << n) - 1 };
let val = self.word & mask;
self.word >>= n;
self.bits -= n;
if self.bits == 0 {
self.refill();
}
val
} else {
let lo_bits = self.bits;
let lo = self.word; self.refill();
let hi_bits = n - lo_bits;
let hi_mask = if hi_bits == 64 {
u64::MAX
} else {
(1u64 << hi_bits) - 1
};
let hi = self.word & hi_mask;
self.word >>= hi_bits;
self.bits -= hi_bits;
if self.bits == 0 {
self.refill();
}
lo | (hi << lo_bits)
}
}
#[inline]
fn read_bit(&mut self) -> bool {
self.read(1) != 0
}
}
const EBIAS_F32: i32 = 127;
macro_rules! impl_pad_partial {
($name:ident, $scalar:ty, $zero:expr) => {
fn $name(block: &mut [$scalar; BLOCK_SIZE], n: usize) {
match n {
0 => {
block[0] = $zero;
block[1] = $zero;
block[2] = $zero;
block[3] = $zero;
}
1 => {
block[1] = block[0];
block[2] = block[1];
block[3] = block[0];
}
2 => {
block[2] = block[1];
block[3] = block[0];
}
3 => {
block[3] = block[0];
}
_ => {}
}
}
};
}
impl_pad_partial!(pad_partial_block_f32, f32, 0.0f32);
impl_pad_partial!(pad_partial_block_f64, f64, 0.0f64);
impl_pad_partial!(pad_partial_block_i32, i32, 0i32);
impl_pad_partial!(pad_partial_block_i64, i64, 0i64);
macro_rules! impl_pad_strided {
($name:ident, $scalar:ty, $zero:expr) => {
fn $name(block: &mut [$scalar], base: usize, n: usize, stride: usize) {
match n {
0 => {
block[base] = $zero;
block[base + stride] = $zero;
block[base + 2 * stride] = $zero;
block[base + 3 * stride] = $zero;
}
1 => {
block[base + stride] = block[base];
block[base + 2 * stride] = block[base + stride];
block[base + 3 * stride] = block[base];
}
2 => {
block[base + 2 * stride] = block[base + stride];
block[base + 3 * stride] = block[base];
}
3 => {
block[base + 3 * stride] = block[base];
}
_ => {}
}
}
};
}
impl_pad_strided!(pad_strided_f32, f32, 0.0f32);
impl_pad_strided!(pad_strided_f64, f64, 0.0f64);
impl_pad_strided!(pad_strided_i32, i32, 0i32);
impl_pad_strided!(pad_strided_i64, i64, 0i64);
fn fwd_cast_f32(vals: &[f32; BLOCK_SIZE]) -> (u32, [i32; BLOCK_SIZE]) {
let mut out = [0i32; BLOCK_SIZE];
let e = fwd_cast_f32_slice(vals, &mut out);
(e, out)
}
fn fwd_cast_f32_slice(vals: &[f32], coeffs: &mut [i32]) -> u32 {
debug_assert_eq!(vals.len(), coeffs.len());
let mut ieee_max: i32 = 0;
for &v in vals {
let e = ((v.to_bits() >> 23) & 0xFF) as i32;
if e > ieee_max {
ieee_max = e;
}
}
if ieee_max == 0 {
for c in coeffs.iter_mut() {
*c = 0;
}
return 0;
}
let emax_biased = (ieee_max + 1) as u32;
let scale_exp = 30 + EBIAS_F32 - 1 - ieee_max;
let scale = pow2_f64(scale_exp);
for i in 0..vals.len() {
coeffs[i] = (vals[i] as f64 * scale) as i32;
}
emax_biased
}
fn inv_cast_f32(emax_biased: u32, coeffs: &[i32; BLOCK_SIZE]) -> [f32; BLOCK_SIZE] {
let mut result = [0.0f32; BLOCK_SIZE];
inv_cast_f32_slice(emax_biased, coeffs, &mut result);
result
}
fn inv_cast_f32_slice(emax_biased: u32, coeffs: &[i32], out: &mut [f32]) {
debug_assert_eq!(coeffs.len(), out.len());
for v in out.iter_mut() {
*v = 0.0;
}
if emax_biased == 0 {
return;
}
let exp = emax_biased as i32 - EBIAS_F32 - 30;
let scale = pow2_f64(exp);
for (i, &c) in coeffs.iter().enumerate() {
if c == 0 {
continue;
}
out[i] = ((c as f64) * scale) as f32;
}
}
const EBIAS_F64: i32 = 1023;
fn fwd_cast_f64(vals: &[f64; BLOCK_SIZE]) -> (u64, [i64; BLOCK_SIZE]) {
let mut out = [0i64; BLOCK_SIZE];
let e = fwd_cast_f64_slice(vals, &mut out);
(e, out)
}
fn fwd_cast_f64_slice(vals: &[f64], coeffs: &mut [i64]) -> u64 {
debug_assert_eq!(vals.len(), coeffs.len());
let mut ieee_max: i32 = 0;
for &v in vals {
let e = ((v.to_bits() >> 52) & 0x7FF) as i32;
if e > ieee_max {
ieee_max = e;
}
}
if ieee_max == 0 {
for c in coeffs.iter_mut() {
*c = 0;
}
return 0;
}
let emax_biased = (ieee_max + 1) as u64;
let scale = pow2_f64_wide(1084 - ieee_max);
for i in 0..vals.len() {
coeffs[i] = (vals[i] * scale) as i64;
}
emax_biased
}
fn inv_cast_f64(emax_biased: u64, coeffs: &[i64; BLOCK_SIZE]) -> [f64; BLOCK_SIZE] {
let mut result = [0.0f64; BLOCK_SIZE];
inv_cast_f64_slice(emax_biased, coeffs, &mut result);
result
}
fn inv_cast_f64_slice(emax_biased: u64, coeffs: &[i64], out: &mut [f64]) {
debug_assert_eq!(coeffs.len(), out.len());
for v in out.iter_mut() {
*v = 0.0;
}
if emax_biased == 0 {
return;
}
let exp = emax_biased as i32 - EBIAS_F64 - 62;
let scale = pow2_f64_wide(exp);
for (i, &c) in coeffs.iter().enumerate() {
if c == 0 {
continue;
}
out[i] = (c as f64) * scale;
}
}
#[inline]
fn pow2_f64_wide(exp: i32) -> f64 {
if (-1022..=1023).contains(&exp) {
return pow2_f64(exp);
}
let mut remaining = exp;
let mut acc = 1.0f64;
while remaining > 1023 {
acc *= pow2_f64(1023);
remaining -= 1023;
}
while remaining < -1022 {
acc *= pow2_f64(-1022);
remaining += 1022;
}
acc * pow2_f64(remaining)
}
#[inline]
fn pow2_f64(exp: i32) -> f64 {
debug_assert!(
(-1022..=1023).contains(&exp),
"pow2_f64 out of normal range"
);
let biased = (exp + 1023) as u64;
f64::from_bits(biased << 52)
}
macro_rules! impl_lift {
($fwd:ident, $inv:ident, $int:ty) => {
#[allow(clippy::many_single_char_names)]
fn $fwd(p: &mut [$int; BLOCK_SIZE]) {
let mut x = p[0];
let mut y = p[1];
let mut z = p[2];
let mut w = p[3];
x = x.wrapping_add(w);
x >>= 1;
w = w.wrapping_sub(x);
z = z.wrapping_add(y);
z >>= 1;
y = y.wrapping_sub(z);
x = x.wrapping_add(z);
x >>= 1;
z = z.wrapping_sub(x);
w = w.wrapping_add(y);
w >>= 1;
y = y.wrapping_sub(w);
w = w.wrapping_add(y >> 1);
y = y.wrapping_sub(w >> 1);
p[0] = x;
p[1] = y;
p[2] = z;
p[3] = w;
}
#[allow(clippy::many_single_char_names)]
fn $inv(p: &mut [$int; BLOCK_SIZE]) {
let mut x = p[0];
let mut y = p[1];
let mut z = p[2];
let mut w = p[3];
y = y.wrapping_add(w >> 1);
w = w.wrapping_sub(y >> 1);
y = y.wrapping_add(w);
w <<= 1;
w = w.wrapping_sub(y);
z = z.wrapping_add(x);
x <<= 1;
x = x.wrapping_sub(z);
y = y.wrapping_add(z);
z <<= 1;
z = z.wrapping_sub(y);
w = w.wrapping_add(x);
x <<= 1;
x = x.wrapping_sub(w);
p[0] = x;
p[1] = y;
p[2] = z;
p[3] = w;
}
};
}
impl_lift!(fwd_lift_i32, inv_lift_i32, i32);
impl_lift!(fwd_lift_i64, inv_lift_i64, i64);
#[inline]
fn fwd_lift_axis_i32(block: &mut [i32], base: usize, stride: usize) {
let mut v = [
block[base],
block[base + stride],
block[base + 2 * stride],
block[base + 3 * stride],
];
fwd_lift_i32(&mut v);
block[base] = v[0];
block[base + stride] = v[1];
block[base + 2 * stride] = v[2];
block[base + 3 * stride] = v[3];
}
#[inline]
fn inv_lift_axis_i32(block: &mut [i32], base: usize, stride: usize) {
let mut v = [
block[base],
block[base + stride],
block[base + 2 * stride],
block[base + 3 * stride],
];
inv_lift_i32(&mut v);
block[base] = v[0];
block[base + stride] = v[1];
block[base + 2 * stride] = v[2];
block[base + 3 * stride] = v[3];
}
#[inline]
fn fwd_lift_axis_i64(block: &mut [i64], base: usize, stride: usize) {
let mut v = [
block[base],
block[base + stride],
block[base + 2 * stride],
block[base + 3 * stride],
];
fwd_lift_i64(&mut v);
block[base] = v[0];
block[base + stride] = v[1];
block[base + 2 * stride] = v[2];
block[base + 3 * stride] = v[3];
}
#[inline]
fn inv_lift_axis_i64(block: &mut [i64], base: usize, stride: usize) {
let mut v = [
block[base],
block[base + stride],
block[base + 2 * stride],
block[base + 3 * stride],
];
inv_lift_i64(&mut v);
block[base] = v[0];
block[base + stride] = v[1];
block[base + 2 * stride] = v[2];
block[base + 3 * stride] = v[3];
}
macro_rules! impl_nd_xform {
($fwd2:ident, $inv2:ident, $fwd3:ident, $inv3:ident, $fwd4:ident, $inv4:ident,
$fwd_axis:ident, $inv_axis:ident, $int:ty) => {
fn $fwd2(block: &mut [$int; 16]) {
for y in 0..4 {
$fwd_axis(block.as_mut_slice(), 4 * y, 1);
}
for x in 0..4 {
$fwd_axis(block.as_mut_slice(), x, 4);
}
}
fn $inv2(block: &mut [$int; 16]) {
for x in 0..4 {
$inv_axis(block.as_mut_slice(), x, 4);
}
for y in 0..4 {
$inv_axis(block.as_mut_slice(), 4 * y, 1);
}
}
fn $fwd3(block: &mut [$int; 64]) {
for z in 0..4 {
for y in 0..4 {
$fwd_axis(block.as_mut_slice(), 16 * z + 4 * y, 1);
}
}
for z in 0..4 {
for x in 0..4 {
$fwd_axis(block.as_mut_slice(), 16 * z + x, 4);
}
}
for y in 0..4 {
for x in 0..4 {
$fwd_axis(block.as_mut_slice(), 4 * y + x, 16);
}
}
}
fn $inv3(block: &mut [$int; 64]) {
for y in 0..4 {
for x in 0..4 {
$inv_axis(block.as_mut_slice(), 4 * y + x, 16);
}
}
for z in 0..4 {
for x in 0..4 {
$inv_axis(block.as_mut_slice(), 16 * z + x, 4);
}
}
for z in 0..4 {
for y in 0..4 {
$inv_axis(block.as_mut_slice(), 16 * z + 4 * y, 1);
}
}
}
fn $fwd4(block: &mut [$int; 256]) {
for w in 0..4 {
for z in 0..4 {
for y in 0..4 {
$fwd_axis(block.as_mut_slice(), 64 * w + 16 * z + 4 * y, 1);
}
}
}
for w in 0..4 {
for z in 0..4 {
for x in 0..4 {
$fwd_axis(block.as_mut_slice(), 64 * w + 16 * z + x, 4);
}
}
}
for w in 0..4 {
for y in 0..4 {
for x in 0..4 {
$fwd_axis(block.as_mut_slice(), 64 * w + 4 * y + x, 16);
}
}
}
for z in 0..4 {
for y in 0..4 {
for x in 0..4 {
$fwd_axis(block.as_mut_slice(), 16 * z + 4 * y + x, 64);
}
}
}
}
fn $inv4(block: &mut [$int; 256]) {
for z in 0..4 {
for y in 0..4 {
for x in 0..4 {
$inv_axis(block.as_mut_slice(), 16 * z + 4 * y + x, 64);
}
}
}
for w in 0..4 {
for y in 0..4 {
for x in 0..4 {
$inv_axis(block.as_mut_slice(), 64 * w + 4 * y + x, 16);
}
}
}
for w in 0..4 {
for z in 0..4 {
for x in 0..4 {
$inv_axis(block.as_mut_slice(), 64 * w + 16 * z + x, 4);
}
}
}
for w in 0..4 {
for z in 0..4 {
for y in 0..4 {
$inv_axis(block.as_mut_slice(), 64 * w + 16 * z + 4 * y, 1);
}
}
}
}
};
}
impl_nd_xform!(
fwd_xform_i32_2d,
inv_xform_i32_2d,
fwd_xform_i32_3d,
inv_xform_i32_3d,
fwd_xform_i32_4d,
inv_xform_i32_4d,
fwd_lift_axis_i32,
inv_lift_axis_i32,
i32
);
impl_nd_xform!(
fwd_xform_i64_2d,
inv_xform_i64_2d,
fwd_xform_i64_3d,
inv_xform_i64_3d,
fwd_xform_i64_4d,
inv_xform_i64_4d,
fwd_lift_axis_i64,
inv_lift_axis_i64,
i64
);
const PERM_2: [u8; 16] = [0, 1, 4, 5, 2, 8, 6, 9, 3, 12, 10, 7, 13, 11, 14, 15];
const PERM_3: [u8; 64] = [
0, 1, 4, 16, 20, 17, 5, 2, 8, 32, 21, 6, 18, 24, 9, 33, 36, 3, 12, 48, 22, 25, 37, 40, 34, 10,
7, 19, 28, 13, 49, 52, 41, 38, 26, 23, 29, 53, 11, 35, 44, 14, 50, 56, 42, 27, 39, 45, 30, 54,
57, 60, 51, 15, 43, 46, 58, 61, 55, 31, 62, 59, 47, 63,
];
const PERM_4: [u8; 256] = [
0, 1, 4, 16, 64, 5, 80, 17, 68, 65, 20, 2, 8, 32, 128, 84, 81, 69, 21, 6, 18, 66, 24, 72, 9,
96, 33, 36, 129, 132, 144, 3, 12, 48, 192, 85, 82, 70, 22, 73, 25, 88, 37, 100, 97, 148, 145,
133, 10, 160, 34, 136, 130, 40, 7, 19, 67, 28, 76, 13, 112, 49, 52, 193, 196, 208, 86, 89, 101,
149, 161, 137, 41, 134, 38, 164, 26, 152, 146, 104, 98, 74, 83, 71, 23, 77, 29, 92, 53, 116,
113, 212, 209, 197, 11, 35, 131, 44, 140, 14, 176, 50, 56, 194, 200, 224, 90, 165, 102, 153,
150, 105, 168, 162, 138, 42, 87, 93, 117, 213, 27, 75, 99, 39, 135, 147, 108, 45, 141, 156, 30,
78, 177, 180, 54, 114, 120, 57, 198, 210, 216, 201, 225, 228, 15, 240, 51, 204, 195, 60, 169,
166, 154, 106, 91, 103, 151, 109, 157, 94, 181, 118, 121, 214, 217, 229, 163, 139, 43, 142, 46,
172, 58, 184, 178, 232, 226, 202, 241, 205, 61, 199, 55, 244, 31, 220, 211, 124, 115, 79, 170,
167, 155, 107, 158, 110, 173, 122, 185, 182, 233, 230, 218, 95, 245, 119, 221, 215, 125, 242,
206, 62, 203, 59, 248, 47, 236, 227, 188, 179, 143, 171, 174, 186, 234, 246, 222, 126, 219,
123, 249, 111, 237, 231, 189, 183, 159, 252, 243, 207, 63, 175, 250, 187, 238, 235, 190, 253,
247, 223, 127, 254, 251, 239, 191, 255,
];
#[inline]
fn int2uint_i32(x: i32) -> u32 {
((x as u32).wrapping_add(NBMASK_U32)) ^ NBMASK_U32
}
#[inline]
fn uint2int_i32(x: u32) -> i32 {
((x ^ NBMASK_U32).wrapping_sub(NBMASK_U32)) as i32
}
#[inline]
fn int2uint_i64(x: i64) -> u64 {
((x as u64).wrapping_add(NBMASK_U64)) ^ NBMASK_U64
}
#[inline]
fn uint2int_i64(x: u64) -> i64 {
((x ^ NBMASK_U64).wrapping_sub(NBMASK_U64)) as i64
}
macro_rules! impl_few_ints {
($enc:ident, $dec:ident, $uint:ty, $intprec:expr) => {
fn $enc(
w: &mut BitWriter,
maxbits: usize,
maxprec: u32,
data: &[$uint],
size: usize,
) -> usize {
let kmin: u32 = $intprec.saturating_sub(maxprec);
let mut bits = maxbits;
let mut n: usize = 0;
let mut k: u32 = $intprec;
while bits > 0 && k > kmin {
k -= 1;
let mut x: u64 = 0;
for i in 0..size {
x |= ((data[i] >> k) as u64 & 1) << i;
}
let m = n.min(bits);
bits -= m;
if m > 0 {
w.write(m as u32, x);
x >>= m;
}
while bits > 0 && n < size {
bits -= 1;
let group = x != 0;
w.write_bit(group);
if !group {
break;
}
while bits > 0 && n < size - 1 {
bits -= 1;
let bit = (x & 1) != 0;
w.write_bit(bit);
if bit {
break;
}
x >>= 1;
n += 1;
}
x >>= 1;
n += 1;
}
}
maxbits - bits
}
fn $dec(
r: &mut BitReader<'_>,
maxbits: usize,
maxprec: u32,
data: &mut [$uint],
size: usize,
) -> usize {
let kmin: u32 = $intprec.saturating_sub(maxprec);
let mut bits = maxbits;
let mut n: usize = 0;
for v in data.iter_mut().take(size) {
*v = 0;
}
let mut k: u32 = $intprec;
while bits > 0 && k > kmin {
k -= 1;
let m = n.min(bits);
bits -= m;
let mut x: u64 = if m > 0 { r.read(m as u32) } else { 0 };
while bits > 0 && n < size {
bits -= 1;
if r.read_bit() {
while bits > 0 && n < size - 1 {
bits -= 1;
if r.read_bit() {
break;
}
n += 1;
}
x |= 1u64 << n;
} else {
break;
}
n += 1;
}
let mut xx = x;
let mut i = 0;
while xx != 0 {
data[i] |= ((xx & 1) as $uint) << k;
xx >>= 1;
i += 1;
}
}
maxbits - bits
}
};
}
impl_few_ints!(encode_few_ints_u32, decode_few_ints_u32, u32, 32u32);
impl_few_ints!(encode_few_ints_u64, decode_few_ints_u64, u64, 64u32);
macro_rules! impl_many_ints {
($enc:ident, $dec:ident, $uint:ty, $intprec:expr) => {
fn $enc(
w: &mut BitWriter,
maxbits: usize,
maxprec: u32,
data: &[$uint],
size: usize,
) -> usize {
let kmin: u32 = $intprec.saturating_sub(maxprec);
let mut bits = maxbits;
let mut n: usize = 0;
let mut k: u32 = $intprec;
while bits > 0 && k > kmin {
k -= 1;
let m = n.min(bits);
bits -= m;
for i in 0..m {
w.write_bit((data[i] >> k) & 1 == 1);
}
let mut c: usize = 0;
for i in m..size {
if (data[i] >> k) & 1 == 1 {
c += 1;
}
}
while bits > 0 && n < size {
bits -= 1;
let group = c > 0;
w.write_bit(group);
if !group {
break;
}
c -= 1;
while bits > 0 && n < size - 1 {
bits -= 1;
let bit = (data[n] >> k) & 1 == 1;
w.write_bit(bit);
if bit {
break;
}
n += 1;
}
n += 1;
}
}
maxbits - bits
}
fn $dec(
r: &mut BitReader<'_>,
maxbits: usize,
maxprec: u32,
data: &mut [$uint],
size: usize,
) -> usize {
let kmin: u32 = $intprec.saturating_sub(maxprec);
let mut bits = maxbits;
let mut n: usize = 0;
for v in data.iter_mut().take(size) {
*v = 0;
}
let mut k: u32 = $intprec;
while bits > 0 && k > kmin {
k -= 1;
let m = n.min(bits);
bits -= m;
for i in 0..m {
if r.read_bit() {
data[i] |= (1 as $uint) << k;
}
}
while bits > 0 && n < size {
bits -= 1;
if r.read_bit() {
while bits > 0 && n < size - 1 {
bits -= 1;
if r.read_bit() {
break;
}
n += 1;
}
data[n] |= (1 as $uint) << k;
} else {
break;
}
n += 1;
}
}
maxbits - bits
}
};
}
impl_many_ints!(encode_many_ints_u32, decode_many_ints_u32, u32, 32u32);
impl_many_ints!(encode_many_ints_u64, decode_many_ints_u64, u64, 64u32);
fn encode_block_f32(w: &mut BitWriter, vals: &[f32; BLOCK_SIZE], maxbits: usize) {
let start = w.position();
let (emax_biased, mut icoeffs) = fwd_cast_f32(vals);
if emax_biased == 0 {
w.write_bit(false);
let remaining = maxbits.saturating_sub(1);
pad_bits(w, remaining);
return;
}
w.write_bit(true);
w.write(EBITS_F32, u64::from(emax_biased));
fwd_lift_i32(&mut icoeffs);
let mut ucoeffs = [0u32; BLOCK_SIZE];
for i in 0..BLOCK_SIZE {
ucoeffs[i] = int2uint_i32(icoeffs[i]);
}
let header_bits = 1 + EBITS_F32 as usize;
let coeff_bits = maxbits.saturating_sub(header_bits);
encode_few_ints_u32(w, coeff_bits, 32, &ucoeffs, BLOCK_SIZE);
let used = w.position() - start;
let remaining = maxbits.saturating_sub(used);
pad_bits(w, remaining);
}
fn decode_block_f32(
r: &mut BitReader<'_>,
maxbits: usize,
) -> Result<[f32; BLOCK_SIZE], FormatError> {
let nonempty = r.read_bit();
if !nonempty {
let remaining = maxbits.saturating_sub(1);
r.read(remaining.min(64) as u32);
if remaining > 64 {
skip_bits(r, remaining - 64);
}
return Ok([0.0f32; BLOCK_SIZE]);
}
let emax_biased = r.read(EBITS_F32) as u32;
let header_bits = 1 + EBITS_F32 as usize;
let coeff_bits = maxbits.saturating_sub(header_bits);
let mut ucoeffs = [0u32; BLOCK_SIZE];
let bits_consumed = decode_few_ints_u32(r, coeff_bits, 32, &mut ucoeffs, BLOCK_SIZE);
let remaining = coeff_bits.saturating_sub(bits_consumed);
skip_bits(r, remaining);
let mut icoeffs = [0i32; BLOCK_SIZE];
for i in 0..BLOCK_SIZE {
icoeffs[i] = uint2int_i32(ucoeffs[i]);
}
inv_lift_i32(&mut icoeffs);
Ok(inv_cast_f32(emax_biased, &icoeffs))
}
fn encode_block_f64(w: &mut BitWriter, vals: &[f64; BLOCK_SIZE], maxbits: usize) {
let start = w.position();
let (emax_biased, mut icoeffs) = fwd_cast_f64(vals);
if emax_biased == 0 {
w.write_bit(false);
pad_bits(w, maxbits.saturating_sub(1));
return;
}
w.write_bit(true);
w.write(EBITS_F64, emax_biased);
fwd_lift_i64(&mut icoeffs);
let mut ucoeffs = [0u64; BLOCK_SIZE];
for i in 0..BLOCK_SIZE {
ucoeffs[i] = int2uint_i64(icoeffs[i]);
}
let header_bits = 1 + EBITS_F64 as usize;
let coeff_bits = maxbits.saturating_sub(header_bits);
encode_few_ints_u64(w, coeff_bits, 64, &ucoeffs, BLOCK_SIZE);
let used = w.position() - start;
pad_bits(w, maxbits.saturating_sub(used));
}
fn decode_block_f64(
r: &mut BitReader<'_>,
maxbits: usize,
) -> Result<[f64; BLOCK_SIZE], FormatError> {
let nonempty = r.read_bit();
if !nonempty {
let remaining = maxbits.saturating_sub(1);
r.read(remaining.min(64) as u32);
if remaining > 64 {
skip_bits(r, remaining - 64);
}
return Ok([0.0f64; BLOCK_SIZE]);
}
let emax_biased = r.read(EBITS_F64);
let header_bits = 1 + EBITS_F64 as usize;
let coeff_bits = maxbits.saturating_sub(header_bits);
let mut ucoeffs = [0u64; BLOCK_SIZE];
let bits_consumed = decode_few_ints_u64(r, coeff_bits, 64, &mut ucoeffs, BLOCK_SIZE);
skip_bits(r, coeff_bits.saturating_sub(bits_consumed));
let mut icoeffs = [0i64; BLOCK_SIZE];
for i in 0..BLOCK_SIZE {
icoeffs[i] = uint2int_i64(ucoeffs[i]);
}
inv_lift_i64(&mut icoeffs);
Ok(inv_cast_f64(emax_biased, &icoeffs))
}
macro_rules! impl_int_block_codec {
($enc:ident, $dec:ident, $int:ty, $uint:ty, $fwd_lift:ident, $inv_lift:ident,
$to_uint:ident, $to_int:ident, $enc_ints:ident, $dec_ints:ident, $intprec:expr) => {
fn $enc(w: &mut BitWriter, vals: &[$int; BLOCK_SIZE], maxbits: usize) {
let start = w.position();
let mut icoeffs = *vals;
$fwd_lift(&mut icoeffs);
let mut ucoeffs = [0 as $uint; BLOCK_SIZE];
for i in 0..BLOCK_SIZE {
ucoeffs[i] = $to_uint(icoeffs[i]);
}
$enc_ints(w, maxbits, $intprec, &ucoeffs, BLOCK_SIZE);
let used = w.position() - start;
pad_bits(w, maxbits.saturating_sub(used));
}
fn $dec(r: &mut BitReader<'_>, maxbits: usize) -> Result<[$int; BLOCK_SIZE], FormatError> {
let mut ucoeffs = [0 as $uint; BLOCK_SIZE];
let bits_consumed = $dec_ints(r, maxbits, $intprec, &mut ucoeffs, BLOCK_SIZE);
skip_bits(r, maxbits.saturating_sub(bits_consumed));
let mut icoeffs = [0 as $int; BLOCK_SIZE];
for i in 0..BLOCK_SIZE {
icoeffs[i] = $to_int(ucoeffs[i]);
}
$inv_lift(&mut icoeffs);
Ok(icoeffs)
}
};
}
impl_int_block_codec!(
encode_block_i32,
decode_block_i32,
i32,
u32,
fwd_lift_i32,
inv_lift_i32,
int2uint_i32,
uint2int_i32,
encode_few_ints_u32,
decode_few_ints_u32,
32u32
);
impl_int_block_codec!(
encode_block_i64,
decode_block_i64,
i64,
u64,
fwd_lift_i64,
inv_lift_i64,
int2uint_i64,
uint2int_i64,
encode_few_ints_u64,
decode_few_ints_u64,
64u32
);
macro_rules! impl_float_block_nd {
($enc:ident, $dec:ident, $bs:expr, $scalar:ty, $int:ty, $uint:ty, $zero:expr,
$fwd_cast_slice:ident, $inv_cast_slice:ident,
$fwd_xform:ident, $inv_xform:ident,
$int2uint:ident, $uint2int:ident, $perm:ident,
$enc_ints:ident, $dec_ints:ident, $intprec:expr,
$ebits:expr, $emax_ty:ty) => {
fn $enc(w: &mut BitWriter, block: &[$scalar; $bs], maxbits: usize) {
let start = w.position();
let mut icoeffs = [0 as $int; $bs];
let emax_biased = $fwd_cast_slice(block, &mut icoeffs);
if emax_biased == 0 {
w.write_bit(false);
pad_bits(w, maxbits.saturating_sub(1));
return;
}
w.write_bit(true);
w.write($ebits, emax_biased as u64);
$fwd_xform(&mut icoeffs);
let mut ucoeffs = [0 as $uint; $bs];
for i in 0..$bs {
ucoeffs[i] = $int2uint(icoeffs[$perm[i] as usize]);
}
let header_bits = 1 + ($ebits as usize);
let coeff_bits = maxbits.saturating_sub(header_bits);
$enc_ints(w, coeff_bits, $intprec, &ucoeffs, $bs);
let used = w.position() - start;
pad_bits(w, maxbits.saturating_sub(used));
}
fn $dec(r: &mut BitReader<'_>, maxbits: usize) -> Result<[$scalar; $bs], FormatError> {
let nonempty = r.read_bit();
if !nonempty {
let remaining = maxbits.saturating_sub(1);
skip_bits(r, remaining);
return Ok([$zero; $bs]);
}
let emax_biased: $emax_ty = r.read($ebits) as $emax_ty;
let header_bits = 1 + ($ebits as usize);
let coeff_bits = maxbits.saturating_sub(header_bits);
let mut ucoeffs = [0 as $uint; $bs];
let bits_consumed = $dec_ints(r, coeff_bits, $intprec, &mut ucoeffs, $bs);
skip_bits(r, coeff_bits.saturating_sub(bits_consumed));
let mut icoeffs = [0 as $int; $bs];
for i in 0..$bs {
icoeffs[$perm[i] as usize] = $uint2int(ucoeffs[i]);
}
$inv_xform(&mut icoeffs);
let mut out = [$zero; $bs];
$inv_cast_slice(emax_biased, &icoeffs, &mut out);
Ok(out)
}
};
}
macro_rules! impl_int_block_nd {
($enc:ident, $dec:ident, $bs:expr, $int:ty, $uint:ty, $zero:expr,
$fwd_xform:ident, $inv_xform:ident,
$int2uint:ident, $uint2int:ident, $perm:ident,
$enc_ints:ident, $dec_ints:ident, $intprec:expr) => {
fn $enc(w: &mut BitWriter, block: &[$int; $bs], maxbits: usize) {
let start = w.position();
let mut icoeffs = *block;
$fwd_xform(&mut icoeffs);
let mut ucoeffs = [0 as $uint; $bs];
for i in 0..$bs {
ucoeffs[i] = $int2uint(icoeffs[$perm[i] as usize]);
}
$enc_ints(w, maxbits, $intprec, &ucoeffs, $bs);
let used = w.position() - start;
pad_bits(w, maxbits.saturating_sub(used));
}
fn $dec(r: &mut BitReader<'_>, maxbits: usize) -> Result<[$int; $bs], FormatError> {
let mut ucoeffs = [0 as $uint; $bs];
let bits_consumed = $dec_ints(r, maxbits, $intprec, &mut ucoeffs, $bs);
skip_bits(r, maxbits.saturating_sub(bits_consumed));
let mut icoeffs = [$zero; $bs];
for i in 0..$bs {
icoeffs[$perm[i] as usize] = $uint2int(ucoeffs[i]);
}
$inv_xform(&mut icoeffs);
Ok(icoeffs)
}
};
}
impl_float_block_nd!(
encode_block_f32_2d,
decode_block_f32_2d,
16,
f32,
i32,
u32,
0.0f32,
fwd_cast_f32_slice,
inv_cast_f32_slice,
fwd_xform_i32_2d,
inv_xform_i32_2d,
int2uint_i32,
uint2int_i32,
PERM_2,
encode_few_ints_u32,
decode_few_ints_u32,
32u32,
EBITS_F32,
u32
);
impl_float_block_nd!(
encode_block_f64_2d,
decode_block_f64_2d,
16,
f64,
i64,
u64,
0.0f64,
fwd_cast_f64_slice,
inv_cast_f64_slice,
fwd_xform_i64_2d,
inv_xform_i64_2d,
int2uint_i64,
uint2int_i64,
PERM_2,
encode_few_ints_u64,
decode_few_ints_u64,
64u32,
EBITS_F64,
u64
);
impl_int_block_nd!(
encode_block_i32_2d,
decode_block_i32_2d,
16,
i32,
u32,
0i32,
fwd_xform_i32_2d,
inv_xform_i32_2d,
int2uint_i32,
uint2int_i32,
PERM_2,
encode_few_ints_u32,
decode_few_ints_u32,
32u32
);
impl_int_block_nd!(
encode_block_i64_2d,
decode_block_i64_2d,
16,
i64,
u64,
0i64,
fwd_xform_i64_2d,
inv_xform_i64_2d,
int2uint_i64,
uint2int_i64,
PERM_2,
encode_few_ints_u64,
decode_few_ints_u64,
64u32
);
impl_float_block_nd!(
encode_block_f32_3d,
decode_block_f32_3d,
64,
f32,
i32,
u32,
0.0f32,
fwd_cast_f32_slice,
inv_cast_f32_slice,
fwd_xform_i32_3d,
inv_xform_i32_3d,
int2uint_i32,
uint2int_i32,
PERM_3,
encode_few_ints_u32,
decode_few_ints_u32,
32u32,
EBITS_F32,
u32
);
impl_float_block_nd!(
encode_block_f64_3d,
decode_block_f64_3d,
64,
f64,
i64,
u64,
0.0f64,
fwd_cast_f64_slice,
inv_cast_f64_slice,
fwd_xform_i64_3d,
inv_xform_i64_3d,
int2uint_i64,
uint2int_i64,
PERM_3,
encode_few_ints_u64,
decode_few_ints_u64,
64u32,
EBITS_F64,
u64
);
impl_int_block_nd!(
encode_block_i32_3d,
decode_block_i32_3d,
64,
i32,
u32,
0i32,
fwd_xform_i32_3d,
inv_xform_i32_3d,
int2uint_i32,
uint2int_i32,
PERM_3,
encode_few_ints_u32,
decode_few_ints_u32,
32u32
);
impl_int_block_nd!(
encode_block_i64_3d,
decode_block_i64_3d,
64,
i64,
u64,
0i64,
fwd_xform_i64_3d,
inv_xform_i64_3d,
int2uint_i64,
uint2int_i64,
PERM_3,
encode_few_ints_u64,
decode_few_ints_u64,
64u32
);
impl_float_block_nd!(
encode_block_f32_4d,
decode_block_f32_4d,
256,
f32,
i32,
u32,
0.0f32,
fwd_cast_f32_slice,
inv_cast_f32_slice,
fwd_xform_i32_4d,
inv_xform_i32_4d,
int2uint_i32,
uint2int_i32,
PERM_4,
encode_many_ints_u32,
decode_many_ints_u32,
32u32,
EBITS_F32,
u32
);
impl_float_block_nd!(
encode_block_f64_4d,
decode_block_f64_4d,
256,
f64,
i64,
u64,
0.0f64,
fwd_cast_f64_slice,
inv_cast_f64_slice,
fwd_xform_i64_4d,
inv_xform_i64_4d,
int2uint_i64,
uint2int_i64,
PERM_4,
encode_many_ints_u64,
decode_many_ints_u64,
64u32,
EBITS_F64,
u64
);
impl_int_block_nd!(
encode_block_i32_4d,
decode_block_i32_4d,
256,
i32,
u32,
0i32,
fwd_xform_i32_4d,
inv_xform_i32_4d,
int2uint_i32,
uint2int_i32,
PERM_4,
encode_many_ints_u32,
decode_many_ints_u32,
32u32
);
impl_int_block_nd!(
encode_block_i64_4d,
decode_block_i64_4d,
256,
i64,
u64,
0i64,
fwd_xform_i64_4d,
inv_xform_i64_4d,
int2uint_i64,
uint2int_i64,
PERM_4,
encode_many_ints_u64,
decode_many_ints_u64,
64u32
);
fn pad_bits(w: &mut BitWriter, n: usize) {
let mut remaining = n;
while remaining >= 64 {
w.write(64, 0);
remaining -= 64;
}
if remaining > 0 {
w.write(remaining as u32, 0);
}
}
fn skip_bits(r: &mut BitReader<'_>, n: usize) {
let mut remaining = n;
while remaining >= 64 {
r.read(64);
remaining -= 64;
}
if remaining > 0 {
r.read(remaining as u32);
}
}
#[inline]
fn f32_from_le(b: [u8; 4]) -> f32 {
f32::from_le_bytes(b)
}
#[inline]
fn f64_from_le(b: [u8; 8]) -> f64 {
f64::from_le_bytes(b)
}
#[inline]
fn i32_from_le(b: [u8; 4]) -> i32 {
i32::from_le_bytes(b)
}
#[inline]
fn i64_from_le(b: [u8; 8]) -> i64 {
i64::from_le_bytes(b)
}
macro_rules! impl_codec {
(
$mod_name:ident, $scalar:ty, $zero_s:expr, $esz:expr, $from_le:path,
$pad1:ident, $pad_s:ident,
$enc1:ident, $dec1:ident,
$enc2:ident, $dec2:ident,
$enc3:ident, $dec3:ident,
$enc4:ident, $dec4:ident
) => {
pub(crate) mod $mod_name {
use super::*;
const SCALAR_BITS: usize = $esz * 8;
fn validate_rate(rate: f64) -> Result<(), FormatError> {
if !rate.is_finite() || rate <= 0.0 || rate > SCALAR_BITS as f64 {
return Err(FormatError::FilterError(format!(
"ZFP: rate must be in (0, {}]; got {}",
SCALAR_BITS, rate
)));
}
Ok(())
}
pub fn compress(
data: &[u8],
dims: &[usize],
rate: f64,
) -> Result<Vec<u8>, FormatError> {
if !matches!(dims.len(), 1..=4) {
return Err(FormatError::FilterError("ZFP: only 1D-4D supported".into()));
}
validate_rate(rate)?;
let expected = dims.iter().product::<usize>() * $esz;
if data.len() != expected {
return Err(FormatError::FilterError(format!(
"ZFP: data length {} does not match dims product × element size ({})",
data.len(),
expected,
)));
}
match dims.len() {
1 => compress_1d(data, dims[0], rate),
2 => compress_2d(data, dims[0], dims[1], rate),
3 => compress_3d(data, dims[0], dims[1], dims[2], rate),
4 => compress_4d(data, dims[0], dims[1], dims[2], dims[3], rate),
_ => unreachable!(),
}
}
pub fn decompress(
compressed: &[u8],
dims: &[usize],
rate: f64,
) -> Result<Vec<u8>, FormatError> {
if !matches!(dims.len(), 1..=4) {
return Err(FormatError::FilterError("ZFP: only 1D-4D supported".into()));
}
validate_rate(rate)?;
match dims.len() {
1 => decompress_1d(compressed, dims[0], rate),
2 => decompress_2d(compressed, dims[0], dims[1], rate),
3 => decompress_3d(compressed, dims[0], dims[1], dims[2], rate),
4 => decompress_4d(compressed, dims[0], dims[1], dims[2], dims[3], rate),
_ => unreachable!(),
}
}
fn compress_1d(data: &[u8], n: usize, rate: f64) -> Result<Vec<u8>, FormatError> {
let maxbits = (rate * 4.0) as usize;
let total_bits = n.div_ceil(4) * maxbits;
let mut w = BitWriter::with_capacity(total_bits.div_ceil(8) + 8);
let mut i = 0;
while i < n {
let mut block = [$zero_s; 4];
let nr = (n - i).min(4);
for j in 0..nr {
let off = (i + j) * $esz;
let mut buf = [0u8; $esz];
buf.copy_from_slice(&data[off..off + $esz]);
block[j] = $from_le(buf);
}
$pad1(&mut block, nr);
$enc1(&mut w, &block, maxbits);
i += 4;
}
let mut out = w.finish();
out.truncate(total_bits.div_ceil(8));
Ok(out)
}
fn decompress_1d(
compressed: &[u8],
n: usize,
rate: f64,
) -> Result<Vec<u8>, FormatError> {
let maxbits = (rate * 4.0) as usize;
let mut r = BitReader::new(compressed);
let mut output = Vec::with_capacity(n * $esz);
let mut i = 0;
while i < n {
let block = $dec1(&mut r, maxbits)?;
let c = (n - i).min(4);
for j in 0..c {
output.extend_from_slice(&block[j].to_le_bytes());
}
i += 4;
}
Ok(output)
}
fn compress_2d(
data: &[u8],
n1: usize,
n0: usize,
rate: f64,
) -> Result<Vec<u8>, FormatError> {
let maxbits = (rate * 16.0) as usize;
let nb1 = n1.div_ceil(4);
let nb0 = n0.div_ceil(4);
let total_bits = nb1 * nb0 * maxbits;
let mut w = BitWriter::with_capacity(total_bits.div_ceil(8) + 8);
for b1 in 0..nb1 {
for b0 in 0..nb0 {
let y0 = b1 * 4;
let x0 = b0 * 4;
let ry = (n1 - y0).min(4);
let rx = (n0 - x0).min(4);
let mut block = [$zero_s; 16];
for y in 0..ry {
for x in 0..rx {
let src = (y0 + y) * n0 + (x0 + x);
let off = src * $esz;
let mut buf = [0u8; $esz];
buf.copy_from_slice(&data[off..off + $esz]);
block[4 * y + x] = $from_le(buf);
}
$pad_s(block.as_mut_slice(), 4 * y, rx, 1);
}
for x in 0..4 {
$pad_s(block.as_mut_slice(), x, ry, 4);
}
$enc2(&mut w, &block, maxbits);
}
}
let mut out = w.finish();
out.truncate(total_bits.div_ceil(8));
Ok(out)
}
fn decompress_2d(
compressed: &[u8],
n1: usize,
n0: usize,
rate: f64,
) -> Result<Vec<u8>, FormatError> {
let maxbits = (rate * 16.0) as usize;
let nb1 = n1.div_ceil(4);
let nb0 = n0.div_ceil(4);
let mut r = BitReader::new(compressed);
let mut output = vec![0u8; n1 * n0 * $esz];
for b1 in 0..nb1 {
for b0 in 0..nb0 {
let y0 = b1 * 4;
let x0 = b0 * 4;
let block = $dec2(&mut r, maxbits)?;
let ry = (n1 - y0).min(4);
let rx = (n0 - x0).min(4);
for y in 0..ry {
for x in 0..rx {
let dst = (y0 + y) * n0 + (x0 + x);
let off = dst * $esz;
output[off..off + $esz]
.copy_from_slice(&block[4 * y + x].to_le_bytes());
}
}
}
}
Ok(output)
}
fn compress_3d(
data: &[u8],
n2: usize,
n1: usize,
n0: usize,
rate: f64,
) -> Result<Vec<u8>, FormatError> {
let maxbits = (rate * 64.0) as usize;
let nb2 = n2.div_ceil(4);
let nb1 = n1.div_ceil(4);
let nb0 = n0.div_ceil(4);
let total_bits = nb2 * nb1 * nb0 * maxbits;
let mut w = BitWriter::with_capacity(total_bits.div_ceil(8) + 8);
for b2 in 0..nb2 {
for b1 in 0..nb1 {
for b0 in 0..nb0 {
let z0 = b2 * 4;
let y0 = b1 * 4;
let x0 = b0 * 4;
let rz = (n2 - z0).min(4);
let ry = (n1 - y0).min(4);
let rx = (n0 - x0).min(4);
let mut block = [$zero_s; 64];
if rz == 4 && ry == 4 && rx == 4 {
for z in 0..4 {
for y in 0..4 {
let row_off = (((z0 + z) * n1 + (y0 + y)) * n0 + x0) * $esz;
let row = &data[row_off..row_off + 4 * $esz];
for x in 0..4 {
let buf: [u8; $esz] =
row[x * $esz..(x + 1) * $esz].try_into().unwrap();
block[16 * z + 4 * y + x] = $from_le(buf);
}
}
}
} else {
for z in 0..rz {
for y in 0..ry {
let row_off = (((z0 + z) * n1 + (y0 + y)) * n0 + x0) * $esz;
let row = &data[row_off..row_off + rx * $esz];
for x in 0..rx {
let buf: [u8; $esz] =
row[x * $esz..(x + 1) * $esz].try_into().unwrap();
block[16 * z + 4 * y + x] = $from_le(buf);
}
$pad_s(block.as_mut_slice(), 16 * z + 4 * y, rx, 1);
}
for x in 0..4 {
$pad_s(block.as_mut_slice(), 16 * z + x, ry, 4);
}
}
for y in 0..4 {
for x in 0..4 {
$pad_s(block.as_mut_slice(), 4 * y + x, rz, 16);
}
}
}
$enc3(&mut w, &block, maxbits);
}
}
}
let mut out = w.finish();
out.truncate(total_bits.div_ceil(8));
Ok(out)
}
fn decompress_3d(
compressed: &[u8],
n2: usize,
n1: usize,
n0: usize,
rate: f64,
) -> Result<Vec<u8>, FormatError> {
let maxbits = (rate * 64.0) as usize;
let nb2 = n2.div_ceil(4);
let nb1 = n1.div_ceil(4);
let nb0 = n0.div_ceil(4);
let mut r = BitReader::new(compressed);
let mut output = vec![0u8; n2 * n1 * n0 * $esz];
for b2 in 0..nb2 {
for b1 in 0..nb1 {
for b0 in 0..nb0 {
let z0 = b2 * 4;
let y0 = b1 * 4;
let x0 = b0 * 4;
let block = $dec3(&mut r, maxbits)?;
let rz = (n2 - z0).min(4);
let ry = (n1 - y0).min(4);
let rx = (n0 - x0).min(4);
if rz == 4 && ry == 4 && rx == 4 {
for z in 0..4 {
for y in 0..4 {
let row_off = (((z0 + z) * n1 + (y0 + y)) * n0 + x0) * $esz;
let row = &mut output[row_off..row_off + 4 * $esz];
for x in 0..4 {
row[x * $esz..(x + 1) * $esz].copy_from_slice(
&block[16 * z + 4 * y + x].to_le_bytes(),
);
}
}
}
} else {
for z in 0..rz {
for y in 0..ry {
let row_off = (((z0 + z) * n1 + (y0 + y)) * n0 + x0) * $esz;
let row = &mut output[row_off..row_off + rx * $esz];
for x in 0..rx {
row[x * $esz..(x + 1) * $esz].copy_from_slice(
&block[16 * z + 4 * y + x].to_le_bytes(),
);
}
}
}
}
}
}
}
Ok(output)
}
fn compress_4d(
data: &[u8],
n3: usize,
n2: usize,
n1: usize,
n0: usize,
rate: f64,
) -> Result<Vec<u8>, FormatError> {
let maxbits = (rate * 256.0) as usize;
let nb3 = n3.div_ceil(4);
let nb2 = n2.div_ceil(4);
let nb1 = n1.div_ceil(4);
let nb0 = n0.div_ceil(4);
let total_bits = nb3 * nb2 * nb1 * nb0 * maxbits;
let mut w = BitWriter::with_capacity(total_bits.div_ceil(8) + 8);
for b3 in 0..nb3 {
for b2 in 0..nb2 {
for b1 in 0..nb1 {
for b0 in 0..nb0 {
let w0 = b3 * 4;
let z0 = b2 * 4;
let y0 = b1 * 4;
let x0 = b0 * 4;
let rw = (n3 - w0).min(4);
let rz = (n2 - z0).min(4);
let ry = (n1 - y0).min(4);
let rx = (n0 - x0).min(4);
let mut block = [$zero_s; 256];
if rw == 4 && rz == 4 && ry == 4 && rx == 4 {
for wi in 0..4 {
for z in 0..4 {
for y in 0..4 {
let row_off = ((((w0 + wi) * n2 + (z0 + z)) * n1
+ (y0 + y))
* n0
+ x0)
* $esz;
let row = &data[row_off..row_off + 4 * $esz];
for x in 0..4 {
let buf: [u8; $esz] = row
[x * $esz..(x + 1) * $esz]
.try_into()
.unwrap();
block[64 * wi + 16 * z + 4 * y + x] =
$from_le(buf);
}
}
}
}
} else {
for wi in 0..rw {
for z in 0..rz {
for y in 0..ry {
for x in 0..rx {
let src = (((w0 + wi) * n2 + (z0 + z)) * n1
+ (y0 + y))
* n0
+ (x0 + x);
let off = src * $esz;
let mut buf = [0u8; $esz];
buf.copy_from_slice(&data[off..off + $esz]);
block[64 * wi + 16 * z + 4 * y + x] =
$from_le(buf);
}
$pad_s(
block.as_mut_slice(),
64 * wi + 16 * z + 4 * y,
rx,
1,
);
}
for x in 0..4 {
$pad_s(
block.as_mut_slice(),
64 * wi + 16 * z + x,
ry,
4,
);
}
}
for y in 0..4 {
for x in 0..4 {
$pad_s(
block.as_mut_slice(),
64 * wi + 4 * y + x,
rz,
16,
);
}
}
}
for z in 0..4 {
for y in 0..4 {
for x in 0..4 {
$pad_s(
block.as_mut_slice(),
16 * z + 4 * y + x,
rw,
64,
);
}
}
}
}
$enc4(&mut w, &block, maxbits);
}
}
}
}
let mut out = w.finish();
out.truncate(total_bits.div_ceil(8));
Ok(out)
}
fn decompress_4d(
compressed: &[u8],
n3: usize,
n2: usize,
n1: usize,
n0: usize,
rate: f64,
) -> Result<Vec<u8>, FormatError> {
let maxbits = (rate * 256.0) as usize;
let nb3 = n3.div_ceil(4);
let nb2 = n2.div_ceil(4);
let nb1 = n1.div_ceil(4);
let nb0 = n0.div_ceil(4);
let mut r = BitReader::new(compressed);
let mut output = vec![0u8; n3 * n2 * n1 * n0 * $esz];
for b3 in 0..nb3 {
for b2 in 0..nb2 {
for b1 in 0..nb1 {
for b0 in 0..nb0 {
let w0 = b3 * 4;
let z0 = b2 * 4;
let y0 = b1 * 4;
let x0 = b0 * 4;
let block = $dec4(&mut r, maxbits)?;
let rw = (n3 - w0).min(4);
let rz = (n2 - z0).min(4);
let ry = (n1 - y0).min(4);
let rx = (n0 - x0).min(4);
if rw == 4 && rz == 4 && ry == 4 && rx == 4 {
for wi in 0..4 {
for z in 0..4 {
for y in 0..4 {
let row_off = ((((w0 + wi) * n2 + (z0 + z)) * n1
+ (y0 + y))
* n0
+ x0)
* $esz;
let row = &mut output[row_off..row_off + 4 * $esz];
for x in 0..4 {
row[x * $esz..(x + 1) * $esz].copy_from_slice(
&block[64 * wi + 16 * z + 4 * y + x]
.to_le_bytes(),
);
}
}
}
}
} else {
for wi in 0..rw {
for z in 0..rz {
for y in 0..ry {
for x in 0..rx {
let dst = (((w0 + wi) * n2 + (z0 + z)) * n1
+ (y0 + y))
* n0
+ (x0 + x);
let off = dst * $esz;
output[off..off + $esz].copy_from_slice(
&block[64 * wi + 16 * z + 4 * y + x]
.to_le_bytes(),
);
}
}
}
}
}
}
}
}
}
Ok(output)
}
}
};
}
impl_codec!(
codec_f32,
f32,
0.0f32,
4,
f32_from_le,
pad_partial_block_f32,
pad_strided_f32,
encode_block_f32,
decode_block_f32,
encode_block_f32_2d,
decode_block_f32_2d,
encode_block_f32_3d,
decode_block_f32_3d,
encode_block_f32_4d,
decode_block_f32_4d
);
impl_codec!(
codec_f64,
f64,
0.0f64,
8,
f64_from_le,
pad_partial_block_f64,
pad_strided_f64,
encode_block_f64,
decode_block_f64,
encode_block_f64_2d,
decode_block_f64_2d,
encode_block_f64_3d,
decode_block_f64_3d,
encode_block_f64_4d,
decode_block_f64_4d
);
impl_codec!(
codec_i32,
i32,
0i32,
4,
i32_from_le,
pad_partial_block_i32,
pad_strided_i32,
encode_block_i32,
decode_block_i32,
encode_block_i32_2d,
decode_block_i32_2d,
encode_block_i32_3d,
decode_block_i32_3d,
encode_block_i32_4d,
decode_block_i32_4d
);
impl_codec!(
codec_i64,
i64,
0i64,
8,
i64_from_le,
pad_partial_block_i64,
pad_strided_i64,
encode_block_i64,
decode_block_i64,
encode_block_i64_2d,
decode_block_i64_2d,
encode_block_i64_3d,
decode_block_i64_3d,
encode_block_i64_4d,
decode_block_i64_4d
);
pub fn compress(
data: &[u8],
dims: &[usize],
rate: f64,
element_type: ZfpElementType,
) -> Result<Vec<u8>, FormatError> {
match element_type {
ZfpElementType::F32 => codec_f32::compress(data, dims, rate),
ZfpElementType::F64 => codec_f64::compress(data, dims, rate),
ZfpElementType::I32 => codec_i32::compress(data, dims, rate),
ZfpElementType::I64 => codec_i64::compress(data, dims, rate),
}
}
pub fn decompress(
compressed: &[u8],
dims: &[usize],
rate: f64,
element_type: ZfpElementType,
) -> Result<Vec<u8>, FormatError> {
match element_type {
ZfpElementType::F32 => codec_f32::decompress(compressed, dims, rate),
ZfpElementType::F64 => codec_f64::decompress(compressed, dims, rate),
ZfpElementType::I32 => codec_i32::decompress(compressed, dims, rate),
ZfpElementType::I64 => codec_i64::decompress(compressed, dims, rate),
}
}
const ZFP_VERSION_NO: u32 = 0x1010; const ZFP_CODEC: u32 = 5;
const H5Z_FILTER_ZFP_VERSION_NO: u32 = 0x111;
fn zfp_meta_for(elem: ZfpElementType, dims: &[usize]) -> u64 {
let zt: u64 = match elem {
ZfpElementType::I32 => 1,
ZfpElementType::I64 => 2,
ZfpElementType::F32 => 3,
ZfpElementType::F64 => 4,
};
let rank = dims.len();
let mut meta: u64 = 0;
match rank {
1 => {
let nx = dims[0] as u64 - 1;
meta = (meta << 48) + nx;
}
2 => {
let ny = dims[0] as u64 - 1;
let nx = dims[1] as u64 - 1;
meta = (meta << 24) + ny;
meta = (meta << 24) + nx;
}
3 => {
let nz = dims[0] as u64 - 1;
let ny = dims[1] as u64 - 1;
let nx = dims[2] as u64 - 1;
meta = (meta << 16) + nz;
meta = (meta << 16) + ny;
meta = (meta << 16) + nx;
}
4 => {
let nw = dims[0] as u64 - 1;
let nz = dims[1] as u64 - 1;
let ny = dims[2] as u64 - 1;
let nx = dims[3] as u64 - 1;
meta = (meta << 12) + nw;
meta = (meta << 12) + nz;
meta = (meta << 12) + ny;
meta = (meta << 12) + nx;
}
_ => unreachable!("rank must be 1..=4 — validated by zfp_cd_values_rate"),
}
meta = (meta << 2) + rank as u64 - 1;
meta = (meta << 2) + zt - 1;
meta
}
pub fn zfp_cd_values_rate(
rate: f64,
element_type: ZfpElementType,
chunk_dims: &[u64],
) -> Result<Vec<u32>, FormatError> {
if !matches!(chunk_dims.len(), 1..=4) {
return Err(FormatError::UnsupportedZfp(format!(
"only 1D-4D chunks are supported, got rank {}",
chunk_dims.len()
)));
}
let dims_usize: Vec<usize> = chunk_dims
.iter()
.map(|&d| d.to_usize())
.collect::<Result<_, _>>()?;
let rank = dims_usize.len();
let block_values: usize = 4usize.pow(rank as u32);
let maxbits = (rate * block_values as f64) as u64;
let mut w = BitWriter::new();
w.write(8, u64::from(b'z'));
w.write(8, u64::from(b'f'));
w.write(8, u64::from(b'p'));
w.write(8, u64::from(ZFP_CODEC as u8));
w.write(52, zfp_meta_for(element_type, &dims_usize));
let header_bits: usize = if maxbits <= 2048 {
let mode = maxbits.saturating_sub(1);
w.write(12, mode);
32 + 52 + 12
} else {
let minbits = maxbits;
let maxprec: u64 = 63; let minexp_enc: u64 = (-1074i64 + 16495) as u64; let mut mode: u64 = 0;
mode = (mode << 15) + minexp_enc;
mode = (mode << 7) + maxprec;
mode = (mode << 15) + (maxbits - 1);
mode = (mode << 15) + (minbits - 1);
mode = (mode << 12) + 0xFFF;
w.write(64, mode);
32 + 52 + 64
};
let mut bytes = w.finish();
bytes.truncate(header_bits.div_ceil(8));
let mut cd: Vec<u32> = Vec::with_capacity(1 + bytes.len().div_ceil(4));
let v0 = (ZFP_VERSION_NO << 16) | (ZFP_CODEC << 12) | H5Z_FILTER_ZFP_VERSION_NO;
cd.push(v0);
for chunk in bytes.chunks(4) {
let mut buf = [0u8; 4];
buf[..chunk.len()].copy_from_slice(chunk);
cd.push(u32::from_le_bytes(buf));
}
Ok(cd)
}
#[derive(Debug, Clone, PartialEq)]
pub struct ZfpFilterMeta {
pub element_type: ZfpElementType,
pub dims: Vec<u64>,
pub rate: f64,
}
pub fn zfp_filter_meta_from_cd_values(cd_values: &[u32]) -> Option<ZfpFilterMeta> {
if cd_values.len() < 4 {
return None;
}
let mut bytes: Vec<u8> = Vec::with_capacity((cd_values.len() - 1) * 4);
for &w in &cd_values[1..] {
bytes.extend_from_slice(&w.to_le_bytes());
}
let mut r = BitReader::new(&bytes);
if r.read(8) != u64::from(b'z') || r.read(8) != u64::from(b'f') || r.read(8) != u64::from(b'p')
{
return None;
}
let _codec = r.read(8); let meta = r.read(52);
let zt = (meta & 0x3) + 1; let rank = ((meta >> 2) & 0x3) + 1; let element_type = match zt {
1 => ZfpElementType::I32,
2 => ZfpElementType::I64,
3 => ZfpElementType::F32,
4 => ZfpElementType::F64,
_ => return None,
};
let mut dims: Vec<u64> = match rank {
1 => {
let n = (meta >> 4) & ((1u64 << 48) - 1);
vec![n + 1]
}
2 => {
let nx = (meta >> 4) & ((1u64 << 24) - 1);
let ny = (meta >> 28) & ((1u64 << 24) - 1);
vec![ny + 1, nx + 1]
}
3 => {
let nx = (meta >> 4) & ((1u64 << 16) - 1);
let ny = (meta >> 20) & ((1u64 << 16) - 1);
let nz = (meta >> 36) & ((1u64 << 16) - 1);
vec![nz + 1, ny + 1, nx + 1]
}
4 => {
let nx = (meta >> 4) & ((1u64 << 12) - 1);
let ny = (meta >> 16) & ((1u64 << 12) - 1);
let nz = (meta >> 28) & ((1u64 << 12) - 1);
let nw = (meta >> 40) & ((1u64 << 12) - 1);
vec![nw + 1, nz + 1, ny + 1, nx + 1]
}
_ => return None,
};
let mode_short = r.read(12);
let maxbits = if mode_short < 0xFFF {
mode_short + 1
} else {
let rest = r.read(52);
let mode = mode_short | (rest << 12);
let maxbits_enc = (mode >> 27) & 0x7FFF;
maxbits_enc + 1
};
let block_values = 4u64.pow(rank as u32);
let rate = maxbits as f64 / block_values as f64;
dims.retain(|&d| d != 0);
if dims.len() as u64 != rank {
return None;
}
Some(ZfpFilterMeta {
element_type,
dims,
rate,
})
}
pub fn zfp_rate_from_cd_values(cd_values: &[u32]) -> Option<f64> {
if cd_values.len() < 4 {
return None;
}
let words = &cd_values[1..];
if read_bits_u32(words, 0, 8)? != u64::from(b'z')
|| read_bits_u32(words, 8, 8)? != u64::from(b'f')
|| read_bits_u32(words, 16, 8)? != u64::from(b'p')
{
return None;
}
let meta = read_bits_u32(words, 32, 52)?;
let rank = ((meta >> 2) & 0x3) + 1;
let mode_short = read_bits_u32(words, 84, 12)?;
let maxbits = if mode_short < 0xFFF {
mode_short + 1
} else {
let rest = read_bits_u32(words, 96, 52)?;
let mode = mode_short | (rest << 12);
let maxbits_enc = (mode >> 27) & 0x7FFF;
maxbits_enc + 1
};
let block_values = 4u64.pow(rank as u32);
Some(maxbits as f64 / block_values as f64)
}
fn read_bits_u32(words: &[u32], bit_pos: u64, n_bits: u32) -> Option<u64> {
debug_assert!(n_bits <= 64);
let mut out: u64 = 0;
let mut remaining = n_bits;
let mut pos = bit_pos;
let mut written: u32 = 0;
while remaining > 0 {
let word_idx = (pos / 32) as usize;
if word_idx >= words.len() {
return None;
}
let bit_off = (pos % 32) as u32;
let avail = 32 - bit_off;
let take = remaining.min(avail);
let mask = if take == 32 {
u32::MAX as u64
} else {
(1u64 << take) - 1
};
let slice = ((words[word_idx] as u64) >> bit_off) & mask;
out |= slice << written;
written += take;
remaining -= take;
pos += take as u64;
}
Some(out)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bitstream_roundtrip() {
let mut w = BitWriter::new();
w.write(3, 0b101);
w.write(8, 0xAB);
w.write(1, 1);
w.write_bit(false);
w.write(64, 0xDEAD_BEEF_CAFE_BABE);
let buf = w.finish();
let mut r = BitReader::new(&buf);
assert_eq!(r.read(3), 0b101);
assert_eq!(r.read(8), 0xAB);
assert_eq!(r.read(1), 1);
assert!(!r.read_bit());
assert_eq!(r.read(64), 0xDEAD_BEEF_CAFE_BABE);
}
#[test]
fn negabinary_roundtrip() {
for x in [-1000, -1, 0, 1, 42, i32::MIN, i32::MAX] {
assert_eq!(uint2int_i32(int2uint_i32(x)), x);
}
}
#[test]
fn lift_roundtrip() {
let original = [100, -200, 300, -400];
let mut p = original;
fwd_lift_i32(&mut p);
inv_lift_i32(&mut p);
assert_eq!(p, original);
}
#[test]
fn lift_zeros() {
let mut p = [0, 0, 0, 0];
fwd_lift_i32(&mut p);
assert_eq!(p, [0, 0, 0, 0]);
inv_lift_i32(&mut p);
assert_eq!(p, [0, 0, 0, 0]);
}
#[test]
fn cast_zeros() {
let vals = [0.0f32; 4];
let (e, c) = fwd_cast_f32(&vals);
assert_eq!(e, 0);
assert_eq!(c, [0, 0, 0, 0]);
}
#[test]
fn cast_roundtrip_ones() {
let vals = [1.0f32, -1.0, 2.0, -0.5];
let (e, c) = fwd_cast_f32(&vals);
assert!(e > 0);
let reconstructed = inv_cast_f32(e, &c);
for i in 0..4 {
let err = (vals[i] - reconstructed[i]).abs();
assert!(
err < 1e-6,
"value {i}: expected {}, got {}, err={err}",
vals[i],
reconstructed[i]
);
}
}
#[test]
fn compress_decompress_zeros() {
let data = vec![0u8; 16]; let compressed = compress(&data, &[4], 16.0, ZfpElementType::F32).unwrap();
let decompressed = decompress(&compressed, &[4], 16.0, ZfpElementType::F32).unwrap();
assert_eq!(decompressed, data);
}
#[test]
fn compress_decompress_ones() {
let vals: Vec<f32> = vec![1.0; 8];
let data: Vec<u8> = vals.iter().flat_map(|v| v.to_le_bytes()).collect();
let compressed = compress(&data, &[8], 16.0, ZfpElementType::F32).unwrap();
let decompressed = decompress(&compressed, &[8], 16.0, ZfpElementType::F32).unwrap();
let recon: Vec<f32> = decompressed
.chunks(4)
.map(|c| f32::from_le_bytes([c[0], c[1], c[2], c[3]]))
.collect();
for (i, (&orig, &rec)) in vals.iter().zip(recon.iter()).enumerate() {
let err = (orig - rec).abs();
assert!(
err < 0.01,
"value {i}: expected {orig}, got {rec}, err={err}"
);
}
}
#[test]
fn compress_decompress_varied() {
let vals: Vec<f32> = vec![
1.0, 2.0, -1.5, 3.0, 100.0, -50.5, 42.0, 80.0, 0.001, 0.002, -0.003, 0.004, ];
let data: Vec<u8> = vals.iter().flat_map(|v| v.to_le_bytes()).collect();
let dims = [vals.len()];
let compressed = compress(&data, &dims, 24.0, ZfpElementType::F32).unwrap();
let decompressed = decompress(&compressed, &dims, 24.0, ZfpElementType::F32).unwrap();
let recon: Vec<f32> = decompressed
.chunks(4)
.map(|c| f32::from_le_bytes([c[0], c[1], c[2], c[3]]))
.collect();
for (i, (&orig, &rec)) in vals.iter().zip(recon.iter()).enumerate() {
let rel_err = if orig.abs() > 1e-10 {
((orig - rec) / orig).abs()
} else {
(orig - rec).abs()
};
assert!(
rel_err < 0.01,
"value {i}: expected {orig}, got {rec}, rel_err={rel_err}"
);
}
}
#[test]
fn compress_decompress_high_rate_lossless_ish() {
let vals: Vec<f32> = vec![1.0, 2.0, 3.0, 4.0];
let data: Vec<u8> = vals.iter().flat_map(|v| v.to_le_bytes()).collect();
let compressed = compress(&data, &[4], 32.0, ZfpElementType::F32).unwrap();
let decompressed = decompress(&compressed, &[4], 32.0, ZfpElementType::F32).unwrap();
let recon: Vec<f32> = decompressed
.chunks(4)
.map(|c| f32::from_le_bytes([c[0], c[1], c[2], c[3]]))
.collect();
for (i, (&orig, &rec)) in vals.iter().zip(recon.iter()).enumerate() {
let err = (orig - rec).abs();
assert!(
err < 1e-6,
"value {i}: expected {orig}, got {rec}, err={err}"
);
}
}
#[test]
fn cd_values_roundtrip() {
let cd = zfp_cd_values_rate(16.0, ZfpElementType::F32, &[16]).unwrap();
let meta = zfp_filter_meta_from_cd_values(&cd).unwrap();
assert_eq!(meta.rate, 16.0);
assert_eq!(meta.element_type, ZfpElementType::F32);
assert_eq!(meta.dims, vec![16]);
}
#[test]
fn cd_values_matches_reference_fixture() {
let cd = zfp_cd_values_rate(16.0, ZfpElementType::F32, &[16]).unwrap();
assert_eq!(cd, vec![0x10105111, 0x0570667a, 0x000000f2, 0x03f00000],);
}
#[test]
fn cd_values_rejects_5d_chunks() {
let err = zfp_cd_values_rate(16.0, ZfpElementType::F32, &[2, 2, 2, 2, 2])
.expect_err("5D chunks should be rejected");
assert!(matches!(err, FormatError::UnsupportedZfp(_)));
}
#[test]
fn cd_values_rejects_zero_rank() {
let err = zfp_cd_values_rate(16.0, ZfpElementType::F32, &[])
.expect_err("rank 0 should be rejected");
assert!(matches!(err, FormatError::UnsupportedZfp(_)));
}
#[test]
fn rate_from_cd_values_matches_full_parse() {
for rank in 1..=4 {
for &elem in &[
ZfpElementType::F32,
ZfpElementType::F64,
ZfpElementType::I32,
ZfpElementType::I64,
] {
let dims: Vec<u64> = (0..rank).map(|i| 4 + i as u64).collect();
let rate = 12.0 + rank as f64;
let cd = zfp_cd_values_rate(rate, elem, &dims).unwrap();
let fast = zfp_rate_from_cd_values(&cd)
.unwrap_or_else(|| panic!("fast parse failed for {elem:?} rank {rank}"));
let slow = zfp_filter_meta_from_cd_values(&cd).unwrap().rate;
assert!(
(fast - slow).abs() < 1e-9,
"fast={fast} slow={slow} for {elem:?} rank {rank}",
);
assert!(
(fast - rate).abs() < 1e-9,
"fast={fast} expected={rate} for {elem:?} rank {rank}",
);
}
}
}
#[test]
fn rate_from_cd_values_matches_reference_fixture() {
let cd = vec![0x10105111, 0x0570667a, 0x000000f2, 0x03f00000];
assert_eq!(zfp_rate_from_cd_values(&cd), Some(16.0));
}
#[test]
fn rate_from_cd_values_rejects_truncated_input() {
let cd = vec![0x10105111, 0x0570667a]; assert_eq!(zfp_rate_from_cd_values(&cd), None);
assert_eq!(zfp_rate_from_cd_values(&[]), None);
assert_eq!(zfp_rate_from_cd_values(&[0; 3]), None);
}
#[test]
fn rate_from_cd_values_rejects_wrong_magic() {
let mut cd = vec![0x10105111, 0x0570667a, 0x000000f2, 0x03f00000];
cd[1] ^= 0x1; assert_eq!(zfp_rate_from_cd_values(&cd), None);
}
#[test]
fn compress_rejects_short_buffer() {
let short = vec![0u8; 3 * 4];
let err =
compress(&short, &[4], 16.0, ZfpElementType::F32).expect_err("short buffer must error");
assert!(matches!(err, FormatError::FilterError(_)), "{err:?}");
}
#[test]
fn compress_rejects_long_buffer() {
let long = vec![0u8; 5 * 4];
let err =
compress(&long, &[4], 16.0, ZfpElementType::F32).expect_err("long buffer must error");
assert!(matches!(err, FormatError::FilterError(_)), "{err:?}");
}
#[test]
fn compress_rejects_bad_rate() {
let data = vec![0u8; 16 * 4];
let err =
compress(&data, &[16], f64::NAN, ZfpElementType::F32).expect_err("NaN must error");
assert!(matches!(err, FormatError::FilterError(_)));
let err =
compress(&data, &[16], f64::INFINITY, ZfpElementType::F32).expect_err("inf must error");
assert!(matches!(err, FormatError::FilterError(_)));
let err = compress(&data, &[16], 0.0, ZfpElementType::F32).expect_err("rate=0 must error");
assert!(matches!(err, FormatError::FilterError(_)));
let err = compress(&data, &[16], -1.0, ZfpElementType::F32)
.expect_err("negative rate must error");
assert!(matches!(err, FormatError::FilterError(_)));
let err = compress(&data, &[16], 33.0, ZfpElementType::F32)
.expect_err("rate > 32 must error for f32");
assert!(matches!(err, FormatError::FilterError(_)));
let err =
compress(&data, &[16], 1e20, ZfpElementType::F32).expect_err("huge rate must error");
assert!(matches!(err, FormatError::FilterError(_)));
}
#[test]
fn compress_rate_at_scalar_width_is_accepted() {
let data = vec![0u8; 4 * 4];
assert!(compress(&data, &[4], 32.0, ZfpElementType::F32).is_ok());
let data = vec![0u8; 4 * 8];
assert!(compress(&data, &[4], 64.0, ZfpElementType::F64).is_ok());
}
#[test]
fn decompress_rejects_bad_rate() {
let c = vec![0u8; 64];
assert!(matches!(
decompress(&c, &[4], 0.0, ZfpElementType::F32),
Err(FormatError::FilterError(_))
));
assert!(matches!(
decompress(&c, &[4], 1e20, ZfpElementType::F32),
Err(FormatError::FilterError(_))
));
assert!(matches!(
decompress(&c, &[4], f64::NAN, ZfpElementType::F32),
Err(FormatError::FilterError(_))
));
}
#[test]
fn partial_block() {
let vals: Vec<f32> = vec![10.0, 20.0, 30.0, 40.0, 50.0, 60.0];
let data: Vec<u8> = vals.iter().flat_map(|v| v.to_le_bytes()).collect();
let compressed = compress(&data, &[6], 16.0, ZfpElementType::F32).unwrap();
let decompressed = decompress(&compressed, &[6], 16.0, ZfpElementType::F32).unwrap();
let recon: Vec<f32> = decompressed
.chunks(4)
.map(|c| f32::from_le_bytes([c[0], c[1], c[2], c[3]]))
.collect();
assert_eq!(recon.len(), 6);
for (i, (&orig, &rec)) in vals.iter().zip(recon.iter()).enumerate() {
let err = (orig - rec).abs();
assert!(
err < 1.0,
"value {i}: expected {orig}, got {rec}, err={err}"
);
}
}
}