1use super::stats::DEFAULT_COARSEST_SORT_DIM;
18use super::*;
19
20pub(super) fn pad_numeric_labels(cell_to_group: &[usize], k: usize) -> Vec<String> {
21 let width = {
22 let mut w = 1usize;
23 let mut n = k.max(1) - 1;
24 while n >= 10 {
25 w += 1;
26 n /= 10;
27 }
28 w
29 };
30 cell_to_group
31 .iter()
32 .map(|g| format!("{:0width$}", g, width = width))
33 .collect()
34}
35
36pub(super) fn fine_to_coarse_from_refined(
43 pbsamp_to_fine: &[usize],
44 pbsamp_to_coarse: &[usize],
45 num_fine: usize,
46) -> Vec<usize> {
47 let mut mapping = vec![usize::MAX; num_fine];
48 for pbsamp in 0..pbsamp_to_fine.len() {
49 let f = pbsamp_to_fine[pbsamp];
50 if mapping[f] == usize::MAX {
51 mapping[f] = pbsamp_to_coarse[pbsamp];
52 } else {
53 debug_assert_eq!(
54 mapping[f], pbsamp_to_coarse[pbsamp],
55 "refinement broke hierarchy at fine group {}",
56 f
57 );
58 }
59 }
60 mapping
61}
62
63pub(super) fn initial_per_level_from_hash(
72 fine_codes: &[usize],
73 pb_sample_to_cells: &[Vec<usize>],
74 level_dims: &[usize],
75 strata_bits: usize,
76) -> Vec<Vec<usize>> {
77 let num_pb = pb_sample_to_cells.len();
78 level_dims
79 .iter()
80 .map(|&d| {
81 let width = d.saturating_add(strata_bits);
82 let mask = if width >= usize::BITS as usize {
83 usize::MAX
84 } else {
85 (1_usize << width).wrapping_sub(1)
86 };
87 let codes: Vec<usize> = (0..num_pb)
88 .map(|pbsamp| fine_codes[pb_sample_to_cells[pbsamp][0]] & mask)
89 .collect();
90 crate::alg::refine_multilevel::compact_labels(&codes).0
91 })
92 .collect()
93}
94
95pub(super) fn build_reproject_offsets(
106 fine_codes: &[usize],
107 pb_sample_to_cells: &[Vec<usize>],
108 level_dims: &[usize],
109 strata_bits: usize,
110) -> Vec<Vec<usize>> {
111 let raw: Vec<usize> = pb_sample_to_cells
112 .iter()
113 .map(|cells| fine_codes[cells[0]])
114 .collect();
115 (0..level_dims.len())
116 .map(|level| {
117 if level + 1 < level_dims.len() {
118 let parent_dim = level_dims[level + 1].saturating_add(strata_bits);
119 let nbits = level_dims[level].saturating_sub(level_dims[level + 1]);
120 let mask = if nbits >= usize::BITS as usize {
121 usize::MAX
122 } else {
123 (1_usize << nbits).wrapping_sub(1)
124 };
125 raw.iter().map(|&c| (c >> parent_dim) & mask).collect()
126 } else {
127 Vec::new()
128 }
129 })
130 .collect()
131}
132
133pub(super) fn refine_or_identity(
136 allow_refine: bool,
137 inputs: &crate::alg::refine_multilevel::RefineInputs<'_>,
138 refine_params: &crate::alg::refine_multilevel::RefineParams,
139) -> anyhow::Result<crate::alg::refine_multilevel::RefinedAssignment> {
140 if allow_refine {
141 crate::alg::refine_multilevel::refine_assignments(inputs, refine_params)
142 } else {
143 let mut pbsamp_to_group: Vec<Vec<usize>> =
144 Vec::with_capacity(inputs.initial_sc_to_group_per_level.len());
145 let mut num_groups_per_level =
146 Vec::with_capacity(inputs.initial_sc_to_group_per_level.len());
147 for lvl in inputs.initial_sc_to_group_per_level {
148 let (compact, k) = crate::alg::refine_multilevel::compact_labels(lvl);
149 num_groups_per_level.push(k);
150 pbsamp_to_group.push(compact);
151 }
152 Ok(crate::alg::refine_multilevel::RefinedAssignment {
153 pbsamp_to_group,
154 num_groups_per_level,
155 })
156 }
157}
158
159pub(super) fn split_anchored_finest_groups(
185 mut refined: crate::alg::refine_multilevel::RefinedAssignment,
186 layout: &PbSampleLayout,
187) -> crate::alg::refine_multilevel::RefinedAssignment {
188 let mut anchored: Vec<(usize, usize)> = layout
191 .singleton_col
192 .iter()
193 .enumerate()
194 .filter_map(|(p, col)| col.map(|c| (c, p)))
195 .collect();
196 if anchored.is_empty() {
197 return refined;
198 }
199 anchored.sort_unstable_by_key(|&(col, _)| col);
200
201 let finest = &mut refined.pbsamp_to_group[0];
202 let ordinary: Vec<usize> = layout
203 .singleton_col
204 .iter()
205 .zip(finest.iter())
206 .filter_map(|(a, &g)| a.is_none().then_some(g))
207 .collect();
208 let (compact, k_new) = crate::alg::refine_multilevel::compact_labels(&ordinary);
209 let mut compact = compact.into_iter();
210 for (p, a) in layout.singleton_col.iter().enumerate() {
211 if a.is_none() {
212 finest[p] = compact
213 .next()
214 .expect("one compacted label per ordinary pb-sample");
215 }
216 }
217 for (j, &(_, p)) in anchored.iter().enumerate() {
218 finest[p] = k_new + j;
219 }
220 refined.num_groups_per_level[0] = k_new + anchored.len();
221 info!(
222 "Append-only finest partition: {} new-data groups + {} carried + {} bulk singletons",
223 k_new,
224 anchored
225 .iter()
226 .filter(|&&(_, p)| !layout.is_bulk(p))
227 .count(),
228 anchored.iter().filter(|&&(_, p)| layout.is_bulk(p)).count(),
229 );
230 refined
231}
232
233#[derive(Clone, Copy)]
237pub(super) struct RefineCollectCtx<'a> {
238 pub(super) fine_codes: &'a [usize],
239 pub(super) group_to_cols_finest: &'a [Vec<usize>],
240 pub(super) level_dims: &'a [usize],
241 pub(super) num_features: usize,
242 pub(super) num_batches: usize,
243 pub(super) knn: usize,
244 pub(super) opt_iter: usize,
245 pub(super) refine_params: &'a crate::alg::refine_multilevel::RefineParams,
246 pub(super) output_calibration: legume_numeric::param::traits::CalibrateTarget,
249 pub(super) anchor_batches: Option<&'a [usize]>,
251 pub(super) summary_batches: Option<&'a [usize]>,
255 pub(super) bulk_batches: Option<&'a [usize]>,
257 pub(super) observe_panels: bool,
259 pub(super) keep_finest_stats: bool,
261 pub(super) pb_tree: Option<&'a PbTree>,
263 pub(super) cell_to_stratum: Option<&'a [usize]>,
265 pub(super) exclude_unmatched_from_delta: bool,
268 pub(super) strata_bits: usize,
271}
272
273pub(super) fn refine_and_collect_single_layer(
284 data_vec: &mut SparseIoVec,
285 proj_kn: &DMatrix<f32>,
286 ctx: &RefineCollectCtx<'_>,
287) -> anyhow::Result<MultilevelCollapseOut> {
288 let RefineCollectCtx {
289 fine_codes,
290 group_to_cols_finest: _,
291 level_dims,
292 num_features,
293 num_batches,
294 knn,
295 opt_iter,
296 refine_params,
297 output_calibration,
298 anchor_batches: _,
299 summary_batches: _,
300 bulk_batches: _,
301 observe_panels: _,
302 keep_finest_stats: _,
303 pb_tree: _,
304 cell_to_stratum: _,
305 exclude_unmatched_from_delta: _,
306 strata_bits,
307 } = *ctx;
308 info!(
309 "Multi-level refinement path (BBKNN + DC-SBM): {} levels",
310 level_dims.len()
311 );
312
313 let pb_samples = build_pb_samples(
315 data_vec,
316 proj_kn,
317 num_features,
318 ctx.summary_batches.unwrap_or(&[]),
319 ctx.bulk_batches.unwrap_or(&[]),
320 ctx.cell_to_stratum,
321 )?;
322 let num_pb = pb_samples.layout.cell_counts.len();
323 let ncells_dbg = proj_kn.ncols();
324 info!(
325 "Built {} pb-samples from {} cells (ratio {:.2}; knn={})",
326 num_pb,
327 ncells_dbg,
328 num_pb as f32 / ncells_dbg.max(1) as f32,
329 knn
330 );
331 if num_pb as f32 > 0.8 * ncells_dbg as f32 {
332 warn!(
333 "pb-sample count ({}) is close to cell count ({}) — hash partition is too fine \
334 (many 1-cell pb-samples). Consider lowering --sort-dim.",
335 num_pb, ncells_dbg
336 );
337 }
338
339 let ncols = proj_kn.ncols();
341 let pb_sample_to_cells = build_pb_sample_to_cells(&pb_samples.layout);
342
343 let initial_per_level =
344 initial_per_level_from_hash(fine_codes, &pb_sample_to_cells, level_dims, strata_bits);
345 let empty: [ColumnDict<usize>; 0] = [];
346 let batch_knn: &[ColumnDict<usize>] = if num_batches >= 2 {
347 data_vec
348 .batch_knn_lookup()
349 .ok_or_else(|| anyhow::anyhow!("batch_knn_lookup not built"))?
350 .as_slice()
351 } else {
352 &empty
353 };
354 let reproject_offsets =
355 build_reproject_offsets(fine_codes, &pb_sample_to_cells, level_dims, strata_bits);
356 let inputs = crate::alg::refine_multilevel::RefineInputs {
357 layout: &pb_samples.layout,
358 gene_sums: &pb_samples.gene_sums,
359 num_genes: num_features,
360 pb_sample_to_cells: &pb_sample_to_cells,
361 batch_knn_lookup: batch_knn,
362 k_per_batch: knn,
363 initial_sc_to_group_per_level: &initial_per_level,
364 reproject_offsets_per_level: &reproject_offsets,
365 };
366 let refined = split_anchored_finest_groups(
367 refine_or_identity(num_batches >= 2, &inputs, refine_params)?,
368 &pb_samples.layout,
369 );
370
371 {
379 let n_leaves = initial_per_level
380 .first()
381 .map(|lvl| lvl.iter().copied().max().map_or(0, |m| m + 1))
382 .unwrap_or(0);
383 let finest = &refined.pbsamp_to_group[0];
384 let k_fin = refined.num_groups_per_level[0];
385 let mut batch_mask = vec![0u128; k_fin];
387 let b2g = &pb_samples.layout.pb_sample_to_batch;
388 for (pb, &g) in finest.iter().enumerate() {
389 let b = b2g[pb];
390 if b < 128 {
391 batch_mask[g] |= 1u128 << b;
392 }
393 }
394 let spans: Vec<u32> = batch_mask.iter().map(|m| m.count_ones()).collect();
395 let multi = spans.iter().filter(|&&c| c > 1).count();
396 let max_b = spans.iter().copied().max().unwrap_or(0);
397 let mean_b = spans.iter().map(|&c| c as f64).sum::<f64>() / k_fin.max(1) as f64;
398 info!(
399 "collapse structure: {} pb-samples, {} batches, {} leaf codes (finest init), \
400 {} refined finest groups; finest groups spanning >1 batch: {}/{} \
401 (max {} batches/group, mean {:.2})",
402 num_pb, num_batches, n_leaves, k_fin, multi, k_fin, max_b, mean_b
403 );
404 }
405
406 let num_levels = level_dims.len();
410 let k_finest = refined.num_groups_per_level[0];
411 let mut cell_to_group_finest = vec![0usize; ncols];
412 for (pbsamp, cells) in pb_sample_to_cells.iter().enumerate() {
413 let g = refined.pbsamp_to_group[0][pbsamp];
414 for &c in cells {
415 cell_to_group_finest[c] = g;
416 }
417 }
418 let finest_str = pad_numeric_labels(&cell_to_group_finest, k_finest);
419 let nthreads = rayon::current_num_threads();
420 info!(
421 "Assigning {} cells to {} finest pb-sample groups ({} rayon threads) ...",
422 ncols, k_finest, nthreads
423 );
424 data_vec.assign_groups(&finest_str, None);
425 debug_assert_eq!(data_vec.num_groups(), k_finest);
426
427 let mut fine_stat = CollapsedStat::new(num_features, k_finest, num_batches);
428 fine_stat.exclude_unmatched_from_delta = ctx.exclude_unmatched_from_delta;
429 info!("Collecting basic stats over {} groups ...", k_finest);
430 data_vec.collect_basic_stat(&mut fine_stat)?;
431 if num_batches >= 2 {
432 info!(
433 "Collecting per-batch stats over {} groups × {} batches ...",
434 k_finest, num_batches
435 );
436 data_vec.collect_batch_stat(&mut fine_stat)?;
437 let batch_knn = data_vec
438 .batch_knn_lookup()
439 .ok_or_else(|| anyhow::anyhow!("batch_knn_lookup not built"))?;
440 info!(
441 "Collecting cross-batch matched stats (knn={}) over {} pb-samples ...",
442 knn, num_pb
443 );
444 collect_matched_stat_coarse(
445 &pb_samples.layout,
446 &pb_samples.gene_sums,
447 &refined.pbsamp_to_group[0],
448 batch_knn.as_slice(),
449 knn,
450 ctx.anchor_batches,
451 &mut fine_stat,
452 )?;
453 }
454
455 info!(
456 "Level 1/{}: refined k={} (finest; {} cells read)",
457 num_levels, k_finest, ncols
458 );
459 if ctx.observe_panels {
460 attach_observability(&mut fine_stat, data_vec)?;
461 }
462
463 let mut results: Vec<CollapsedOut> = Vec::with_capacity(num_levels);
464 let finest_out = optimize(
465 &fine_stat,
466 (1.0, 1.0),
467 opt_iter,
468 &format!("Fit L1/{}", num_levels),
469 output_calibration,
470 ctx.keep_finest_stats,
471 )?;
472 results.push(finest_out);
473
474 let mut prev_stat = fine_stat;
475 for level in 1..num_levels {
476 let k_prev = refined.num_groups_per_level[level - 1];
477 let k_level = refined.num_groups_per_level[level];
478 let fine_to_coarse = fine_to_coarse_from_refined(
479 &refined.pbsamp_to_group[level - 1],
480 &refined.pbsamp_to_group[level],
481 k_prev,
482 );
483 let coarse_stat = merge_stat(&prev_stat, &fine_to_coarse, k_level);
484 info!(
485 "Level {}/{}: refined k={} (merged from {})",
486 level + 1,
487 num_levels,
488 k_level,
489 k_prev
490 );
491 let level_opt_iter = (opt_iter / 2).max(10);
492 let out = optimize(
493 &coarse_stat,
494 (1.0, 1.0),
495 level_opt_iter,
496 &format!("Fit L{}/{}", level + 1, num_levels),
497 output_calibration,
498 false,
499 )?;
500 results.push(out);
501 prev_stat = coarse_stat;
502 }
503 info!(
504 "Fitted pseudobulk posteriors for {} refined levels: k = {:?} (finest first)",
505 num_levels, refined.num_groups_per_level
506 );
507
508 let cell_to_pb_per_level = cell_to_pb_per_level(&refined, &pb_sample_to_cells, ncols);
509
510 let pb_tree = ctx.pb_tree.map(|tree| {
512 let mut tree = tree.clone();
513 let mut leaf_pbs: HashMap<usize, std::collections::BTreeSet<usize>> = HashMap::default();
514 if let Some(finest) = cell_to_pb_per_level.first() {
515 for (c, &code) in fine_codes.iter().enumerate() {
516 leaf_pbs.entry(code).or_default().insert(finest[c]);
517 }
518 }
519 let mut leaves: Vec<(usize, Vec<usize>)> = leaf_pbs
520 .into_iter()
521 .map(|(code, pbs)| (code, pbs.into_iter().collect()))
522 .collect();
523 leaves.sort_by_key(|x| x.0);
524 tree.leaf_to_finest_pb = leaves;
525 tree
526 });
527
528 Ok(MultilevelCollapseOut {
529 levels: results,
530 cell_to_pb_per_level,
531 pb_tree,
532 })
533}
534
535fn cell_to_pb_per_level(
538 refined: &crate::alg::refine_multilevel::RefinedAssignment,
539 pb_sample_to_cells: &[Vec<usize>],
540 ncols: usize,
541) -> Vec<Vec<usize>> {
542 refined
543 .pbsamp_to_group
544 .iter()
545 .map(|groups| {
546 let mut c2g = vec![0usize; ncols];
547 for (pbsamp, cells) in pb_sample_to_cells.iter().enumerate() {
548 for &c in cells {
549 c2g[c] = groups[pbsamp];
550 }
551 }
552 c2g
553 })
554 .collect()
555}
556
557pub struct StackCollapseOut {
561 pub levels: Vec<Vec<CollapsedOut>>,
562 pub cell_to_pb_per_level: Vec<Vec<usize>>,
563}
564
565pub(super) fn refine_and_collect_stack(
571 stack: &mut SparseIoStack,
572 proj_kn: &DMatrix<f32>,
573 ctx: &RefineCollectCtx<'_>,
574) -> anyhow::Result<StackCollapseOut> {
575 let RefineCollectCtx {
576 fine_codes,
577 group_to_cols_finest,
578 level_dims,
579 num_features: _,
580 num_batches,
581 knn,
582 opt_iter,
583 refine_params,
584 output_calibration,
585 anchor_batches: _,
586 summary_batches: _,
587 bulk_batches: _,
588 observe_panels: _,
589 keep_finest_stats,
590 pb_tree: _,
591 cell_to_stratum,
592 exclude_unmatched_from_delta,
593 strata_bits,
594 } = *ctx;
595 let num_layers = stack.num_types();
596 info!(
597 "Multi-level stack refinement (BBKNN + DC-SBM): {} layers × {} levels",
598 num_layers,
599 level_dims.len()
600 );
601
602 let ncols = proj_kn.ncols();
603 let col_to_batch: Vec<usize> = stack.stack[0].get_batch_membership(0..ncols);
604
605 let layout = build_pb_sample_layout(
608 group_to_cols_finest,
609 &col_to_batch,
610 proj_kn,
611 None,
612 &[],
613 &[],
614 cell_to_stratum,
615 )?;
616 let num_pb = layout.cell_counts.len();
617
618 let owner_num_features = stack.stack[0].num_rows();
620 let gene_sums_owner = collect_pb_sample_gene_sums(
621 &stack.stack[0],
622 group_to_cols_finest,
623 &layout.cell_to_pbsamp,
624 num_pb,
625 )?;
626
627 let pb_sample_to_cells = build_pb_sample_to_cells(&layout);
628
629 let initial_per_level =
630 initial_per_level_from_hash(fine_codes, &pb_sample_to_cells, level_dims, strata_bits);
631 let empty: [ColumnDict<usize>; 0] = [];
632 let batch_knn: &[ColumnDict<usize>] = if num_batches >= 2 {
633 stack.stack[0]
634 .batch_knn_lookup()
635 .ok_or_else(|| anyhow::anyhow!("batch_knn_lookup not built"))?
636 .as_slice()
637 } else {
638 &empty
639 };
640 let reproject_offsets =
641 build_reproject_offsets(fine_codes, &pb_sample_to_cells, level_dims, strata_bits);
642 let inputs = crate::alg::refine_multilevel::RefineInputs {
643 layout: &layout,
644 gene_sums: &gene_sums_owner,
645 num_genes: owner_num_features,
646 pb_sample_to_cells: &pb_sample_to_cells,
647 batch_knn_lookup: batch_knn,
648 k_per_batch: knn,
649 initial_sc_to_group_per_level: &initial_per_level,
650 reproject_offsets_per_level: &reproject_offsets,
651 };
652 let refined = refine_or_identity(num_batches >= 2, &inputs, refine_params)?;
653
654 let mut per_layer_gene_sums: Vec<GeneSums> = Vec::with_capacity(num_layers);
656 for (d, layer) in stack.stack.iter().enumerate() {
657 if d == 0 {
658 per_layer_gene_sums.push(gene_sums_owner.clone());
659 } else {
660 per_layer_gene_sums.push(collect_pb_sample_gene_sums(
661 layer,
662 group_to_cols_finest,
663 &layout.cell_to_pbsamp,
664 num_pb,
665 )?);
666 }
667 }
668
669 let num_levels = level_dims.len();
672 let k_finest = refined.num_groups_per_level[0];
673 let mut cell_to_group_finest = vec![0usize; ncols];
674 for (pbsamp, cells) in pb_sample_to_cells.iter().enumerate() {
675 let g = refined.pbsamp_to_group[0][pbsamp];
676 for &c in cells {
677 cell_to_group_finest[c] = g;
678 }
679 }
680 let finest_str = pad_numeric_labels(&cell_to_group_finest, k_finest);
681 let nthreads = rayon::current_num_threads();
682 info!(
683 "Assigning {} cells to {} finest pb-sample groups across {} layers ({} rayon threads) ...",
684 ncols, k_finest, num_layers, nthreads
685 );
686 for layer in stack.stack.iter_mut() {
687 layer.assign_groups(&finest_str, None);
688 }
689
690 let mut fine_stats: Vec<CollapsedStat> = Vec::with_capacity(num_layers);
691 let mut finest_layer_results = Vec::with_capacity(num_layers);
692 for (d, layer) in stack.stack.iter().enumerate() {
693 let num_features = layer.num_rows();
694 let mut stat = CollapsedStat::new(num_features, k_finest, num_batches);
695 stat.exclude_unmatched_from_delta = exclude_unmatched_from_delta;
696 info!(
697 "Layer {}/{}: collecting basic stats over {} groups ...",
698 d + 1,
699 num_layers,
700 k_finest
701 );
702 layer.collect_basic_stat(&mut stat)?;
703 if num_batches >= 2 {
704 info!(
705 "Layer {}/{}: collecting per-batch stats ({} batches) ...",
706 d + 1,
707 num_layers,
708 num_batches
709 );
710 layer.collect_batch_stat(&mut stat)?;
711 let batch_knn = layer
712 .batch_knn_lookup()
713 .ok_or_else(|| anyhow::anyhow!("batch_knn_lookup not built"))?;
714 info!(
715 "Layer {}/{}: collecting cross-batch matched stats (knn={}) over {} pb-samples ...",
716 d + 1,
717 num_layers,
718 knn,
719 num_pb
720 );
721 collect_matched_stat_coarse(
722 &layout,
723 &per_layer_gene_sums[d],
724 &refined.pbsamp_to_group[0],
725 batch_knn.as_slice(),
726 knn,
727 ctx.anchor_batches,
728 &mut stat,
729 )?;
730 }
731 let out = optimize(
732 &stat,
733 (1.0, 1.0),
734 opt_iter,
735 &format!("Fit L1/{} layer {}/{}", num_levels, d + 1, num_layers),
736 output_calibration,
737 keep_finest_stats,
738 )?;
739 finest_layer_results.push(out);
740 fine_stats.push(stat);
741 }
742 info!(
743 "Level 1/{}: refined k={} (finest; {} layers × {} cells)",
744 num_levels, k_finest, num_layers, ncols
745 );
746 let mut results: Vec<Vec<CollapsedOut>> = Vec::with_capacity(num_levels);
747 results.push(finest_layer_results);
748
749 let mut prev_stats = fine_stats;
750 for level in 1..num_levels {
751 let k_prev = refined.num_groups_per_level[level - 1];
752 let k_level = refined.num_groups_per_level[level];
753 let fine_to_coarse = fine_to_coarse_from_refined(
754 &refined.pbsamp_to_group[level - 1],
755 &refined.pbsamp_to_group[level],
756 k_prev,
757 );
758 let level_opt_iter = (opt_iter / 2).max(10);
759 let mut layer_results = Vec::with_capacity(num_layers);
760 let mut coarse_stats = Vec::with_capacity(num_layers);
761 for (d, prev_stat) in prev_stats.iter().enumerate() {
762 let coarse_stat = merge_stat(prev_stat, &fine_to_coarse, k_level);
763 let out = optimize(
764 &coarse_stat,
765 (1.0, 1.0),
766 level_opt_iter,
767 &format!(
768 "Fit L{}/{} layer {}/{}",
769 level + 1,
770 num_levels,
771 d + 1,
772 num_layers
773 ),
774 output_calibration,
775 false,
776 )?;
777 layer_results.push(out);
778 coarse_stats.push(coarse_stat);
779 }
780 info!(
781 "Level {}/{}: refined k={} (merged from {}, {} layers)",
782 level + 1,
783 num_levels,
784 k_level,
785 k_prev,
786 num_layers
787 );
788 results.push(layer_results);
789 prev_stats = coarse_stats;
790 }
791
792 Ok(StackCollapseOut {
793 levels: results,
794 cell_to_pb_per_level: cell_to_pb_per_level(&refined, &pb_sample_to_cells, ncols),
795 })
796}
797
798pub(super) fn compute_level_sort_dims(finest_sort_dim: usize, num_levels: usize) -> Vec<usize> {
802 if num_levels <= 1 {
803 return vec![finest_sort_dim];
804 }
805 let coarsest = DEFAULT_COARSEST_SORT_DIM.min(finest_sort_dim);
806 let mut dims = Vec::with_capacity(num_levels);
807 for level in 0..num_levels {
808 let t = level as f32 / (num_levels - 1) as f32;
810 let dim = finest_sort_dim as f32 - t * (finest_sort_dim - coarsest) as f32;
811 let dim = dim.round() as usize;
812 if dims.last() != Some(&dim) {
813 dims.push(dim);
814 }
815 }
816 dims
817}
818
819#[cfg(test)]
820#[path = "refine_tests.rs"]
821mod reproject_tests;