1use crate::abac::{BitModel, Decoder, Encoder, UintModel};
28use crate::bitio::{ByteReader, ByteWriter, DecodeError};
29use crate::codec::{Decoded, Dtype, ErrorBound};
30use crate::coefcode::{
31 band_class, code_bands, code_bands_tiled, fit_predictors, read_preds, tile_live, write_preds,
32 Bits, HandModel, Pred, NCLASS,
33};
34use crate::wavelet::{
35 as3, forward_53, forward_97, inverse_53, inverse_97, Pyramid, MAX_LEVELS, MAX_LEVELS_1D,
36};
37
38pub const MAGIC: &[u8; 4] = b"TCW1";
40
41#[derive(Clone, Debug, Default)]
43pub enum ModelChoice {
44 #[default]
46 Hand,
47 Learned(crate::learned::LearnedSpec),
49}
50
51#[derive(Clone, Debug)]
53pub struct WavConfig {
54 pub bound: ErrorBound,
56 pub rho: f64,
58 pub model: ModelChoice,
60 pub fast: bool,
63 pub compact: bool,
68}
69
70impl WavConfig {
71 pub fn new(bound: ErrorBound) -> Self {
73 Self {
74 bound,
75 rho: 0.45,
76 model: ModelChoice::Hand,
77 fast: false,
78 compact: false,
79 }
80 }
81}
82
83#[derive(Clone, Debug, Default)]
85pub struct WavStats {
86 pub bytes: usize,
88 pub bits_per_value: f64,
90 pub coef_bytes: usize,
92 pub model_bytes: usize,
94 pub outlier_bytes: usize,
96 pub n_outliers: usize,
98 pub delta: f64,
100 pub rmse: f64,
102 pub max_abs: f64,
104 pub range: f64,
106 pub passes: usize,
108}
109
110const NDELTA: usize = NCLASS * 3;
111
112fn max_levels(dims: &[usize]) -> usize {
113 if dims.iter().filter(|&&d| d > 1).count() <= 1 {
114 MAX_LEVELS_1D
115 } else {
116 MAX_LEVELS
117 }
118}
119
120pub struct Coeffs {
122 pub p: Pyramid,
124 pub c: Vec<f64>,
126 pub w: Vec<f64>,
128 pub cls: Vec<u8>,
130 pub mean: f64,
132}
133
134impl Coeffs {
135 pub fn new(x: &[f64], dims: &[usize]) -> Self {
137 let p = Pyramid::new(as3(dims), max_levels(dims));
138 let mean = x.iter().sum::<f64>() / x.len().max(1) as f64;
139 let mut c: Vec<f64> = x.iter().map(|&v| v - mean).collect();
140 forward_97(&p, &mut c);
141 let (w, cls) = band_maps(&p);
142 for (v, wi) in c.iter_mut().zip(&w) {
143 *v *= wi;
144 }
145 Self { p, c, w, cls, mean }
146 }
147}
148
149fn band_maps(p: &Pyramid) -> (Vec<f64>, Vec<u8>) {
150 let [_, ny, nx] = p.dims;
151 let mut w = vec![0.0; p.len()];
152 let mut cls = vec![0u8; p.len()];
153 for b in &p.bands {
154 let wb = p.band_weight(b);
155 let cb = band_class(b);
156 for z in b.start[0]..b.end[0] {
157 for y in b.start[1]..b.end[1] {
158 for x in b.start[2]..b.end[2] {
159 let i = (z * ny + y) * nx + x;
160 w[i] = wb;
161 cls[i] = cb;
162 }
163 }
164 }
165 }
166 (w, cls)
167}
168
169pub fn quantize(co: &Coeffs, delta: f64, rho: f64) -> (Vec<i32>, [u8; NDELTA]) {
171 let inv = 1.0 / delta;
172 let mut sum = [0.0f64; NDELTA];
173 let mut cnt = [0u64; NDELTA];
174 let q: Vec<i32> =
175 co.c.iter()
176 .zip(&co.cls)
177 .map(|(&c, &k)| {
178 let a = c.abs() * inv;
179 let m = (a + rho).floor();
180 if m >= 1.0 {
181 let m = m.min(1.0e9);
182 let j = k as usize * 3 + (m as usize).min(3) - 1;
183 sum[j] += m - a;
184 cnt[j] += 1;
185 if c < 0.0 {
186 -(m as i32)
187 } else {
188 m as i32
189 }
190 } else {
191 0
192 }
193 })
194 .collect();
195 let mut d = [0u8; NDELTA];
196 for j in 0..NDELTA {
197 if cnt[j] > 0 {
198 d[j] = ((sum[j] / cnt[j] as f64).clamp(0.0, 0.498) * 512.0).round() as u8;
199 }
200 }
201 (q, d)
202}
203
204pub fn reconstruct(p: &Pyramid, q: &[i32], delta: f64, d: &[u8; NDELTA], mean: f64) -> Vec<f64> {
206 let (w, cls) = band_maps(p);
207 let mut c: Vec<f64> = q
208 .iter()
209 .zip(w.iter().zip(&cls))
210 .map(|(&v, (&wi, &k))| {
211 if v == 0 {
212 return 0.0;
213 }
214 let m = v.unsigned_abs() as f64;
215 let j = k as usize * 3 + (v.unsigned_abs() as usize).min(3) - 1;
216 let r = (m - d[j] as f64 / 512.0) * delta / wi;
217 if v < 0 {
218 -r
219 } else {
220 r
221 }
222 })
223 .collect();
224 inverse_97(p, &mut c);
225 for v in c.iter_mut() {
226 *v += mean;
227 }
228 c
229}
230
231fn cast(y: &[f64], dt: Dtype) -> Vec<f64> {
232 match dt {
233 Dtype::F32 => y.iter().map(|&v| v as f32 as f64).collect(),
234 Dtype::F64 => y.to_vec(),
235 }
236}
237
238fn err_stats(x: &[f64], y: &[f64]) -> (f64, f64) {
239 let mut se = 0.0;
240 let mut mx = 0.0f64;
241 for (a, b) in x.iter().zip(y) {
242 let e = a - b;
243 se += e * e;
244 mx = mx.max(e.abs());
245 }
246 ((se / x.len() as f64).sqrt(), mx)
247}
248
249fn range_of(x: &[f64]) -> f64 {
250 let (lo, hi) = x
251 .iter()
252 .fold((f64::INFINITY, f64::NEG_INFINITY), |(a, b), &v| {
253 (a.min(v), b.max(v))
254 });
255 hi - lo
256}
257
258const TAG_HAND_TILED: u8 = 3;
260
261fn code_coefs(p: &Pyramid, q: &[i32], model: &ModelChoice, w: &mut ByteWriter) -> usize {
262 code_coefs_opt(p, q, model, false, w)
263}
264
265fn code_coefs_opt(
268 p: &Pyramid,
269 q: &[i32],
270 model: &ModelChoice,
271 compact: bool,
272 w: &mut ByteWriter,
273) -> usize {
274 let preds: Vec<Pred> = if std::env::var("TC_NOPRED").is_ok() {
275 vec![Pred::default(); p.bands.len()]
276 } else {
277 fit_predictors(p, q)
278 };
279 write_preds(w, &preds);
280 let body = |m: Option<&ModelChoice>| -> (Vec<u8>, Vec<u8>) {
282 let mut h = ByteWriter::new();
283 let mut e = Encoder::new();
284 let mut qq = q.to_vec();
285 match m {
286 Some(ModelChoice::Hand) => {
287 h.u8(0);
288 code_bands(p, &mut qq, &preds, &mut e, &mut HandModel::default());
289 }
290 Some(ModelChoice::Learned(spec)) => {
291 let mut mm = spec.prepare(p, q, &preds, &mut h);
292 code_bands(p, &mut qq, &preds, &mut e, &mut mm);
293 }
294 None => {
295 h.u8(TAG_HAND_TILED);
296 let live = tile_live(p, q, &preds);
297 code_bands_tiled(
298 p,
299 &mut qq,
300 &preds,
301 &mut e,
302 &mut HandModel::default(),
303 Some(&live),
304 );
305 }
306 }
307 (h.buf, e.finish())
308 };
309 let mut opts = vec![body(Some(model))];
310 if compact {
311 if !matches!(model, ModelChoice::Hand) {
312 opts.push(body(Some(&ModelChoice::Hand)));
313 }
314 opts.push(body(None));
315 }
316 let (h, pay) = opts
317 .into_iter()
318 .min_by_key(|(h, p)| h.len() + p.len())
319 .unwrap();
320 w.bytes(&h);
321 w.block(&pay);
322 h.len()
323}
324
325fn decode_coefs(p: &Pyramid, r: &mut ByteReader) -> Result<Vec<i32>, DecodeError> {
326 let preds = read_preds(r, p.bands.len())?;
327 let tag = r.u8()?;
328 let mut q = vec![0i32; p.len()];
329 match tag {
330 TAG_HAND_TILED => {
331 let buf = r.block()?;
332 let mut d = Decoder::new(buf);
333 code_bands_tiled(p, &mut q, &preds, &mut d, &mut HandModel::default(), None);
334 if d.overrun() {
335 return Err(DecodeError::Truncated);
336 }
337 }
338 0 => {
339 let buf = r.block()?;
340 let mut d = Decoder::new(buf);
341 code_bands(p, &mut q, &preds, &mut d, &mut HandModel::default());
342 if d.overrun() {
343 return Err(DecodeError::Truncated);
344 }
345 }
346 _ => {
347 let mut m = crate::learned::read_model(tag, r)?;
348 let buf = r.block()?;
349 let mut d = Decoder::new(buf);
350 code_bands(p, &mut q, &preds, &mut d, &mut m);
351 if d.overrun() {
352 return Err(DecodeError::Truncated);
353 }
354 }
355 }
356 Ok(q)
357}
358
359struct OutlierModel {
362 flag: [BitModel; 8],
363 sign: BitModel,
364 mag: UintModel,
365}
366
367impl Default for OutlierModel {
368 fn default() -> Self {
369 Self {
370 flag: [BitModel::new(2000); 8],
371 sign: BitModel::default(),
372 mag: UintModel::default(),
373 }
374 }
375}
376
377fn code_outliers<B: Bits>(dims: [usize; 3], k: &mut [i32], b: &mut B) {
378 let mut m = OutlierModel::default();
379 let [nz, ny, nx] = dims;
380 for z in 0..nz {
381 for y in 0..ny {
382 for x in 0..nx {
383 let i = (z * ny + y) * nx + x;
384 let c = (x > 0 && k[i - 1] != 0) as usize
385 | ((y > 0 && k[i - nx] != 0) as usize) << 1
386 | ((z > 0 && k[i - nx * ny] != 0) as usize) << 2;
387 if !b.bit(k[i] != 0, &mut m.flag[c]) {
388 k[i] = 0;
389 continue;
390 }
391 let neg = b.bit(k[i] < 0, &mut m.sign);
392 let a = b.uint(k[i].unsigned_abs().saturating_sub(1) as u64, &mut m.mag) as i32 + 1;
393 k[i] = if neg { -a } else { a };
394 }
395 }
396 }
397}
398
399fn apply_outliers(y: &mut [f64], k: &[i32], b: f64) {
401 for (v, &ki) in y.iter_mut().zip(k) {
402 if ki != 0 {
403 *v += 2.0 * b * ki as f64;
404 }
405 }
406}
407
408struct Header {
411 dt: Dtype,
412 dims: Vec<usize>,
413 lossless: bool,
414}
415
416fn write_header(w: &mut ByteWriter, h: &Header) {
417 w.bytes(MAGIC);
418 w.u8(match h.dt {
419 Dtype::F32 => 0,
420 Dtype::F64 => 1,
421 });
422 w.varint(h.dims.len() as u64);
423 for &d in &h.dims {
424 w.varint(d as u64);
425 }
426 w.u8(h.lossless as u8);
427}
428
429fn encode_at(
430 x: &[f64],
431 co: &Coeffs,
432 h: &Header,
433 delta: f64,
434 cfg: &WavConfig,
435 maxabs: Option<f64>,
436) -> (Vec<u8>, WavStats, Vec<f64>) {
437 let (q, d) = quantize(co, delta, cfg.rho);
438 let mut w = ByteWriter::new();
439 write_header(&mut w, h);
440 w.f64(delta);
441 w.f64(co.mean);
442 w.bytes(&d);
443 let b0 = w.buf.len();
444 let model_bytes = code_coefs_opt(&co.p, &q, &cfg.model, cfg.compact, &mut w);
445 let coef_bytes = w.buf.len() - b0 - model_bytes;
446 let mut y = cast(&reconstruct(&co.p, &q, delta, &d, co.mean), h.dt);
447 let mut st = WavStats {
448 delta,
449 coef_bytes,
450 model_bytes,
451 ..Default::default()
452 };
453 if let Some(b) = maxabs {
454 let b0 = w.buf.len();
455 w.u8(1);
456 w.f64(b);
457 let k: Vec<i32> = x
458 .iter()
459 .zip(&y)
460 .map(|(&a, &v)| {
461 let r = a - v;
462 if r.abs() > b {
463 (r / (2.0 * b)).round() as i32
464 } else {
465 0
466 }
467 })
468 .collect();
469 let mut e = Encoder::new();
470 code_outliers(co.p.dims, &mut k.clone(), &mut e);
471 w.block(&e.finish());
472 apply_outliers(&mut y, &k, b);
473 let mut y = cast(&y, h.dt);
474 let esc: Vec<usize> = (0..x.len()).filter(|&i| (x[i] - y[i]).abs() > b).collect();
476 w.varint(esc.len() as u64);
477 let mut last = 0;
478 for &i in &esc {
479 w.varint((i - last) as u64);
480 last = i;
481 match h.dt {
482 Dtype::F32 => w.bytes(&(x[i] as f32).to_le_bytes()),
483 Dtype::F64 => w.f64(x[i]),
484 }
485 y[i] = x[i];
486 }
487 st.n_outliers = k.iter().filter(|&&v| v != 0).count() + esc.len();
488 st.outlier_bytes = w.buf.len() - b0;
489 let (rmse, mx) = err_stats(x, &y);
490 st.rmse = rmse;
491 st.max_abs = mx;
492 st.bytes = w.buf.len();
493 return (w.buf, st, y);
494 }
495 w.u8(0); let (rmse, mx) = err_stats(x, &y);
497 st.rmse = rmse;
498 st.max_abs = mx;
499 st.bytes = w.buf.len();
500 (w.buf, st, y)
501}
502
503pub const FAST_K_RMSE: f64 = 4.0;
506pub const FAST_K_MAXABS: f64 = 2.2;
508
509pub fn fast_step(t: f64, checks: usize, rmse_at: impl Fn(f64) -> f64) -> (f64, usize) {
515 let k = std::env::var("TC_FAST_K")
516 .ok()
517 .and_then(|v| v.parse().ok())
518 .unwrap_or(FAST_K_RMSE);
519 let aim = 0.99 * t;
520 let mut d = k * t;
521 let mut prev: Option<(f64, f64)> = None;
522 let mut best: Option<f64> = None;
523 let mut n = 0;
524 while n < checks {
525 let e = rmse_at(d).max(f64::MIN_POSITIVE);
526 n += 1;
527 if e <= t && best.is_none_or(|b| d > b) {
528 best = Some(d);
529 }
530 if (e - aim).abs() <= 0.01 * t {
531 break;
532 }
533 let g = match prev {
534 Some((d0, e0)) if (e / e0).ln().abs() > 1e-6 => {
535 ((e / e0).ln() / (d / d0).ln()).clamp(0.2, 2.0)
536 }
537 _ => 1.0,
538 };
539 prev = Some((d, e));
540 d *= (aim / e).powf(1.0 / g);
541 }
542 (best.unwrap_or(d * 0.5), n)
543}
544
545fn coef_rmse(co: &Coeffs, delta: f64, rho: f64) -> f64 {
548 let (q, d) = quantize(co, delta, rho);
549 let mut se = 0.0;
550 for ((&c, &v), &k) in co.c.iter().zip(&q).zip(&co.cls) {
551 let r = if v == 0 {
552 0.0
553 } else {
554 let j = k as usize * 3 + (v.unsigned_abs() as usize).min(3) - 1;
555 (v.unsigned_abs() as f64 - d[j] as f64 / 512.0) * delta * (v.signum() as f64)
556 };
557 se += (c - r) * (c - r);
558 }
559 (se / co.c.len().max(1) as f64).sqrt()
560}
561
562fn trial_rmse(x: &[f64], co: &Coeffs, delta: f64, rho: f64, dt: Dtype) -> f64 {
564 let (q, d) = quantize(co, delta, rho);
565 err_stats(x, &cast(&reconstruct(&co.p, &q, delta, &d, co.mean), dt)).0
566}
567
568fn encode_impl(x: &[f64], dims: &[usize], dt: Dtype, cfg: &WavConfig) -> (Vec<u8>, WavStats) {
569 assert_eq!(dims.iter().product::<usize>(), x.len());
570 let range = range_of(x);
571 let co = Coeffs::new(x, dims);
572 let h = Header {
573 dt,
574 dims: dims.to_vec(),
575 lossless: false,
576 };
577 let rmse_target = match cfg.bound {
578 ErrorBound::Rmse(r) => Some(r),
579 ErrorBound::Psnr(db) => Some(range * 10f64.powf(-db / 20.0)),
580 ErrorBound::MaxAbs(_) => None,
581 };
582 let (bytes, mut st) = if let Some(t) = rmse_target {
583 let t = t.max(f64::MIN_POSITIVE);
584 if cfg.fast {
585 let (mut d, mut passes) = fast_step(t, 5, |d| coef_rmse(&co, d, cfg.rho));
587 for _ in 0..3 {
588 let e = trial_rmse(x, &co, d, cfg.rho, dt);
589 passes += 1;
590 if e <= t {
591 break;
592 }
593 d *= 0.99 * t / e;
594 }
595 while trial_rmse(x, &co, d, cfg.rho, dt) > t && passes < 60 {
597 d *= 0.5;
598 passes += 1;
599 }
600 let (b, mut s, _) = encode_at(x, &co, &h, d, cfg, None);
601 s.passes = passes;
602 s.range = range;
603 s.bits_per_value = 8.0 * b.len() as f64 / x.len() as f64;
604 return (b, s);
605 }
606 let mut lo = t * 0.5;
608 let mut hi = t * 8.0;
609 let mut passes = 0;
610 while trial_rmse(x, &co, lo, cfg.rho, dt) > t && passes < 60 {
611 hi = lo;
612 lo *= 0.5;
613 passes += 1;
614 }
615 while trial_rmse(x, &co, hi, cfg.rho, dt) <= t && passes < 60 {
616 lo = hi;
617 hi *= 2.0;
618 passes += 1;
619 }
620 for _ in 0..14 {
621 let mid = (lo * hi).sqrt();
622 if trial_rmse(x, &co, mid, cfg.rho, dt) <= t {
623 lo = mid;
624 } else {
625 hi = mid;
626 }
627 passes += 1;
628 }
629 let (b, mut s, _) = encode_at(x, &co, &h, lo, cfg, None);
630 s.passes = passes;
631 (b, s)
632 } else {
633 let ErrorBound::MaxAbs(b) = cfg.bound else {
634 unreachable!()
635 };
636 if cfg.fast {
637 let k = std::env::var("TC_FAST_KM")
638 .ok()
639 .and_then(|v| v.parse().ok())
640 .unwrap_or(FAST_K_MAXABS);
641 let (bytes, mut s, _) = encode_at(x, &co, &h, k * b, cfg, Some(b));
642 s.passes = 1;
643 s.range = range;
644 s.bits_per_value = 8.0 * bytes.len() as f64 / x.len() as f64;
645 return (bytes, s);
646 }
647 let hand = WavConfig {
650 model: ModelChoice::Hand,
651 ..cfg.clone()
652 };
653 let mut best: Option<(usize, f64)> = None;
654 let mut passes = 0;
655 for f in [1.0, 1.4, 1.8, 2.2, 2.7, 3.3, 4.0, 5.0] {
656 let (bytes, _, _) = encode_at(x, &co, &h, f * b, &hand, Some(b));
657 passes += 1;
658 if best.is_none_or(|(n, _)| bytes.len() < n) {
659 best = Some((bytes.len(), f));
660 }
661 }
662 let (bytes, mut s, _) = encode_at(x, &co, &h, best.unwrap().1 * b, cfg, Some(b));
663 s.passes = passes;
664 (bytes, s)
665 };
666 st.range = range;
667 st.bits_per_value = 8.0 * bytes.len() as f64 / x.len() as f64;
668 (bytes, st)
669}
670
671pub fn encode_f32(x: &[f32], dims: &[usize], cfg: &WavConfig) -> (Vec<u8>, WavStats) {
673 let x: Vec<f64> = x.iter().map(|&v| v as f64).collect();
674 encode_impl(&x, dims, Dtype::F32, cfg)
675}
676
677pub fn encode_f64(x: &[f64], dims: &[usize], cfg: &WavConfig) -> (Vec<u8>, WavStats) {
679 encode_impl(x, dims, Dtype::F64, cfg)
680}
681
682pub fn encode_i32_lossless(x: &[i32], dims: &[usize]) -> Vec<u8> {
684 let p = Pyramid::new(as3(dims), max_levels(dims));
685 let mut c: Vec<i64> = x.iter().map(|&v| v as i64).collect();
686 forward_53(&p, &mut c);
687 let mut w = ByteWriter::new();
688 write_header(
689 &mut w,
690 &Header {
691 dt: Dtype::F32,
692 dims: dims.to_vec(),
693 lossless: true,
694 },
695 );
696 assert!(
698 c.iter().all(|v| v.unsigned_abs() < (1 << 30)),
699 "input range too large for lossless mode"
700 );
701 let q: Vec<i32> = c.iter().map(|&v| v as i32).collect();
702 code_coefs(&p, &q, &ModelChoice::Hand, &mut w);
703 w.buf
704}
705
706pub fn decode_i32_lossless(bytes: &[u8]) -> Result<(Vec<usize>, Vec<i32>), DecodeError> {
708 let mut r = ByteReader::new(bytes);
709 let (_, dims, lossless) = read_header(&mut r)?;
710 if !lossless {
711 return Err(DecodeError::Invalid("not a lossless stream"));
712 }
713 let p = Pyramid::new(as3(&dims), max_levels(&dims));
714 let q = decode_coefs(&p, &mut r)?;
715 let mut c: Vec<i64> = q.iter().map(|&v| v as i64).collect();
716 inverse_53(&p, &mut c);
717 Ok((dims, c.iter().map(|&v| v as i32).collect()))
718}
719
720fn read_header(r: &mut ByteReader) -> Result<(Dtype, Vec<usize>, bool), DecodeError> {
721 if r.take(4)? != MAGIC {
722 return Err(DecodeError::Invalid("magic"));
723 }
724 let dt = match r.u8()? {
725 0 => Dtype::F32,
726 1 => Dtype::F64,
727 _ => return Err(DecodeError::Invalid("dtype")),
728 };
729 let nd = r.varint()? as usize;
730 if nd == 0 || nd > 3 {
731 return Err(DecodeError::Invalid("ndims"));
732 }
733 let mut dims = Vec::with_capacity(nd);
734 for _ in 0..nd {
735 dims.push(r.varint()? as usize);
736 }
737 if dims.contains(&0)
738 || dims
739 .iter()
740 .try_fold(1usize, |a, &d| a.checked_mul(d))
741 .is_none_or(|n| n as u64 > 1 << 34)
742 {
743 return Err(DecodeError::Invalid("dims"));
744 }
745 let lossless = r.u8()? != 0;
746 Ok((dt, dims, lossless))
747}
748
749pub fn decode(bytes: &[u8]) -> Result<Decoded, DecodeError> {
751 let mut r = ByteReader::new(bytes);
752 let (dt, dims, lossless) = read_header(&mut r)?;
753 if lossless {
754 return Err(DecodeError::Invalid(
755 "lossless stream: use decode_i32_lossless",
756 ));
757 }
758 let p = Pyramid::new(as3(&dims), max_levels(&dims));
759 let delta = r.f64()?;
760 let mean = r.f64()?;
761 let mut d = [0u8; NDELTA];
762 d.copy_from_slice(r.take(NDELTA)?);
763 let q = decode_coefs(&p, &mut r)?;
764 let mut y = cast(&reconstruct(&p, &q, delta, &d, mean), dt);
765 if r.u8()? == 1 {
766 let b = r.f64()?;
767 {
768 let buf = r.block()?;
769 let mut k = vec![0i32; p.len()];
770 let mut dd = Decoder::new(buf);
771 code_outliers(p.dims, &mut k, &mut dd);
772 apply_outliers(&mut y, &k, b);
773 y = cast(&y, dt);
774 let n = r.varint()? as usize;
775 let mut i = 0usize;
776 for _ in 0..n {
777 i += r.varint()? as usize;
778 if i >= y.len() {
779 return Err(DecodeError::Invalid("escape index"));
780 }
781 y[i] = match dt {
782 Dtype::F32 => f32::from_le_bytes(r.take(4)?.try_into().unwrap()) as f64,
783 Dtype::F64 => r.f64()?,
784 };
785 }
786 }
787 }
788 Ok(match dt {
789 Dtype::F32 => Decoded::F32 {
790 dims,
791 data: y.iter().map(|&v| v as f32).collect(),
792 },
793 Dtype::F64 => Decoded::F64 { dims, data: y },
794 })
795}
796
797#[cfg(test)]
798mod tests {
799 use super::*;
800
801 fn field(d: [usize; 3]) -> Vec<f32> {
802 let mut v = Vec::new();
803 for i in 0..d[0] {
804 for j in 0..d[1] {
805 for k in 0..d[2] {
806 let (x, y, z) = (
807 i as f64 / d[0] as f64,
808 j as f64 / d[1] as f64,
809 k as f64 / d[2] as f64,
810 );
811 v.push(
812 ((6.0 * x + 3.0 * y).sin() * (4.0 * z).cos()
813 + 0.3 * (20.0 * x * y).sin()
814 + if z > 0.6 { 0.5 } else { 0.0 }) as f32,
815 );
816 }
817 }
818 }
819 v
820 }
821
822 #[test]
823 fn psnr_target_met_and_deterministic() {
824 let d = [20, 33, 47];
825 let x = field(d);
826 for db in [40.0, 70.0, 100.0] {
827 let cfg = WavConfig::new(ErrorBound::Psnr(db));
828 let (bytes, st) = encode_f32(&x, &d, &cfg);
829 let Decoded::F32 { data, .. } = decode(&bytes).unwrap() else {
830 panic!()
831 };
832 let xd: Vec<f64> = x.iter().map(|&v| v as f64).collect();
833 let yd: Vec<f64> = data.iter().map(|&v| v as f64).collect();
834 let (rmse, _) = err_stats(&xd, &yd);
835 assert_eq!(rmse, st.rmse, "decoder matches encoder self-check");
836 assert!(20.0 * (st.range / rmse).log10() >= db - 1e-9);
837 assert_eq!(encode_f32(&x, &d, &cfg).0, bytes);
838 }
839 }
840
841 #[test]
842 fn maxabs_bound_holds() {
843 let d = [16, 24, 40];
844 let x = field(d);
845 let range = 2.6;
846 for rel in [1e-2, 1e-4] {
847 let b = rel * range;
848 let (bytes, st) = encode_f32(&x, &d, &WavConfig::new(ErrorBound::MaxAbs(b)));
849 let Decoded::F32 { data, .. } = decode(&bytes).unwrap() else {
850 panic!()
851 };
852 let mx = x
853 .iter()
854 .zip(&data)
855 .map(|(&a, &v)| (a as f64 - v as f64).abs())
856 .fold(0.0, f64::max);
857 assert!(mx <= b, "{mx} > {b}");
858 assert_eq!(mx, st.max_abs);
859 }
860 }
861
862 #[test]
863 fn compact_round_trips_and_never_grows() {
864 let d = [1, 200, 260];
866 let x = field(d);
867 for b in [
868 ErrorBound::Psnr(45.0),
869 ErrorBound::Psnr(90.0),
870 ErrorBound::MaxAbs(1e-3),
871 ] {
872 let plain = encode_f32(&x, &d, &WavConfig::new(b)).0;
873 let cfg = WavConfig {
874 compact: true,
875 ..WavConfig::new(b)
876 };
877 let (bytes, st) = encode_f32(&x, &d, &cfg);
878 assert!(bytes.len() <= plain.len());
879 let Decoded::F32 { data, .. } = decode(&bytes).unwrap() else {
880 panic!()
881 };
882 let xd: Vec<f64> = x.iter().map(|&v| v as f64).collect();
883 let yd: Vec<f64> = data.iter().map(|&v| v as f64).collect();
884 assert_eq!(err_stats(&xd, &yd), (st.rmse, st.max_abs));
885 }
886 for d in [[1, 1, 3000], [1, 200, 260], [20, 33, 47]] {
888 let x: Vec<f64> = field(d).iter().map(|&v| v as f64).collect();
889 let co = Coeffs::new(&x, &d);
890 let (q, _) = quantize(&co, 0.05, 0.45);
891 let preds = fit_predictors(&co.p, &q);
892 let live = tile_live(&co.p, &q, &preds);
893 let mut e = Encoder::new();
894 let mut qq = q.clone();
895 code_bands_tiled(
896 &co.p,
897 &mut qq,
898 &preds,
899 &mut e,
900 &mut HandModel::default(),
901 Some(&live),
902 );
903 assert_eq!(qq, q);
904 let buf = e.finish();
905 let mut got = vec![0i32; q.len()];
906 let mut dd = Decoder::new(&buf);
907 code_bands_tiled(
908 &co.p,
909 &mut got,
910 &preds,
911 &mut dd,
912 &mut HandModel::default(),
913 None,
914 );
915 assert_eq!(got, q, "{d:?}");
916 }
917 }
918
919 #[test]
920 fn one_d_and_f64() {
921 let x: Vec<f64> = (0..5000)
922 .map(|i| (i as f64 * 0.01).sin() + (i as f64 * 0.37).cos() * 0.1)
923 .collect();
924 let (bytes, st) = encode_f64(&x, &[5000], &WavConfig::new(ErrorBound::Psnr(80.0)));
925 let Decoded::F64 { data, .. } = decode(&bytes).unwrap() else {
926 panic!()
927 };
928 assert_eq!(err_stats(&x, &data).0, st.rmse);
929 }
930
931 #[test]
932 fn lossless_round_trip() {
933 let d = [9, 17, 30];
934 let x: Vec<i32> = field(d)
935 .iter()
936 .enumerate()
937 .map(|(i, &v)| (v * 30000.0) as i32 + (i % 5) as i32)
938 .collect();
939 let bytes = encode_i32_lossless(&x, &d);
940 let (dd, y) = decode_i32_lossless(&bytes).unwrap();
941 assert_eq!(dd, d.to_vec());
942 assert_eq!(y, x);
943 assert!(bytes.len() < x.len() * 4);
944 }
945}