data_beans/sparse_io_vector/
batch.rs1#![allow(dead_code)]
2
3use super::*;
4
5impl SparseIoVec {
6 #[cfg(feature = "ndarray")]
17 pub fn register_batches_ndarray<T>(
18 &mut self,
19 feature_matrix: &ndarray::Array2<f32>,
20 batch_membership: &[T],
21 ) -> anyhow::Result<()>
22 where
23 T: Sync + Send + std::hash::Hash + Eq + Clone + ToString,
24 {
25 anyhow::ensure!(
26 feature_matrix.ncols() == batch_membership.len(),
27 "the feature matrix has {} columns but there are {} batch labels",
28 feature_matrix.ncols(),
29 batch_membership.len(),
30 );
31 self._register_batches(
32 feature_matrix,
33 batch_membership,
34 |feature_matrix, batch_cells| {
35 let columns = batch_cells
36 .iter()
37 .map(|&c| feature_matrix.column(c))
38 .collect::<Vec<_>>();
39 ColumnDict::<usize>::from_ndarray_views(columns, batch_cells.clone())
40 },
41 )
42 }
43
44 pub fn register_batches_dmatrix<T>(
51 &mut self,
52 feature_matrix: &nalgebra::DMatrix<f32>,
53 batch_membership: &[T],
54 ) -> anyhow::Result<()>
55 where
56 T: Sync + Send + std::hash::Hash + Eq + Clone + ToString,
57 {
58 anyhow::ensure!(
59 feature_matrix.ncols() == batch_membership.len(),
60 "the feature matrix has {} columns but there are {} batch labels",
61 feature_matrix.ncols(),
62 batch_membership.len(),
63 );
64
65 self._register_batches(
66 feature_matrix,
67 batch_membership,
68 |feature_matrix, batch_cells| {
69 let columns = batch_cells
70 .iter()
71 .map(|&c| feature_matrix.column(c))
72 .collect::<Vec<_>>();
73 ColumnDict::<usize>::from_dvector_views(columns, batch_cells.clone())
74 },
75 )
76 }
77
78 fn _register_batches<M, F, T>(
79 &mut self,
80 feature_matrix: &M,
81 batch_membership: &[T],
82 create_column_dict: F,
83 ) -> anyhow::Result<()>
84 where
85 M: Sync,
86 F: Fn(&M, &Vec<usize>) -> ColumnDict<usize> + Sync,
87 T: Sync + Send + std::hash::Hash + Eq + Clone + ToString,
88 {
89 let ntot = self.num_columns();
90 anyhow::ensure!(
91 batch_membership.len() == ntot,
92 "batch membership has {} entries but there are {ntot} columns",
93 batch_membership.len(),
94 );
95 let batches = partition_by_membership(batch_membership, None);
96 let mut col_to_batch = vec![0; ntot];
97
98 let n_threads = rayon::current_num_threads();
107 let outer_parallel = batches.len() >= n_threads;
108
109 info!(
110 "building per-batch kNN indices ({} batches, {} cells, {}) ...",
111 batches.len(),
112 ntot,
113 if outer_parallel {
114 "parallel over batches"
115 } else {
116 "sequential over batches"
117 }
118 );
119
120 let prog_bar =
121 crate::sparse_data_visitors::styled_progress_bar(batches.len() as u64, "batches kNN");
122
123 let mut batches_vec: Vec<_> = batches.into_iter().collect();
130 batches_vec.sort_by_key(|a| a.0.to_string());
131 let mut enumerated: Vec<_> = batches_vec.iter().enumerate().collect();
135 enumerated.sort_by_key(|(_, (_, cells))| std::cmp::Reverse(cells.len()));
136 let mut idx_name_glob_dict: Vec<_> = if outer_parallel {
137 enumerated
138 .into_par_iter()
139 .progress_with(prog_bar.clone())
140 .map(|(batch_index, (batch_name, batch_glob_indices))| {
141 (
142 batch_index,
143 batch_name.to_string().into_boxed_str(),
144 batch_glob_indices.clone(),
145 create_column_dict(feature_matrix, batch_glob_indices),
146 )
147 })
148 .collect()
149 } else {
150 enumerated
151 .into_iter()
152 .progress_with(prog_bar.clone())
153 .map(|(batch_index, (batch_name, batch_glob_indices))| {
154 (
155 batch_index,
156 batch_name.to_string().into_boxed_str(),
157 batch_glob_indices.clone(),
158 create_column_dict(feature_matrix, batch_glob_indices),
159 )
160 })
161 .collect()
162 };
163 prog_bar.finish_and_clear();
164
165 idx_name_glob_dict.sort_by_key(|&(idx, _, _, _)| idx);
166
167 let mut batch_names = vec![];
168 let mut batch_to_cols = vec![];
169 let mut dictionaries = vec![];
170
171 for (batch_idx, batch_name, glob_indices, dict) in idx_name_glob_dict.into_iter() {
172 dict.names()
173 .iter()
174 .for_each(|&cell| col_to_batch[cell] = batch_idx);
175
176 batch_names.push(batch_name);
177 batch_to_cols.push(glob_indices);
178 dictionaries.push(dict);
179 }
180
181 self.derived.batch_knn_lookup = Some(dictionaries);
182 self.derived.col_to_batch = Some(col_to_batch);
183 self.derived.batch_to_cols = Some(batch_to_cols);
184 self.derived.batch_idx_to_name = Some(batch_names);
185
186 if self.num_batches() > 2 {
187 self.sort_batch_proximity()?;
188 }
189
190 Ok(())
191 }
192
193 fn sort_batch_proximity(&mut self) -> anyhow::Result<()> {
194 let lookups = self
195 .derived
196 .batch_knn_lookup
197 .as_ref()
198 .ok_or(anyhow::anyhow!("no knn lookup"))?;
199
200 use nalgebra::DMatrix;
201
202 info!("retrieving batch-specific lookups");
203 let batch_data = lookups
204 .iter()
205 .flat_map(|dict| {
206 let data: Vec<f32> = dict.points().flatten().copied().collect();
207 let ncols = dict.num_points();
208 let nrows = data.len() / ncols;
209 DMatrix::from_vec(nrows, ncols, data)
210 .column_mean()
211 .data
212 .as_vec()
213 .clone()
214 })
215 .collect::<Vec<_>>();
216
217 let ncols = self.num_batches();
218 let nrows = batch_data.len() / ncols;
219 let batch_features = DMatrix::<f32>::from_vec(nrows, ncols, batch_data);
220
221 info!(
222 "built feature matrix across batches: {} x {}",
223 batch_features.nrows(),
224 batch_features.ncols()
225 );
226
227 let nbatches = self.num_batches();
228 let batches = (0..nbatches).collect();
229
230 let dict = ColumnDict::<usize>::from_dvector_views(
231 batch_features.column_iter().collect(),
232 batches,
233 );
234
235 let ret: Vec<Vec<usize>> = (0..nbatches)
236 .into_par_iter()
237 .map(|b| {
238 dict.search_by_query_name(&b, nbatches, false)
239 .map(|(others, _)| others)
240 })
241 .collect::<anyhow::Result<Vec<Vec<usize>>>>()?;
242 self.derived.between_batch_proximity = Some(ret);
243
244 Ok(())
245 }
246
247 pub fn batch_name_map(&self) -> Option<HashMap<Box<str>, usize>> {
248 self.derived.batch_idx_to_name.as_ref().map(|names| {
249 names
250 .iter()
251 .enumerate()
252 .map(|(idx, name)| (name.clone(), idx))
253 .collect::<HashMap<Box<str>, usize>>()
254 })
255 }
256
257 pub fn num_batches(&self) -> usize {
258 if let Some(v) = &self.derived.batch_to_cols {
259 v.len()
260 } else if let Some(v) = &self.derived.batch_knn_lookup {
261 v.len()
262 } else {
263 0
264 }
265 }
266
267 pub fn batch_knn_lookup(&self) -> Option<&Vec<ColumnDict<usize>>> {
271 self.derived.batch_knn_lookup.as_ref()
272 }
273
274 pub fn register_batch_membership<T>(&mut self, batch_membership: &[T])
283 where
284 T: Sync + Send + std::hash::Hash + Eq + Clone + ToString,
285 {
286 self.try_register_batch_membership(batch_membership)
287 .unwrap_or_else(|e| panic!("{e}"));
288 }
289
290 pub fn try_register_batch_membership<T>(&mut self, batch_membership: &[T]) -> anyhow::Result<()>
298 where
299 T: Sync + Send + std::hash::Hash + Eq + Clone + ToString,
300 {
301 let ntot = self.num_columns();
302 anyhow::ensure!(
303 batch_membership.len() == ntot,
304 "batch membership has {} entries but there are {ntot} columns",
305 batch_membership.len(),
306 );
307 let batches = partition_by_membership(batch_membership, None);
308 let mut col_to_batch = vec![0; ntot];
309
310 let mut sorted_batches: Vec<_> = batches.into_iter().collect();
311 sorted_batches.sort_by_key(|a| a.0.to_string());
312
313 let mut batch_names = Vec::with_capacity(sorted_batches.len());
314 let mut batch_to_cols = Vec::with_capacity(sorted_batches.len());
315
316 for (batch_idx, (batch_name, glob_indices)) in sorted_batches.into_iter().enumerate() {
317 for &cell in &glob_indices {
318 col_to_batch[cell] = batch_idx;
319 }
320 batch_names.push(batch_name.to_string().into_boxed_str());
321 batch_to_cols.push(glob_indices);
322 }
323
324 self.derived.col_to_batch = Some(col_to_batch);
325 self.derived.batch_to_cols = Some(batch_to_cols);
326 self.derived.batch_idx_to_name = Some(batch_names);
327 Ok(())
328 }
329
330 pub fn register_column_multiplicity(&mut self, multiplicity: &[f32]) -> anyhow::Result<()> {
349 let ntot = self.num_columns();
350 anyhow::ensure!(
351 multiplicity.len() == ntot,
352 "column multiplicity has {} entries but there are {ntot} columns",
353 multiplicity.len(),
354 );
355 if let Some((i, w)) = multiplicity
356 .iter()
357 .enumerate()
358 .find(|(_, w)| !w.is_finite() || **w <= 0.0)
359 {
360 anyhow::bail!("column {i} has multiplicity {w}; weights must be finite and positive");
361 }
362 self.col_multiplicity = Some(multiplicity.to_vec());
363 Ok(())
364 }
365
366 #[must_use]
368 pub fn column_multiplicity(&self, col: usize) -> f32 {
369 self.col_multiplicity.as_ref().map_or(1.0, |m| m[col])
370 }
371
372 #[must_use]
374 pub fn has_column_multiplicity(&self) -> bool {
375 self.col_multiplicity.is_some()
376 }
377
378 #[must_use]
385 pub fn column_multiplicities(&self) -> Option<&[f32]> {
386 self.col_multiplicity.as_deref()
387 }
388
389 pub fn batch_names(&self) -> Option<Vec<Box<str>>> {
390 self.derived.batch_idx_to_name.clone()
391 }
392
393 pub fn batch_to_columns(&self, batch: usize) -> Option<&Vec<usize>> {
394 if let Some(batch_to_cols) = &self.derived.batch_to_cols {
395 Some(&batch_to_cols[batch])
396 } else {
397 None
398 }
399 }
400
401 pub fn get_batch_membership<I>(&self, cells: I) -> Vec<usize>
402 where
403 I: Iterator<Item = usize>,
404 {
405 let cell_to_batch = self
406 .derived
407 .col_to_batch
408 .as_ref()
409 .expect("cell_to_batch not initialized");
410 cells.into_iter().map(|c| cell_to_batch[c]).collect()
411 }
412
413 pub fn column_names(&self) -> anyhow::Result<Vec<Box<str>>> {
414 debug_assert_eq!(self.num_columns(), self.column_names_with_data_tag.len());
415 Ok(self.column_names_with_data_tag.clone())
416 }
417}