data_beans/alg/
batch_mixing.rs1use crate::alg::random_projection::binary_sort_columns;
23use nalgebra::DMatrix;
24use std::collections::{HashMap, HashSet};
25
26pub const CENTRING_ROUNDS: usize = 10;
29
30pub const MAX_STATE_BITS: usize = 10;
32
33pub const DEFAULT_MERGE_LEVELS: usize = 8;
35
36pub const DEFAULT_MIN_BATCHES_PER_GROUP: usize = 3;
38
39pub fn default_state_bits(n_cols: usize, n_batches: usize, dim: usize) -> usize {
42 let per_bin = (2 * n_batches.max(1)) as f64;
43 let bits = (n_cols as f64 / per_bin).log2().floor().max(1.0) as usize;
44 bits.min(dim.max(1)).min(MAX_STATE_BITS)
45}
46
47fn group_means(x: &DMatrix<f32>, group: &[usize], n_groups: usize) -> DMatrix<f32> {
49 let mut sum = DMatrix::<f32>::zeros(x.nrows(), n_groups);
50 let mut count = vec![0f32; n_groups];
51 for (j, &g) in group.iter().enumerate() {
52 let mut col = sum.column_mut(g);
53 col += x.column(j);
54 count[g] += 1.0;
55 }
56 for (g, &c) in count.iter().enumerate() {
57 if c > 0.0 {
58 sum.column_mut(g).unscale_mut(c);
59 }
60 }
61 sum
62}
63
64fn subtract(x: &DMatrix<f32>, offsets: &DMatrix<f32>, group: &[usize]) -> DMatrix<f32> {
66 let mut out = x.clone();
67 for (j, &g) in group.iter().enumerate() {
68 let mut col = out.column_mut(j);
69 col -= offsets.column(g);
70 }
71 out
72}
73
74pub fn centre_batches_within_state(
81 proj: &DMatrix<f32>,
82 batch: &[usize],
83 bits: usize,
84) -> anyhow::Result<DMatrix<f32>> {
85 anyhow::ensure!(
86 batch.len() == proj.ncols(),
87 "batch membership size mismatch"
88 );
89 let n_batches = batch.iter().max().map_or(0, |&m| m + 1);
90 let bits = bits.min(proj.nrows()).min(proj.ncols()).max(1);
91
92 let mut shift = group_means(proj, batch, n_batches);
93 let mut codes: Vec<usize> = Vec::new();
94 for _ in 0..CENTRING_ROUNDS {
95 let centred = subtract(proj, &shift, batch);
96 let next = binary_sort_columns(¢red, bits)?;
97 if next == codes {
98 break;
99 }
100 codes = next;
101 let n_bins = codes.iter().max().map_or(0, |&m| m + 1);
102 let states = group_means(¢red, &codes, n_bins);
103 shift = group_means(&subtract(proj, &states, &codes), batch, n_batches);
104 }
105 let mut centred = subtract(proj, &shift, batch);
106 let mean = centred.column_mean();
107 for mut col in centred.column_iter_mut() {
108 col -= &mean;
109 }
110 Ok(centred)
111}
112
113fn node_label(level: usize, code: usize) -> usize {
116 (1 << level) | code
117}
118
119pub fn merge_poorly_mixed_bins(
128 codes: &[usize],
129 batch: &[usize],
130 bits: usize,
131 levels: usize,
132 min_batches: usize,
133) -> Vec<usize> {
134 let mut node: Vec<(usize, usize)> = codes.iter().map(|&c| (bits, c)).collect();
135 let lowest = bits.saturating_sub(levels);
136 for b in (lowest + 1..=bits).rev() {
137 let mut members: HashMap<usize, HashSet<usize>> = HashMap::default();
138 for (&(level, code), &k) in node.iter().zip(batch) {
139 if level == b {
140 members.entry(code).or_default().insert(k);
141 }
142 }
143 let parent_mask = (1 << (b - 1)) - 1;
144 let poor_parents: HashSet<usize> = members
145 .iter()
146 .filter(|(_, ks)| ks.len() < min_batches)
147 .map(|(&code, _)| code & parent_mask)
148 .collect();
149 if poor_parents.is_empty() {
150 continue;
151 }
152 for (level, code) in node.iter_mut() {
153 let parent = *code & parent_mask;
154 if *level >= b && poor_parents.contains(&parent) {
155 *level = b - 1;
156 *code = parent;
157 }
158 }
159 }
160 node.into_iter().map(|(l, c)| node_label(l, c)).collect()
161}
162
163pub fn batch_indices<T>(batch_membership: &[T]) -> Vec<usize>
165where
166 T: std::hash::Hash + Eq + Clone,
167{
168 let mut index: HashMap<T, usize> = HashMap::default();
169 batch_membership
170 .iter()
171 .map(|t| {
172 let next = index.len();
173 *index.entry(t.clone()).or_insert(next)
174 })
175 .collect()
176}