use rayon::prelude::*;
use crate::lz76::{lz76_factorization, LzArgs, LzShuffle};
use crate::sequence::{shuffle_copy_seeded, Sequence};
use crate::NO_ALPHABET;
pub fn max_block_size(size: usize) -> usize {
if size < 10 {
return 1;
}
let mut m: usize = 10;
let mut m_old: usize = 1;
let mut count: u32 = 0;
while count < 100 {
let est = ((size as f64) / (m_old as f64)).ln() / 2f64.ln();
m = est.round() as usize;
count += 1;
if m == m_old {
break;
}
m_old = m;
}
m
}
pub fn shuffle_factorization(seq: &Sequence, args: &LzArgs) -> (Vec<i32>, usize) {
let mut mm: i64 = args.block_size as i64;
if mm <= 0 {
mm = max_block_size(seq.len()) as i64;
if seq.len() > 50 {
mm += 10;
}
}
let mm = mm as usize;
let mut res: Vec<i32> = vec![0; mm + 3];
let base = fnv1a(seq.as_bytes());
let computed: Vec<(usize, i32)> = (1..=mm)
.into_par_iter()
.map(|idx| {
let seed = seed_from_base(base, idx);
let rand_seq = shuffle_copy_seeded(seq, idx as u32, (seq.len() / 2) as u32, seed);
let c = lz76_factorization(&rand_seq, args);
(idx, c as i32)
})
.collect();
for (idx, c) in computed {
res[idx] = c;
}
(res, mm)
}
pub fn shuffle_entropy_calculation(
seq: &Sequence,
args: &LzArgs,
complexity: i32,
h_rand: &[i32],
mm: usize,
) -> LzShuffle {
let mut result = LzShuffle {
max_block_size: mm as i32,
..Default::default()
};
if args.get_shuffle_terms {
result.summands = vec![0.0; mm];
}
let log_base = if args.log_base == NO_ALPHABET {
seq.alphabet_size().max(2)
} else {
args.log_base.max(2)
} as f64;
let n = seq.len() as f64;
if n <= 1.0 || mm == 0 {
return result;
}
let g = (n.ln() / log_base.ln()) / n;
let h_hat = complexity as f64 * g;
let len = mm.min(h_rand.len().saturating_sub(1));
if len == 0 {
return result;
}
let raw: Vec<f64> = (1..=len)
.map(|l| l as f64 * (h_rand[l] as f64 * g - h_hat))
.collect();
result.multi_information = raw[0];
let fitted = non_negative_isotonic(&raw);
let mut prev = 0.0f64; for l in 1..=len {
let term = fitted[l - 1] - prev;
if args.get_shuffle_terms {
result.summands[l - 1] = term;
}
prev = fitted[l - 1];
}
result.emc_value = prev; result
}
fn non_negative_isotonic(y: &[f64]) -> Vec<f64> {
let mut sums: Vec<f64> = Vec::with_capacity(y.len());
let mut counts: Vec<usize> = Vec::with_capacity(y.len());
for &v in y {
sums.push(v);
counts.push(1);
while sums.len() > 1 {
let k = sums.len();
let last = sums[k - 1] / counts[k - 1] as f64;
let prev = sums[k - 2] / counts[k - 2] as f64;
if prev <= last {
break;
}
sums[k - 2] += sums[k - 1];
counts[k - 2] += counts[k - 1];
sums.pop();
counts.pop();
}
}
let mut out = Vec::with_capacity(y.len());
for (s, c) in sums.iter().zip(counts.iter()) {
let level = (s / *c as f64).max(0.0);
out.extend(std::iter::repeat(level).take(*c));
}
out
}
pub fn lz76_random_shuffle_complexity(seq: &Sequence, args: &LzArgs) -> LzShuffle {
lz76_random_shuffle_complexity_with(seq, args, lz76_factorization(seq, args) as i32)
}
pub fn lz76_random_shuffle_complexity_with(
seq: &Sequence,
args: &LzArgs,
complexity: i32,
) -> LzShuffle {
let (h_rand, mm) = shuffle_factorization(seq, args);
shuffle_entropy_calculation(seq, args, complexity, &h_rand, mm)
}
pub fn lz76_paired_shuffle_complexity(seq: &Sequence, args: &LzArgs) -> LzShuffle {
let mid = seq.len() / 2;
let (past, future) = seq.split_at(mid);
let merged = merge_sequences(&past, &future);
let complexity = lz76_factorization(&merged, args) as i32;
let (h_rand, mm) = shuffle_factorization(&merged, args);
shuffle_entropy_calculation(&merged, args, complexity, &h_rand, mm)
}
pub fn merge_sequences(s1: &Sequence, s2: &Sequence) -> Sequence {
let max_iter = s1.len().min(s2.len());
let mut buf = Vec::with_capacity(max_iter);
let mut lookup = vec![0u8; 65536];
let mut next_symbol: u8 = b'0';
let a = s1.as_bytes();
let b = s2.as_bytes();
for i in 0..max_iter {
let key = ((a[i] as u16) << 8) | (b[i] as u16);
let entry = &mut lookup[key as usize];
if *entry == 0 {
*entry = next_symbol;
next_symbol = next_symbol.wrapping_add(1);
}
buf.push(*entry);
}
Sequence::from_bytes_with_alphabet(buf, s1.alphabet_size().saturating_mul(s2.alphabet_size()))
}
fn fnv1a(bytes: &[u8]) -> u64 {
let mut h: u64 = 14695981039346656037; for &c in bytes {
h ^= c as u64;
h = h.wrapping_mul(1099511628211);
}
h
}
fn seed_from_base(base: u64, idx: usize) -> u64 {
base ^ ((idx as u64).wrapping_mul(0x9E3779B97F4A7C15))
}
#[cfg(test)]
mod tests {
use super::*;
fn fixture() -> (Sequence, LzArgs, f64) {
let mut data = vec![b'0'; 50];
data.extend(std::iter::repeat(b'1').take(50));
let seq = Sequence::from_bytes(data);
let mut args = LzArgs::new();
args.get_shuffle_terms = true;
let n = 100.0f64;
(seq, args, (n.ln() / 2f64.ln()) / n)
}
#[test]
fn emc_projects_the_excess_ladder() {
let (seq, args, g) = fixture();
let r = shuffle_entropy_calculation(&seq, &args, 10, &[0, 20, 15, 12], 3);
let level = (26.0 / 3.0) * g;
assert!((r.emc_value - level).abs() < 1e-12, "{}", r.emc_value);
assert!((r.summands[0] - level).abs() < 1e-12);
assert_eq!(r.summands[1], 0.0);
assert_eq!(r.summands[2], 0.0);
assert!((r.multi_information - 10.0 * g).abs() < 1e-12);
}
#[test]
fn emc_keeps_the_closed_form_when_the_ladder_is_monotone() {
let (seq, args, g) = fixture();
let r = shuffle_entropy_calculation(&seq, &args, 10, &[0, 11, 11, 11], 3);
assert!((r.emc_value - 3.0 * g).abs() < 1e-12, "{}", r.emc_value);
for s in &r.summands {
assert!((s - g).abs() < 1e-12, "{s}");
}
}
#[test]
fn emc_is_never_negative() {
let (seq, args, g) = fixture();
let r = shuffle_entropy_calculation(&seq, &args, 20, &[0, 10, 10, 10], 3);
assert_eq!(r.emc_value, 0.0);
assert!(r.summands.iter().all(|&s| s == 0.0), "{:?}", r.summands);
assert!((r.multi_information + 10.0 * g).abs() < 1e-12);
}
#[test]
fn isotonic_pools_adjacent_violators() {
assert_eq!(
non_negative_isotonic(&[1.0, 3.0, 2.0, 4.0]),
vec![1.0, 2.5, 2.5, 4.0]
);
assert_eq!(non_negative_isotonic(&[-1.0, 0.5]), vec![0.0, 0.5]);
assert_eq!(non_negative_isotonic(&[6.0, 3.0, 0.0]), vec![3.0, 3.0, 3.0]);
assert_eq!(non_negative_isotonic(&[]), Vec::<f64>::new());
}
#[test]
fn summands_are_non_negative_and_sum_to_the_value() {
let data: Vec<u8> = b"00010111".iter().copied().cycle().take(2000).collect();
let seq = Sequence::from_bytes(data);
let mut args = LzArgs::new();
args.get_shuffle_terms = true;
let r = lz76_random_shuffle_complexity(&seq, &args);
assert_eq!(r.summands.len(), r.max_block_size as usize);
assert!(r.emc_value > 0.0, "expected structure, got {}", r.emc_value);
assert!(r.summands.iter().all(|&s| s >= 0.0), "{:?}", r.summands);
let total: f64 = r.summands.iter().sum();
assert!(
(total - r.emc_value).abs() < 1e-12,
"{total} vs {}",
r.emc_value
);
let mut acc = 0.0;
for &s in &r.summands {
acc += s;
assert!(acc <= r.emc_value + 1e-12);
}
}
}