Skip to content

Commit 93cc06c

Browse files
authored
Merge pull request #229 from AdaWorldAPI/claude/codec-mode-histogram-probe
examples/codec_mode_histogram: measure shipped hpc::codec mode split + rate-distortion curve
2 parents ffb12fd + 5bdd864 commit 93cc06c

2 files changed

Lines changed: 172 additions & 0 deletions

File tree

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@ required-features = ["std"]
7979
name = "edge_codec_compare"
8080
required-features = ["std"]
8181

82+
[[example]]
83+
name = "codec_mode_histogram"
84+
required-features = ["codec"]
85+
8286
[[example]]
8387
name = "entropy_ladder_probe"
8488
required-features = ["std"]

examples/codec_mode_histogram.rs

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
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 =
129+
480.0 + 400.0 * (x as f64 * 0.018).sin() * (y as f64 * 0.018).cos() + 60.0 * (x as f64 * 0.09).sin();
130+
b[y * W + x] = f.round().clamp(0.0, 1023.0) as i64;
131+
}
132+
}
133+
print_row("weather-like (smooth)", &encode(&b, &cfg));
134+
135+
// (3) Incoherent, in codebook range: random in [0, 1024). No spatial
136+
// coherence, but δ always ≤ STEP/2 so Delta covers it (no escape).
137+
let mut rng = Sm::new(0xC0FFEE);
138+
let c: Vec<i64> = (0..N).map(|_| rng.range(1024)).collect();
139+
print_row("incoherent in-range", &encode(&c, &cfg));
140+
141+
// (4) Incoherent, OUT of codebook range: random in [0, 4096). Nearest
142+
// basin is often > i8 away → Escape → worst case (should exceed 8B,
143+
// the honest "codebook doesn't fit the data" failure mode).
144+
let mut rng2 = Sm::new(0xBADF00D);
145+
let d: Vec<i64> = (0..N).map(|_| rng2.range(4096)).collect();
146+
print_row("incoherent out-of-range", &encode(&d, &cfg));
147+
148+
println!(
149+
"\nDense baseline = 8 B/cell (raw u64). Skip=2 Merge=3 Delta=3 Escape=6+8payload bytes.\n\
150+
Read: the compression is exactly as large as the data is coherent; escape only\n\
151+
bites when the codebook doesn't span the data (row 4)."
152+
);
153+
154+
// λ sweep on the weather-like field — the rate-distortion curve. λ=0
155+
// accepts quantization-to-basin (Skip, 2B) for distortion; high λ keeps
156+
// it lossless (Delta, 3B). THIS is the actual "optimize" lever: how much
157+
// compression you buy past the lossless Delta floor for how much error.
158+
println!("\nλ sweep on weather-like field (rate-distortion curve — the tuning lever):");
159+
for (label, cfg) in [
160+
("λ=0 (rate-only)", RdoConfig::RATE_ONLY),
161+
("λ=1", RdoConfig::from_lambda_q8(1 << 8)),
162+
("λ=4", RdoConfig::from_lambda_q8(4 << 8)),
163+
("λ=16 (default)", RdoConfig::default()),
164+
("λ=∞ (lossless)", RdoConfig::LOSSLESS),
165+
] {
166+
print_row(label, &encode(&b, &cfg));
167+
}
168+
}

0 commit comments

Comments
 (0)