Skip to main content

otspot_core/sparse/
vec.rs

1use crate::tolerances::ZERO_TOL;
2
3/// 疎ベクトル(インデックス・値のペアリスト、インデックスで昇順ソート済み)
4///
5/// ゼロでない要素のみをインデックスと値のペアで保持する。
6/// `indices` は常に昇順にソートされており、二分探索による O(log n) アクセスが可能。
7/// ゼロ近傍の値(絶対値が `ZERO_TOL` 以下)は自動的に除去される。
8#[derive(Debug, Clone)]
9pub struct SparseVec {
10    pub indices: Vec<usize>,
11    pub values: Vec<f64>,
12    pub len: usize,
13}
14
15impl SparseVec {
16    /// Creates a `SparseVec` from a dense slice, dropping entries with `|v| ≤ ZERO_TOL`.
17    pub fn from_dense(dense: &[f64]) -> Self {
18        let mut indices = Vec::new();
19        let mut values = Vec::new();
20        for (i, &v) in dense.iter().enumerate() {
21            if v.abs() > ZERO_TOL {
22                indices.push(i);
23                values.push(v);
24            }
25        }
26        Self {
27            indices,
28            values,
29            len: dense.len(),
30        }
31    }
32
33    pub fn to_dense(&self) -> Vec<f64> {
34        let mut dense = vec![0.0; self.len];
35        for (k, &idx) in self.indices.iter().enumerate() {
36            dense[idx] = self.values[k];
37        }
38        dense
39    }
40
41    /// Writes to a pre-allocated buffer (zero-fills first). Avoids heap allocation in hot loops.
42    pub fn to_dense_into(&self, buf: &mut [f64]) {
43        for v in buf.iter_mut() {
44            *v = 0.0;
45        }
46        for (k, &idx) in self.indices.iter().enumerate() {
47            buf[idx] = self.values[k];
48        }
49    }
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55
56    #[test]
57    fn test_sparse_vec_from_dense_to_dense() {
58        let dense = vec![1.0, 0.0, 0.0, 3.5, 0.0, -2.0];
59        let sv = SparseVec::from_dense(&dense);
60        assert_eq!(sv.len, 6);
61        assert_eq!(sv.indices, vec![0, 3, 5]);
62        assert_eq!(sv.values, vec![1.0, 3.5, -2.0]);
63
64        let back = sv.to_dense();
65        assert_eq!(back, dense);
66    }
67}