#![warn(missing_docs)]
#![allow(clippy::needless_range_loop)]
pub mod abac;
pub mod bitio;
pub mod codec;
pub mod coefcode;
#[cfg(feature = "gpu")]
pub mod gpudec;
pub mod gpuwav;
pub mod gpuwav3;
pub mod img;
#[cfg(feature = "gpu")]
pub mod imggpu;
pub mod interp;
pub mod irdec;
pub mod kvcodec;
pub mod kvgpu;
pub mod learned;
pub mod linalg;
pub mod lmodel3;
pub mod lorenzo;
pub mod rans;
pub mod shipdec;
pub mod tensorize;
pub mod tt;
pub mod tucker;
pub mod wavcodec;
pub mod wavelet;
pub use codec::{decode, encode_f32, encode_f64, Config, Decoded, Dtype, EncodeStats, ErrorBound};
pub use tensorize::{DigitOrder, Layout, PadPolicy};
pub use tt::{tt_svd, TensorTrain, Truncation};
pub fn decode_any(bytes: &[u8]) -> Result<Decoded, bitio::DecodeError> {
if let Some(&t) = bytes.first() {
if (TAG_WAVELET..=TAG_LORENZO).contains(&t) {
return decode_any(&bytes[1..]);
}
}
match bytes.get(..4) {
Some(m) if m == wavcodec::MAGIC => wavcodec::decode(bytes),
Some(m) if m == tucker::MAGIC => tucker::decode(bytes),
Some(m) if m == interp::MAGIC => interp::decode(bytes),
Some(m) if m == lorenzo::MAGIC => lorenzo::decode(bytes),
_ => codec::decode(bytes),
}
}
pub const TAG_WAVELET: u8 = 0xA0;
pub const TAG_TUCKER: u8 = 0xA1;
pub const TAG_QTT: u8 = 0xA2;
pub const TAG_INTERP: u8 = 0xA3;
pub const TAG_LORENZO: u8 = 0xA4;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Candidate {
Wavelet,
Tucker,
Qtt,
Interp,
QttSparse,
Lorenzo,
}
impl Candidate {
pub fn tag(self) -> u8 {
match self {
Candidate::Wavelet => TAG_WAVELET,
Candidate::Tucker => TAG_TUCKER,
Candidate::Qtt => TAG_QTT,
Candidate::Interp => TAG_INTERP,
Candidate::QttSparse => TAG_QTT,
Candidate::Lorenzo => TAG_LORENZO,
}
}
pub fn name(self) -> &'static str {
match self {
Candidate::Wavelet => "wavelet",
Candidate::Tucker => "tucker",
Candidate::Qtt => "qtt",
Candidate::Interp => "interp",
Candidate::QttSparse => "qtt-sparse",
Candidate::Lorenzo => "lorenzo",
}
}
}
#[derive(Clone, Debug)]
pub struct AutoReport {
pub pick: Candidate,
pub tried: Vec<(Candidate, usize, f64)>,
pub skipped: Vec<Candidate>,
pub probe_rank: usize,
}
pub fn decimate(x: &[f32], dims: &[usize], stride: usize) -> (Vec<f32>, Vec<usize>) {
let st: Vec<usize> = dims
.iter()
.map(|&d| if d >= 4 * stride { stride } else { 1 })
.collect();
let nd: Vec<usize> = dims.iter().zip(&st).map(|(&d, &s)| d.div_ceil(s)).collect();
let n: usize = nd.iter().product();
let mut out = Vec::with_capacity(n);
let mut idx = vec![0usize; dims.len()];
for _ in 0..n {
let mut off = 0;
for (k, &i) in idx.iter().enumerate() {
off = off * dims[k] + i * st[k];
}
out.push(x[off]);
for k in (0..dims.len()).rev() {
idx[k] += 1;
if idx[k] < nd[k] {
break;
}
idx[k] = 0;
}
}
(out, nd)
}
pub fn qtt_probe(
x: &[f32],
dims: &[usize],
bound: codec::ErrorBound,
stride: usize,
cap: usize,
) -> (usize, usize, f64) {
let t = std::time::Instant::now();
let b = match bound {
codec::ErrorBound::Psnr(p) => {
let (lo, hi) = x
.iter()
.fold((f32::INFINITY, f32::NEG_INFINITY), |(a, b), &v| {
(a.min(v), b.max(v))
});
codec::ErrorBound::Rmse((hi - lo) as f64 / 10f64.powf(p / 20.0))
}
b => b,
};
let (y, nd) = decimate(x, dims, stride);
let mut q = codec::Config::new(b);
q.order = DigitOrder::RowMajor;
q.max_rank = cap;
let (s, st) = codec::encode_f32(&y, &nd, &q);
let r = st.ranks.iter().copied().max().unwrap_or(0);
(s.len(), r, t.elapsed().as_secs_f64())
}
pub const QTT_PROBE_CAP: usize = 64;
pub fn prescreen(
x: &[f32],
dims: &[usize],
bound: codec::ErrorBound,
) -> (Vec<Candidate>, Vec<Candidate>, usize) {
let maxabs = matches!(bound, codec::ErrorBound::MaxAbs(_));
let multi = dims.iter().filter(|&&d| d > 1).count() >= 2;
let (_, rank, _) = qtt_probe(x, dims, bound, 2, QTT_PROBE_CAP);
let mut keep = vec![Candidate::Wavelet];
let mut skip = vec![];
if !maxabs && multi {
keep.push(Candidate::Tucker);
}
keep.push(Candidate::Interp);
keep.push(Candidate::Lorenzo);
if maxabs {
keep.push(Candidate::QttSparse);
}
if rank < QTT_PROBE_CAP && (!maxabs || rank > 1) {
keep.push(Candidate::Qtt);
} else {
skip.push(Candidate::Qtt);
}
(keep, skip, rank)
}
pub fn encode_candidate_f32(
c: Candidate,
x: &[f32],
dims: &[usize],
cfg: &wavcodec::WavConfig,
) -> Vec<u8> {
match c {
Candidate::Wavelet => {
let cfg = wavcodec::WavConfig {
compact: true,
..cfg.clone()
};
wavcodec::encode_f32(x, dims, &cfg).0
}
Candidate::Tucker => tucker::encode_f32(x, dims, cfg.bound).0,
Candidate::Qtt => {
let mut q = codec::Config::new(cfg.bound);
q.order = DigitOrder::RowMajor;
codec::encode_f32(x, dims, &q).0
}
Candidate::Interp => match cfg.bound {
codec::ErrorBound::MaxAbs(b) => interp::encode_f32(x, dims, b),
codec::ErrorBound::Rmse(r) => interp::encode_rmse_f32(x, dims, r),
codec::ErrorBound::Psnr(p) => interp::encode_psnr_f32(x, dims, p),
},
Candidate::QttSparse => {
let mut q = codec::Config::new(cfg.bound);
q.order = DigitOrder::RowMajor;
q.residual_only = true;
codec::encode_f32(x, dims, &q).0
}
Candidate::Lorenzo => match cfg.bound {
codec::ErrorBound::MaxAbs(b) => lorenzo::encode_f32(x, dims, b),
codec::ErrorBound::Rmse(r) => lorenzo::encode_rmse_f32(x, dims, r),
codec::ErrorBound::Psnr(p) => lorenzo::encode_psnr_f32(x, dims, p),
},
}
}
pub fn encode_portfolio_f32(
x: &[f32],
dims: &[usize],
cfg: &wavcodec::WavConfig,
screen: bool,
) -> (Vec<u8>, AutoReport) {
let (mut keep, mut skip, probe_rank) = prescreen(x, dims, cfg.bound);
if !screen {
keep.append(&mut skip);
}
let mut best: Option<(Candidate, Vec<u8>)> = None;
let mut tried = vec![];
for c in keep {
let t = std::time::Instant::now();
let Ok(s) = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
encode_candidate_f32(c, x, dims, cfg)
})) else {
skip.push(c);
continue;
};
tried.push((c, s.len(), t.elapsed().as_secs_f64()));
if best.as_ref().is_none_or(|(_, b)| s.len() < b.len()) {
best = Some((c, s));
}
}
let (pick, body) = best.unwrap();
let mut out = Vec::with_capacity(body.len() + 1);
out.push(pick.tag());
out.extend(body);
(
out,
AutoReport {
pick,
tried,
skipped: skip,
probe_rank,
},
)
}
pub fn center_crop(x: &[f32], dims: &[usize]) -> (Vec<f32>, Vec<usize>) {
let f = if dims.iter().filter(|&&d| d > 1).count() == 1 {
8
} else {
2
};
let nd: Vec<usize> = dims
.iter()
.map(|&d| if d >= 16 { d / f } else { d })
.collect();
let lo: Vec<usize> = dims.iter().zip(&nd).map(|(&d, &n)| (d - n) / 2).collect();
let n: usize = nd.iter().product();
let mut out = Vec::with_capacity(n);
let mut idx = vec![0usize; dims.len()];
for _ in 0..n {
let mut off = 0;
for k in 0..dims.len() {
off = off * dims[k] + lo[k] + idx[k];
}
out.push(x[off]);
for k in (0..dims.len()).rev() {
idx[k] += 1;
if idx[k] < nd[k] {
break;
}
idx[k] = 0;
}
}
(out, nd)
}
pub fn fast_pick(x: &[f32], dims: &[usize], cfg: &wavcodec::WavConfig) -> Candidate {
let bound = match cfg.bound {
codec::ErrorBound::Psnr(p) => {
let (lo, hi) = x
.iter()
.fold((f32::INFINITY, f32::NEG_INFINITY), |(a, b), &v| {
(a.min(v), b.max(v))
});
codec::ErrorBound::Rmse((hi - lo) as f64 / 10f64.powf(p / 20.0))
}
b => b,
};
let three = dims.iter().filter(|&&d| d > 1).count() == 3;
let cands: Vec<Candidate> = match bound {
codec::ErrorBound::MaxAbs(_) => {
vec![
Candidate::Wavelet,
Candidate::Interp,
Candidate::Lorenzo,
Candidate::QttSparse,
]
}
_ if three => vec![Candidate::Wavelet, Candidate::Tucker],
_ => return Candidate::Wavelet,
};
let (y, nd) = center_crop(x, dims);
let c = wavcodec::WavConfig {
bound,
..cfg.clone()
};
cands
.into_iter()
.min_by_key(|&k| try_fast(k, &y, &nd, &c).map_or(usize::MAX, |s| s.len()))
.unwrap()
}
fn try_fast(c: Candidate, x: &[f32], dims: &[usize], cfg: &wavcodec::WavConfig) -> Option<Vec<u8>> {
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
encode_candidate_fast_f32(c, x, dims, cfg)
}))
.ok()
}
pub fn encode_candidate_fast_f32(
c: Candidate,
x: &[f32],
dims: &[usize],
cfg: &wavcodec::WavConfig,
) -> Vec<u8> {
match c {
Candidate::Wavelet => {
let cfg = wavcodec::WavConfig {
fast: true,
compact: true,
..cfg.clone()
};
wavcodec::encode_f32(x, dims, &cfg).0
}
Candidate::Tucker => tucker::encode_fast_f32(x, dims, cfg.bound).0,
Candidate::Interp => match cfg.bound {
codec::ErrorBound::MaxAbs(b) => interp::encode_fast_f32(x, dims, b),
_ => panic!("interp codes max-abs bounds only"),
},
Candidate::Lorenzo => match cfg.bound {
codec::ErrorBound::MaxAbs(b) => lorenzo::encode_f32(x, dims, b),
_ => panic!("fast Lorenzo codes max-abs bounds only"),
},
Candidate::Qtt | Candidate::QttSparse => encode_candidate_f32(c, x, dims, cfg),
}
}
pub fn encode_fast_f32(
x: &[f32],
dims: &[usize],
cfg: &wavcodec::WavConfig,
) -> (Vec<u8>, Candidate) {
let pick = match std::env::var("TC_FAST_PICK").ok().as_deref() {
Some("wavelet") => Candidate::Wavelet,
Some("tucker") => Candidate::Tucker,
Some("interp") => Candidate::Interp,
Some("lorenzo") => Candidate::Lorenzo,
_ => fast_pick(x, dims, cfg),
};
let (pick, body) = match try_fast(pick, x, dims, cfg) {
Some(b) => (pick, b),
None => (
Candidate::Wavelet,
encode_candidate_fast_f32(Candidate::Wavelet, x, dims, cfg),
),
};
let mut out = Vec::with_capacity(body.len() + 1);
out.push(pick.tag());
out.extend(body);
(out, pick)
}
pub fn encode_auto_f32(
x: &[f32],
dims: &[usize],
cfg: &wavcodec::WavConfig,
) -> (Vec<u8>, Candidate) {
let (b, r) = encode_portfolio_f32(x, dims, cfg, true);
(b, r.pick)
}
#[cfg(test)]
mod auto_tests {
use super::*;
#[test]
fn auto_round_trips_and_keeps_the_smaller() {
let d = [16, 24, 20];
let x: Vec<f32> = (0..d.iter().product::<usize>())
.map(|i| ((i % 20) as f32 * 0.3).sin() + ((i / 480) as f32 * 0.2).cos())
.collect();
for b in [
codec::ErrorBound::Psnr(70.0),
codec::ErrorBound::MaxAbs(1e-3),
] {
let cfg = wavcodec::WavConfig::new(b);
let (bytes, _) = encode_auto_f32(&x, &d, &cfg);
let w = wavcodec::encode_f32(&x, &d, &cfg).0.len();
assert!(bytes.len() <= w + 1);
let Decoded::F32 { data, .. } = decode_any(&bytes).unwrap() else {
panic!()
};
assert_eq!(data.len(), x.len());
if let codec::ErrorBound::MaxAbs(e) = b {
assert!(x
.iter()
.zip(&data)
.all(|(a, c)| ((a - c) as f64).abs() <= e));
}
}
}
}