sim_lib_discrete_algebra/
sparse.rs1use crate::error::AlgebraError;
9use crate::matrix::Matrix;
10use crate::semiring::Semiring;
11
12#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct SparseEntry<S> {
15 pub row: usize,
17 pub col: usize,
19 pub value: S,
21}
22
23#[derive(Debug, Clone, PartialEq)]
25pub struct SparseMatrix<S: Semiring> {
26 pub rows: usize,
28 pub cols: usize,
30 pub entries: Vec<SparseEntry<S>>,
32}
33
34impl<S: Semiring> SparseMatrix<S> {
35 pub fn new(rows: usize, cols: usize) -> Self {
37 SparseMatrix {
38 rows,
39 cols,
40 entries: Vec::new(),
41 }
42 }
43
44 pub fn try_new(rows: usize, cols: usize) -> Result<Self, AlgebraError> {
46 let m = SparseMatrix {
47 rows,
48 cols,
49 entries: Vec::new(),
50 };
51 m.validate()?;
52 Ok(m)
53 }
54
55 pub fn row_count(&self) -> usize {
57 self.rows
58 }
59
60 pub fn col_count(&self) -> usize {
62 self.cols
63 }
64
65 pub fn entries(&self) -> &[SparseEntry<S>] {
67 &self.entries
68 }
69
70 pub fn from_entries(
72 rows: usize,
73 cols: usize,
74 entries: Vec<SparseEntry<S>>,
75 ) -> Result<Self, AlgebraError> {
76 let mut m = SparseMatrix {
77 rows,
78 cols,
79 entries,
80 };
81 m.validate()?;
82 m.canonicalize();
83 Ok(m)
84 }
85
86 pub fn validate(&self) -> Result<(), AlgebraError> {
88 let len = self
89 .rows
90 .checked_mul(self.cols)
91 .ok_or(AlgebraError::DimensionOverflow {
92 rows: self.rows,
93 cols: self.cols,
94 })?;
95 for e in &self.entries {
96 if e.row >= self.rows || e.col >= self.cols {
97 return Err(AlgebraError::IndexOutOfBounds {
98 index: e.row.saturating_mul(self.cols).saturating_add(e.col),
99 len,
100 });
101 }
102 }
103 Ok(())
104 }
105
106 pub fn canonicalize(&mut self) {
109 self.entries.sort_by_key(|e| (e.row, e.col));
110 let mut merged: Vec<SparseEntry<S>> = Vec::with_capacity(self.entries.len());
111 for e in self.entries.drain(..) {
112 match merged.last_mut() {
113 Some(last) if last.row == e.row && last.col == e.col => {
114 last.value = last.value.add(&e.value);
115 }
116 _ => merged.push(e),
117 }
118 }
119 merged.retain(|e| !e.value.is_zero());
120 self.entries = merged;
121 }
122
123 pub fn to_dense(&self) -> Result<Matrix<S>, AlgebraError> {
125 self.validate()?;
126 let mut m = Matrix::try_new(self.rows, self.cols)?;
127 for e in &self.entries {
128 m.set(e.row, e.col, e.value.clone())?;
129 }
130 Ok(m)
131 }
132
133 pub fn from_dense_nonzero(dense: &Matrix<S>) -> Result<Self, AlgebraError> {
136 dense.validate()?;
137 let mut entries = Vec::new();
138 for r in 0..dense.rows {
139 for c in 0..dense.cols {
140 let v = dense.get(r, c)?;
141 if !v.is_zero() {
142 entries.push(SparseEntry {
143 row: r,
144 col: c,
145 value: v.clone(),
146 });
147 }
148 }
149 }
150 Ok(SparseMatrix {
151 rows: dense.rows,
152 cols: dense.cols,
153 entries,
154 })
155 }
156}
157
158#[cfg(test)]
159mod tests {
160 use super::*;
161 use crate::Counting;
162
163 fn e(row: usize, col: usize, v: u64) -> SparseEntry<Counting> {
164 SparseEntry {
165 row,
166 col,
167 value: Counting::from_u64(v),
168 }
169 }
170
171 #[test]
172 fn from_entries_rejects_out_of_range() {
173 let r = SparseMatrix::from_entries(2, 2, vec![e(0, 0, 1), e(2, 0, 1)]);
174 assert!(matches!(r, Err(AlgebraError::IndexOutOfBounds { .. })));
175 }
176
177 #[test]
178 fn canonicalize_merges_duplicates_and_sorts() {
179 let m = SparseMatrix::from_entries(2, 2, vec![e(1, 1, 2), e(0, 0, 3), e(1, 1, 5)]).unwrap();
180 assert_eq!(m.entries, vec![e(0, 0, 3), e(1, 1, 7)]);
181 }
182
183 #[test]
184 fn canonicalize_drops_zero() {
185 let m = SparseMatrix::from_entries(1, 2, vec![e(0, 0, 0), e(0, 1, 4)]).unwrap();
188 assert_eq!(m.entries, vec![e(0, 1, 4)]);
189 }
190
191 #[test]
192 fn dense_sparse_round_trip() {
193 let m = SparseMatrix::from_entries(2, 2, vec![e(0, 1, 7), e(1, 0, 9)]).unwrap();
194 let dense = m.to_dense().unwrap();
195 let back = SparseMatrix::from_dense_nonzero(&dense).unwrap();
196 assert_eq!(back, m);
197 }
198
199 #[test]
200 fn invalid_public_sparse_matrix_fails_before_densifying() {
201 let bad = SparseMatrix {
202 rows: 1,
203 cols: 1,
204 entries: vec![e(1, 0, 4)],
205 };
206
207 assert!(matches!(
208 bad.validate(),
209 Err(AlgebraError::IndexOutOfBounds { .. })
210 ));
211 assert!(matches!(
212 bad.to_dense(),
213 Err(AlgebraError::IndexOutOfBounds { .. })
214 ));
215 }
216
217 #[test]
218 fn checked_sparse_constructor_rejects_dimension_overflow() {
219 assert!(matches!(
220 SparseMatrix::<Counting>::try_new(usize::MAX, 2),
221 Err(AlgebraError::DimensionOverflow { .. })
222 ));
223 }
224}