1use crate::abac::{Decoder, Encoder, UintModel, ONE};
29use crate::bitio::{ByteReader, ByteWriter, DecodeError};
30use crate::codec::{Decoded, ErrorBound};
31use crate::coefcode::{code_bands, fit_predictors, read_preds, write_preds, Bits, HandModel};
32use crate::linalg::{
33 cholesky, gram_cols, gram_rows, matmul, matmul_tn, solve_lower, solve_lower_t, sym_eig_desc,
34 transpose,
35};
36use crate::wavelet::{as3, Pyramid, MAX_LEVELS};
37
38pub const MAGIC: &[u8; 4] = b"TCT1";
40
41const RHO: f64 = 0.45;
42const PHI: f64 = 1.0;
44
45#[derive(Clone, Debug, Default)]
47pub struct TuckerStats {
48 pub core_bytes: usize,
50 pub factor_bytes: usize,
52 pub ranks: Vec<usize>,
54 pub delta: f64,
56 pub rmse: f64,
58 pub max_abs: f64,
60}
61
62fn mode_mul(x: &[f64], d: [usize; 3], k: usize, m: &[f64], r: usize) -> (Vec<f64>, [usize; 3]) {
65 let n = d[k];
66 let mut od = d;
67 od[k] = r;
68 let y = match k {
69 0 => matmul_tn(m, x, n, r, d[1] * d[2]),
70 2 => matmul(x, m, d[0] * d[1], n, r),
71 _ => {
72 let mut y = Vec::with_capacity(d[0] * r * d[2]);
73 for i in 0..d[0] {
74 let s = &x[i * d[1] * d[2]..(i + 1) * d[1] * d[2]];
75 y.extend(matmul_tn(m, s, n, r, d[2]));
76 }
77 y
78 }
79 };
80 (y, od)
81}
82
83fn mode_mul_t(x: &[f64], d: [usize; 3], k: usize, m: &[f64], n: usize) -> (Vec<f64>, [usize; 3]) {
86 let r = d[k];
87 let mt = transpose(m, n, r);
88 mode_mul(x, d, k, &mt, n)
89}
90
91fn mode_gram(x: &[f64], d: [usize; 3], k: usize) -> Vec<f64> {
92 match k {
93 0 => gram_rows(x, d[0], d[1] * d[2]),
94 2 => gram_cols(x, d[0] * d[1], d[2]),
95 _ => {
96 let mut g = vec![0.0; d[1] * d[1]];
97 for i in 0..d[0] {
98 let s = &x[i * d[1] * d[2]..(i + 1) * d[1] * d[2]];
99 for (a, b) in g.iter_mut().zip(gram_rows(s, d[1], d[2])) {
100 *a += b;
101 }
102 }
103 g
104 }
105 }
106}
107
108fn slice_norms(c: &[f64], d: [usize; 3], k: usize) -> Vec<f64> {
110 let mut s = vec![0.0; d[k]];
111 for i in 0..d[0] {
112 for j in 0..d[1] {
113 for l in 0..d[2] {
114 let v = c[(i * d[1] + j) * d[2] + l];
115 s[[i, j, l][k]] += v * v;
116 }
117 }
118 }
119 s.iter().map(|v| v.sqrt()).collect()
120}
121
122fn quantize_core(c: &[f64], delta: f64) -> (Vec<i32>, [u8; 2]) {
123 let inv = 1.0 / delta;
124 let (mut s, mut n) = ([0.0f64; 2], [0u64; 2]);
125 let q = c
126 .iter()
127 .map(|&v| {
128 let a = v.abs() * inv;
129 let m = (a + RHO).floor().min(1e9);
130 if m < 1.0 {
131 return 0;
132 }
133 let j = (m >= 2.0) as usize;
134 s[j] += m - a;
135 n[j] += 1;
136 if v < 0.0 {
137 -(m as i32)
138 } else {
139 m as i32
140 }
141 })
142 .collect();
143 let d = [0, 1].map(|j| {
144 if n[j] > 0 {
145 ((s[j] / n[j] as f64).clamp(0.0, 0.498) * 512.0).round() as u8
146 } else {
147 0
148 }
149 });
150 (q, d)
151}
152
153fn dequantize_core(q: &[i32], delta: f64, d: [u8; 2]) -> Vec<f64> {
154 q.iter()
155 .map(|&v| {
156 if v == 0 {
157 return 0.0;
158 }
159 let m = v.unsigned_abs() as f64 - d[(v.unsigned_abs() >= 2) as usize] as f64 / 512.0;
160 if v < 0 {
161 -m * delta
162 } else {
163 m * delta
164 }
165 })
166 .collect()
167}
168
169#[derive(Clone, Debug)]
171struct QFactor {
172 n: usize,
173 r: usize,
174 exps: Vec<i32>,
175 q: Vec<i64>, }
177
178impl QFactor {
179 fn quantize(u: &[f64], n: usize, full_r: usize, r: usize, norms: &[f64], delta: f64) -> Self {
180 let mut exps = Vec::with_capacity(r);
181 let mut q = Vec::with_capacity(n * r);
182 for j in 0..r {
183 let s = PHI * delta / norms[j].max(1e-300);
184 let e = (4.0 * s.log2()).round().clamp(-4000.0, 4000.0) as i32;
185 let s = 2f64.powf(e as f64 / 4.0);
186 exps.push(e);
187 for i in 0..n {
188 q.push((u[i * full_r + j] / s).round().clamp(-4e15, 4e15) as i64);
189 }
190 }
191 Self { n, r, exps, q }
192 }
193 fn dequantize(&self) -> Vec<f64> {
194 let mut u = vec![0.0; self.n * self.r];
195 for j in 0..self.r {
196 let s = 2f64.powf(self.exps[j] as f64 / 4.0);
197 for i in 0..self.n {
198 u[i * self.r + j] = self.q[j * self.n + i] as f64 * s;
199 }
200 }
201 for j in 0..self.r {
203 if (0..self.n).all(|i| u[i * self.r + j] == 0.0) {
204 u[(j % self.n) * self.r + j] = 2f64.powf(self.exps[j] as f64 / 4.0);
205 }
206 }
207 u
208 }
209}
210
211#[inline]
212fn dpcm_pred(c: &[i64], i: usize, order: usize) -> i64 {
213 match (order, i) {
214 (0, _) | (_, 0) => 0,
215 (1, _) | (2, 1) => c[i - 1],
216 _ => 2 * c[i - 1] - c[i - 2],
217 }
218}
219
220fn zz(v: i64) -> u64 {
221 ((v << 1) ^ (v >> 63)) as u64
222}
223
224fn unzz(u: u64) -> i64 {
225 ((u >> 1) as i64) ^ -((u & 1) as i64)
226}
227
228fn code_factor<B: Bits>(b: &mut B, f: &mut QFactor, km: &mut UintModel, hm: &mut UintModel) {
229 for j in 0..f.r {
230 let col = f.q[j * f.n..(j + 1) * f.n].to_vec();
231 let res_of = |o: usize| -> Vec<u64> {
233 (0..col.len())
234 .map(|i| zz(col[i] - dpcm_pred(&col, i, o)))
235 .collect()
236 };
237 let bo = (0..3)
238 .min_by_key(|&o| res_of(o).iter().sum::<u64>())
239 .unwrap();
240 let res = res_of(bo);
241 let mean = res.iter().sum::<u64>() / f.n.max(1) as u64;
242 let k = if mean > 2 { (mean / 2).ilog2() } else { 0 };
243 let o =
244 b.bitp(bo & 1 == 1, ONE / 2) as usize | (b.bitp(bo & 2 == 2, ONE / 2) as usize) << 1;
245 let k = b.uint(k as u64, km) as u32;
246 if k > 62 || o > 2 {
247 return; }
249 let mut out: Vec<i64> = Vec::with_capacity(f.n);
250 for i in 0..f.n {
251 let u = res[i];
252 let hi = b.uint(u >> k, hm);
253 let mut lo = 0u64;
254 for t in (0..k).rev() {
255 lo = (lo << 1) | b.bitp((u >> t) & 1 == 1, ONE / 2) as u64;
256 }
257 let v = unzz((hi << k) | lo) + dpcm_pred(&out, i, o);
258 out.push(v);
259 }
260 f.q[j * f.n..(j + 1) * f.n].copy_from_slice(&out);
261 }
262}
263
264fn ls_core(x: &[f64], d: [usize; 3], us: &[Vec<f64>], rs: [usize; 3]) -> Vec<f64> {
266 let mut c = x.to_vec();
267 let mut cd = d;
268 for k in 0..3 {
269 let (n, r) = (d[k], rs[k]);
270 let g = matmul_tn(&us[k], &us[k], n, r, r);
271 let tr = (0..r).map(|i| g[i * r + i]).sum::<f64>() / r as f64;
275 let l = cholesky(&g, r)
276 .or_else(|| {
277 (1..=12).find_map(|e| {
278 let mut gg = g.clone();
279 for i in 0..r {
280 gg[i * r + i] += tr * 10f64.powi(e - 16);
281 }
282 cholesky(&gg, r)
283 })
284 })
285 .expect("factor Gram not SPD even with ridge");
286 let mut z = transpose(&us[k], n, r);
288 solve_lower(&l, &mut z, r, n);
289 solve_lower_t(&l, &mut z, r, n);
290 let m = transpose(&z, r, n);
291 let (y, yd) = mode_mul(&c, cd, k, &m, r);
292 c = y;
293 cd = yd;
294 }
295 c
296}
297
298fn reconstruct(core: &[f64], rs: [usize; 3], us: &[Vec<f64>], d: [usize; 3]) -> Vec<f64> {
299 let mut y = core.to_vec();
300 let mut yd = rs;
301 for k in 0..3 {
302 let (z, zd) = mode_mul_t(&y, yd, k, &us[k], d[k]);
303 y = z;
304 yd = zd;
305 }
306 y
307}
308
309struct Hosvd {
310 d: [usize; 3],
311 us: Vec<Vec<f64>>, core: Vec<f64>,
313}
314
315fn hosvd(x: &[f64], d: [usize; 3]) -> Hosvd {
316 let us: Vec<Vec<f64>> = (0..3)
317 .map(|k| sym_eig_desc(&mode_gram(x, d, k), d[k]).1)
318 .collect();
319 let mut c = x.to_vec();
320 let mut cd = d;
321 for k in 0..3 {
322 let (y, yd) = mode_mul(&c, cd, k, &us[k], d[k]);
323 c = y;
324 cd = yd;
325 }
326 Hosvd { d, us, core: c }
327}
328
329struct Trial {
331 rs: [usize; 3],
332 facs: Vec<QFactor>,
333 q: Vec<i32>,
334 dl: [u8; 2],
335 y: Vec<f64>,
336}
337
338fn trial(x: &[f64], h: &Hosvd, delta: f64) -> Trial {
339 let d = h.d;
340 let (q0, _) = quantize_core(&h.core, delta);
341 let mut rs = [1usize; 3];
342 for i in 0..d[0] {
343 for j in 0..d[1] {
344 for l in 0..d[2] {
345 if q0[(i * d[1] + j) * d[2] + l] != 0 {
346 rs = [rs[0].max(i + 1), rs[1].max(j + 1), rs[2].max(l + 1)];
347 }
348 }
349 }
350 }
351 let facs: Vec<QFactor> = (0..3)
352 .map(|k| {
353 QFactor::quantize(
354 &h.us[k],
355 d[k],
356 d[k],
357 rs[k],
358 &slice_norms(&h.core, d, k),
359 delta,
360 )
361 })
362 .collect();
363 let ud: Vec<Vec<f64>> = facs.iter().map(|f| f.dequantize()).collect();
364 let core = ls_core(x, d, &ud, rs);
365 let (q, dl) = quantize_core(&core, delta);
366 let y = reconstruct(&dequantize_core(&q, delta, dl), rs, &ud, d);
367 Trial { rs, facs, q, dl, y }
368}
369
370fn err_of(x: &[f64], y: &[f64]) -> (f64, f64) {
371 let mut se = 0.0;
372 let mut mx = 0.0f64;
373 for (a, b) in x.iter().zip(y) {
374 let e = a - *b as f32 as f64;
375 se += e * e;
376 mx = mx.max(e.abs());
377 }
378 ((se / x.len() as f64).sqrt(), mx)
379}
380
381fn search(target: f64, ok: &impl Fn(f64) -> bool) -> f64 {
382 let (mut lo, mut hi) = (target * 0.5, target * 6.0);
383 let mut guard = 0;
384 while !ok(lo) && guard < 40 {
385 hi = lo;
386 lo *= 0.5;
387 guard += 1;
388 }
389 while ok(hi) && guard < 80 {
390 lo = hi;
391 hi *= 2.0;
392 guard += 1;
393 }
394 for _ in 0..12 {
395 let mid = (lo * hi).sqrt();
396 if ok(mid) {
397 lo = mid;
398 } else {
399 hi = mid;
400 }
401 }
402 lo
403}
404
405pub fn encode_f32(x: &[f32], dims: &[usize], bound: ErrorBound) -> (Vec<u8>, TuckerStats) {
407 encode_f32_with(x, dims, bound, false)
408}
409
410pub fn encode_fast_f32(x: &[f32], dims: &[usize], bound: ErrorBound) -> (Vec<u8>, TuckerStats) {
413 encode_f32_with(x, dims, bound, true)
414}
415
416fn encode_f32_with(
417 x: &[f32],
418 dims: &[usize],
419 bound: ErrorBound,
420 fast: bool,
421) -> (Vec<u8>, TuckerStats) {
422 let x: Vec<f64> = x.iter().map(|&v| v as f64).collect();
423 let d = as3(dims);
424 assert_eq!(d.iter().product::<usize>(), x.len());
425 let (lo_v, hi_v) = x
426 .iter()
427 .fold((f64::INFINITY, f64::NEG_INFINITY), |(a, b), &v| {
428 (a.min(v), b.max(v))
429 });
430 let target = match bound {
431 ErrorBound::Rmse(r) => r,
432 ErrorBound::Psnr(db) => (hi_v - lo_v) * 10f64.powf(-db / 20.0),
433 ErrorBound::MaxAbs(_) => {
434 panic!("the Tucker codec has no max-abs mode; use the wavelet codec")
435 }
436 }
437 .max(f64::MIN_POSITIVE);
438 let h = hosvd(&x, d);
439 let ok = |delta: f64| err_of(&x, &trial(&x, &h, delta).y).0 <= target;
440 let delta = if fast {
441 let (d, _) = crate::wavcodec::fast_step(target, 4, |d| err_of(&x, &trial(&x, &h, d).y).0);
442 if ok(d) {
443 d
444 } else {
445 search(target, &ok)
446 }
447 } else {
448 search(target, &ok)
449 };
450 let t = trial(&x, &h, delta);
451 let mut w = ByteWriter::new();
452 w.bytes(MAGIC);
453 w.u8(0); w.varint(dims.len() as u64);
455 for &v in dims {
456 w.varint(v as u64);
457 }
458 w.f64(delta);
459 for k in 0..3 {
460 w.varint(t.rs[k] as u64);
461 }
462 w.bytes(&t.dl);
463 for f in &t.facs {
464 let mut prev = 0;
465 for &e in &f.exps {
466 w.svarint((e - prev) as i64);
467 prev = e;
468 }
469 }
470 let fb0 = w.buf.len();
471 let mut e = Encoder::new();
472 let (mut km, mut hm) = (UintModel::default(), UintModel::default());
473 for f in &t.facs {
474 let mut f = f.clone();
475 code_factor(&mut e, &mut f, &mut km, &mut hm);
476 }
477 w.block(&e.finish());
478 let factor_bytes = w.buf.len() - fb0;
479 let p = Pyramid::new(t.rs, MAX_LEVELS);
480 let preds = fit_predictors(&p, &t.q);
481 let cb0 = w.buf.len();
482 write_preds(&mut w, &preds);
483 w.u8(0);
484 let mut e = Encoder::new();
485 let mut qq = t.q.clone();
486 code_bands(&p, &mut qq, &preds, &mut e, &mut HandModel::default());
487 w.block(&e.finish());
488 let core_bytes = w.buf.len() - cb0;
489 let (rmse, max_abs) = err_of(&x, &t.y);
490 let st = TuckerStats {
491 core_bytes,
492 factor_bytes,
493 ranks: t.rs.to_vec(),
494 delta,
495 rmse,
496 max_abs,
497 };
498 (w.buf, st)
499}
500
501pub fn decode(bytes: &[u8]) -> Result<Decoded, DecodeError> {
503 let mut r = ByteReader::new(bytes);
504 if r.take(4)? != MAGIC {
505 return Err(DecodeError::Invalid("magic"));
506 }
507 if r.u8()? != 0 {
508 return Err(DecodeError::Invalid("dtype"));
509 }
510 let nd = r.varint()? as usize;
511 if nd == 0 || nd > 3 {
512 return Err(DecodeError::Invalid("ndims"));
513 }
514 let mut dims = Vec::new();
515 for _ in 0..nd {
516 dims.push(r.varint()? as usize);
517 }
518 let d = as3(&dims);
519 if d.iter()
520 .try_fold(1usize, |a, &v| a.checked_mul(v))
521 .is_none_or(|n| n == 0 || n as u64 > 1 << 34)
522 {
523 return Err(DecodeError::Invalid("dims"));
524 }
525 let delta = r.f64()?;
526 let mut rs = [0usize; 3];
527 for k in 0..3 {
528 rs[k] = r.varint()? as usize;
529 if rs[k] == 0 || rs[k] > d[k] {
530 return Err(DecodeError::Invalid("rank"));
531 }
532 }
533 let dl = [r.u8()?, r.u8()?];
534 let mut facs = Vec::new();
535 for k in 0..3 {
536 let mut exps = Vec::new();
537 let mut prev = 0i64;
538 for _ in 0..rs[k] {
539 prev += r.svarint()?;
540 exps.push(prev.clamp(-4000, 4000) as i32);
541 }
542 facs.push(QFactor {
543 n: d[k],
544 r: rs[k],
545 exps,
546 q: vec![0; d[k] * rs[k]],
547 });
548 }
549 let buf = r.block()?;
550 let mut dd = Decoder::new(buf);
551 let (mut km, mut hm) = (UintModel::default(), UintModel::default());
552 for f in facs.iter_mut() {
553 code_factor(&mut dd, f, &mut km, &mut hm);
554 }
555 let ud: Vec<Vec<f64>> = facs.iter().map(|f| f.dequantize()).collect();
556 let p = Pyramid::new(rs, MAX_LEVELS);
557 let preds = read_preds(&mut r, p.bands.len())?;
558 if r.u8()? != 0 {
559 return Err(DecodeError::Invalid("core model"));
560 }
561 let buf = r.block()?;
562 let mut dd = Decoder::new(buf);
563 let mut q = vec![0i32; p.len()];
564 code_bands(&p, &mut q, &preds, &mut dd, &mut HandModel::default());
565 let y = reconstruct(&dequantize_core(&q, delta, dl), rs, &ud, d);
566 Ok(Decoded::F32 {
567 dims,
568 data: y.iter().map(|&v| v as f32).collect(),
569 })
570}
571
572#[cfg(test)]
573mod tests {
574 use super::*;
575
576 #[test]
577 fn tucker_round_trip_meets_psnr() {
578 let d = [12, 20, 16];
579 let mut x = Vec::new();
580 for i in 0..d[0] {
581 for j in 0..d[1] {
582 for k in 0..d[2] {
583 let (a, b, c) = (i as f32 / 12.0, j as f32 / 20.0, k as f32 / 16.0);
584 x.push((3.0 * a).sin() * (2.0 * b).cos() + c * c + 0.1 * (9.0 * a * c).sin());
585 }
586 }
587 }
588 for db in [50.0, 90.0] {
589 let (bytes, st) = encode_f32(&x, &d, ErrorBound::Psnr(db));
590 let Decoded::F32 { data, .. } = decode(&bytes).unwrap() else {
591 panic!()
592 };
593 let xd: Vec<f64> = x.iter().map(|&v| v as f64).collect();
594 let (rmse, _) = err_of(&xd, &data.iter().map(|&v| v as f64).collect::<Vec<_>>());
595 assert_eq!(rmse, st.rmse);
596 let range = xd.iter().cloned().fold(f64::MIN, f64::max)
597 - xd.iter().cloned().fold(f64::MAX, f64::min);
598 assert!(20.0 * (range / rmse).log10() >= db - 1e-9);
599 assert_eq!(encode_f32(&x, &d, ErrorBound::Psnr(db)).0, bytes);
600 }
601 }
602}