1#![warn(missing_docs)]
49#![allow(clippy::needless_range_loop)]
52
53pub mod abac;
54pub mod bitio;
55pub mod codec;
56pub mod coefcode;
57#[cfg(feature = "gpu")]
58pub mod gpudec;
59pub mod gpuwav;
60pub mod gpuwav3;
61pub mod img;
62#[cfg(feature = "gpu")]
63pub mod imggpu;
64pub mod interp;
65pub mod irdec;
66pub mod kvcodec;
67pub mod kvgpu;
68pub mod learned;
69pub mod linalg;
70pub mod lmodel3;
71pub mod lorenzo;
72pub mod rans;
73pub mod shipdec;
74pub mod tensorize;
75pub mod tt;
76pub mod tucker;
77pub mod wavcodec;
78pub mod wavelet;
79
80pub use codec::{decode, encode_f32, encode_f64, Config, Decoded, Dtype, EncodeStats, ErrorBound};
81pub use tensorize::{DigitOrder, Layout, PadPolicy};
82pub use tt::{tt_svd, TensorTrain, Truncation};
83
84pub fn decode_any(bytes: &[u8]) -> Result<Decoded, bitio::DecodeError> {
87 if let Some(&t) = bytes.first() {
88 if (TAG_WAVELET..=TAG_LORENZO).contains(&t) {
89 return decode_any(&bytes[1..]);
90 }
91 }
92 match bytes.get(..4) {
93 Some(m) if m == wavcodec::MAGIC => wavcodec::decode(bytes),
94 Some(m) if m == tucker::MAGIC => tucker::decode(bytes),
95 Some(m) if m == interp::MAGIC => interp::decode(bytes),
96 Some(m) if m == lorenzo::MAGIC => lorenzo::decode(bytes),
97 _ => codec::decode(bytes),
98 }
99}
100
101pub const TAG_WAVELET: u8 = 0xA0;
103pub const TAG_TUCKER: u8 = 0xA1;
105pub const TAG_QTT: u8 = 0xA2;
107pub const TAG_INTERP: u8 = 0xA3;
109pub const TAG_LORENZO: u8 = 0xA4;
111
112#[derive(Clone, Copy, Debug, PartialEq, Eq)]
114pub enum Candidate {
115 Wavelet,
117 Tucker,
119 Qtt,
121 Interp,
123 QttSparse,
125 Lorenzo,
127}
128
129impl Candidate {
130 pub fn tag(self) -> u8 {
132 match self {
133 Candidate::Wavelet => TAG_WAVELET,
134 Candidate::Tucker => TAG_TUCKER,
135 Candidate::Qtt => TAG_QTT,
136 Candidate::Interp => TAG_INTERP,
137 Candidate::QttSparse => TAG_QTT,
138 Candidate::Lorenzo => TAG_LORENZO,
139 }
140 }
141 pub fn name(self) -> &'static str {
143 match self {
144 Candidate::Wavelet => "wavelet",
145 Candidate::Tucker => "tucker",
146 Candidate::Qtt => "qtt",
147 Candidate::Interp => "interp",
148 Candidate::QttSparse => "qtt-sparse",
149 Candidate::Lorenzo => "lorenzo",
150 }
151 }
152}
153
154#[derive(Clone, Debug)]
156pub struct AutoReport {
157 pub pick: Candidate,
159 pub tried: Vec<(Candidate, usize, f64)>,
161 pub skipped: Vec<Candidate>,
163 pub probe_rank: usize,
165}
166
167pub fn decimate(x: &[f32], dims: &[usize], stride: usize) -> (Vec<f32>, Vec<usize>) {
170 let st: Vec<usize> = dims
171 .iter()
172 .map(|&d| if d >= 4 * stride { stride } else { 1 })
173 .collect();
174 let nd: Vec<usize> = dims.iter().zip(&st).map(|(&d, &s)| d.div_ceil(s)).collect();
175 let n: usize = nd.iter().product();
176 let mut out = Vec::with_capacity(n);
177 let mut idx = vec![0usize; dims.len()];
178 for _ in 0..n {
179 let mut off = 0;
180 for (k, &i) in idx.iter().enumerate() {
181 off = off * dims[k] + i * st[k];
182 }
183 out.push(x[off]);
184 for k in (0..dims.len()).rev() {
185 idx[k] += 1;
186 if idx[k] < nd[k] {
187 break;
188 }
189 idx[k] = 0;
190 }
191 }
192 (out, nd)
193}
194
195pub fn qtt_probe(
199 x: &[f32],
200 dims: &[usize],
201 bound: codec::ErrorBound,
202 stride: usize,
203 cap: usize,
204) -> (usize, usize, f64) {
205 let t = std::time::Instant::now();
206 let b = match bound {
207 codec::ErrorBound::Psnr(p) => {
208 let (lo, hi) = x
209 .iter()
210 .fold((f32::INFINITY, f32::NEG_INFINITY), |(a, b), &v| {
211 (a.min(v), b.max(v))
212 });
213 codec::ErrorBound::Rmse((hi - lo) as f64 / 10f64.powf(p / 20.0))
214 }
215 b => b,
216 };
217 let (y, nd) = decimate(x, dims, stride);
218 let mut q = codec::Config::new(b);
219 q.order = DigitOrder::RowMajor;
220 q.max_rank = cap;
221 let (s, st) = codec::encode_f32(&y, &nd, &q);
222 let r = st.ranks.iter().copied().max().unwrap_or(0);
223 (s.len(), r, t.elapsed().as_secs_f64())
224}
225
226pub const QTT_PROBE_CAP: usize = 64;
229
230pub fn prescreen(
248 x: &[f32],
249 dims: &[usize],
250 bound: codec::ErrorBound,
251) -> (Vec<Candidate>, Vec<Candidate>, usize) {
252 let maxabs = matches!(bound, codec::ErrorBound::MaxAbs(_));
253 let multi = dims.iter().filter(|&&d| d > 1).count() >= 2;
254 let (_, rank, _) = qtt_probe(x, dims, bound, 2, QTT_PROBE_CAP);
255 let mut keep = vec![Candidate::Wavelet];
256 let mut skip = vec![];
257 if !maxabs && multi {
258 keep.push(Candidate::Tucker);
259 }
260 keep.push(Candidate::Interp);
261 keep.push(Candidate::Lorenzo);
262 if maxabs {
263 keep.push(Candidate::QttSparse);
264 }
265 if rank < QTT_PROBE_CAP && (!maxabs || rank > 1) {
266 keep.push(Candidate::Qtt);
267 } else {
268 skip.push(Candidate::Qtt);
269 }
270 (keep, skip, rank)
271}
272
273pub fn encode_candidate_f32(
275 c: Candidate,
276 x: &[f32],
277 dims: &[usize],
278 cfg: &wavcodec::WavConfig,
279) -> Vec<u8> {
280 match c {
281 Candidate::Wavelet => {
282 let cfg = wavcodec::WavConfig {
283 compact: true,
284 ..cfg.clone()
285 };
286 wavcodec::encode_f32(x, dims, &cfg).0
287 }
288 Candidate::Tucker => tucker::encode_f32(x, dims, cfg.bound).0,
289 Candidate::Qtt => {
290 let mut q = codec::Config::new(cfg.bound);
291 q.order = DigitOrder::RowMajor;
292 codec::encode_f32(x, dims, &q).0
293 }
294 Candidate::Interp => match cfg.bound {
295 codec::ErrorBound::MaxAbs(b) => interp::encode_f32(x, dims, b),
296 codec::ErrorBound::Rmse(r) => interp::encode_rmse_f32(x, dims, r),
297 codec::ErrorBound::Psnr(p) => interp::encode_psnr_f32(x, dims, p),
298 },
299 Candidate::QttSparse => {
300 let mut q = codec::Config::new(cfg.bound);
301 q.order = DigitOrder::RowMajor;
302 q.residual_only = true;
303 codec::encode_f32(x, dims, &q).0
304 }
305 Candidate::Lorenzo => match cfg.bound {
306 codec::ErrorBound::MaxAbs(b) => lorenzo::encode_f32(x, dims, b),
307 codec::ErrorBound::Rmse(r) => lorenzo::encode_rmse_f32(x, dims, r),
308 codec::ErrorBound::Psnr(p) => lorenzo::encode_psnr_f32(x, dims, p),
309 },
310 }
311}
312
313pub fn encode_portfolio_f32(
317 x: &[f32],
318 dims: &[usize],
319 cfg: &wavcodec::WavConfig,
320 screen: bool,
321) -> (Vec<u8>, AutoReport) {
322 let (mut keep, mut skip, probe_rank) = prescreen(x, dims, cfg.bound);
323 if !screen {
324 keep.append(&mut skip);
325 }
326 let mut best: Option<(Candidate, Vec<u8>)> = None;
327 let mut tried = vec![];
328 for c in keep {
329 let t = std::time::Instant::now();
330 let Ok(s) = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
334 encode_candidate_f32(c, x, dims, cfg)
335 })) else {
336 skip.push(c);
337 continue;
338 };
339 tried.push((c, s.len(), t.elapsed().as_secs_f64()));
340 if best.as_ref().is_none_or(|(_, b)| s.len() < b.len()) {
341 best = Some((c, s));
342 }
343 }
344 let (pick, body) = best.unwrap();
345 let mut out = Vec::with_capacity(body.len() + 1);
346 out.push(pick.tag());
347 out.extend(body);
348 (
349 out,
350 AutoReport {
351 pick,
352 tried,
353 skipped: skip,
354 probe_rank,
355 },
356 )
357}
358
359pub fn center_crop(x: &[f32], dims: &[usize]) -> (Vec<f32>, Vec<usize>) {
362 let f = if dims.iter().filter(|&&d| d > 1).count() == 1 {
363 8
364 } else {
365 2
366 };
367 let nd: Vec<usize> = dims
368 .iter()
369 .map(|&d| if d >= 16 { d / f } else { d })
370 .collect();
371 let lo: Vec<usize> = dims.iter().zip(&nd).map(|(&d, &n)| (d - n) / 2).collect();
372 let n: usize = nd.iter().product();
373 let mut out = Vec::with_capacity(n);
374 let mut idx = vec![0usize; dims.len()];
375 for _ in 0..n {
376 let mut off = 0;
377 for k in 0..dims.len() {
378 off = off * dims[k] + lo[k] + idx[k];
379 }
380 out.push(x[off]);
381 for k in (0..dims.len()).rev() {
382 idx[k] += 1;
383 if idx[k] < nd[k] {
384 break;
385 }
386 idx[k] = 0;
387 }
388 }
389 (out, nd)
390}
391
392pub fn fast_pick(x: &[f32], dims: &[usize], cfg: &wavcodec::WavConfig) -> Candidate {
397 let bound = match cfg.bound {
398 codec::ErrorBound::Psnr(p) => {
399 let (lo, hi) = x
400 .iter()
401 .fold((f32::INFINITY, f32::NEG_INFINITY), |(a, b), &v| {
402 (a.min(v), b.max(v))
403 });
404 codec::ErrorBound::Rmse((hi - lo) as f64 / 10f64.powf(p / 20.0))
405 }
406 b => b,
407 };
408 let three = dims.iter().filter(|&&d| d > 1).count() == 3;
409 let cands: Vec<Candidate> = match bound {
410 codec::ErrorBound::MaxAbs(_) => {
411 vec![
412 Candidate::Wavelet,
413 Candidate::Interp,
414 Candidate::Lorenzo,
415 Candidate::QttSparse,
416 ]
417 }
418 _ if three => vec![Candidate::Wavelet, Candidate::Tucker],
419 _ => return Candidate::Wavelet,
420 };
421 let (y, nd) = center_crop(x, dims);
422 let c = wavcodec::WavConfig {
423 bound,
424 ..cfg.clone()
425 };
426 cands
427 .into_iter()
428 .min_by_key(|&k| try_fast(k, &y, &nd, &c).map_or(usize::MAX, |s| s.len()))
429 .unwrap()
430}
431
432fn try_fast(c: Candidate, x: &[f32], dims: &[usize], cfg: &wavcodec::WavConfig) -> Option<Vec<u8>> {
435 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
436 encode_candidate_fast_f32(c, x, dims, cfg)
437 }))
438 .ok()
439}
440
441pub fn encode_candidate_fast_f32(
445 c: Candidate,
446 x: &[f32],
447 dims: &[usize],
448 cfg: &wavcodec::WavConfig,
449) -> Vec<u8> {
450 match c {
451 Candidate::Wavelet => {
452 let cfg = wavcodec::WavConfig {
453 fast: true,
454 compact: true,
455 ..cfg.clone()
456 };
457 wavcodec::encode_f32(x, dims, &cfg).0
458 }
459 Candidate::Tucker => tucker::encode_fast_f32(x, dims, cfg.bound).0,
460 Candidate::Interp => match cfg.bound {
461 codec::ErrorBound::MaxAbs(b) => interp::encode_fast_f32(x, dims, b),
462 _ => panic!("interp codes max-abs bounds only"),
463 },
464 Candidate::Lorenzo => match cfg.bound {
465 codec::ErrorBound::MaxAbs(b) => lorenzo::encode_f32(x, dims, b),
466 _ => panic!("fast Lorenzo codes max-abs bounds only"),
467 },
468 Candidate::Qtt | Candidate::QttSparse => encode_candidate_f32(c, x, dims, cfg),
469 }
470}
471
472pub fn encode_fast_f32(
476 x: &[f32],
477 dims: &[usize],
478 cfg: &wavcodec::WavConfig,
479) -> (Vec<u8>, Candidate) {
480 let pick = match std::env::var("TC_FAST_PICK").ok().as_deref() {
481 Some("wavelet") => Candidate::Wavelet,
482 Some("tucker") => Candidate::Tucker,
483 Some("interp") => Candidate::Interp,
484 Some("lorenzo") => Candidate::Lorenzo,
485 _ => fast_pick(x, dims, cfg),
486 };
487 let (pick, body) = match try_fast(pick, x, dims, cfg) {
489 Some(b) => (pick, b),
490 None => (
491 Candidate::Wavelet,
492 encode_candidate_fast_f32(Candidate::Wavelet, x, dims, cfg),
493 ),
494 };
495 let mut out = Vec::with_capacity(body.len() + 1);
496 out.push(pick.tag());
497 out.extend(body);
498 (out, pick)
499}
500
501pub fn encode_auto_f32(
504 x: &[f32],
505 dims: &[usize],
506 cfg: &wavcodec::WavConfig,
507) -> (Vec<u8>, Candidate) {
508 let (b, r) = encode_portfolio_f32(x, dims, cfg, true);
509 (b, r.pick)
510}
511
512#[cfg(test)]
513mod auto_tests {
514 use super::*;
515
516 #[test]
517 fn auto_round_trips_and_keeps_the_smaller() {
518 let d = [16, 24, 20];
519 let x: Vec<f32> = (0..d.iter().product::<usize>())
520 .map(|i| ((i % 20) as f32 * 0.3).sin() + ((i / 480) as f32 * 0.2).cos())
521 .collect();
522 for b in [
523 codec::ErrorBound::Psnr(70.0),
524 codec::ErrorBound::MaxAbs(1e-3),
525 ] {
526 let cfg = wavcodec::WavConfig::new(b);
527 let (bytes, _) = encode_auto_f32(&x, &d, &cfg);
528 let w = wavcodec::encode_f32(&x, &d, &cfg).0.len();
529 assert!(bytes.len() <= w + 1);
530 let Decoded::F32 { data, .. } = decode_any(&bytes).unwrap() else {
531 panic!()
532 };
533 assert_eq!(data.len(), x.len());
534 if let codec::ErrorBound::MaxAbs(e) = b {
535 assert!(x
536 .iter()
537 .zip(&data)
538 .all(|(a, c)| ((a - c) as f64).abs() <= e));
539 }
540 }
541 }
542}