Skip to main content

vole_document/encode/
candidates.rs

1//! Candidate generators.
2//!
3//! Each generator proposes a bounded, deterministic reconstruction hypothesis.
4//! Generators are cheap to decline and never trusted because their logic
5//! "looks obvious": every candidate reaches the common court.
6
7use crate::SOURCE_FORMAT_OPAQUE;
8use crate::adapter::opaque;
9use crate::container::{Descriptor, UNIVERSE};
10use crate::dra::{Op, Program};
11#[cfg(feature = "rans")]
12use crate::entropy::{
13    CODER_ORDER0_BYTE_RANS, CODER_VERSION_1, EntropyChannelDescriptor, EntropyModel, encode_channel,
14};
15use crate::error::Result;
16use crate::integrity::sha256;
17use crate::limits::Limits;
18
19/// Stable candidate identity. The discriminant order is the final
20/// deterministic tie-breaker when prices and work are equal.
21#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
22#[repr(u8)]
23pub enum CandidateKind {
24    /// Single literal object, literal program (Phase 1 floor).
25    Raw = 0,
26    /// Run-length generated repeats (Phase 2).
27    Rle = 1,
28    /// Typed byte rANS entropy channel (Phase 2).
29    ByteRans = 2,
30    /// PDF physical span partition as literal-span DRA ops (Phase 3).
31    PdfPhysical = 3,
32    /// PDF typed lexical channels, each entropy-coded (Phase 4).
33    PdfChannels = 4,
34}
35
36impl CandidateKind {
37    /// Stable short name for reports and receipts.
38    pub const fn name(self) -> &'static str {
39        match self {
40            CandidateKind::Raw => "RAW",
41            CandidateKind::Rle => "RLE",
42            CandidateKind::ByteRans => "BYTE_RANS",
43            CandidateKind::PdfPhysical => "PDF_PHYSICAL",
44            CandidateKind::PdfChannels => "PDF_CHANNELS",
45        }
46    }
47}
48
49/// A proposed descriptor together with its originating family.
50#[derive(Debug, Clone)]
51pub struct Candidate {
52    /// Which family produced this proposal.
53    pub kind: CandidateKind,
54    /// The proposed descriptor.
55    pub descriptor: Descriptor,
56}
57
58/// Generate the complete bounded candidate set for `input`.
59///
60/// Every candidate that currently applies is returned: RAW (always), then RLE,
61/// BYTE_RANS, PDF_PHYSICAL, and PDF_CHANNELS, each only when its generator can
62/// express the input within `limits`. Order is deterministic and matches the
63/// [`CandidateKind`] discriminant order, so the court's final tie-break is
64/// stable.
65///
66/// This is the honest ablation surface: forcing a single kind must select from
67/// exactly the same set the unforced court would have priced.
68pub fn propose_all(input: &[u8], limits: Limits) -> Result<Vec<Candidate>> {
69    let mut out = vec![Candidate {
70        kind: CandidateKind::Raw,
71        descriptor: opaque::propose(input, limits)?,
72    }];
73    if let Some(rle) = propose_rle(input, limits)? {
74        out.push(rle);
75    }
76    #[cfg(feature = "rans")]
77    if let Some(byte_rans) = propose_byte_rans(input, limits)? {
78        out.push(byte_rans);
79    }
80    if let Some(pdf) = crate::adapter::pdf::propose_pdf(input, limits)? {
81        out.push(pdf);
82    }
83    #[cfg(feature = "rans")]
84    if let Some(pdf_channels) = crate::adapter::pdf::propose_pdf_channels(input, limits)? {
85        out.push(pdf_channels);
86    }
87    Ok(out)
88}
89
90/// Generate the bounded candidate set for `input`.
91///
92/// Thin alias for [`propose_all`] kept for existing callers; it proposes exactly
93/// the same set in the same order.
94pub fn propose(input: &[u8], limits: Limits) -> Result<Vec<Candidate>> {
95    propose_all(input, limits)
96}
97
98/// Propose an inline-run + `REPEAT_LAST` representation of `input`.
99///
100/// Each maximal run of equal bytes becomes one `INLINE` of that single byte,
101/// optionally followed by a `REPEAT_LAST` repeating it `length - 1` more times.
102/// The sequence is therefore always `INLINE, REPEAT_LAST, INLINE, ...`, so no
103/// two `REPEAT_LAST` instructions are ever adjacent. `objects` is empty because
104/// every byte is carried in the graph.
105///
106/// Returns `Ok(None)`, declining honestly, when the run list cannot be
107/// expressed within `limits`: the program would need more instructions than
108/// `max_graph_ops`, or a run is too long to reconstruct with a single
109/// `REPEAT_LAST` (`length - 1` exceeds `max_repeat_count` or `u32::MAX`).
110pub fn propose_rle(input: &[u8], limits: Limits) -> Result<Option<Candidate>> {
111    // Maximal runs of equal bytes: (byte value, run length).
112    let mut runs: Vec<(u8, u64)> = Vec::new();
113    for &b in input {
114        match runs.last_mut() {
115            Some((last, len)) if *last == b => *len += 1,
116            _ => runs.push((b, 1)),
117        }
118    }
119
120    // Worst case is one INLINE plus one REPEAT_LAST per run.
121    if runs.len().saturating_mul(2) > limits.max_graph_ops as usize {
122        return Ok(None);
123    }
124
125    // A run must be reconstructible by a single INLINE + REPEAT_LAST pair.
126    for &(_, length) in &runs {
127        let extra = length - 1;
128        if extra > limits.max_repeat_count || extra > u32::MAX as u64 {
129            return Ok(None);
130        }
131    }
132
133    let mut ops = Vec::with_capacity(runs.len() * 2);
134    for &(byte, length) in &runs {
135        ops.push(Op::Inline { bytes: vec![byte] });
136        if length > 1 {
137            ops.push(Op::RepeatLast {
138                count: (length - 1) as u32,
139            });
140        }
141    }
142
143    let descriptor = Descriptor {
144        universe: UNIVERSE.to_string(),
145        source_format: SOURCE_FORMAT_OPAQUE,
146        format_basis: "opaque;rle-runs".to_string(),
147        models: vec![],
148        channels: vec![],
149        objects: vec![],
150        program: Program::new(ops),
151        source_sha256: sha256(input),
152        source_len: input.len() as u64,
153    };
154    Ok(Some(Candidate {
155        kind: CandidateKind::Rle,
156        descriptor,
157    }))
158}
159
160/// Propose an order-0 byte-rANS representation of the whole `input` as one
161/// entropy channel.
162///
163/// The 256-entry normalized model and the channel header are fully serialized
164/// and charged by the court, so this candidate only wins when order-0 coding
165/// recovers more bytes than the model costs. Declines (`Ok(None)`) honestly:
166///
167/// - empty input: RAW is trivially smaller and there is nothing to code;
168/// - `input.len() > limits.max_channel_symbols`: a single channel cannot carry
169///   it, so the candidate is not expressible within the declared bounds.
170///
171/// Determinism follows from the pure `from_counts` normalizer and from
172/// `encode_channel`, which both depend only on their inputs.
173#[cfg(feature = "rans")]
174pub fn propose_byte_rans(input: &[u8], limits: Limits) -> Result<Option<Candidate>> {
175    if input.is_empty() || input.len() as u64 > limits.max_channel_symbols {
176        return Ok(None);
177    }
178
179    let mut counts = [0u64; 256];
180    for &b in input {
181        counts[b as usize] += 1;
182    }
183    let model = EntropyModel::from_counts(&counts, 12)?;
184    let capsule = encode_channel(&model, input)?;
185
186    let channel = EntropyChannelDescriptor {
187        coder: CODER_ORDER0_BYTE_RANS,
188        coder_version: CODER_VERSION_1,
189        scale_bits: model.scale_bits,
190        lane_count: 1,
191        model_id: 0,
192        symbol_count: capsule.symbol_count,
193        decoded_length: capsule.decoded_length,
194        initial_state: capsule.initial_state,
195        payload: capsule.payload,
196    };
197
198    let descriptor = Descriptor {
199        universe: UNIVERSE.to_string(),
200        source_format: SOURCE_FORMAT_OPAQUE,
201        format_basis: "opaque;byte-rans".to_string(),
202        models: vec![model],
203        channels: vec![channel],
204        objects: vec![],
205        program: Program::new(vec![Op::DecodeChannel { channel_id: 0 }]),
206        source_sha256: sha256(input),
207        source_len: input.len() as u64,
208    };
209
210    Ok(Some(Candidate {
211        kind: CandidateKind::ByteRans,
212        descriptor,
213    }))
214}
215
216#[cfg(test)]
217mod tests {
218    use super::*;
219
220    /// Deterministic xorshift64 PRNG for incompressible test data.
221    fn xorshift64(state: &mut u64) -> u64 {
222        let mut x = *state;
223        x ^= x << 13;
224        x ^= x >> 7;
225        x ^= x << 17;
226        *state = x;
227        x
228    }
229
230    fn xorshift_bytes(n: usize, seed: u64) -> Vec<u8> {
231        let mut state = seed | 1; // avoid the zero fixed point
232        let mut out = Vec::with_capacity(n + 8);
233        while out.len() < n {
234            out.extend_from_slice(&xorshift64(&mut state).to_le_bytes());
235        }
236        out.truncate(n);
237        out
238    }
239
240    fn assert_exact(bytes: &[u8], input: &[u8], limits: Limits) {
241        let (out, parsed) = crate::materialize::decode_to_bytes(bytes, limits).unwrap();
242        assert_eq!(out, input, "materialized bytes must equal the source");
243        assert_eq!(out.len() as u64, parsed.descriptor.source_len);
244        assert_eq!(
245            crate::integrity::sha256(&out),
246            crate::integrity::sha256(input)
247        );
248    }
249
250    #[test]
251    fn rle_wins_on_zeros() {
252        let input = vec![0u8; 65536];
253        let (bytes, report) = crate::encode::encode(&input, Limits::DEFAULT).unwrap();
254        assert_eq!(report.kind, CandidateKind::Rle);
255        assert!(
256            report.encoded_len < 512,
257            "RLE encoding of zeros was {} bytes",
258            report.encoded_len
259        );
260        assert_exact(&bytes, &input, Limits::DEFAULT);
261    }
262
263    #[test]
264    fn rle_wins_on_long_runs() {
265        let mut input = Vec::new();
266        input.extend_from_slice(&[0xAAu8; 1000]);
267        input.extend_from_slice(&[0x00, 0x01]);
268        input.extend_from_slice(&[0x55u8; 5000]);
269        input.extend_from_slice(b"tail");
270
271        let (bytes, report) = crate::encode::encode(&input, Limits::DEFAULT).unwrap();
272        assert_eq!(report.kind, CandidateKind::Rle);
273        assert!(report.encoded_len < 512);
274        assert_exact(&bytes, &input, Limits::DEFAULT);
275    }
276
277    #[test]
278    fn raw_wins_on_incompressible() {
279        let input = xorshift_bytes(64 * 1024, 0x9E37_79B9_7F4A_7C15);
280        let (bytes, report) = crate::encode::encode(&input, Limits::DEFAULT).unwrap();
281        assert_eq!(
282            report.kind,
283            CandidateKind::Raw,
284            "incompressible data must be stored RAW"
285        );
286        assert_exact(&bytes, &input, Limits::DEFAULT);
287    }
288
289    #[test]
290    fn single_byte_is_rle() {
291        let input = [7u8];
292        let (bytes, report) = crate::encode::encode(&input, Limits::DEFAULT).unwrap();
293        assert_eq!(report.kind, CandidateKind::Rle);
294        assert!(report.encoded_len < 512);
295        assert_exact(&bytes, &input, Limits::DEFAULT);
296    }
297
298    #[test]
299    fn rle_declines_when_graph_too_big() {
300        let input = vec![0u8; 100];
301        let limits = Limits {
302            max_graph_ops: 1,
303            ..Limits::DEFAULT
304        };
305        assert!(
306            propose_rle(&input, limits).unwrap().is_none(),
307            "100 single-byte runs cannot fit in a one-op graph"
308        );
309        // With RLE declined, the court must fall back to another exact lane.
310        // (The compact v2 model makes BYTE_RANS the winner here; whether it or
311        // RAW wins depends on model cost, so only "not RLE" is pinned.)
312        let (bytes, report) = crate::encode::encode(&input, limits).unwrap();
313        assert_ne!(report.kind, CandidateKind::Rle);
314        assert_exact(&bytes, &input, limits);
315    }
316
317    #[cfg(feature = "rans")]
318    #[test]
319    fn byte_rans_wins_on_text() {
320        let input = b"The quick brown fox jumps over the lazy dog. ".repeat(1500);
321        let (bytes, report) = crate::encode::encode(&input, Limits::DEFAULT).unwrap();
322        assert_eq!(report.kind, CandidateKind::ByteRans);
323        assert!(
324            report.encoded_len < report.source_len,
325            "order-0 rANS must beat RAW on low-entropy text: {} vs {}",
326            report.encoded_len,
327            report.source_len
328        );
329        assert_exact(&bytes, &input, Limits::DEFAULT);
330    }
331
332    #[cfg(feature = "rans")]
333    #[test]
334    fn byte_rans_exact_on_all_byte_values() {
335        // Exercise every byte value through the channel. A uniform `0..=255`
336        // stream is incompressible at order 0 (and the 516-byte model makes rANS
337        // lose to RAW), so the body is a skewed, deterministic stream whose head
338        // guarantees all 256 symbols are present and nonzero-frequency.
339        let mut input: Vec<u8> = (0..=255u8).collect();
340        let mut state = 0x2545_F491_4F6C_DD1D;
341        while input.len() < 64 * 1024 {
342            let r = xorshift64(&mut state);
343            if !r.is_multiple_of(8) {
344                input.push(0x00);
345            } else {
346                input.push((r >> 32) as u8);
347            }
348        }
349        input.truncate(64 * 1024);
350
351        let (bytes, report) = crate::encode::encode(&input, Limits::DEFAULT).unwrap();
352        assert_eq!(report.kind, CandidateKind::ByteRans);
353        assert_exact(&bytes, &input, Limits::DEFAULT);
354    }
355
356    #[test]
357    fn byte_rans_is_deterministic() {
358        let input = b"deterministic byte rANS stream ".repeat(600);
359        let (a, _) = crate::encode::encode(&input, Limits::DEFAULT).unwrap();
360        let (b, _) = crate::encode::encode(&input, Limits::DEFAULT).unwrap();
361        assert_eq!(a, b, ".voldoc bytes must be identical across encodes");
362    }
363
364    #[cfg(feature = "rans")]
365    #[test]
366    fn byte_rans_declines_empty() {
367        assert!(
368            propose_byte_rans(&[], Limits::DEFAULT).unwrap().is_none(),
369            "empty input must decline: RAW is trivially smaller"
370        );
371    }
372
373    #[cfg(feature = "rans")]
374    #[test]
375    fn model_cost_is_charged() {
376        // On a two-byte input the 516-byte canonical model cannot pay for
377        // itself, so BYTE_RANS must lose the complete-cost court. This documents
378        // model-cost honesty.
379        let input = b"ab";
380        let (bytes, report) = crate::encode::encode(input, Limits::DEFAULT).unwrap();
381        assert_ne!(report.kind, CandidateKind::ByteRans);
382        assert_exact(&bytes, input, Limits::DEFAULT);
383    }
384}