Skip to main content

lzcomplexity_core/
lz76.rs

1//! LZ76 factorization and the user-facing result containers.
2
3use crate::lpf::lpf;
4use crate::sequence::Sequence;
5use crate::suffix_array::build as build_sa_lcp;
6use crate::NO_ALPHABET;
7
8/// Mirror of `LZ_Args` in the C++ — configuration knobs for all `lz76*`
9/// functions. Defaults match the C++ no-arg constructor (which uses the
10/// `NO_ALPHABET` sentinel for auto-detection).
11#[derive(Clone, Debug)]
12pub struct LzArgs {
13    pub chunks: i32,
14    pub max_context: i32,
15    pub block_size: i32,
16    pub get_shuffle_terms: bool,
17    pub alphabet: u32,
18    pub log_base: u32,
19}
20
21impl LzArgs {
22    pub fn new() -> Self {
23        Self {
24            chunks: 0,
25            max_context: 0,
26            block_size: -1,
27            get_shuffle_terms: false,
28            alphabet: NO_ALPHABET,
29            log_base: NO_ALPHABET,
30        }
31    }
32}
33
34impl Default for LzArgs {
35    fn default() -> Self {
36        Self::new()
37    }
38}
39
40impl PartialEq for LzArgs {
41    fn eq(&self, other: &Self) -> bool {
42        self.chunks == other.chunks
43            && self.max_context == other.max_context
44            && self.block_size == other.block_size
45            && self.get_shuffle_terms == other.get_shuffle_terms
46            && self.alphabet == other.alphabet
47    }
48}
49
50/// Mirror of `LZ_Result`.
51#[derive(Clone, Debug, Default)]
52pub struct Lz76Result {
53    pub factorization: u32,
54    pub epsilon: f64,
55    pub lzf: Vec<u32>,
56}
57
58/// Mirror of `LZ_Shuffle`.
59#[derive(Clone, Debug)]
60pub struct LzShuffle {
61    pub max_block_size: i32,
62    pub emc_value: f64,
63    pub multi_information: f64,
64    pub summands: Vec<f64>,
65}
66
67impl LzShuffle {
68    pub fn new(max_block_size: i32, emc_value: f64, multi_information: f64) -> Self {
69        Self {
70            max_block_size,
71            emc_value,
72            multi_information,
73            summands: Vec::new(),
74        }
75    }
76}
77
78impl Default for LzShuffle {
79    fn default() -> Self {
80        Self::new(-1, 0.0, 0.0)
81    }
82}
83
84/// Mirror of `LZ_Extra`.
85#[derive(Clone, Debug, Default)]
86pub struct LzExtra {
87    pub lz_rajski_distance: f64,
88    pub redundancy: f64,
89    pub fh_uncertainty: f64,
90    pub lh_uncertainty: f64,
91    pub lz_pearson_coefficient: f64,
92}
93
94/// Mirror of `LempelZiv` — the rich result returned by `lz76(seq, args)`.
95#[derive(Clone, Debug, Default)]
96pub struct LempelZiv {
97    pub complexity: u32,
98    pub entropy_density: f64,
99    pub random_shuffle_complexity: LzShuffle,
100    pub paired_shuffle_complexity: LzShuffle,
101    pub lz_normal_errors: f64,
102    pub lz_poison_errors: f64,
103    pub epsilon: f64,
104    pub factors_stddev: f64,
105    pub factors: Vec<u32>,
106    pub extras: LzExtra,
107}
108
109/// Core: factorize a sequence into LZ76 factors. Mirrors `LempelZiv76::Factorize`.
110///
111/// Returns `(factorization_count, epsilon, factor_boundaries, factor_length_stddev)`.
112pub fn factorize(seq: &Sequence, args: &LzArgs) -> (u32, f64, Vec<u32>, f64) {
113    let n = seq.len();
114    if n == 0 {
115        return (0, 0.0, Vec::new(), 0.0);
116    }
117
118    let sa = build_sa_lcp(seq.as_bytes());
119    let log_base = if args.log_base == NO_ALPHABET {
120        seq.alphabet_size()
121    } else {
122        args.log_base
123    };
124    let alphabet = if args.alphabet == NO_ALPHABET {
125        seq.alphabet_size()
126    } else {
127        args.alphabet
128    };
129
130    let alphabet_f = alphabet.max(2) as f64;
131    let log_base_f = log_base.max(2) as f64;
132    let logn = log_base_f.ln();
133    let n_f = n as f64;
134    let epsilon = 2.0 * (1.0 + ((alphabet_f * n_f).ln() / logn).ln() / logn) / (n_f.ln() / logn);
135
136    let mut lpf_arr = vec![0u32; n];
137    // Move the SA/LCP vectors into `lpf` (it consumes them); `sa` is dead after
138    // this, so no clone is needed.
139    lpf(&mut lpf_arr, sa.sa, sa.lcp, n);
140
141    let mut lzf: Vec<u32> = Vec::with_capacity(n);
142    lzf.push(0);
143    lzf.push(1);
144    let mut i: usize = 1;
145    while i < n {
146        let last = *lzf.last().unwrap() as usize;
147        let advance = lpf_arr.get(i).copied().unwrap_or(0) as usize;
148        i = last + advance + 1;
149        lzf.push(i as u32);
150    }
151
152    let factorization = if (*lzf.last().unwrap() as usize) <= n {
153        (lzf.len() - 1) as u32
154    } else {
155        (lzf.len() - 2) as u32
156    };
157
158    let stddev = factors_stddev(&lzf);
159    (factorization, epsilon, lzf, stddev)
160}
161
162fn factors_stddev(lzf: &[u32]) -> f64 {
163    if lzf.len() < 2 {
164        return 0.0;
165    }
166    // Single pass over `lzf`: the mean is derived from `lzf` directly, so we
167    // accumulate the variance and track the max factor size at once (same
168    // arithmetic order as before → bit-identical result, one fewer allocation).
169    let mean = (lzf[lzf.len() - 1] as f64 - 1.0) / lzf.len() as f64;
170    let mut max_factor_size = 0u32;
171    let mut sq_sum = 0.0;
172    for i in 1..lzf.len() {
173        let sz = lzf[i].saturating_sub(lzf[i - 1]);
174        if sz > max_factor_size {
175            max_factor_size = sz;
176        }
177        let diff = sz as f64 - mean;
178        sq_sum += diff * diff;
179    }
180    if max_factor_size == 0 {
181        0.0
182    } else {
183        (sq_sum / max_factor_size as f64).sqrt()
184    }
185}
186
187/// True iff the sequence has at least 2 distinct symbols. Early-exit scan —
188/// no allocation (was `char_density().len() > 1`, which built a whole map).
189fn has_multiple_symbols(seq: &Sequence) -> bool {
190    let data = seq.as_bytes();
191    match data.first() {
192        None => false,
193        Some(&first) => data[1..].iter().any(|&b| b != first),
194    }
195}
196
197/// Compute just the LZ76 complexity (factor count).
198pub fn lz76_factorization(seq: &Sequence, args: &LzArgs) -> u32 {
199    if !has_multiple_symbols(seq) {
200        return 1;
201    }
202    let (c, _, _, _) = factorize(seq, args);
203    c
204}
205
206/// Compute LZ76 complexity + factor boundaries.
207pub fn lz76_factors(seq: &Sequence, args: &LzArgs) -> Lz76Result {
208    if !has_multiple_symbols(seq) {
209        return Lz76Result {
210            factorization: 1,
211            epsilon: 0.0,
212            lzf: vec![0, 1, seq.len() as u32],
213        };
214    }
215    let (factorization, epsilon, lzf, _) = factorize(seq, args);
216    Lz76Result {
217        factorization,
218        epsilon,
219        lzf,
220    }
221}
222
223/// Normalized entropy density from an already-computed factor count. Lets
224/// callers that already have the complexity avoid re-factorizing.
225pub fn entropy_density_from(count: u32, seq: &Sequence, args: &LzArgs) -> f64 {
226    let log_base = if args.log_base == NO_ALPHABET {
227        seq.alphabet_size().max(2)
228    } else {
229        args.log_base.max(2)
230    } as f64;
231    let n = seq.len() as f64;
232    if n <= 1.0 {
233        return 0.0;
234    }
235    let div = n / (n.ln() / log_base.ln());
236    count as f64 / div
237}
238
239/// Compute the normalized LZ76 entropy density.
240pub fn lz76_entropy_density(seq: &Sequence, args: &LzArgs) -> f64 {
241    entropy_density_from(lz76_factorization(seq, args), seq, args)
242}