Files
wifi-densepose/crates/ruvector-temporal-tensor/tests/property_tests.rs
ruv d803bfe2b1 Squashed 'vendor/ruvector/' content from commit b64c2172
git-subtree-dir: vendor/ruvector
git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
2026-02-28 14:39:40 -05:00

822 lines
29 KiB
Rust

//! Property-based roundtrip tests for temporal tensor compression.
//!
//! Verifies quantization roundtrip correctness across many random inputs
//! using a deterministic PRNG. No external dependencies.
//!
//! Run with:
//! ```sh
//! cargo test --release -p ruvector-temporal-tensor --test property_tests -- --nocapture
//! ```
use ruvector_temporal_tensor::bitpack;
use ruvector_temporal_tensor::delta;
use ruvector_temporal_tensor::f16;
use ruvector_temporal_tensor::quantizer;
use ruvector_temporal_tensor::segment;
use ruvector_temporal_tensor::tiering::{self, BlockMeta, TierConfig};
// ---------------------------------------------------------------------------
// Deterministic PRNG (LCG) -- no external deps
// ---------------------------------------------------------------------------
/// Simple linear congruential generator. Constants from Knuth MMIX.
struct SimpleRng {
state: u64,
}
impl SimpleRng {
fn new(seed: u64) -> Self {
Self { state: seed }
}
fn next_u64(&mut self) -> u64 {
self.state = self
.state
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
self.state
}
fn next_f32(&mut self) -> f32 {
(self.next_u64() >> 40) as f32 / (1u64 << 24) as f32
}
fn next_f32_range(&mut self, lo: f32, hi: f32) -> f32 {
lo + self.next_f32() * (hi - lo)
}
fn next_usize_range(&mut self, lo: usize, hi: usize) -> usize {
let range = (hi - lo) as u64;
if range == 0 {
return lo;
}
lo + (self.next_u64() % range) as usize
}
}
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
const GROUP_LEN: usize = 64;
/// Generate a random f32 vector of the given length with values in [lo, hi].
fn random_vec(rng: &mut SimpleRng, len: usize, lo: f32, hi: f32) -> Vec<f32> {
(0..len).map(|_| rng.next_f32_range(lo, hi)).collect()
}
/// Compute group-level maximum absolute values for error bounding.
fn group_max_abs(frame: &[f32], group_len: usize) -> Vec<f32> {
frame
.chunks(group_len)
.map(|chunk| {
chunk
.iter()
.filter(|v| v.is_finite())
.map(|v| v.abs())
.fold(0.0f32, f32::max)
})
.collect()
}
// ---------------------------------------------------------------------------
// 1. Quantize/Dequant Roundtrip Property
// ---------------------------------------------------------------------------
#[test]
fn prop_roundtrip_error_bounded() {
let mut rng = SimpleRng::new(0xDEAD_BEEF_CAFE_BABE);
// Error bounds as fraction of each group's max absolute value.
// The absolute error per element is bounded by:
// scale * 1 (one quantization step) + f16 rounding (~0.1% of scale)
// where scale = group_max_abs / qmax. So the error fraction of group_max is
// approximately 1/qmax + small f16 term.
// 8-bit: qmax=127, ~0.8% + margin -> 1%
// 7-bit: qmax=63, ~1.6% + margin -> 2%
// 5-bit: qmax=15, ~6.7% + margin -> 7%
// 3-bit: qmax=3, ~33% + margin -> 35%
let bit_configs: &[(u8, f32)] = &[
(8, 0.01), // 8-bit: < 1% of group max
(7, 0.02), // 7-bit: < 2% of group max
(5, 0.07), // 5-bit: < 7% of group max
(3, 0.35), // 3-bit: < 35% of group max
];
for trial in 0..1000 {
let len = rng.next_usize_range(64, 513); // 64..512 inclusive
let frame = random_vec(&mut rng, len, -10.0, 10.0);
for &(bits, max_err_frac) in bit_configs {
let scales = quantizer::compute_scales(&frame, GROUP_LEN, bits);
let scales_f32 = quantizer::scales_to_f32(&scales);
let mut packed = Vec::new();
quantizer::quantize_and_pack_f32(&frame, &scales_f32, GROUP_LEN, bits, &mut packed);
let mut decoded = Vec::new();
quantizer::dequantize_f32(
&packed,
&scales_f32,
GROUP_LEN,
bits,
frame.len(),
1,
&mut decoded,
);
assert_eq!(
decoded.len(),
frame.len(),
"trial={trial}, bits={bits}: length mismatch"
);
// Compute per-group max absolute value for error bounding.
let gmax = group_max_abs(&frame, GROUP_LEN);
for (i, (&orig, &dec)) in frame.iter().zip(decoded.iter()).enumerate() {
let abs_err = (orig - dec).abs();
let group_idx = i / GROUP_LEN;
let group_m = if group_idx < gmax.len() {
gmax[group_idx]
} else {
1.0
};
// Bound: max_err_frac * group_max + small absolute floor for near-zero groups.
let bound = max_err_frac * group_m + 1e-6;
assert!(
abs_err <= bound,
"trial={trial}, bits={bits}, i={i}: orig={orig}, dec={dec}, \
abs_err={abs_err}, bound={bound}, group_max={group_m}"
);
}
}
}
}
// ---------------------------------------------------------------------------
// 2. Bit Packing Roundtrip Property
// ---------------------------------------------------------------------------
#[test]
fn prop_bitpack_roundtrip() {
let mut rng = SimpleRng::new(0x1234_5678_9ABC_DEF0);
let bit_widths: &[u32] = &[3, 5, 7, 8];
for _trial in 0..1000 {
let count = rng.next_usize_range(1, 513);
for &bits in bit_widths {
let max_val = (1u32 << bits) - 1;
let codes: Vec<u32> = (0..count)
.map(|_| (rng.next_u64() as u32) % (max_val + 1))
.collect();
let mut packed = Vec::new();
bitpack::pack(&codes, bits, &mut packed);
let mut unpacked = Vec::new();
bitpack::unpack(&packed, bits, count, &mut unpacked);
assert_eq!(
codes, unpacked,
"bits={bits}, count={count}: pack/unpack mismatch"
);
}
}
}
// ---------------------------------------------------------------------------
// 3. Segment Encode/Decode Property
// ---------------------------------------------------------------------------
#[test]
fn prop_segment_roundtrip() {
let mut rng = SimpleRng::new(0xFEED_FACE_DEAD_C0DE);
let tensor_lens: &[usize] = &[32, 64, 128, 256, 512];
let frame_counts: &[usize] = &[1, 2, 5, 10, 20];
let bit_widths: &[u8] = &[3, 5, 7, 8];
for _trial in 0..200 {
let tensor_len = tensor_lens[rng.next_usize_range(0, tensor_lens.len())];
let frame_count = frame_counts[rng.next_usize_range(0, frame_counts.len())];
let bits = bit_widths[rng.next_usize_range(0, bit_widths.len())];
// Generate the first frame and compute scales from it (shared across frames).
let first_frame = random_vec(&mut rng, tensor_len, -5.0, 5.0);
let scales = quantizer::compute_scales(&first_frame, GROUP_LEN, bits);
let scales_f32 = quantizer::scales_to_f32(&scales);
// Quantize all frames with the same scales.
let mut packed = Vec::new();
quantizer::quantize_and_pack_f32(&first_frame, &scales_f32, GROUP_LEN, bits, &mut packed);
for _ in 1..frame_count {
// Subsequent frames use values within the first frame's range to fit scales.
let frame = random_vec(&mut rng, tensor_len, -4.0, 4.0);
quantizer::quantize_and_pack_f32(&frame, &scales_f32, GROUP_LEN, bits, &mut packed);
}
// Encode into segment format.
let mut seg = Vec::new();
segment::encode(
bits,
GROUP_LEN as u32,
tensor_len as u32,
frame_count as u32,
&scales,
&packed,
&mut seg,
);
// Decode the segment.
let mut decoded = Vec::new();
segment::decode(&seg, &mut decoded);
assert_eq!(
decoded.len(),
tensor_len * frame_count,
"trial={_trial}, bits={bits}, tensor_len={tensor_len}, frames={frame_count}: \
decoded length mismatch"
);
// Parse the header and verify metadata.
let header = segment::parse_header(&seg).expect("header should parse");
assert_eq!(header.bits, bits);
assert_eq!(header.tensor_len, tensor_len as u32);
assert_eq!(header.frame_count, frame_count as u32);
assert_eq!(header.group_len, GROUP_LEN as u32);
}
}
// ---------------------------------------------------------------------------
// 4. f16 Roundtrip Property
// ---------------------------------------------------------------------------
#[test]
fn prop_f16_roundtrip() {
let mut rng = SimpleRng::new(0xAAAA_BBBB_CCCC_DDDD);
for _trial in 0..10_000 {
// Generate value in scale-relevant range [1e-4, 1e4].
let v = rng.next_f32_range(1e-4, 1e4);
// Randomly negate half the values.
let v = if rng.next_u64() & 1 == 0 { v } else { -v };
let h = f16::f32_to_f16_bits(v);
let back = f16::f16_bits_to_f32(h);
// f16 has ~0.1% relative error for normal values in this range.
let rel_err = ((back - v) / v).abs();
assert!(
rel_err < 0.002,
"trial={_trial}: v={v}, back={back}, rel_err={rel_err}"
);
}
}
// ---------------------------------------------------------------------------
// 5. Delta Compute/Apply Property
// ---------------------------------------------------------------------------
#[test]
fn prop_delta_apply_recovers_new() {
let mut rng = SimpleRng::new(0x0123_4567_89AB_CDEF);
for trial in 0..500 {
let len = rng.next_usize_range(8, 257);
let old = random_vec(&mut rng, len, -5.0, 5.0);
// Create "new" as old with a small number of perturbations.
let mut new = old.clone();
let num_changes = rng.next_usize_range(1, (len / 4).max(2));
for _ in 0..num_changes {
let idx = rng.next_usize_range(0, len);
new[idx] += rng.next_f32_range(-1.0, 1.0);
}
let threshold = 0.001;
let max_change_frac = 0.8;
let result =
delta::compute_delta(&old, &new, trial as u128, 0, 0, threshold, max_change_frac);
match result {
Some(d) => {
// Apply delta to old, verify it approximates new.
let mut reconstructed = old.clone();
delta::apply_delta(&mut reconstructed, &d);
for i in 0..len {
let err = (reconstructed[i] - new[i]).abs();
// Two sources of error:
// 1. Entries below threshold are not captured in the delta,
// so the reconstruction error for those is up to `threshold`.
// 2. Captured entries have i16 quantization error of at most
// delta_scale / 2 (half a quantization step).
let tolerance = threshold + d.delta_scale * 1.5 + 1e-6;
assert!(
err <= tolerance,
"trial={trial}, i={i}: recon={}, new={}, err={err}, tol={tolerance}",
reconstructed[i],
new[i]
);
}
}
None => {
// Delta was too large (>= max_change_fraction).
// Verify that indeed many values changed.
let changed = old
.iter()
.zip(new.iter())
.filter(|(&o, &n)| (o - n).abs() >= threshold)
.count();
let fraction = changed as f32 / len as f32;
assert!(
fraction >= max_change_frac,
"trial={trial}: delta was None but change fraction={fraction} < {max_change_frac}"
);
}
}
}
}
// ---------------------------------------------------------------------------
// 6. Compression Ratio Property
// ---------------------------------------------------------------------------
#[test]
fn prop_compression_ratio_matches_theory() {
let mut rng = SimpleRng::new(0xCAFE_D00D_BEEF_FEED);
let expected: &[(u8, f32)] = &[(8, 3.5), (7, 4.0), (5, 5.5), (3, 8.5)];
for &(bits, min_ratio) in expected {
// Use a 512-element tensor with group_len=64 for consistent measurement.
let frame = random_vec(&mut rng, 512, -1.0, 1.0);
let scales = quantizer::compute_scales(&frame, GROUP_LEN, bits);
let mut packed = Vec::new();
quantizer::quantize_and_pack(&frame, &scales, GROUP_LEN, bits, &mut packed);
let raw_bytes = frame.len() * 4; // f32 = 4 bytes
let compressed = packed.len() + scales.len() * 2; // packed data + f16 scales
let ratio = raw_bytes as f32 / compressed as f32;
assert!(
ratio >= min_ratio,
"bits={bits}: ratio={ratio:.2}x < expected={min_ratio}x \
(raw={raw_bytes}, compressed={compressed})"
);
}
}
// ---------------------------------------------------------------------------
// 7. Score Monotonicity Property
// ---------------------------------------------------------------------------
#[test]
fn prop_score_monotonic_with_access() {
let mut rng = SimpleRng::new(0x7777_8888_9999_AAAA);
let config = TierConfig::default();
for _trial in 0..100 {
let start_tick = rng.next_u64() % 1000;
let mut meta = BlockMeta::new(start_tick);
// Score before any touch.
let score_before = tiering::compute_score(&config, start_tick, &meta);
// Touch the block.
tiering::touch(&config, start_tick + 1, &mut meta);
let score_after_touch = tiering::compute_score(&config, start_tick + 1, &meta);
// Touching should increase (or at minimum maintain) the score.
assert!(
score_after_touch >= score_before - 1e-6,
"trial={_trial}: score decreased after touch: \
before={score_before}, after={score_after_touch}"
);
// Now let time pass without access -- score should decrease.
let score_at_touch = tiering::compute_score(&config, start_tick + 1, &meta);
let score_later = tiering::compute_score(&config, start_tick + 1000, &meta);
assert!(
score_later <= score_at_touch + 1e-6,
"trial={_trial}: score increased without access: \
at_touch={score_at_touch}, later={score_later}"
);
}
}
// ---------------------------------------------------------------------------
// 8. Zero Vector Property
// ---------------------------------------------------------------------------
#[test]
fn prop_zero_vector_roundtrip() {
let bit_widths: &[u8] = &[3, 5, 7, 8];
for &len in &[64, 128, 256, 512] {
let frame = vec![0.0f32; len];
for &bits in bit_widths {
let scales = quantizer::compute_scales(&frame, GROUP_LEN, bits);
let scales_f32 = quantizer::scales_to_f32(&scales);
// All scales should be zero for a zero vector.
for (i, &s) in scales_f32.iter().enumerate() {
assert_eq!(
s, 0.0,
"len={len}, bits={bits}, group={i}: scale should be 0.0, got {s}"
);
}
let mut packed = Vec::new();
quantizer::quantize_and_pack_f32(&frame, &scales_f32, GROUP_LEN, bits, &mut packed);
let mut decoded = Vec::new();
quantizer::dequantize_f32(&packed, &scales_f32, GROUP_LEN, bits, len, 1, &mut decoded);
assert_eq!(decoded.len(), len);
for (i, &v) in decoded.iter().enumerate() {
assert_eq!(
v, 0.0,
"len={len}, bits={bits}, i={i}: expected 0.0, got {v}"
);
}
}
}
}
// ---------------------------------------------------------------------------
// 9. Single-Value (Uniform) Vector Property
// ---------------------------------------------------------------------------
#[test]
fn prop_uniform_vector_roundtrip() {
let mut rng = SimpleRng::new(0xBBBB_CCCC_DDDD_EEEE);
let bit_widths: &[u8] = &[3, 5, 7, 8];
for _trial in 0..200 {
let len = rng.next_usize_range(64, 513);
let value = rng.next_f32_range(-10.0, 10.0);
let frame = vec![value; len];
for &bits in bit_widths {
let qmax = bitpack::qmax_from_bits(bits);
if qmax == 0 {
continue;
}
let scales = quantizer::compute_scales(&frame, GROUP_LEN, bits);
let scales_f32 = quantizer::scales_to_f32(&scales);
let mut packed = Vec::new();
quantizer::quantize_and_pack_f32(&frame, &scales_f32, GROUP_LEN, bits, &mut packed);
let mut decoded = Vec::new();
quantizer::dequantize_f32(&packed, &scales_f32, GROUP_LEN, bits, len, 1, &mut decoded);
assert_eq!(decoded.len(), len);
// For a uniform vector, the quantization step is value.abs() / qmax.
// Max error should be at most half a step (rounding) plus f16 scale error.
let step = if value.abs() > 0.0 {
value.abs() / qmax as f32
} else {
0.0
};
// Allow step/2 plus a small f16 rounding margin.
let max_err = step * 0.5 + value.abs() * 0.002 + 1e-6;
for (i, &dec) in decoded.iter().enumerate() {
let err = (dec - value).abs();
assert!(
err <= max_err,
"trial={_trial}, bits={bits}, i={i}: value={value}, dec={dec}, \
err={err}, max_err={max_err}, step={step}"
);
}
}
}
}
// ---------------------------------------------------------------------------
// 10. Extreme Value Property
// ---------------------------------------------------------------------------
#[test]
fn prop_extreme_values_dont_panic() {
let bit_widths: &[u8] = &[3, 5, 7, 8];
// Frames where scales stay within f16 representable range -- decoded values
// must be finite.
let finite_frames: Vec<Vec<f32>> = vec![
// Very small positive values
vec![f32::MIN_POSITIVE; 128],
// Contains infinities and NaN (quantizer maps non-finite to 0)
{
let mut v = vec![1.0f32; 128];
v[0] = f32::INFINITY;
v[1] = f32::NEG_INFINITY;
v[2] = f32::NAN;
v[3] = -0.0;
v
},
// All subnormal
vec![1e-40f32; 128],
// Alternating zero and large (within f16 scale range)
(0..128)
.map(|i| if i % 2 == 0 { 0.0 } else { 1e4 })
.collect(),
];
// Frames with magnitudes that overflow f16 scales -- we only assert
// no panics and correct output length. The decoded values may be NaN/Inf
// because scale overflows to f16 infinity.
let overflow_frames: Vec<Vec<f32>> = vec![
// All f32::MAX
vec![f32::MAX; 128],
// All f32::MIN (most negative finite)
vec![f32::MIN; 128],
// Mixed signs of large magnitude
(0..128)
.map(|i| if i % 2 == 0 { f32::MAX } else { f32::MIN })
.collect(),
// Mix of tiny and huge
(0..128)
.map(|i| {
if i % 3 == 0 {
f32::MIN_POSITIVE
} else if i % 3 == 1 {
1e30
} else {
-1e30
}
})
.collect(),
];
// Test finite-output frames: no panics, correct length, all decoded finite.
for (frame_idx, frame) in finite_frames.iter().enumerate() {
for &bits in bit_widths {
let scales = quantizer::compute_scales(frame, GROUP_LEN, bits);
let scales_f32 = quantizer::scales_to_f32(&scales);
let mut packed = Vec::new();
quantizer::quantize_and_pack_f32(frame, &scales_f32, GROUP_LEN, bits, &mut packed);
let mut decoded = Vec::new();
quantizer::dequantize_f32(
&packed,
&scales_f32,
GROUP_LEN,
bits,
frame.len(),
1,
&mut decoded,
);
assert_eq!(
decoded.len(),
frame.len(),
"finite frame_idx={frame_idx}, bits={bits}: length mismatch"
);
for (i, &d) in decoded.iter().enumerate() {
assert!(
d.is_finite(),
"finite frame_idx={frame_idx}, bits={bits}, i={i}: \
decoded value is not finite: {d}"
);
}
}
}
// Test overflow frames: no panics, correct length (decoded may contain NaN/Inf).
for (frame_idx, frame) in overflow_frames.iter().enumerate() {
for &bits in bit_widths {
let scales = quantizer::compute_scales(frame, GROUP_LEN, bits);
let scales_f32 = quantizer::scales_to_f32(&scales);
let mut packed = Vec::new();
quantizer::quantize_and_pack_f32(frame, &scales_f32, GROUP_LEN, bits, &mut packed);
let mut decoded = Vec::new();
quantizer::dequantize_f32(
&packed,
&scales_f32,
GROUP_LEN,
bits,
frame.len(),
1,
&mut decoded,
);
assert_eq!(
decoded.len(),
frame.len(),
"overflow frame_idx={frame_idx}, bits={bits}: length mismatch"
);
}
}
// Bitpack roundtrip with boundary codes -- must not panic and must be exact.
for &bits in bit_widths {
let qmax = bitpack::qmax_from_bits(bits) as u32;
if qmax > 0 {
let max_code = qmax * 2;
let codes: Vec<u32> = (0..128).map(|i| i as u32 % (max_code + 1)).collect();
let mut bp = Vec::new();
bitpack::pack(&codes, bits as u32, &mut bp);
let mut unpacked = Vec::new();
bitpack::unpack(&bp, bits as u32, codes.len(), &mut unpacked);
assert_eq!(codes, unpacked);
}
}
}
// ---------------------------------------------------------------------------
// 11. Segment Compression Ratio is Positive
// ---------------------------------------------------------------------------
#[test]
fn prop_segment_compression_ratio_positive() {
let mut rng = SimpleRng::new(0x1111_2222_3333_4444);
for _trial in 0..100 {
let tensor_len = 128;
let bits = [3u8, 5, 7, 8][rng.next_usize_range(0, 4)];
let frame = random_vec(&mut rng, tensor_len, -1.0, 1.0);
let scales = quantizer::compute_scales(&frame, GROUP_LEN, bits);
let mut packed = Vec::new();
quantizer::quantize_and_pack(&frame, &scales, GROUP_LEN, bits, &mut packed);
let mut seg = Vec::new();
segment::encode(
bits,
GROUP_LEN as u32,
tensor_len as u32,
1,
&scales,
&packed,
&mut seg,
);
let ratio = segment::compression_ratio(&seg);
assert!(
ratio > 1.0,
"trial={_trial}, bits={bits}: compression ratio {ratio} should be > 1.0"
);
}
}
// ---------------------------------------------------------------------------
// 12. Single-Frame Decode Matches Full Decode
// ---------------------------------------------------------------------------
#[test]
fn prop_single_frame_decode_consistency() {
let mut rng = SimpleRng::new(0x5555_6666_7777_8888);
for _trial in 0..100 {
let tensor_len = 64;
let frame_count = rng.next_usize_range(1, 6);
let bits = [3u8, 5, 7, 8][rng.next_usize_range(0, 4)];
let first_frame = random_vec(&mut rng, tensor_len, -3.0, 3.0);
let scales = quantizer::compute_scales(&first_frame, GROUP_LEN, bits);
let scales_f32 = quantizer::scales_to_f32(&scales);
let mut packed = Vec::new();
quantizer::quantize_and_pack_f32(&first_frame, &scales_f32, GROUP_LEN, bits, &mut packed);
for _ in 1..frame_count {
let frame = random_vec(&mut rng, tensor_len, -2.5, 2.5);
quantizer::quantize_and_pack_f32(&frame, &scales_f32, GROUP_LEN, bits, &mut packed);
}
let mut seg = Vec::new();
segment::encode(
bits,
GROUP_LEN as u32,
tensor_len as u32,
frame_count as u32,
&scales,
&packed,
&mut seg,
);
// Full decode.
let mut all_decoded = Vec::new();
segment::decode(&seg, &mut all_decoded);
assert_eq!(all_decoded.len(), tensor_len * frame_count);
// Single-frame decode should match the corresponding slice.
for f in 0..frame_count {
let single = segment::decode_single_frame(&seg, f);
assert!(
single.is_some(),
"trial={_trial}, frame={f}: single-frame decode returned None"
);
let single = single.unwrap();
let expected = &all_decoded[f * tensor_len..(f + 1) * tensor_len];
assert_eq!(
single.len(),
expected.len(),
"trial={_trial}, frame={f}: length mismatch"
);
for (i, (&s, &e)) in single.iter().zip(expected.iter()).enumerate() {
assert!(
(s - e).abs() < 1e-6,
"trial={_trial}, frame={f}, i={i}: single={s}, full={e}"
);
}
}
}
}
// ---------------------------------------------------------------------------
// 13. Delta Encode/Decode Binary Roundtrip
// ---------------------------------------------------------------------------
#[test]
fn prop_delta_encode_decode_binary() {
let mut rng = SimpleRng::new(0x9999_0000_1111_2222);
for trial in 0..500 {
let nnz = rng.next_usize_range(0, 100);
let entries: Vec<delta::SparseEntry> = (0..nnz)
.map(|_| delta::SparseEntry {
index: (rng.next_u64() % 65536) as u16,
value: (rng.next_u64() % 65536) as i16,
})
.collect();
let scale = rng.next_f32_range(1e-6, 100.0);
let record = delta::DeltaRecord {
header: delta::DeltaHeader {
tensor_id: rng.next_u64() as u128 | ((rng.next_u64() as u128) << 64),
block_index: rng.next_u64() as u32,
base_epoch: rng.next_u64(),
nnz: nnz as u16,
},
delta_scale: scale,
entries,
};
let bytes = delta::encode_delta(&record);
let decoded = delta::decode_delta(&bytes)
.unwrap_or_else(|e| panic!("trial={trial}: decode failed: {e:?}"));
assert_eq!(decoded.header.tensor_id, record.header.tensor_id);
assert_eq!(decoded.header.block_index, record.header.block_index);
assert_eq!(decoded.header.base_epoch, record.header.base_epoch);
assert_eq!(decoded.header.nnz, record.header.nnz);
assert!(
(decoded.delta_scale - record.delta_scale).abs() < 1e-10,
"trial={trial}: scale mismatch"
);
assert_eq!(decoded.entries.len(), record.entries.len());
for (i, (a, b)) in decoded
.entries
.iter()
.zip(record.entries.iter())
.enumerate()
{
assert_eq!(a.index, b.index, "trial={trial}, entry={i}: index mismatch");
assert_eq!(a.value, b.value, "trial={trial}, entry={i}: value mismatch");
}
}
}
// ---------------------------------------------------------------------------
// 14. Quantization is Deterministic
// ---------------------------------------------------------------------------
#[test]
fn prop_quantization_deterministic() {
let mut rng = SimpleRng::new(0xABCD_EF01_2345_6789);
for _trial in 0..200 {
let len = rng.next_usize_range(64, 257);
let frame = random_vec(&mut rng, len, -5.0, 5.0);
let bits = [3u8, 5, 7, 8][rng.next_usize_range(0, 4)];
let scales = quantizer::compute_scales(&frame, GROUP_LEN, bits);
let scales_f32 = quantizer::scales_to_f32(&scales);
let mut packed1 = Vec::new();
quantizer::quantize_and_pack_f32(&frame, &scales_f32, GROUP_LEN, bits, &mut packed1);
let mut packed2 = Vec::new();
quantizer::quantize_and_pack_f32(&frame, &scales_f32, GROUP_LEN, bits, &mut packed2);
assert_eq!(
packed1, packed2,
"trial={_trial}, bits={bits}: quantization is not deterministic"
);
}
}