|
| 1 | +//! Codec mode-histogram probe — drives the SHIPPED `hpc::codec` RDO |
| 2 | +//! selector over synthetic fields of varying coherence and reports the |
| 3 | +//! actual %skip/merge/delta/escape mode histogram + bytes/cell. |
| 4 | +//! |
| 5 | +//! The point: HEVC-family compression is entirely a function of the |
| 6 | +//! DATA's coherence. This measures where different data lands, so the |
| 7 | +//! "3.3× / 10-50×" design targets can be read against a real mode split |
| 8 | +//! instead of assumed. Raster scan with causal N/W neighbours — exactly |
| 9 | +//! how a real encoder feeds Merge (E/S aren't decided yet). |
| 10 | +//! |
| 11 | +//! Run: `cargo run --release --example codec_mode_histogram --features codec` |
| 12 | +
|
| 13 | +use ndarray::hpc::codec::rdo::{rdo_select, RdoConfig, RdoContext}; |
| 14 | +use ndarray::hpc::codec::{packed_byte_len, CellMode, LeafCu}; |
| 15 | + |
| 16 | +const W: usize = 256; |
| 17 | +const H: usize = 256; |
| 18 | +const N: usize = W * H; |
| 19 | +const NBASINS: i64 = 16; |
| 20 | +const STEP: i64 = 64; // basin values 0, 64, .. 960 — codebook spans [0, 960] |
| 21 | + |
| 22 | +/// SplitMix64 — deterministic, dependency-free. |
| 23 | +struct Sm(u64); |
| 24 | +impl Sm { |
| 25 | + fn new(s: u64) -> Self { |
| 26 | + Self(s) |
| 27 | + } |
| 28 | + fn next(&mut self) -> u64 { |
| 29 | + self.0 = self.0.wrapping_add(0x9E37_79B9_7F4A_7C15); |
| 30 | + let mut z = self.0; |
| 31 | + z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9); |
| 32 | + z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB); |
| 33 | + z ^ (z >> 31) |
| 34 | + } |
| 35 | + fn range(&mut self, n: i64) -> i64 { |
| 36 | + (self.next() % n as u64) as i64 |
| 37 | + } |
| 38 | +} |
| 39 | + |
| 40 | +/// Nearest basin: index + its value. Codebook spans [0, (NBASINS-1)*STEP]. |
| 41 | +fn basin_of(v: i64) -> (u16, i64) { |
| 42 | + let idx = ((v + STEP / 2).div_euclid(STEP)).clamp(0, NBASINS - 1); |
| 43 | + (idx as u16, idx * STEP) |
| 44 | +} |
| 45 | + |
| 46 | +struct Report { |
| 47 | + hist: [usize; 4], // skip, merge, delta, escape |
| 48 | + total_bytes: usize, |
| 49 | + mean_distortion: f64, // reconstruction error, u8-quant units |
| 50 | +} |
| 51 | + |
| 52 | +/// Encode `values` through the shipped RDO selector, raster order, causal |
| 53 | +/// N/W neighbours. Escape feasible (cursor supplied) so out-of-range δ is |
| 54 | +/// lossless. Bytes = leaf wire size + 8-byte escape payload per escape. |
| 55 | +fn encode(values: &[i64], cfg: &RdoConfig) -> Report { |
| 56 | + let mut leaves: Vec<LeafCu> = Vec::with_capacity(N); |
| 57 | + let mut hist = [0usize; 4]; |
| 58 | + let mut bytes = 0usize; |
| 59 | + let mut dist_sum = 0u64; |
| 60 | + let mut escape_cursor = 0u32; |
| 61 | + |
| 62 | + for y in 0..H { |
| 63 | + for x in 0..W { |
| 64 | + let v = values[y * W + x]; |
| 65 | + let (basin_idx, basin_val) = basin_of(v); |
| 66 | + let delta = (v - basin_val) as i32; |
| 67 | + let north = if y > 0 { Some(&leaves[(y - 1) * W + x]) } else { None }; |
| 68 | + let west = if x > 0 { Some(&leaves[y * W + (x - 1)]) } else { None }; |
| 69 | + let ctx = RdoContext { |
| 70 | + basin_idx, |
| 71 | + delta_i32: delta, |
| 72 | + // [North, East, West, South] — E/S undecided in raster order |
| 73 | + neighbours: [north, None, west, None], |
| 74 | + }; |
| 75 | + let choice = rdo_select(&ctx, cfg, Some(&mut escape_cursor)); |
| 76 | + let m = choice.leaf.mode; |
| 77 | + hist[m as usize] += 1; |
| 78 | + bytes += packed_byte_len(m); |
| 79 | + if m == CellMode::Escape { |
| 80 | + bytes += 8; // full 64-bit value in the escape vector |
| 81 | + } |
| 82 | + dist_sum += choice.distortion as u64; |
| 83 | + leaves.push(choice.leaf); |
| 84 | + } |
| 85 | + } |
| 86 | + Report { |
| 87 | + hist, |
| 88 | + total_bytes: bytes, |
| 89 | + mean_distortion: dist_sum as f64 / N as f64, |
| 90 | + } |
| 91 | +} |
| 92 | + |
| 93 | +fn print_row(name: &str, r: &Report) { |
| 94 | + let pct = |k: usize| 100.0 * r.hist[k] as f64 / N as f64; |
| 95 | + let bpc = r.total_bytes as f64 / N as f64; |
| 96 | + println!( |
| 97 | + " {name:<22} skip={:5.1}% merge={:5.1}% delta={:5.1}% escape={:5.1}% | {:.2} B/cell {:.2}× vs 8B meanDist={:.2}", |
| 98 | + pct(0), |
| 99 | + pct(1), |
| 100 | + pct(2), |
| 101 | + pct(3), |
| 102 | + bpc, |
| 103 | + 8.0 / bpc, |
| 104 | + r.mean_distortion, |
| 105 | + ); |
| 106 | +} |
| 107 | + |
| 108 | +fn main() { |
| 109 | + let cfg = RdoConfig::default(); // λ = 16, fidelity-biased (design's realistic setting) |
| 110 | + println!("Shipped hpc::codec RDO selector — {W}×{H} = {N} cells, λ=16 (default), 16-basin codebook\n"); |
| 111 | + |
| 112 | + // (1) Coherent: flat 16×16 tiles, each tile sits exactly on a basin → δ=0. |
| 113 | + let mut a = vec![0i64; N]; |
| 114 | + for y in 0..H { |
| 115 | + for x in 0..W { |
| 116 | + let tile = ((y / 16) * (W / 16) + (x / 16)) as i64; |
| 117 | + a[y * W + x] = (tile % NBASINS) * STEP; // exact basin value |
| 118 | + } |
| 119 | + } |
| 120 | + print_row("coherent (flat tiles)", &encode(&a, &cfg)); |
| 121 | + |
| 122 | + // (2) Weather-like: smooth low-frequency field, neighbours nearly equal, |
| 123 | + // small residuals off the nearest basin. The realistic estimate for |
| 124 | + // a correlated physical field (the whole point of the thread). |
| 125 | + let mut b = vec![0i64; N]; |
| 126 | + for y in 0..H { |
| 127 | + for x in 0..W { |
| 128 | + let f = 480.0 |
| 129 | + + 400.0 * (x as f64 * 0.018).sin() * (y as f64 * 0.018).cos() |
| 130 | + + 60.0 * (x as f64 * 0.09).sin(); |
| 131 | + b[y * W + x] = f.round().clamp(0.0, 1023.0) as i64; |
| 132 | + } |
| 133 | + } |
| 134 | + print_row("weather-like (smooth)", &encode(&b, &cfg)); |
| 135 | + |
| 136 | + // (3) Incoherent, in codebook range: random in [0, 1024). No spatial |
| 137 | + // coherence, but δ always ≤ STEP/2 so Delta covers it (no escape). |
| 138 | + let mut rng = Sm::new(0xC0FFEE); |
| 139 | + let c: Vec<i64> = (0..N).map(|_| rng.range(1024)).collect(); |
| 140 | + print_row("incoherent in-range", &encode(&c, &cfg)); |
| 141 | + |
| 142 | + // (4) Incoherent, OUT of codebook range: random in [0, 4096). Nearest |
| 143 | + // basin is often > i8 away → Escape → worst case (should exceed 8B, |
| 144 | + // the honest "codebook doesn't fit the data" failure mode). |
| 145 | + let mut rng2 = Sm::new(0xBADF00D); |
| 146 | + let d: Vec<i64> = (0..N).map(|_| rng2.range(4096)).collect(); |
| 147 | + print_row("incoherent out-of-range", &encode(&d, &cfg)); |
| 148 | + |
| 149 | + println!( |
| 150 | + "\nDense baseline = 8 B/cell (raw u64). Skip=2 Merge=3 Delta=3 Escape=6+8payload bytes.\n\ |
| 151 | + Read: the compression is exactly as large as the data is coherent; escape only\n\ |
| 152 | + bites when the codebook doesn't span the data (row 4)." |
| 153 | + ); |
| 154 | + |
| 155 | + // λ sweep on the weather-like field — the rate-distortion curve. λ=0 |
| 156 | + // accepts quantization-to-basin (Skip, 2B) for distortion; high λ keeps |
| 157 | + // it lossless (Delta, 3B). THIS is the actual "optimize" lever: how much |
| 158 | + // compression you buy past the lossless Delta floor for how much error. |
| 159 | + println!("\nλ sweep on weather-like field (rate-distortion curve — the tuning lever):"); |
| 160 | + for (label, cfg) in [ |
| 161 | + ("λ=0 (rate-only)", RdoConfig::RATE_ONLY), |
| 162 | + ("λ=1", RdoConfig::from_lambda_q8(1 << 8)), |
| 163 | + ("λ=4", RdoConfig::from_lambda_q8(4 << 8)), |
| 164 | + ("λ=16 (default)", RdoConfig::default()), |
| 165 | + ("λ=∞ (lossless)", RdoConfig::LOSSLESS), |
| 166 | + ] { |
| 167 | + print_row(label, &encode(&b, &cfg)); |
| 168 | + } |
| 169 | +} |
0 commit comments