use super::samples::{SampleSet, TrainingError, refuse};
use std::{io, vec, vec::Vec};
#[derive(Clone, Copy, Default)]
struct Segment {
begin: usize,
end: usize,
score: u64,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) struct Epochs {
pub(super) num: usize,
pub(super) size: usize,
}
impl Epochs {
#[inline]
pub(super) fn bounds(self, epoch: usize, nb_dmers: usize) -> (usize, usize) {
let begin = epoch * self.size;
let end = if epoch + 1 == self.num {
nb_dmers
} else {
begin + self.size
};
(begin, end)
}
}
pub(super) fn compute_epochs(
max_dict_size: usize,
nb_dmers: usize,
k: usize,
passes: usize,
) -> Epochs {
debug_assert!(nb_dmers > 0 && k > 0 && passes > 0);
let min_epoch_size = if k > nb_dmers / 10 { nb_dmers } else { k * 10 };
let num = (max_dict_size / k / passes).max(1);
let size = nb_dmers / num;
if size >= min_epoch_size {
return Epochs { num, size };
}
Epochs {
num: nb_dmers / min_epoch_size,
size: min_epoch_size,
}
}
#[derive(Clone, Copy, Default)]
pub(super) struct DmerState {
freq: u32,
active: u32,
}
pub(super) struct CoverContext<'s> {
data: &'s [u8],
dmer_at: Vec<u32>,
initial: Vec<DmerState>,
epoch_dmers: usize,
d: usize,
}
impl<'s> CoverContext<'s> {
pub(super) fn new(samples: &SampleSet<'s>, train: usize, d: usize) -> io::Result<Self> {
debug_assert!(d > 0);
let data = samples.leading(train);
let Some(nb_dmers) = data.len().checked_sub(d).map(|n| n + 1) else {
return Err(refuse(
TrainingError::Samples,
&std::format!(
"the training samples total {} bytes; COVER needs at least {d}",
data.len()
),
));
};
let epoch_dmers = data.len().checked_sub(d.max(8)).map_or(nb_dmers, |n| n + 1);
if u32::try_from(nb_dmers).is_err() {
return Err(refuse(
TrainingError::Samples,
"the training samples are too large for COVER (4 GiB at most)",
));
}
samples.check_holds_dmer(train, d)?;
let (dmer_at, initial) = if d <= 8 {
index_dmers::<false>(data, nb_dmers, d, samples.offsets())
} else {
index_dmers::<true>(data, nb_dmers, d, samples.offsets())
};
Ok(Self {
data,
dmer_at,
initial,
epoch_dmers,
d,
})
}
pub(super) fn build<'o>(
&self,
state: &mut Vec<DmerState>,
out: &'o mut Vec<u8>,
capacity: usize,
k: usize,
) -> &'o [u8] {
state.clear();
state.extend_from_slice(&self.initial);
let d = self.d;
debug_assert!(d <= k);
let nb_dmers = self.dmer_at.len();
let epochs = compute_epochs(capacity, self.epoch_dmers, k, 4);
let max_zero_score_run = (epochs.num >> 3).clamp(10, 100).min(epochs.num);
let dmers_in_k = k - d + 1;
out.resize(capacity, 0);
let mut tail = capacity;
let mut zero_score_run = 0usize;
let mut epoch = 0usize;
while tail > 0 {
let (begin, end) = epochs.bounds(epoch, nb_dmers);
epoch = (epoch + 1) % epochs.num;
let segment = select_segment(&self.dmer_at, state, begin, end, dmers_in_k);
if segment.score == 0 {
zero_score_run += 1;
if zero_score_run >= max_zero_score_run {
break;
}
continue;
}
zero_score_run = 0;
let segment_size = (segment.end - segment.begin + d - 1).min(tail);
if segment_size < d {
break;
}
tail -= segment_size;
out[tail..tail + segment_size]
.copy_from_slice(&self.data[segment.begin..segment.begin + segment_size]);
}
&out[tail..]
}
}
fn select_segment(
dmer_at: &[u32],
state: &mut [DmerState],
begin: usize,
end: usize,
dmers_in_k: usize,
) -> Segment {
let mut best = Segment::default();
let mut active_begin = begin;
let mut active_end = begin;
let mut score = 0u64;
while active_end < end {
let dmer = &mut state[dmer_at[active_end] as usize];
if dmer.active == 0 {
score += u64::from(dmer.freq);
}
dmer.active += 1;
active_end += 1;
if active_end - active_begin == dmers_in_k + 1 {
let dmer = &mut state[dmer_at[active_begin] as usize];
active_begin += 1;
dmer.active -= 1;
if dmer.active == 0 {
score -= u64::from(dmer.freq);
}
}
if score > best.score {
best = Segment {
begin: active_begin,
end: active_end,
score,
};
}
}
for &id in &dmer_at[active_begin..active_end] {
state[id as usize].active = 0;
}
let mut trimmed_begin = best.end;
let mut trimmed_end = best.begin;
for pos in best.begin..best.end {
if state[dmer_at[pos] as usize].freq != 0 {
trimmed_begin = trimmed_begin.min(pos);
trimmed_end = pos + 1;
}
}
best.begin = trimmed_begin;
best.end = trimmed_end.max(trimmed_begin);
for &id in &dmer_at[best.begin..best.end] {
state[id as usize].freq = 0;
}
best
}
const EMPTY: u32 = u32::MAX;
const HASH_MULTIPLIER: u64 = 0x9E37_79B9_7F4A_7C15;
#[inline]
fn short_key(data: &[u8], pos: usize, mask: u64) -> u64 {
let word: [u8; 8] = match data.get(pos..pos + 8) {
Some(word) => word.try_into().expect("eight bytes"),
None => {
let mut word = [0u8; 8];
let tail = &data[pos..];
word[..tail.len()].copy_from_slice(tail);
word
}
};
u64::from_le_bytes(word) & mask
}
const ROLL_BASE: u64 = 0x100_0000_01B3;
#[derive(Default)]
struct Rolling {
fingerprint: u64,
first_weight: u64,
}
impl Rolling {
fn at(data: &[u8], pos: usize, d: usize) -> Self {
let mut fingerprint = 0u64;
for &byte in &data[pos..pos + d] {
fingerprint = fingerprint
.wrapping_mul(ROLL_BASE)
.wrapping_add(u64::from(byte) + 1);
}
Self {
fingerprint,
first_weight: ROLL_BASE.wrapping_pow((d - 1) as u32),
}
}
fn advance(&mut self, data: &[u8], pos: usize, d: usize) {
self.fingerprint = self
.fingerprint
.wrapping_sub((u64::from(data[pos]) + 1).wrapping_mul(self.first_weight))
.wrapping_mul(ROLL_BASE)
.wrapping_add(u64::from(data[pos + d]) + 1);
}
}
fn index_dmers<const LONG: bool>(
data: &[u8],
nb_dmers: usize,
d: usize,
offsets: &[usize],
) -> (Vec<u32>, Vec<DmerState>) {
let mask = if d >= 8 {
u64::MAX
} else {
(1u64 << (8 * d)) - 1
};
let start = || {
if LONG {
Rolling::at(data, 0, d)
} else {
Rolling::default()
}
};
let tag_at = |rolling: &Rolling, pos: usize| {
if LONG {
rolling.fingerprint
} else {
short_key(data, pos, mask)
}
};
let mut slots: Vec<u32> = vec![EMPTY; 1 << 12];
let mut fingerprints: Vec<u64> = if LONG { vec![0; 1 << 12] } else { Vec::new() };
let same_dmer = |fingerprints: &[u64], slot: usize, first: usize, pos: usize, tag: u64| {
if LONG {
fingerprints[slot] == tag && data[first..first + d] == data[pos..pos + d]
} else {
short_key(data, first, mask) == tag
}
};
let mut shift = 64 - 12;
let mut dmers: Vec<DmerState> = Vec::new();
let mut dmer_at: Vec<u32> = Vec::with_capacity(nb_dmers);
let mut sample = 0usize;
let mut rolling = start();
for pos in 0..nb_dmers {
if LONG && pos > 0 {
rolling.advance(data, pos - 1, d);
}
while offsets[sample + 1] <= pos {
sample += 1;
}
let slot_mask = slots.len() - 1;
let tag = tag_at(&rolling, pos);
let mut slot = (tag.wrapping_mul(HASH_MULTIPLIER) >> shift) as usize;
let id = loop {
let first = slots[slot];
if first == EMPTY {
let id = dmers.len() as u32;
slots[slot] = pos as u32;
if LONG {
fingerprints[slot] = tag;
}
dmers.push(DmerState {
freq: 0,
active: EMPTY,
});
break id;
}
if same_dmer(&fingerprints, slot, first as usize, pos, tag) {
break dmer_at[first as usize];
}
slot = (slot + 1) & slot_mask;
};
let dmer = &mut dmers[id as usize];
if pos + d <= offsets[sample + 1] && dmer.active != sample as u32 {
dmer.active = sample as u32;
dmer.freq += 1;
}
dmer_at.push(id);
if dmers.len() * 2 > slots.len() {
let len = slots.len() * 2;
shift -= 1;
let old = core::mem::replace(&mut slots, vec![EMPTY; len]);
let old_fingerprints = if LONG {
core::mem::replace(&mut fingerprints, vec![0; len])
} else {
Vec::new()
};
for (at, &first) in old.iter().enumerate() {
if first == EMPTY {
continue;
}
let tag = if LONG {
old_fingerprints[at]
} else {
short_key(data, first as usize, mask)
};
let mut slot = (tag.wrapping_mul(HASH_MULTIPLIER) >> shift) as usize;
while slots[slot] != EMPTY {
slot = (slot + 1) & (len - 1);
}
slots[slot] = first;
if LONG {
fingerprints[slot] = tag;
}
}
}
}
for dmer in &mut dmers {
dmer.active = 0;
}
(dmer_at, dmers)
}
#[cfg(test)]
mod tests;