1use crate::tensor::Tensor;
9use crate::error::{GhostError, Result};
10use std::collections::HashMap;
11
12#[derive(Debug, Clone)]
14pub struct SparseTensorCOO {
15 pub values: Vec<f32>,
17 pub rows: Vec<usize>,
19 pub cols: Vec<usize>,
21 pub shape: Vec<usize>,
23 pub nnz: usize,
25}
26
27impl SparseTensorCOO {
28 pub fn new(values: Vec<f32>, rows: Vec<usize>, cols: Vec<usize>, shape: Vec<usize>) -> Result<Self> {
30 if values.len() != rows.len() || values.len() != cols.len() {
31 return Err(GhostError::InvalidShape(
32 "Values, rows, and cols must have the same length".to_string()
33 ));
34 }
35
36 let nnz = values.len();
37
38 Ok(SparseTensorCOO {
39 values,
40 rows,
41 cols,
42 shape,
43 nnz,
44 })
45 }
46
47 pub fn from_dense(tensor: &Tensor, threshold: f32) -> Self {
49 let data = tensor.data_f32();
50 let shape = tensor.dims().to_vec();
51
52 let mut values = Vec::new();
53 let mut rows = Vec::new();
54 let mut cols = Vec::new();
55
56 if shape.len() == 2 {
57 let (nrows, ncols) = (shape[0], shape[1]);
58 for i in 0..nrows {
59 for j in 0..ncols {
60 let val = data[i * ncols + j];
61 if val.abs() > threshold {
62 values.push(val);
63 rows.push(i);
64 cols.push(j);
65 }
66 }
67 }
68 }
69
70 let nnz = values.len();
71 SparseTensorCOO {
72 values,
73 rows,
74 cols,
75 shape,
76 nnz,
77 }
78 }
79
80 pub fn to_dense(&self) -> Result<Tensor> {
82 if self.shape.len() != 2 {
83 return Err(GhostError::InvalidShape(
84 "Only 2D sparse tensors supported".to_string()
85 ));
86 }
87
88 let (nrows, ncols) = (self.shape[0], self.shape[1]);
89 let mut data = vec![0.0f32; nrows * ncols];
90
91 for i in 0..self.nnz {
92 let row = self.rows[i];
93 let col = self.cols[i];
94 let val = self.values[i];
95 data[row * ncols + col] = val;
96 }
97
98 Tensor::from_slice(&data, &self.shape)
99 }
100
101 pub fn spmv(&self, vec: &Tensor) -> Result<Tensor> {
103 if self.shape.len() != 2 {
104 return Err(GhostError::InvalidShape(
105 "SpMV requires 2D sparse matrix".to_string()
106 ));
107 }
108
109 let vec_data = vec.data_f32();
110 if vec_data.len() != self.shape[1] {
111 return Err(GhostError::InvalidShape(
112 format!("Vector length {} doesn't match matrix columns {}", vec_data.len(), self.shape[1])
113 ));
114 }
115
116 let mut result = vec![0.0f32; self.shape[0]];
117
118 for i in 0..self.nnz {
119 let row = self.rows[i];
120 let col = self.cols[i];
121 let val = self.values[i];
122 result[row] += val * vec_data[col];
123 }
124
125 Tensor::from_slice(&result, &[self.shape[0]])
126 }
127
128 pub fn spmm(&self, other: &SparseTensorCOO) -> Result<SparseTensorCOO> {
130 if self.shape[1] != other.shape[0] {
131 return Err(GhostError::InvalidShape(
132 format!("Matrix dimensions don't match: {:?} x {:?}", self.shape, other.shape)
133 ));
134 }
135
136 let mut other_map: HashMap<(usize, usize), f32> = HashMap::new();
138 for i in 0..other.nnz {
139 other_map.insert((other.rows[i], other.cols[i]), other.values[i]);
140 }
141
142 let mut result_map: HashMap<(usize, usize), f32> = HashMap::new();
143
144 for i in 0..self.nnz {
146 let row = self.rows[i];
147 let k = self.cols[i];
148 let a_val = self.values[i];
149
150 for j in 0..other.nnz {
152 if other.rows[j] == k {
153 let col = other.cols[j];
154 let b_val = other.values[j];
155 *result_map.entry((row, col)).or_insert(0.0) += a_val * b_val;
156 }
157 }
158 }
159
160 let mut values = Vec::new();
162 let mut rows = Vec::new();
163 let mut cols = Vec::new();
164
165 for ((row, col), val) in result_map {
166 if val.abs() > 1e-10 {
167 values.push(val);
168 rows.push(row);
169 cols.push(col);
170 }
171 }
172
173 SparseTensorCOO::new(values, rows, cols, vec![self.shape[0], other.shape[1]])
174 }
175
176 pub fn add(&self, other: &SparseTensorCOO) -> Result<SparseTensorCOO> {
178 if self.shape != other.shape {
179 return Err(GhostError::InvalidShape(
180 format!("Shape mismatch: {:?} vs {:?}", self.shape, other.shape)
181 ));
182 }
183
184 let mut result_map: HashMap<(usize, usize), f32> = HashMap::new();
185
186 for i in 0..self.nnz {
188 let key = (self.rows[i], self.cols[i]);
189 *result_map.entry(key).or_insert(0.0) += self.values[i];
190 }
191
192 for i in 0..other.nnz {
194 let key = (other.rows[i], other.cols[i]);
195 *result_map.entry(key).or_insert(0.0) += other.values[i];
196 }
197
198 let mut values = Vec::new();
200 let mut rows = Vec::new();
201 let mut cols = Vec::new();
202
203 for ((row, col), val) in result_map {
204 if val.abs() > 1e-10 {
205 values.push(val);
206 rows.push(row);
207 cols.push(col);
208 }
209 }
210
211 SparseTensorCOO::new(values, rows, cols, self.shape.clone())
212 }
213
214 pub fn transpose(&self) -> Result<SparseTensorCOO> {
216 if self.shape.len() != 2 {
217 return Err(GhostError::InvalidShape(
218 "Transpose only supported for 2D tensors".to_string()
219 ));
220 }
221
222 SparseTensorCOO::new(
223 self.values.clone(),
224 self.cols.clone(),
225 self.rows.clone(),
226 vec![self.shape[1], self.shape[0]],
227 )
228 }
229
230 pub fn sparsity(&self) -> f32 {
232 let total_elements: usize = self.shape.iter().product();
233 1.0 - (self.nnz as f32 / total_elements as f32)
234 }
235}
236
237#[derive(Debug, Clone)]
239pub struct SparseTensorCSR {
240 pub values: Vec<f32>,
242 pub col_indices: Vec<usize>,
244 pub row_ptrs: Vec<usize>,
246 pub shape: Vec<usize>,
248 pub nnz: usize,
250}
251
252impl SparseTensorCSR {
253 pub fn new(values: Vec<f32>, col_indices: Vec<usize>, row_ptrs: Vec<usize>, shape: Vec<usize>) -> Result<Self> {
255 if values.len() != col_indices.len() {
256 return Err(GhostError::InvalidShape(
257 "Values and col_indices must have the same length".to_string()
258 ));
259 }
260
261 let nnz = values.len();
262
263 Ok(SparseTensorCSR {
264 values,
265 col_indices,
266 row_ptrs,
267 shape,
268 nnz,
269 })
270 }
271
272 pub fn from_coo(coo: &SparseTensorCOO) -> Result<Self> {
274 if coo.shape.len() != 2 {
275 return Err(GhostError::InvalidShape(
276 "CSR only supports 2D tensors".to_string()
277 ));
278 }
279
280 let nrows = coo.shape[0];
281
282 let mut indices: Vec<usize> = (0..coo.nnz).collect();
284 indices.sort_by_key(|&i| (coo.rows[i], coo.cols[i]));
285
286 let mut values = Vec::with_capacity(coo.nnz);
287 let mut col_indices = Vec::with_capacity(coo.nnz);
288 let mut row_ptrs = vec![0; nrows + 1];
289
290 for &i in &indices {
291 values.push(coo.values[i]);
292 col_indices.push(coo.cols[i]);
293 }
294
295 for &i in &indices {
297 row_ptrs[coo.rows[i] + 1] += 1;
298 }
299
300 for i in 1..=nrows {
301 row_ptrs[i] += row_ptrs[i - 1];
302 }
303
304 SparseTensorCSR::new(values, col_indices, row_ptrs, coo.shape.clone())
305 }
306
307 pub fn to_coo(&self) -> Result<SparseTensorCOO> {
309 let mut values = Vec::with_capacity(self.nnz);
310 let mut rows = Vec::with_capacity(self.nnz);
311 let mut cols = Vec::with_capacity(self.nnz);
312
313 for row in 0..self.shape[0] {
314 let start = self.row_ptrs[row];
315 let end = self.row_ptrs[row + 1];
316
317 for i in start..end {
318 values.push(self.values[i]);
319 rows.push(row);
320 cols.push(self.col_indices[i]);
321 }
322 }
323
324 SparseTensorCOO::new(values, rows, cols, self.shape.clone())
325 }
326
327 pub fn spmv(&self, vec: &Tensor) -> Result<Tensor> {
329 let vec_data = vec.data_f32();
330 if vec_data.len() != self.shape[1] {
331 return Err(GhostError::InvalidShape(
332 format!("Vector length {} doesn't match matrix columns {}", vec_data.len(), self.shape[1])
333 ));
334 }
335
336 let mut result = vec![0.0f32; self.shape[0]];
337
338 for row in 0..self.shape[0] {
339 let start = self.row_ptrs[row];
340 let end = self.row_ptrs[row + 1];
341
342 let mut sum = 0.0;
343 for i in start..end {
344 let col = self.col_indices[i];
345 let val = self.values[i];
346 sum += val * vec_data[col];
347 }
348 result[row] = sum;
349 }
350
351 Tensor::from_slice(&result, &[self.shape[0]])
352 }
353}
354
355#[derive(Debug, Clone)]
357pub struct SparseTensorCSC {
358 pub values: Vec<f32>,
360 pub row_indices: Vec<usize>,
362 pub col_ptrs: Vec<usize>,
364 pub shape: Vec<usize>,
366 pub nnz: usize,
368}
369
370impl SparseTensorCSC {
371 pub fn new(values: Vec<f32>, row_indices: Vec<usize>, col_ptrs: Vec<usize>, shape: Vec<usize>) -> Result<Self> {
373 if values.len() != row_indices.len() {
374 return Err(GhostError::InvalidShape(
375 "Values and row_indices must have the same length".to_string()
376 ));
377 }
378
379 let nnz = values.len();
380
381 Ok(SparseTensorCSC {
382 values,
383 row_indices,
384 col_ptrs,
385 shape,
386 nnz,
387 })
388 }
389
390 pub fn from_coo(coo: &SparseTensorCOO) -> Result<Self> {
392 if coo.shape.len() != 2 {
393 return Err(GhostError::InvalidShape(
394 "CSC only supports 2D tensors".to_string()
395 ));
396 }
397
398 let ncols = coo.shape[1];
399
400 let mut indices: Vec<usize> = (0..coo.nnz).collect();
402 indices.sort_by_key(|&i| (coo.cols[i], coo.rows[i]));
403
404 let mut values = Vec::with_capacity(coo.nnz);
405 let mut row_indices = Vec::with_capacity(coo.nnz);
406 let mut col_ptrs = vec![0; ncols + 1];
407
408 for &i in &indices {
409 values.push(coo.values[i]);
410 row_indices.push(coo.rows[i]);
411 }
412
413 for &i in &indices {
415 col_ptrs[coo.cols[i] + 1] += 1;
416 }
417
418 for i in 1..=ncols {
419 col_ptrs[i] += col_ptrs[i - 1];
420 }
421
422 SparseTensorCSC::new(values, row_indices, col_ptrs, coo.shape.clone())
423 }
424}
425
426#[cfg(test)]
427mod tests {
428 use super::*;
429
430 #[test]
431 fn test_sparse_coo_creation() {
432 let values = vec![1.0, 2.0, 3.0];
433 let rows = vec![0, 1, 2];
434 let cols = vec![0, 1, 2];
435 let shape = vec![3, 3];
436
437 let sparse = SparseTensorCOO::new(values, rows, cols, shape).unwrap();
438 assert_eq!(sparse.nnz, 3);
439 assert_eq!(sparse.sparsity(), 2.0 / 3.0);
440 }
441
442 #[test]
443 fn test_sparse_from_dense() {
444 let data = vec![1.0, 0.0, 0.0, 0.0, 2.0, 0.0, 0.0, 0.0, 3.0];
445 let dense = Tensor::from_slice(&data, &[3, 3]).unwrap();
446
447 let sparse = SparseTensorCOO::from_dense(&dense, 0.5);
448 assert_eq!(sparse.nnz, 3);
449 }
450
451 #[test]
452 fn test_sparse_to_dense() {
453 let values = vec![1.0, 2.0, 3.0];
454 let rows = vec![0, 1, 2];
455 let cols = vec![0, 1, 2];
456 let shape = vec![3, 3];
457
458 let sparse = SparseTensorCOO::new(values, rows, cols, shape).unwrap();
459 let dense = sparse.to_dense().unwrap();
460
461 assert_eq!(dense.dims(), &[3, 3]);
462 assert_eq!(dense.data_f32()[0], 1.0);
463 assert_eq!(dense.data_f32()[4], 2.0);
464 assert_eq!(dense.data_f32()[8], 3.0);
465 }
466
467 #[test]
468 fn test_sparse_spmv() {
469 let values = vec![1.0, 2.0, 3.0];
470 let rows = vec![0, 1, 2];
471 let cols = vec![0, 1, 2];
472 let shape = vec![3, 3];
473
474 let sparse = SparseTensorCOO::new(values, rows, cols, shape).unwrap();
475 let vec = Tensor::from_slice(&[1.0, 2.0, 3.0], &[3]).unwrap();
476
477 let result = sparse.spmv(&vec).unwrap();
478 assert_eq!(result.data_f32(), vec![1.0, 4.0, 9.0]);
479 }
480
481 #[test]
482 fn test_csr_conversion() {
483 let values = vec![1.0, 2.0, 3.0];
484 let rows = vec![0, 1, 2];
485 let cols = vec![0, 1, 2];
486 let shape = vec![3, 3];
487
488 let coo = SparseTensorCOO::new(values, rows, cols, shape).unwrap();
489 let csr = SparseTensorCSR::from_coo(&coo).unwrap();
490
491 assert_eq!(csr.nnz, 3);
492 assert_eq!(csr.row_ptrs.len(), 4);
493 }
494}