use super::types::IndexedSample;
use crate::matrix::traits::CandleDataLoaderOps;
use indicatif::ParallelProgressIterator;
use nalgebra_sparse::CscMatrix;
use rayon::prelude::*;
pub fn top_k_indices_weighted(row: &[f32], weights: &[f32], k: usize) -> (Vec<u32>, Vec<f32>) {
debug_assert_eq!(row.len(), weights.len());
top_k_from_entries(
row.iter()
.zip(weights.iter())
.enumerate()
.map(|(i, (&v, &w))| (i as u32, v, w)),
k,
)
}
pub(crate) fn top_k_from_entries<I>(entries: I, k: usize) -> (Vec<u32>, Vec<f32>)
where
I: Iterator<Item = (u32, f32, f32)>,
{
let mut scored: Vec<(f32, u32, f32)> = entries
.filter_map(|(i, v, w)| {
let score = v.max(0.0).ln_1p() * w;
(score > 0.0).then_some((score, i, v))
})
.collect();
let k = k.min(scored.len());
if k == 0 {
return (Vec::new(), Vec::new());
}
scored.select_nth_unstable_by(k - 1, |a, b| {
b.0.partial_cmp(&a.0).unwrap_or(std::cmp::Ordering::Equal)
});
let mut top: Vec<(u32, f32)> = scored[..k].iter().map(|&(_, i, v)| (i, v)).collect();
top.sort_unstable_by_key(|&(i, _)| i);
let idx: Vec<u32> = top.iter().map(|&(i, _)| i).collect();
let values: Vec<f32> = top.iter().map(|&(_, v)| v).collect();
(idx, values)
}
pub fn csc_columns_to_indexed_samples(
x_dn: &CscMatrix<f32>,
shortlist_weights: &[f32],
context_size: usize,
gene_remap: Option<&[Option<usize>]>,
) -> Vec<IndexedSample> {
debug_assert_eq!(
x_dn.nrows(),
gene_remap.map_or(shortlist_weights.len(), <[_]>::len)
);
(0..x_dn.ncols())
.map(|j| {
let col = x_dn.col(j);
let pairs = col.row_indices().iter().zip(col.values().iter());
let (indices, values) = match gene_remap {
Some(rm) => top_k_from_entries(
pairs.filter_map(|(&r, &v)| {
rm[r].map(|rt| (rt as u32, v, shortlist_weights[rt]))
}),
context_size,
),
None => top_k_from_entries(
pairs.map(|(&r, &v)| (r as u32, v, shortlist_weights[r])),
context_size,
),
};
IndexedSample { indices, values }
})
.collect()
}
pub fn build_indexed_samples<D: CandleDataLoaderOps + Sync>(
data: &D,
n_samples: usize,
context_size: usize,
shortlist_weights: &[f32],
label: &str,
) -> Vec<IndexedSample> {
let prog_bar = super::labeled_bar(label, n_samples as u64);
let out = (0..n_samples)
.into_par_iter()
.progress_with(prog_bar.clone())
.map(|i| {
let row = data.row_to_f32_vec(i);
let (indices, values) = top_k_indices_weighted(&row, shortlist_weights, context_size);
IndexedSample { indices, values }
})
.collect();
prog_bar.finish_and_clear();
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_top_k_indices_weighted() {
let row = vec![0.1, 0.5, 0.3, 0.9, 0.2, 0.7];
let weights = vec![1.0, 0.1, 1.0, 0.01, 1.0, 0.05];
let (indices, values) = top_k_indices_weighted(&row, &weights, 3);
assert_eq!(indices, vec![0, 2, 4]);
assert_eq!(values, vec![0.1, 0.3, 0.2]);
}
#[test]
fn test_top_k_drops_zero_valued_features() {
let row = vec![0.0, 0.4, 0.0, 0.0, 0.7, 0.0];
let weights = vec![1.0; 6];
let (indices, values) = top_k_indices_weighted(&row, &weights, 5);
assert_eq!(indices, vec![1, 4]);
assert_eq!(values, vec![0.4, 0.7]);
let zero_row = vec![0.0; 6];
let (idx0, val0) = top_k_indices_weighted(&zero_row, &weights, 3);
assert!(idx0.is_empty());
assert!(val0.is_empty());
let row_wz = vec![0.4, 0.7, 0.3];
let w_wz = vec![0.0, 1.0, 0.0];
let (idx_wz, val_wz) = top_k_indices_weighted(&row_wz, &w_wz, 3);
assert_eq!(idx_wz, vec![1]);
assert_eq!(val_wz, vec![0.7]);
}
#[test]
fn test_csc_columns_match_dense_top_k() {
let n = 4;
let d = 6;
let dense = [
[0.1f32, 0.5, 0.3, 0.9, 0.2, 0.7],
[0.8, 0.1, 0.6, 0.2, 0.9, 0.3],
[0.0, 0.7, 0.0, 0.4, 0.6, 0.0],
[0.2, 0.3, 0.8, 0.1, 0.5, 0.9],
];
let weights = vec![1.0f32, 0.1, 1.0, 0.01, 1.0, 0.05];
let mut coo = nalgebra_sparse::CooMatrix::<f32>::new(d, n);
for (j, row) in dense.iter().enumerate() {
for (i, &v) in row.iter().enumerate() {
if v != 0.0 {
coo.push(i, j, v);
}
}
}
let csc = CscMatrix::from(&coo);
let from_csc = csc_columns_to_indexed_samples(&csc, &weights, 3, None);
assert_eq!(from_csc.len(), n);
for (j, row) in dense.iter().enumerate() {
let (exp_idx, exp_val) = top_k_indices_weighted(row, &weights, 3);
assert_eq!(from_csc[j].indices, exp_idx, "sample {j} indices");
assert_eq!(from_csc[j].values, exp_val, "sample {j} values");
}
}
}