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 group_means_of_residuals(
67 x: &DMatrix<f32>,
68 offsets: &DMatrix<f32>,
69 offset_group: &[usize],
70 group: &[usize],
71 n_groups: usize,
72) -> DMatrix<f32> {
73 let mut sum = DMatrix::<f32>::zeros(x.nrows(), n_groups);
74 let mut count = vec![0f32; n_groups];
75 for (j, (&g, &o)) in group.iter().zip(offset_group).enumerate() {
76 let (x_j, o_j) = (x.column(j), offsets.column(o));
77 for ((acc, &v), &off) in sum.column_mut(g).iter_mut().zip(x_j.iter()).zip(o_j.iter()) {
78 *acc += v - off;
79 }
80 count[g] += 1.0;
81 }
82 for (g, &c) in count.iter().enumerate() {
83 if c > 0.0 {
84 sum.column_mut(g).unscale_mut(c);
85 }
86 }
87 sum
88}
89
90fn subtract_offsets(x: &mut DMatrix<f32>, offsets: &DMatrix<f32>, group: &[usize]) {
92 for (j, &g) in group.iter().enumerate() {
93 let mut col = x.column_mut(j);
94 col -= offsets.column(g);
95 }
96}
97
98pub fn centre_batches_within_state(
105 proj: &DMatrix<f32>,
106 batch: &[usize],
107 bits: usize,
108) -> anyhow::Result<DMatrix<f32>> {
109 anyhow::ensure!(
110 batch.len() == proj.ncols(),
111 "batch membership size mismatch"
112 );
113 let n_batches = batch.iter().max().map_or(0, |&m| m + 1);
114 let bits = bits.min(proj.nrows()).min(proj.ncols()).max(1);
115
116 let mut shift = group_means(proj, batch, n_batches);
119 let mut centred = proj.clone();
120 subtract_offsets(&mut centred, &shift, batch);
121 let mut codes: Vec<usize> = Vec::new();
122 for _ in 0..CENTRING_ROUNDS {
123 let next = binary_sort_columns(¢red, bits)?;
124 if next == codes {
125 break;
126 }
127 codes = next;
128 let n_bins = codes.iter().max().map_or(0, |&m| m + 1);
129 let states = group_means(¢red, &codes, n_bins);
130 shift = group_means_of_residuals(proj, &states, &codes, batch, n_batches);
131 centred.copy_from(proj);
132 subtract_offsets(&mut centred, &shift, batch);
133 }
134 let mean = centred.column_mean();
135 for mut col in centred.column_iter_mut() {
136 col -= &mean;
137 }
138 Ok(centred)
139}
140
141fn node_label(level: usize, code: usize) -> usize {
144 (1 << level) | code
145}
146
147pub fn merge_poorly_mixed_bins(
156 codes: &[usize],
157 batch: &[usize],
158 bits: usize,
159 levels: usize,
160 min_batches: usize,
161) -> Vec<usize> {
162 let mut node: Vec<(usize, usize)> = codes.iter().map(|&c| (bits, c)).collect();
163 let lowest = bits.saturating_sub(levels);
164 for b in (lowest + 1..=bits).rev() {
165 let mut members: HashMap<usize, HashSet<usize>> = HashMap::default();
166 for (&(level, code), &k) in node.iter().zip(batch) {
167 if level == b {
168 members.entry(code).or_default().insert(k);
169 }
170 }
171 let parent_mask = (1 << (b - 1)) - 1;
172 let poor_parents: HashSet<usize> = members
173 .iter()
174 .filter(|(_, ks)| ks.len() < min_batches)
175 .map(|(&code, _)| code & parent_mask)
176 .collect();
177 if poor_parents.is_empty() {
178 continue;
179 }
180 for (level, code) in node.iter_mut() {
181 let parent = *code & parent_mask;
182 if *level >= b && poor_parents.contains(&parent) {
183 *level = b - 1;
184 *code = parent;
185 }
186 }
187 }
188 node.into_iter().map(|(l, c)| node_label(l, c)).collect()
189}
190
191pub fn batch_indices<T>(batch_membership: &[T]) -> Vec<usize>
193where
194 T: std::hash::Hash + Eq + Clone,
195{
196 let mut index: HashMap<T, usize> = HashMap::default();
197 batch_membership
198 .iter()
199 .map(|t| {
200 let next = index.len();
201 *index.entry(t.clone()).or_insert(next)
202 })
203 .collect()
204}