Skip to main content

legume_numeric/matrix/
dmatrix_util.rs

1use nalgebra::{ComplexField, DMatrix, Matrix};
2use nalgebra_sparse::{coo::CooMatrix, csc::CscMatrix, csr::CsrMatrix};
3
4use crate::matrix::rand_util::{collect_seeded, entropy_seed};
5use num_traits::Float;
6use rand_distr::{Gamma, StandardNormal, Uniform};
7use rayon::prelude::*;
8
9pub use crate::matrix::dmatrix_rsvd::nystrom_basis;
10use crate::matrix::traits::*;
11
12/// Build a `DMatrix<f32>` column-by-column in parallel. `build_col(j, col)`
13/// fills the `j`-th output column (length `nrows`) directly into the
14/// matrix's backing slice — no per-column `Vec` allocation, no stitching.
15///
16/// Each task gets a disjoint `&mut [f32]`, so reductions stay deterministic
17/// (per-column accumulation order is whatever `build_col` does internally).
18pub fn build_columns_par<F>(nrows: usize, ncols: usize, build_col: F) -> DMatrix<f32>
19where
20    F: Fn(usize, &mut [f32]) + Sync + Send,
21{
22    let mut out = DMatrix::<f32>::zeros(nrows, ncols);
23    if nrows > 0 && ncols > 0 {
24        out.as_mut_slice()
25            .par_chunks_mut(nrows)
26            .enumerate()
27            .for_each(|(j, col)| build_col(j, col));
28    }
29    out
30}
31
32pub fn subset_columns<T, D, S, I>(
33    matrix: &Matrix<T, nalgebra::Dyn, D, S>,
34    indices: I,
35) -> anyhow::Result<DMatrix<T>>
36where
37    T: nalgebra::RealField,
38    D: nalgebra::Dim,
39    S: nalgebra::RawStorage<T, nalgebra::Dyn, D>,
40    I: IntoIterator<Item = usize>,
41{
42    let cols = indices
43        .into_iter()
44        .map(|j| matrix.column(j))
45        .collect::<Vec<_>>();
46
47    concatenate_horizontal(&cols)
48}
49
50pub fn subset_rows<T, D, S, I>(
51    matrix: &Matrix<T, D, nalgebra::Dyn, S>,
52    indices: I,
53) -> anyhow::Result<DMatrix<T>>
54where
55    T: nalgebra::RealField,
56    D: nalgebra::Dim,
57    S: nalgebra::RawStorage<T, D, nalgebra::Dyn>,
58    I: IntoIterator<Item = usize>,
59{
60    let rows = indices
61        .into_iter()
62        .map(|j| matrix.row(j))
63        .collect::<Vec<_>>();
64
65    concatenate_vertical(&rows)
66}
67
68pub fn assign_columns<T, D, S, R, I>(
69    source: &Matrix<T, nalgebra::Dyn, D, S>,
70    indices: I,
71    target: &mut Matrix<T, nalgebra::Dyn, D, R>,
72) where
73    T: nalgebra::RealField,
74    D: nalgebra::Dim,
75    S: nalgebra::RawStorage<T, nalgebra::Dyn, D>,
76    R: nalgebra::RawStorageMut<T, nalgebra::Dyn, D>,
77    I: IntoIterator<Item = usize>,
78{
79    indices
80        .into_iter()
81        .zip(source.column_iter())
82        .for_each(|(j, x_j)| {
83            target.column_mut(j).copy_from(&x_j);
84        });
85}
86
87/// concatenate matrices or row vectors vertically
88pub fn concatenate_vertical<T, D, S>(
89    matrices: &[Matrix<T, D, nalgebra::Dyn, S>],
90) -> anyhow::Result<DMatrix<T>>
91where
92    T: nalgebra::RealField,
93    D: nalgebra::Dim,
94    S: nalgebra::RawStorage<T, D, nalgebra::Dyn>,
95{
96    if matrices.is_empty() {
97        anyhow::bail!("empty in concatenate_vertical");
98    }
99
100    let ncols = matrices[0].ncols();
101    assert!(
102        matrices.iter().all(|m| m.ncols() == ncols),
103        "should have the same number of columns"
104    );
105
106    let rows = matrices
107        .iter()
108        .flat_map(|m| m.row_iter().map(|row| row.into_owned()))
109        .collect::<Vec<_>>();
110
111    Ok(DMatrix::from_rows(&rows))
112}
113
114/// concatenate matrices or column vectors horizontally
115pub fn concatenate_horizontal<T, D, S>(
116    matrices: &[Matrix<T, nalgebra::Dyn, D, S>],
117) -> anyhow::Result<DMatrix<T>>
118where
119    T: nalgebra::RealField,
120    D: nalgebra::Dim,
121    S: nalgebra::RawStorage<T, nalgebra::Dyn, D>,
122{
123    if matrices.is_empty() {
124        anyhow::bail!("empty in concatenate_horizontal");
125    }
126
127    let nrows = matrices[0].nrows();
128    assert!(
129        matrices.iter().all(|m| m.nrows() == nrows),
130        "should have the same number of rows"
131    );
132
133    let cols = matrices
134        .iter()
135        .flat_map(|m| m.column_iter().map(|col| col.into_owned()))
136        .collect::<Vec<_>>();
137
138    Ok(DMatrix::from_columns(&cols))
139}
140
141impl<T> AdjustByDivisionOp<nalgebra_sparse::CscMatrix<T>, T> for nalgebra_sparse::CscMatrix<T>
142where
143    T: nalgebra::RealField + Copy + std::iter::Sum<T>,
144{
145    fn adjust_by_division_inplace(&mut self, denom: &nalgebra_sparse::CscMatrix<T>) {
146        self.col_iter_mut()
147            .zip(denom.col_iter())
148            .for_each(|(mut x_j, d_j)| {
149                let dsum = d_j.values().iter().copied().sum::<T>();
150                let xsum = x_j.values().iter().copied().sum::<T>();
151                let scale = if dsum > T::zero() {
152                    xsum / dsum
153                } else {
154                    T::one()
155                };
156
157                let (x_rows, x_values) = x_j.rows_and_values_mut();
158
159                let mut d_j_values = vec![T::zero(); x_rows.len()];
160
161                x_rows.iter().enumerate().for_each(|(idx, &i)| {
162                    if let Some(pos) = d_j.row_indices().iter().position(|&d_i| d_i == i) {
163                        d_j_values[idx] = d_j.values()[pos];
164                    }
165                });
166
167                x_values
168                    .iter_mut()
169                    .zip(d_j_values)
170                    .for_each(|(x_ij, d_ij)| {
171                        if d_ij > T::zero() {
172                            *x_ij /= d_ij * scale;
173                        }
174                    });
175            });
176    }
177
178    fn adjust_by_division_of_selected_inplace(
179        &mut self,
180        denom_db: &nalgebra_sparse::CscMatrix<T>,
181        batches: &[usize],
182    ) {
183        self.col_iter_mut().zip(batches).for_each(|(mut x_j, &b)| {
184            let d_j = denom_db.col(b);
185
186            let dsum = d_j.values().iter().copied().sum::<T>();
187            let xsum = x_j.values().iter().copied().sum::<T>();
188            let scale = if dsum > T::zero() {
189                xsum / dsum
190            } else {
191                T::one()
192            };
193
194            let (x_rows, x_values) = x_j.rows_and_values_mut();
195
196            let mut d_j_values = vec![T::zero(); x_rows.len()];
197
198            x_rows.iter().enumerate().for_each(|(idx, &i)| {
199                if let Some(pos) = d_j.row_indices().iter().position(|&d_i| d_i == i) {
200                    d_j_values[idx] = d_j.values()[pos];
201                }
202            });
203
204            x_values
205                .iter_mut()
206                .zip(d_j_values)
207                .for_each(|(x_ij, d_ij)| {
208                    if d_ij > T::zero() {
209                        *x_ij /= d_ij * scale;
210                    }
211                });
212        });
213    }
214}
215
216/// Self-normalizing Poisson-ratio divide of one sparse column's values, in place.
217///
218/// Models `Y[g] ~ Poisson(X[g]·λ)`: estimate `λ = Σ Y / Σ divisor` over the
219/// column's nonzeros, then replace each `Y[g]` by the residual `Y[g] / (divisor(g)·λ)`
220/// when `divisor(g) > 0` (entries with a non-positive divisor are left unchanged).
221/// `divisor(k)` returns the divisor for the `k`-th value, `k` in `0..vals.len()`.
222///
223/// This is the shared core of the [`AdjustByDivisionOp`] `DMatrix` impls below; it
224/// is also callable directly on any `(values, divisor)` pair without materializing
225/// a matrix — e.g. one cell's counts indexed into a per-pseudobulk fold-factor.
226pub fn adjust_by_poisson_ratio<T, F>(vals: &mut [T], divisor: F)
227where
228    T: nalgebra::RealField + Copy + std::iter::Sum<T>,
229    F: Fn(usize) -> T,
230{
231    let dsum: T = (0..vals.len()).map(&divisor).sum();
232    let xsum: T = vals.iter().copied().sum();
233    let scale = if dsum > T::zero() {
234        xsum / dsum
235    } else {
236        T::one()
237    };
238    for (k, x) in vals.iter_mut().enumerate() {
239        let d = divisor(k);
240        if d > T::zero() {
241            *x /= d * scale;
242        }
243    }
244}
245
246impl<T> AdjustByDivisionOp<nalgebra::DMatrix<T>, T> for nalgebra_sparse::CscMatrix<T>
247where
248    T: nalgebra::RealField + Copy + std::iter::Sum<T>,
249{
250    fn adjust_by_division_of_selected_inplace(
251        &mut self,
252        denom_db: &nalgebra::DMatrix<T>,
253        batches: &[usize],
254    ) {
255        self.col_iter_mut().zip(batches).for_each(|(mut x_j, &b)| {
256            let (rows, vals) = x_j.rows_and_values_mut();
257            adjust_by_poisson_ratio(vals, |k| denom_db[(rows[k], b)]);
258        });
259    }
260
261    fn adjust_by_division_inplace(&mut self, denom: &nalgebra::DMatrix<T>) {
262        self.col_iter_mut()
263            .zip(denom.column_iter())
264            .for_each(|(mut x_j, d_col)| {
265                let (rows, vals) = x_j.rows_and_values_mut();
266                adjust_by_poisson_ratio(vals, |k| d_col[rows[k]]);
267            });
268    }
269}
270
271/// Generate one-hot membership matrix (`row x K`) where the number of
272/// rows corresponds to the length of the membership vector and the
273/// `K` corresponds to the maximum membership value + 1.
274///
275pub fn row_membership_matrix<T>(row_membership: Vec<usize>) -> anyhow::Result<DMatrix<T>>
276where
277    T: nalgebra::RealField + Copy,
278{
279    let kk = match row_membership.iter().max() {
280        Some(&m) => m + 1,
281        _ => 1,
282    };
283
284    let mut ret_dm = DMatrix::zeros(row_membership.len(), kk);
285    let oneval = T::from_f32(1.).ok_or(anyhow::anyhow!("cannot find 1 value"))?;
286    for (i, k) in row_membership.into_iter().enumerate() {
287        ret_dm[(i, k)] += oneval;
288    }
289
290    Ok(ret_dm)
291}
292
293fn csc_euclidean_distance_on_select_columns<T>(
294    lhs: &CscMatrix<T>,
295    rhs: &CscMatrix<T>,
296    select_columns_in_rhs: Option<&[usize]>,
297) -> anyhow::Result<Vec<(usize, usize, T)>>
298where
299    T: nalgebra::RealField + Copy + std::iter::Sum<T>,
300{
301    let all_rhs = (0..rhs.ncols()).collect::<Vec<_>>();
302    let select_columns_in_rhs = select_columns_in_rhs.unwrap_or(all_rhs.as_slice());
303
304    if select_columns_in_rhs.len() != rhs.ncols() {
305        return Err(anyhow::anyhow!(
306            "found mismatches in `select_columns` maps: lhs -> rhs"
307        ));
308    }
309
310    let mut ret = Vec::with_capacity(select_columns_in_rhs.len() * lhs.ncols());
311
312    for (src_pos, src_col) in lhs.col_iter().enumerate() {
313        for &tgt_pos in select_columns_in_rhs {
314            let tgt_col = rhs.col(tgt_pos);
315
316            let nn = src_col.nrows();
317            let denom = T::from_usize(nn).unwrap_or(T::one());
318
319            let idx_src = src_col.row_indices();
320            let idx_tgt = tgt_col.row_indices();
321            let val_src = src_col.values();
322            let val_tgt = tgt_col.values();
323
324            // sum_g (src[g] - tgt[g])^2 / sum_g 1
325            // sum_g tgt[g]^2 + sum_g src[g]^2 - 2 sum_g tgt[g] * src[g]
326            let mut s: usize = 0;
327            let mut t: usize = 0;
328
329            let tgt_sq_sum = val_tgt.iter().map(|&x| x * x).sum::<T>();
330            let src_sq_sum = val_src.iter().map(|&x| x * x).sum::<T>();
331            let mut overlap = T::zero();
332            while s < idx_src.len() && t < idx_tgt.len() {
333                if idx_src[s] == idx_tgt[t] {
334                    overlap += val_src[s] * val_tgt[t];
335                    s += 1;
336                    t += 1;
337                } else if idx_src[s] < idx_tgt[t] {
338                    s += 1;
339                } else {
340                    t += 1;
341                }
342            }
343
344            let dist = ((src_sq_sum + tgt_sq_sum - overlap - overlap) / denom).sqrt();
345
346            ret.push((src_pos, tgt_pos, dist));
347        }
348    }
349
350    Ok(ret)
351}
352
353impl<T> DistanceOps for CscMatrix<T>
354where
355    T: nalgebra::RealField + Copy + std::iter::Sum<T>,
356{
357    type Scalar = T;
358    type Other = CscMatrix<T>;
359
360    fn euclidean_distance(
361        &self,
362        other: &Self::Other,
363    ) -> anyhow::Result<Vec<(usize, usize, Self::Scalar)>> {
364        csc_euclidean_distance_on_select_columns(self, other, None)
365    }
366
367    fn euclidean_distance_on_select_columns(
368        &self,
369        other: &Self::Other,
370        select_columns_in_other: &[usize],
371    ) -> anyhow::Result<Vec<(usize, usize, Self::Scalar)>> {
372        csc_euclidean_distance_on_select_columns(self, other, Some(select_columns_in_other))
373    }
374}
375
376impl<T> MeltOps for DMatrix<T>
377where
378    T: nalgebra::RealField + Copy,
379{
380    type Scalar = T;
381    type Mat = Self;
382    fn melt_with_indexes(&self) -> (Vec<Self::Scalar>, Vec<Vec<usize>>) {
383        let nelem = self.nrows() * self.ncols();
384
385        let mut idx: Vec<Vec<usize>> = vec![Vec::with_capacity(nelem), Vec::with_capacity(nelem)];
386        let mut val: Vec<Self::Scalar> = Vec::with_capacity(nelem);
387
388        // Iterate column-major to match nalgebra's storage layout for cache efficiency
389        for c in 0..self.ncols() {
390            for r in 0..self.nrows() {
391                idx[0].push(r);
392                idx[1].push(c);
393                val.push(self[(r, c)]);
394            }
395        }
396
397        (val, idx)
398    }
399
400    fn melt(&self) -> Vec<Self::Scalar> {
401        let nelem = self.len();
402        let mut val: Vec<Self::Scalar> = Vec::with_capacity(nelem);
403        // Iterate column-major to match nalgebra's storage layout for cache efficiency
404        for c in 0..self.ncols() {
405            for r in 0..self.nrows() {
406                val.push(self[(r, c)]);
407            }
408        }
409        val
410    }
411
412    fn melt_many_with_indexes(
413        &self,
414        others: &[&Self],
415    ) -> (Vec<Vec<Self::Scalar>>, Vec<Vec<usize>>) {
416        let nrows = self.nrows();
417        let ncols = self.ncols();
418        let nelem = nrows * ncols;
419        let n_matrices = 1 + others.len();
420
421        // Pre-allocate all vectors
422        let mut values: Vec<Vec<Self::Scalar>> =
423            (0..n_matrices).map(|_| Vec::with_capacity(nelem)).collect();
424        let mut idx: Vec<Vec<usize>> = vec![Vec::with_capacity(nelem), Vec::with_capacity(nelem)];
425
426        // Single traversal in column-major order for cache efficiency
427        for c in 0..ncols {
428            for r in 0..nrows {
429                idx[0].push(r);
430                idx[1].push(c);
431                values[0].push(self[(r, c)]);
432                for (i, other) in others.iter().enumerate() {
433                    values[i + 1].push(other[(r, c)]);
434                }
435            }
436        }
437
438        (values, idx)
439    }
440}
441
442impl<T> EncodingOps for DMatrix<T>
443where
444    T: nalgebra::RealField + Float,
445    f32: From<T>,
446{
447    type Scalar = T;
448    type Mat = Self;
449
450    fn positional_embedding_columns(&self, emb_dim: usize) -> anyhow::Result<Self::Mat> {
451        let ncols = self.ncols();
452
453        let ncodes_per_col = emb_dim * 2;
454
455        let mut output = Self::zeros(self.nrows(), ncols * ncodes_per_col);
456
457        for j in 0..ncols {
458            let x_j = self.column(j);
459
460            for i in 0..emb_dim {
461                let power = T::from(2.0 * i as f32 / emb_dim as f32).unwrap();
462                let denom = T::from(10000_f32.powf(f32::from(power))).unwrap();
463
464                let column_data = if i % 2 == 0 {
465                    x_j.map(|x_ij| ComplexField::sin(x_ij / denom))
466                } else {
467                    x_j.map(|x_ij| ComplexField::cos(x_ij / denom))
468                };
469
470                // Assign the computed column to the output matrix
471                output
472                    .column_mut(j * ncodes_per_col + i)
473                    .copy_from(&column_data);
474            }
475        }
476        Ok(output)
477    }
478}
479
480impl<T> CompositeOps for DMatrix<T>
481where
482    T: nalgebra::RealField + Copy,
483{
484    type Scalar = T;
485    type Mat = Self;
486    type Other = CscMatrix<T>;
487
488    /// `self[:,col] += other[:,col]`
489    /// * `col` - column index
490    fn add_assign_column(&mut self, other: &Self::Other, j: usize) {
491        debug_assert_eq!(self.nrows(), other.nrows());
492        debug_assert_eq!(self.ncols(), other.ncols());
493        if let Some(x_j) = other.get_col(j) {
494            let vals = x_j.values();
495            let rows = x_j.row_indices();
496            for k in 0..vals.len() {
497                let i = rows[k];
498                let x_ij = vals[k];
499                self[(i, j)] += x_ij;
500            }
501        }
502    }
503
504    /// `self += other`
505    /// * `other` - `CscMatrix`
506    fn add_assign(&mut self, other: &Self::Other) {
507        debug_assert_eq!(self.nrows(), other.nrows());
508        debug_assert_eq!(self.ncols(), other.ncols());
509        for j in 0..other.ncols() {
510            if let Some(x_j) = other.get_col(j) {
511                let vals = x_j.values();
512                let rows = x_j.row_indices();
513                for k in 0..vals.len() {
514                    let i = rows[k];
515                    let x_ij = vals[k];
516                    self[(i, j)] += x_ij;
517                }
518            }
519        }
520    }
521}
522
523#[cfg(feature = "tensor")]
524impl<T> ConvertMatOps for DMatrix<T>
525where
526    T: nalgebra::RealField + Copy + candle_core::WithDType,
527{
528    type Mat = Self;
529    type Scalar = T;
530
531    fn from_tensor(tensor: &candle_core::Tensor) -> anyhow::Result<Self::Mat> {
532        if tensor.dims().len() != 2 {
533            return Err(anyhow::anyhow!("expected 2D tensor"));
534        }
535
536        let nrows = tensor.dims()[0];
537        let ncols = tensor.dims()[1];
538        let data: Vec<T> = tensor.flatten_all()?.to_vec1()?;
539        Ok(Self::from_row_iterator(nrows, ncols, data.iter().cloned()))
540    }
541
542    fn to_tensor(&self, dev: &candle_core::Device) -> anyhow::Result<candle_core::Tensor> {
543        use candle_core::Tensor;
544        // Note: x.as_slice() will take values in the column-major order
545        // However, Tensor::from_slice will take them in the row-major order
546        let nrow = self.nrows();
547        let ncol = self.ncols();
548        Ok(Tensor::from_slice(self.as_slice(), (ncol, nrow), dev)?.transpose(0, 1)?)
549    }
550}
551
552#[cfg(feature = "tensor")]
553impl<T> ConvertMatOps for CscMatrix<T>
554where
555    T: nalgebra::RealField + Copy + candle_core::WithDType,
556{
557    type Mat = Self;
558    type Scalar = T;
559    fn from_tensor(tensor: &candle_core::Tensor) -> anyhow::Result<Self::Mat> {
560        if tensor.dims().len() != 2 {
561            return Err(anyhow::anyhow!("expected 2D tensor"));
562        }
563
564        let nrows = tensor.dims()[0];
565        let ncols = tensor.dims()[1];
566        let data: Vec<T> = tensor.flatten_all()?.to_vec1()?;
567
568        let dense_matrix = DMatrix::from_row_iterator(nrows, ncols, data.iter().cloned());
569        let csc_matrix = CscMatrix::from(&dense_matrix);
570
571        Ok(csc_matrix)
572    }
573
574    fn to_tensor(&self, dev: &candle_core::Device) -> anyhow::Result<candle_core::Tensor> {
575        use candle_core::Tensor;
576        let dense_matrix = DMatrix::from(self);
577        let nrow = dense_matrix.nrows();
578        let ncol = dense_matrix.ncols();
579        // Note: x.as_slice() will take values in the column-major order
580        // However, Tensor::from_slice will take them in the row-major order
581        Ok(Tensor::from_slice(dense_matrix.as_slice(), (ncol, nrow), dev)?.transpose(0, 1)?)
582    }
583}
584
585impl<T> SampleOps for DMatrix<T>
586where
587    T: nalgebra::RealField + Float,
588{
589    type Mat = Self;
590    type Scalar = T;
591
592    fn runif(nrow: usize, ncol: usize) -> Self::Mat {
593        Self::runif_seeded(nrow, ncol, entropy_seed())
594    }
595
596    fn rnorm(nrow: usize, ncol: usize) -> Self::Mat {
597        Self::rnorm_seeded(nrow, ncol, entropy_seed())
598    }
599
600    fn rgamma(nrow: usize, ncol: usize, param: (f32, f32)) -> Self::Mat {
601        Self::rgamma_seeded(nrow, ncol, param, entropy_seed())
602    }
603
604    fn runif_seeded(nrow: usize, ncol: usize, seed: u64) -> Self::Mat {
605        let u01 = Uniform::<f32>::new(0., 1.).expect("failed to create uniform distribution");
606        DMatrix::<T>::from_vec(nrow, ncol, collect_seeded(nrow * ncol, u01, seed))
607    }
608
609    fn rnorm_seeded(nrow: usize, ncol: usize, seed: u64) -> Self::Mat {
610        DMatrix::<T>::from_vec(
611            nrow,
612            ncol,
613            collect_seeded(nrow * ncol, StandardNormal, seed),
614        )
615    }
616
617    fn rgamma_seeded(nrow: usize, ncol: usize, param: (f32, f32), seed: u64) -> Self::Mat {
618        let (shape, scale) = param;
619        let pdf = Gamma::new(shape, scale).unwrap();
620        DMatrix::<T>::from_vec(nrow, ncol, collect_seeded(nrow * ncol, pdf, seed))
621    }
622}
623
624impl<T> MatElemOps for CscMatrix<T>
625where
626    T: nalgebra::RealField + Copy,
627{
628    type Mat = Self;
629    type Scalar = T;
630
631    fn log1p_inplace(&mut self) {
632        for x in self.values_mut() {
633            *x = (*x).ln_1p();
634        }
635    }
636
637    fn log1p(&self) -> Self::Mat {
638        let mut ret = self.clone();
639        ret.log1p_inplace();
640        ret
641    }
642}
643
644impl<T> MatOps for CscMatrix<T>
645where
646    T: nalgebra::RealField + Copy,
647{
648    type Mat = Self;
649    type Scalar = T;
650
651    fn normalize_exp_logits_columns_inplace(&mut self) {
652        let ncol = self.ncols();
653
654        for j in 0..ncol {
655            if let Some(log_j) = self.get_col(j) {
656                let mut log_max = log_j.values()[0];
657                for &logx_ij in log_j.values() {
658                    log_max = log_max.min(logx_ij);
659                }
660
661                let mut denom = T::zero();
662                for &logx_ij in log_j.values() {
663                    denom += (logx_ij - log_max).exp();
664                }
665
666                if let Some(mut log_j) = self.get_col_mut(j) {
667                    for x_ij in log_j.values_mut() {
668                        *x_ij = (*x_ij - log_max).exp() / denom;
669                    }
670                }
671            }
672        }
673    }
674
675    fn normalize_exp_logits_columns(&self) -> Self::Mat {
676        let mut ret = self.clone();
677        ret.normalize_exp_logits_columns_inplace();
678        ret
679    }
680
681    fn log_softmax_columns_inplace(&mut self) {
682        for j in 0..self.ncols() {
683            let lse = {
684                let Some(col) = self.get_col(j) else { continue };
685                let vals = col.values();
686                if vals.is_empty() {
687                    continue;
688                }
689                let mut log_max = vals[0];
690                for &v in vals {
691                    if v > log_max {
692                        log_max = v;
693                    }
694                }
695                let mut denom = T::zero();
696                for &v in vals {
697                    denom += (v - log_max).exp();
698                }
699                log_max + denom.ln()
700            };
701            if let Some(mut col) = self.get_col_mut(j) {
702                for x in col.values_mut() {
703                    *x -= lse;
704                }
705            }
706        }
707    }
708
709    fn log_softmax_columns(&self) -> Self::Mat {
710        let mut ret = self.clone();
711        ret.log_softmax_columns_inplace();
712        ret
713    }
714
715    fn sum_to_one_columns_inplace(&mut self) {
716        let ncol = self.ncols();
717
718        for j in 0..ncol {
719            if let Some(x_j) = self.get_col(j) {
720                let mut denom = T::zero();
721                for &x_ij in x_j.values() {
722                    denom += x_ij;
723                }
724                if let Some(mut x_j) = self.get_col_mut(j) {
725                    if denom > T::zero() {
726                        for x_ij in x_j.values_mut() {
727                            *x_ij /= denom;
728                        }
729                    }
730                }
731            }
732        }
733    }
734
735    fn sum_to_one_columns(&self) -> Self::Mat {
736        let mut ret = self.clone();
737        ret.sum_to_one_columns_inplace();
738        ret
739    }
740
741    fn sum_to_one_rows(&self) -> Self::Mat {
742        let mut ret = self.clone();
743        ret.sum_to_one_rows_inplace();
744        ret
745    }
746
747    fn sum_to_one_rows_inplace(&mut self) {
748        let nrow = self.nrows();
749        let mut denom = vec![T::zero(); nrow];
750
751        for col in self.col_iter() {
752            col.row_indices()
753                .iter()
754                .zip(col.values().iter())
755                .for_each(|(&i, &x_ij)| {
756                    denom[i] += x_ij;
757                });
758        }
759
760        for mut col in self.col_iter_mut() {
761            let (row_indices, values) = col.rows_and_values_mut();
762            row_indices.iter().zip(values).for_each(|(&i, x_ij)| {
763                *x_ij /= denom[i];
764            });
765        }
766    }
767
768    fn normalize_columns_inplace(&mut self) {
769        let ncol = self.ncols();
770
771        for j in 0..ncol {
772            if let Some(x_j) = self.get_col(j) {
773                let mut denom = T::zero();
774                for &x_ij in x_j.values() {
775                    denom += x_ij * x_ij;
776                }
777                denom = denom.sqrt().max(T::from_f64(1e-8).unwrap());
778
779                if let Some(mut x_j) = self.get_col_mut(j) {
780                    for x_ij in x_j.values_mut() {
781                        *x_ij /= denom;
782                    }
783                }
784            }
785        }
786    }
787
788    fn normalize_columns(&self) -> Self::Mat {
789        let mut ret = self.clone();
790        ret.normalize_columns_inplace();
791        ret
792    }
793
794    fn scale_columns_inplace(&mut self) {
795        let ncol = self.ncols();
796
797        for j in 0..ncol {
798            if let Some(x_j) = self.get_col(j) {
799                let mut s0 = T::zero();
800                let mut s1 = T::zero();
801                let mut s2 = T::zero();
802
803                for &x_ij in x_j.values() {
804                    s0 += T::one();
805                    s1 += x_ij;
806                    s2 += x_ij * x_ij;
807                }
808
809                let mu = s1 / s0.max(T::one());
810                let sig = (s2 / s0.max(T::one()) - mu * mu).sqrt();
811
812                if let Some(mut x_j) = self.get_col_mut(j) {
813                    if sig > T::zero() {
814                        for x_ij in x_j.values_mut() {
815                            *x_ij = (*x_ij - mu) / sig;
816                        }
817                    } else {
818                        for x_ij in x_j.values_mut() {
819                            *x_ij -= mu;
820                        }
821                    }
822                }
823            }
824        }
825    }
826
827    fn scale_rows_inplace(&mut self) {
828        let nrow = self.nrows();
829        let ncol = self.ncols();
830        let mut s0 = vec![T::zero(); nrow];
831        let mut s1 = vec![T::zero(); nrow];
832        let mut s2 = vec![T::zero(); nrow];
833
834        for j in 0..ncol {
835            if let Some(x_j) = self.get_col(j) {
836                for (&x_ij, &i) in x_j.values().iter().zip(x_j.row_indices()) {
837                    s0[i] += T::one();
838                    s1[i] += x_ij;
839                    s2[i] += x_ij * x_ij;
840                }
841            }
842        }
843
844        let mu = s1
845            .into_iter()
846            .zip(s0.iter())
847            .map(|(x, &n)| x / n.max(T::one()))
848            .collect::<Vec<_>>();
849
850        let sig = s2
851            .into_iter()
852            .zip(mu.iter())
853            .zip(s0.iter())
854            .map(|((s2, &mu), &s0)| (s2 / s0.max(T::one()) - mu * mu).sqrt())
855            .collect::<Vec<_>>();
856
857        for j in 0..ncol {
858            if let Some(mut x_j) = self.get_col_mut(j) {
859                let (rows, values) = x_j.rows_and_values_mut();
860
861                for (&i, x_ij) in rows.iter().zip(values) {
862                    let mu_i = mu[i];
863                    let sig_i = sig[i];
864                    if sig_i > T::zero() {
865                        *x_ij = (*x_ij - mu_i) / sig_i;
866                    } else {
867                        *x_ij -= mu_i;
868                    }
869                }
870            }
871        }
872    }
873
874    fn scale_columns(&self) -> Self::Mat {
875        let mut ret = self.clone();
876        ret.scale_columns_inplace();
877        ret
878    }
879
880    fn scale_rows(&self) -> Self::Mat {
881        let mut ret = self.clone();
882        ret.scale_rows_inplace();
883        ret
884    }
885
886    fn centre_columns_inplace(&mut self) {
887        let ncol = self.ncols();
888
889        for j in 0..ncol {
890            if let Some(x_j) = self.get_col(j) {
891                let mut s0 = T::zero();
892                let mut s1 = T::zero();
893
894                for &x_ij in x_j.values() {
895                    s0 += T::one();
896                    s1 += x_ij;
897                }
898
899                let mu = s1 / s0.max(T::one());
900
901                if let Some(mut x_j) = self.get_col_mut(j) {
902                    for x_ij in x_j.values_mut() {
903                        *x_ij -= mu;
904                    }
905                }
906            }
907        }
908    }
909
910    fn centre_columns(&self) -> Self::Mat {
911        let mut ret = self.clone();
912        ret.centre_columns_inplace();
913        ret
914    }
915}
916
917impl<T> MatOps for DMatrix<T>
918where
919    T: nalgebra::RealField,
920{
921    type Mat = Self;
922    type Scalar = T;
923
924    fn normalize_exp_logits_columns_inplace(&mut self) {
925        for mut x_j in self.column_iter_mut() {
926            let log_max = x_j.max();
927            let denom = x_j.map(|l| (l - log_max.clone()).exp()).sum();
928            x_j.iter_mut()
929                .for_each(|l| *l = (l.clone() - log_max.clone()).exp() / denom.clone());
930        }
931    }
932
933    fn normalize_exp_logits_columns(&self) -> Self::Mat {
934        let mut ret = self.clone();
935        ret.normalize_exp_logits_columns_inplace();
936        ret
937    }
938
939    fn log_softmax_columns_inplace(&mut self) {
940        for mut x_j in self.column_iter_mut() {
941            let log_max = x_j.max();
942            let denom = x_j.map(|l| (l - log_max.clone()).exp()).sum();
943            let lse = log_max + denom.ln();
944            x_j.iter_mut().for_each(|l| *l = l.clone() - lse.clone());
945        }
946    }
947
948    fn log_softmax_columns(&self) -> Self::Mat {
949        let mut ret = self.clone();
950        ret.log_softmax_columns_inplace();
951        ret
952    }
953
954    fn sum_to_one_columns_inplace(&mut self) {
955        self.column_iter_mut()
956            .for_each(|mut c| c.unscale_mut(c.sum()));
957    }
958
959    fn sum_to_one_columns(&self) -> Self::Mat {
960        let mut ret = self.clone();
961        ret.sum_to_one_columns_inplace();
962        ret
963    }
964
965    fn sum_to_one_rows_inplace(&mut self) {
966        self.row_iter_mut().for_each(|mut r| r.unscale_mut(r.sum()));
967    }
968
969    fn sum_to_one_rows(&self) -> Self::Mat {
970        let mut ret = self.clone();
971        ret.sum_to_one_rows_inplace();
972        ret
973    }
974
975    fn normalize_columns_inplace(&mut self) {
976        for mut xx_j in self.column_iter_mut() {
977            let denom = xx_j.norm().max(T::from_f64(1e-8).unwrap());
978            xx_j /= denom;
979        }
980    }
981
982    fn normalize_columns(&self) -> Self::Mat {
983        let mut ret = self.clone();
984        ret.normalize_columns_inplace();
985        ret
986    }
987
988    fn scale_columns_inplace(&mut self) {
989        for mut xx_j in self.column_iter_mut() {
990            let mu = xx_j.mean();
991            let sig = xx_j.variance().sqrt();
992            xx_j.add_scalar_mut(-mu);
993            if sig > T::zero() {
994                xx_j /= sig;
995            }
996        }
997    }
998
999    fn scale_rows_inplace(&mut self) {
1000        for mut xx_i in self.row_iter_mut() {
1001            let mu = xx_i.mean();
1002            let sig = xx_i.variance().sqrt();
1003            xx_i.add_scalar_mut(-mu);
1004            if sig > T::zero() {
1005                xx_i /= sig;
1006            }
1007        }
1008    }
1009
1010    fn scale_columns(&self) -> Self::Mat {
1011        let mut ret = self.clone();
1012        ret.scale_columns_inplace();
1013        ret
1014    }
1015
1016    fn scale_rows(&self) -> Self::Mat {
1017        let mut ret = self.clone();
1018        ret.scale_rows_inplace();
1019        ret
1020    }
1021
1022    fn centre_columns_inplace(&mut self) {
1023        for mut xx_j in self.column_iter_mut() {
1024            let mu = xx_j.mean();
1025            xx_j.add_scalar_mut(-mu);
1026        }
1027    }
1028
1029    fn centre_columns(&self) -> Self::Mat {
1030        let mut ret = self.clone();
1031        ret.centre_columns_inplace();
1032        ret
1033    }
1034}
1035
1036/// TF-IDF (Term Frequency–Inverse Document Frequency)
1037///
1038/// A numerical statistic reflecting how important a word (term) is to a document
1039/// in a collection or corpus. (Wikipedia)
1040///
1041/// Given a term-document matrix where rows are terms and columns are documents:
1042///
1043/// - **TF (Term Frequency)**: The raw count or frequency of term t in document d.
1044///   Here we use the matrix values directly as TF.
1045///
1046/// - **IDF (Inverse Document Frequency)**: A measure of how much information
1047///   the term provides, i.e., how common or rare it is across all documents.
1048///
1049///   IDF(t) = log(N / df(t))
1050///
1051///   where N = total number of documents, df(t) = number of documents containing term t.
1052///   We use log(N / (df(t) + 1)) to avoid division by zero.
1053///
1054/// - **TF-IDF(t, d) = TF(t, d) × IDF(t)**
1055///
1056/// Terms appearing in many documents get lower weight; terms specific to few
1057/// documents get higher weight.
1058impl<T> TfIdfOps for DMatrix<T>
1059where
1060    T: nalgebra::RealField,
1061{
1062    type Mat = Self;
1063
1064    fn tfidf(&self) -> Self::Mat {
1065        let (nrows, ncols) = self.shape();
1066        let n_docs = T::from_usize(ncols).unwrap();
1067        let mut result = self.clone();
1068
1069        for i in 0..nrows {
1070            // df(t) = document frequency = number of documents containing term t
1071            let df = (0..ncols).filter(|&j| self[(i, j)] != T::zero()).count();
1072            let df_t = T::from_usize(df).unwrap();
1073            // IDF(t) = log(N / (df(t) + 1))
1074            let idf = (n_docs.clone() / (df_t + T::one())).ln();
1075            // TF-IDF(t, d) = TF(t, d) × IDF(t)
1076            for j in 0..ncols {
1077                result[(i, j)] = result[(i, j)].clone() * idf.clone();
1078            }
1079        }
1080        result
1081    }
1082
1083    fn tfidf_normalize_columns(&self) -> Self::Mat {
1084        let mut result = self.tfidf();
1085        result.normalize_columns_inplace();
1086        result
1087    }
1088}
1089
1090////////////////////////////////////////
1091// Input and output in triplet format //
1092////////////////////////////////////////
1093
1094impl<T> MatTriplets for DMatrix<T>
1095where
1096    T: nalgebra::RealField + Float,
1097{
1098    type Mat = Self;
1099    type Scalar = T;
1100
1101    fn from_nonzero_triplets<I>(
1102        nrow: usize,
1103        ncol: usize,
1104        triplets: &[(I, I, T)],
1105    ) -> anyhow::Result<Self::Mat>
1106    where
1107        I: TryInto<usize> + Copy,
1108        <I as TryInto<usize>>::Error: std::fmt::Debug,
1109    {
1110        let mut data = vec![T::zero(); ncol * nrow];
1111        for &(ii, jj, x_ij) in triplets {
1112            let ii: usize = ii.try_into().expect("failed to convert index ii");
1113            let jj: usize = jj.try_into().expect("failed to convert index jj");
1114            data[ii * ncol + jj] = x_ij;
1115        }
1116        Ok(DMatrix::from_row_slice(nrow, ncol, &data))
1117    }
1118
1119    fn to_nonzero_triplets(&self) -> anyhow::Result<NRowNColTriplets<Self::Scalar>> {
1120        if let Some(eps) = T::from(1e-6) {
1121            let nrow = self.nrows();
1122            let ncol = self.ncols();
1123            let mut triplets = vec![];
1124            for j in 0..ncol {
1125                for i in 0..nrow {
1126                    let x_ij = &self[(i, j)];
1127                    if x_ij.abs() > eps {
1128                        triplets.push((i, j, *x_ij));
1129                    }
1130                }
1131            }
1132            Ok(NRowNColTriplets {
1133                nrow,
1134                ncol,
1135                triplets,
1136            })
1137        } else {
1138            anyhow::bail!("eps is not defined")
1139        }
1140    }
1141}
1142
1143impl<T> MatTriplets for CsrMatrix<T>
1144where
1145    T: nalgebra::RealField + Float,
1146{
1147    type Mat = Self;
1148    type Scalar = T;
1149
1150    fn from_nonzero_triplets<I>(
1151        nrow: usize,
1152        ncol: usize,
1153        triplets: &[(I, I, Self::Scalar)],
1154    ) -> anyhow::Result<Self::Mat>
1155    where
1156        I: TryInto<usize> + Copy,
1157        <I as TryInto<usize>>::Error: std::fmt::Debug,
1158    {
1159        let mut coo = CooMatrix::<T>::new(nrow, ncol);
1160        for &(ii, jj, x_ij) in triplets {
1161            let ii: usize = ii.try_into().expect("failed to convert index ii");
1162            let jj: usize = jj.try_into().expect("failed to convert index jj");
1163            coo.push(ii, jj, x_ij);
1164        }
1165        Ok(CsrMatrix::from(&coo))
1166    }
1167    fn to_nonzero_triplets(&self) -> anyhow::Result<NRowNColTriplets<Self::Scalar>> {
1168        if let Some(eps) = T::from(1e-6) {
1169            let nrow = self.nrows();
1170            let ncol = self.ncols();
1171
1172            let mut triplets = Vec::new();
1173
1174            for i in 0..nrow {
1175                if let Some(x_i) = self.get_row(i) {
1176                    let cols = x_i.col_indices();
1177                    let vals = x_i.values();
1178                    for k in 0..cols.len() {
1179                        let j = cols[k];
1180                        let x_ij = vals[k];
1181                        if x_ij > eps {
1182                            triplets.push((i, j, x_ij))
1183                        };
1184                    }
1185                }
1186            }
1187
1188            Ok(NRowNColTriplets {
1189                nrow,
1190                ncol,
1191                triplets,
1192            })
1193        } else {
1194            anyhow::bail!("eps is not defined")
1195        }
1196    }
1197}
1198
1199impl<T> MatTriplets for CscMatrix<T>
1200where
1201    T: nalgebra::RealField + Float,
1202{
1203    type Mat = Self;
1204    type Scalar = T;
1205
1206    fn from_nonzero_triplets<I>(
1207        nrow: usize,
1208        ncol: usize,
1209        triplets: &[(I, I, Self::Scalar)],
1210    ) -> anyhow::Result<Self::Mat>
1211    where
1212        I: TryInto<usize> + Copy,
1213        <I as TryInto<usize>>::Error: std::fmt::Debug,
1214    {
1215        let mut coo = CooMatrix::<T>::new(nrow, ncol);
1216        for &(ii, jj, x_ij) in triplets {
1217            let ii: usize = ii.try_into().expect("failed to convert index ii");
1218            let jj: usize = jj.try_into().expect("failed to convert index jj");
1219            coo.push(ii, jj, x_ij);
1220        }
1221        Ok(CscMatrix::from(&coo))
1222    }
1223
1224    fn to_nonzero_triplets(&self) -> anyhow::Result<NRowNColTriplets<Self::Scalar>> {
1225        if let Some(eps) = T::from(1e-6) {
1226            let nrow = self.nrows();
1227            let ncol = self.ncols();
1228
1229            let mut triplets = Vec::new();
1230            for j in 0..ncol {
1231                if let Some(x_j) = self.get_col(j) {
1232                    let rows = x_j.row_indices();
1233                    let vals = x_j.values();
1234                    for k in 0..rows.len() {
1235                        let i = rows[k];
1236                        let x_ij = vals[k];
1237                        if x_ij > eps {
1238                            triplets.push((i, j, x_ij))
1239                        }
1240                    }
1241                }
1242            }
1243
1244            Ok(NRowNColTriplets {
1245                nrow,
1246                ncol,
1247                triplets,
1248            })
1249        } else {
1250            anyhow::bail!("eps is not defined")
1251        }
1252    }
1253}
1254
1255#[cfg(feature = "tensor")]
1256impl<T> CandleDataLoaderOps for DMatrix<T>
1257where
1258    T: nalgebra::RealField + Copy + candle_core::WithDType,
1259{
1260    type Scalar = T;
1261    type Mat = Self;
1262
1263    // fn transpose(&self) -> Self::Mat {
1264    //     self.transpose()
1265    // }
1266
1267    fn rows_to_tensor_vec(&self) -> Vec<candle_core::Tensor> {
1268        let mut idx_data = self
1269            .row_iter()
1270            .enumerate()
1271            .par_bridge()
1272            .map(|(i, row)| {
1273                let mut v =
1274                    candle_core::Tensor::from_iter(row.iter().copied(), &candle_core::Device::Cpu)
1275                        .expect("failed to create tensor");
1276                v = v.reshape((1, row.len())).expect("failed to reshape");
1277                (i, v)
1278            })
1279            .collect::<Vec<_>>();
1280
1281        idx_data.sort_by_key(|(i, _)| *i);
1282        idx_data.into_iter().map(|(_, t)| t).collect()
1283    }
1284
1285    fn data_shape(&self) -> (usize, usize) {
1286        (self.nrows(), self.ncols())
1287    }
1288
1289    fn row_to_f32_vec(&self, i: usize) -> Vec<f32> {
1290        let row_data: Vec<T> = self.row(i).iter().copied().collect();
1291        let t = candle_core::Tensor::from_vec(row_data, self.ncols(), &candle_core::Device::Cpu)
1292            .expect("tensor from row");
1293        t.to_dtype(candle_core::DType::F32)
1294            .expect("to f32")
1295            .to_vec1::<f32>()
1296            .expect("to vec")
1297    }
1298}