use crate::alg::random_projection::binary_sort_columns;
use nalgebra::DMatrix;
use std::collections::{HashMap, HashSet};
pub const CENTRING_ROUNDS: usize = 10;
pub const MAX_STATE_BITS: usize = 10;
pub const DEFAULT_MERGE_LEVELS: usize = 8;
pub const DEFAULT_MIN_BATCHES_PER_GROUP: usize = 3;
pub fn default_state_bits(n_cols: usize, n_batches: usize, dim: usize) -> usize {
let per_bin = (2 * n_batches.max(1)) as f64;
let bits = (n_cols as f64 / per_bin).log2().floor().max(1.0) as usize;
bits.min(dim.max(1)).min(MAX_STATE_BITS)
}
fn group_means(x: &DMatrix<f32>, group: &[usize], n_groups: usize) -> DMatrix<f32> {
let mut sum = DMatrix::<f32>::zeros(x.nrows(), n_groups);
let mut count = vec![0f32; n_groups];
for (j, &g) in group.iter().enumerate() {
let mut col = sum.column_mut(g);
col += x.column(j);
count[g] += 1.0;
}
for (g, &c) in count.iter().enumerate() {
if c > 0.0 {
sum.column_mut(g).unscale_mut(c);
}
}
sum
}
fn subtract(x: &DMatrix<f32>, offsets: &DMatrix<f32>, group: &[usize]) -> DMatrix<f32> {
let mut out = x.clone();
for (j, &g) in group.iter().enumerate() {
let mut col = out.column_mut(j);
col -= offsets.column(g);
}
out
}
pub fn centre_batches_within_state(
proj: &DMatrix<f32>,
batch: &[usize],
bits: usize,
) -> anyhow::Result<DMatrix<f32>> {
anyhow::ensure!(
batch.len() == proj.ncols(),
"batch membership size mismatch"
);
let n_batches = batch.iter().max().map_or(0, |&m| m + 1);
let bits = bits.min(proj.nrows()).min(proj.ncols()).max(1);
let mut shift = group_means(proj, batch, n_batches);
let mut codes: Vec<usize> = Vec::new();
for _ in 0..CENTRING_ROUNDS {
let centred = subtract(proj, &shift, batch);
let next = binary_sort_columns(¢red, bits)?;
if next == codes {
break;
}
codes = next;
let n_bins = codes.iter().max().map_or(0, |&m| m + 1);
let states = group_means(¢red, &codes, n_bins);
shift = group_means(&subtract(proj, &states, &codes), batch, n_batches);
}
let mut centred = subtract(proj, &shift, batch);
let mean = centred.column_mean();
for mut col in centred.column_iter_mut() {
col -= &mean;
}
Ok(centred)
}
fn node_label(level: usize, code: usize) -> usize {
(1 << level) | code
}
pub fn merge_poorly_mixed_bins(
codes: &[usize],
batch: &[usize],
bits: usize,
levels: usize,
min_batches: usize,
) -> Vec<usize> {
let mut node: Vec<(usize, usize)> = codes.iter().map(|&c| (bits, c)).collect();
let lowest = bits.saturating_sub(levels);
for b in (lowest + 1..=bits).rev() {
let mut members: HashMap<usize, HashSet<usize>> = HashMap::default();
for (&(level, code), &k) in node.iter().zip(batch) {
if level == b {
members.entry(code).or_default().insert(k);
}
}
let parent_mask = (1 << (b - 1)) - 1;
let poor_parents: HashSet<usize> = members
.iter()
.filter(|(_, ks)| ks.len() < min_batches)
.map(|(&code, _)| code & parent_mask)
.collect();
if poor_parents.is_empty() {
continue;
}
for (level, code) in node.iter_mut() {
let parent = *code & parent_mask;
if *level >= b && poor_parents.contains(&parent) {
*level = b - 1;
*code = parent;
}
}
}
node.into_iter().map(|(l, c)| node_label(l, c)).collect()
}
pub fn batch_indices<T>(batch_membership: &[T]) -> Vec<usize>
where
T: std::hash::Hash + Eq + Clone,
{
let mut index: HashMap<T, usize> = HashMap::default();
batch_membership
.iter()
.map(|t| {
let next = index.len();
*index.entry(t.clone()).or_insert(next)
})
.collect()
}