1use crate::types::{Precision, DimensionType, IndexType, NodeId};
7use crate::error::{SolverError, Result};
8use alloc::{vec::Vec, collections::BTreeMap};
9use core::iter;
10
11#[derive(Debug, Clone)]
15#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
16pub struct CSRStorage {
17 pub values: Vec<Precision>,
19 pub col_indices: Vec<IndexType>,
21 pub row_ptr: Vec<IndexType>,
23}
24
25#[derive(Debug, Clone)]
29#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
30pub struct CSCStorage {
31 pub values: Vec<Precision>,
33 pub row_indices: Vec<IndexType>,
35 pub col_ptr: Vec<IndexType>,
37}
38
39#[derive(Debug, Clone)]
43#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
44pub struct COOStorage {
45 pub row_indices: Vec<IndexType>,
47 pub col_indices: Vec<IndexType>,
49 pub values: Vec<Precision>,
51}
52
53#[derive(Debug, Clone)]
57#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
58pub struct GraphStorage {
59 pub out_edges: Vec<Vec<GraphEdge>>,
61 pub in_edges: Vec<Vec<GraphEdge>>,
63 pub degrees: Vec<Precision>,
65}
66
67#[derive(Debug, Clone, Copy, PartialEq)]
69#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
70pub struct GraphEdge {
71 pub target: NodeId,
73 pub weight: Precision,
75}
76
77impl CSRStorage {
79 pub fn from_coo(coo: &COOStorage, rows: DimensionType, cols: DimensionType) -> Result<Self> {
81 if coo.is_empty() {
82 return Ok(Self {
83 values: Vec::new(),
84 col_indices: Vec::new(),
85 row_ptr: vec![0; rows + 1],
86 });
87 }
88
89 let mut sorted_entries: Vec<_> = coo.row_indices.iter()
91 .zip(&coo.col_indices)
92 .zip(&coo.values)
93 .map(|((&r, &c), &v)| (r as usize, c, v))
94 .collect();
95 sorted_entries.sort_by(|a, b| a.0.cmp(&b.0).then(a.1.cmp(&b.1)));
96
97 let mut values = Vec::new();
98 let mut col_indices = Vec::new();
99 let mut row_ptr = vec![0; rows + 1];
100
101 let mut current_row = 0;
102 let mut nnz_count = 0;
103
104 for (row, col, value) in sorted_entries {
105 if value == 0.0 {
107 continue;
108 }
109
110 while current_row < row {
112 current_row += 1;
113 row_ptr[current_row] = nnz_count as IndexType;
114 }
115
116 values.push(value);
117 col_indices.push(col);
118 nnz_count += 1;
119 }
120
121 while current_row < rows {
123 current_row += 1;
124 row_ptr[current_row] = nnz_count as IndexType;
125 }
126
127 Ok(Self {
128 values,
129 col_indices,
130 row_ptr,
131 })
132 }
133
134 pub fn from_csc(csc: &CSCStorage, rows: DimensionType, cols: DimensionType) -> Result<Self> {
136 let triplets = csc.to_triplets()?;
137 let coo = COOStorage::from_triplets(triplets)?;
138 Self::from_coo(&coo, rows, cols)
139 }
140
141 pub fn get(&self, row: usize, col: usize) -> Option<Precision> {
143 if row >= self.row_ptr.len() - 1 {
144 return None;
145 }
146
147 let start = self.row_ptr[row] as usize;
148 let end = self.row_ptr[row + 1] as usize;
149
150 match self.col_indices[start..end].binary_search(&(col as IndexType)) {
152 Ok(pos) => Some(self.values[start + pos]),
153 Err(_) => None,
154 }
155 }
156
157 pub fn row_iter(&self, row: usize) -> CSRRowIter {
159 if row >= self.row_ptr.len() - 1 {
160 return CSRRowIter {
161 col_indices: &[],
162 values: &[],
163 pos: 0,
164 };
165 }
166
167 let start = self.row_ptr[row] as usize;
168 let end = self.row_ptr[row + 1] as usize;
169
170 CSRRowIter {
171 col_indices: &self.col_indices[start..end],
172 values: &self.values[start..end],
173 pos: 0,
174 }
175 }
176
177 pub fn col_iter(&self, col: usize) -> CSRColIter {
179 CSRColIter {
180 storage: self,
181 col: col as IndexType,
182 row: 0,
183 }
184 }
185
186 pub fn multiply_vector(&self, x: &[Precision], result: &mut [Precision]) {
188 result.fill(0.0);
189 self.multiply_vector_add(x, result);
190 }
191
192 pub fn multiply_vector_add(&self, x: &[Precision], result: &mut [Precision]) {
194 for (row, mut row_sum) in result.iter_mut().enumerate() {
195 let start = self.row_ptr[row] as usize;
196 let end = self.row_ptr[row + 1] as usize;
197
198 for i in start..end {
199 let col = self.col_indices[i] as usize;
200 *row_sum += self.values[i] * x[col];
201 }
202 }
203 }
204
205 pub fn nnz(&self) -> usize {
207 self.values.len()
208 }
209
210 pub fn to_triplets(&self) -> Result<Vec<(usize, usize, Precision)>> {
212 let mut triplets = Vec::new();
213
214 for row in 0..self.row_ptr.len() - 1 {
215 let start = self.row_ptr[row] as usize;
216 let end = self.row_ptr[row + 1] as usize;
217
218 for i in start..end {
219 let col = self.col_indices[i] as usize;
220 let value = self.values[i];
221 triplets.push((row, col, value));
222 }
223 }
224
225 Ok(triplets)
226 }
227
228 pub fn scale(&mut self, factor: Precision) {
230 for value in &mut self.values {
231 *value *= factor;
232 }
233 }
234
235 pub fn add_diagonal(&mut self, alpha: Precision) {
237 for row in 0..self.row_ptr.len() - 1 {
238 let start = self.row_ptr[row] as usize;
239 let end = self.row_ptr[row + 1] as usize;
240
241 if let Ok(pos) = self.col_indices[start..end].binary_search(&(row as IndexType)) {
243 self.values[start + pos] += alpha;
244 }
245 }
247 }
248}
249
250pub struct CSRRowIter<'a> {
252 col_indices: &'a [IndexType],
253 values: &'a [Precision],
254 pos: usize,
255}
256
257impl<'a> Iterator for CSRRowIter<'a> {
258 type Item = (IndexType, Precision);
259
260 fn next(&mut self) -> Option<Self::Item> {
261 if self.pos < self.col_indices.len() {
262 let col = self.col_indices[self.pos];
263 let val = self.values[self.pos];
264 self.pos += 1;
265 Some((col, val))
266 } else {
267 None
268 }
269 }
270}
271
272pub struct CSRColIter<'a> {
274 storage: &'a CSRStorage,
275 col: IndexType,
276 row: usize,
277}
278
279impl<'a> Iterator for CSRColIter<'a> {
280 type Item = (IndexType, Precision);
281
282 fn next(&mut self) -> Option<Self::Item> {
283 while self.row < self.storage.row_ptr.len() - 1 {
284 let start = self.storage.row_ptr[self.row] as usize;
285 let end = self.storage.row_ptr[self.row + 1] as usize;
286
287 if let Ok(pos) = self.storage.col_indices[start..end].binary_search(&self.col) {
288 let value = self.storage.values[start + pos];
289 let row = self.row as IndexType;
290 self.row += 1;
291 return Some((row, value));
292 }
293
294 self.row += 1;
295 }
296 None
297 }
298}
299
300impl CSCStorage {
302 pub fn from_coo(coo: &COOStorage, rows: DimensionType, cols: DimensionType) -> Result<Self> {
304 if coo.is_empty() {
305 return Ok(Self {
306 values: Vec::new(),
307 row_indices: Vec::new(),
308 col_ptr: vec![0; cols + 1],
309 });
310 }
311
312 let mut sorted_entries: Vec<_> = coo.row_indices.iter()
314 .zip(&coo.col_indices)
315 .zip(&coo.values)
316 .map(|((&r, &c), &v)| (r, c as usize, v))
317 .collect();
318 sorted_entries.sort_by(|a, b| a.1.cmp(&b.1).then(a.0.cmp(&b.0)));
319
320 let mut values = Vec::new();
321 let mut row_indices = Vec::new();
322 let mut col_ptr = vec![0; cols + 1];
323
324 let mut current_col = 0;
325 let mut nnz_count = 0;
326
327 for (row, col, value) in sorted_entries {
328 if value == 0.0 {
330 continue;
331 }
332
333 while current_col < col {
335 current_col += 1;
336 col_ptr[current_col] = nnz_count as IndexType;
337 }
338
339 values.push(value);
340 row_indices.push(row);
341 nnz_count += 1;
342 }
343
344 while current_col < cols {
346 current_col += 1;
347 col_ptr[current_col] = nnz_count as IndexType;
348 }
349
350 Ok(Self {
351 values,
352 row_indices,
353 col_ptr,
354 })
355 }
356
357 pub fn from_csr(csr: &CSRStorage, rows: DimensionType, cols: DimensionType) -> Result<Self> {
359 let triplets = csr.to_triplets()?;
360 let coo = COOStorage::from_triplets(triplets)?;
361 Self::from_coo(&coo, rows, cols)
362 }
363
364 pub fn get(&self, row: usize, col: usize) -> Option<Precision> {
366 if col >= self.col_ptr.len() - 1 {
367 return None;
368 }
369
370 let start = self.col_ptr[col] as usize;
371 let end = self.col_ptr[col + 1] as usize;
372
373 match self.row_indices[start..end].binary_search(&(row as IndexType)) {
375 Ok(pos) => Some(self.values[start + pos]),
376 Err(_) => None,
377 }
378 }
379
380 pub fn row_iter(&self, row: usize) -> CSCRowIter {
382 CSCRowIter {
383 storage: self,
384 row: row as IndexType,
385 col: 0,
386 }
387 }
388
389 pub fn col_iter(&self, col: usize) -> CSCColIter {
391 if col >= self.col_ptr.len() - 1 {
392 return CSCColIter {
393 row_indices: &[],
394 values: &[],
395 pos: 0,
396 };
397 }
398
399 let start = self.col_ptr[col] as usize;
400 let end = self.col_ptr[col + 1] as usize;
401
402 CSCColIter {
403 row_indices: &self.row_indices[start..end],
404 values: &self.values[start..end],
405 pos: 0,
406 }
407 }
408
409 pub fn multiply_vector(&self, x: &[Precision], result: &mut [Precision]) {
411 result.fill(0.0);
412 self.multiply_vector_add(x, result);
413 }
414
415 pub fn multiply_vector_add(&self, x: &[Precision], result: &mut [Precision]) {
417 for col in 0..self.col_ptr.len() - 1 {
418 let x_col = x[col];
419 if x_col == 0.0 {
420 continue;
421 }
422
423 let start = self.col_ptr[col] as usize;
424 let end = self.col_ptr[col + 1] as usize;
425
426 for i in start..end {
427 let row = self.row_indices[i] as usize;
428 result[row] += self.values[i] * x_col;
429 }
430 }
431 }
432
433 pub fn nnz(&self) -> usize {
435 self.values.len()
436 }
437
438 pub fn to_triplets(&self) -> Result<Vec<(usize, usize, Precision)>> {
440 let mut triplets = Vec::new();
441
442 for col in 0..self.col_ptr.len() - 1 {
443 let start = self.col_ptr[col] as usize;
444 let end = self.col_ptr[col + 1] as usize;
445
446 for i in start..end {
447 let row = self.row_indices[i] as usize;
448 let value = self.values[i];
449 triplets.push((row, col, value));
450 }
451 }
452
453 Ok(triplets)
454 }
455
456 pub fn scale(&mut self, factor: Precision) {
458 for value in &mut self.values {
459 *value *= factor;
460 }
461 }
462
463 pub fn add_diagonal(&mut self, alpha: Precision) {
465 for col in 0..self.col_ptr.len() - 1 {
466 let start = self.col_ptr[col] as usize;
467 let end = self.col_ptr[col + 1] as usize;
468
469 if let Ok(pos) = self.row_indices[start..end].binary_search(&(col as IndexType)) {
471 self.values[start + pos] += alpha;
472 }
473 }
474 }
475}
476
477pub struct CSCRowIter<'a> {
479 storage: &'a CSCStorage,
480 row: IndexType,
481 col: usize,
482}
483
484impl<'a> Iterator for CSCRowIter<'a> {
485 type Item = (IndexType, Precision);
486
487 fn next(&mut self) -> Option<Self::Item> {
488 while self.col < self.storage.col_ptr.len() - 1 {
489 let start = self.storage.col_ptr[self.col] as usize;
490 let end = self.storage.col_ptr[self.col + 1] as usize;
491
492 if let Ok(pos) = self.storage.row_indices[start..end].binary_search(&self.row) {
493 let value = self.storage.values[start + pos];
494 let col = self.col as IndexType;
495 self.col += 1;
496 return Some((col, value));
497 }
498
499 self.col += 1;
500 }
501 None
502 }
503}
504
505pub struct CSCColIter<'a> {
507 row_indices: &'a [IndexType],
508 values: &'a [Precision],
509 pos: usize,
510}
511
512impl<'a> Iterator for CSCColIter<'a> {
513 type Item = (IndexType, Precision);
514
515 fn next(&mut self) -> Option<Self::Item> {
516 if self.pos < self.row_indices.len() {
517 let row = self.row_indices[self.pos];
518 let val = self.values[self.pos];
519 self.pos += 1;
520 Some((row, val))
521 } else {
522 None
523 }
524 }
525}
526
527impl COOStorage {
529 pub fn from_triplets(triplets: Vec<(usize, usize, Precision)>) -> Result<Self> {
531 let mut row_indices = Vec::new();
532 let mut col_indices = Vec::new();
533 let mut values = Vec::new();
534
535 for (row, col, value) in triplets {
536 if value != 0.0 {
537 row_indices.push(row as IndexType);
538 col_indices.push(col as IndexType);
539 values.push(value);
540 }
541 }
542
543 Ok(Self {
544 row_indices,
545 col_indices,
546 values,
547 })
548 }
549
550 pub fn is_empty(&self) -> bool {
552 self.values.is_empty()
553 }
554
555 pub fn get(&self, row: usize, col: usize) -> Option<Precision> {
557 for i in 0..self.values.len() {
558 if self.row_indices[i] as usize == row && self.col_indices[i] as usize == col {
559 return Some(self.values[i]);
560 }
561 }
562 None
563 }
564
565 pub fn row_iter(&self, row: usize) -> COORowIter {
567 COORowIter {
568 storage: self,
569 target_row: row as IndexType,
570 pos: 0,
571 }
572 }
573
574 pub fn col_iter(&self, col: usize) -> COOColIter {
576 COOColIter {
577 storage: self,
578 target_col: col as IndexType,
579 pos: 0,
580 }
581 }
582
583 pub fn multiply_vector(&self, x: &[Precision], result: &mut [Precision]) {
585 result.fill(0.0);
586 self.multiply_vector_add(x, result);
587 }
588
589 pub fn multiply_vector_add(&self, x: &[Precision], result: &mut [Precision]) {
591 for i in 0..self.values.len() {
592 let row = self.row_indices[i] as usize;
593 let col = self.col_indices[i] as usize;
594 result[row] += self.values[i] * x[col];
595 }
596 }
597
598 pub fn nnz(&self) -> usize {
600 self.values.len()
601 }
602
603 pub fn to_triplets(&self) -> Vec<(usize, usize, Precision)> {
605 self.row_indices.iter()
606 .zip(&self.col_indices)
607 .zip(&self.values)
608 .map(|((&r, &c), &v)| (r as usize, c as usize, v))
609 .collect()
610 }
611
612 pub fn scale(&mut self, factor: Precision) {
614 for value in &mut self.values {
615 *value *= factor;
616 }
617 }
618
619 pub fn add_diagonal(&mut self, alpha: Precision, rows: DimensionType) {
621 for i in 0..self.values.len() {
624 if self.row_indices[i] == self.col_indices[i] {
625 self.values[i] += alpha;
626 }
627 }
628 }
629}
630
631pub struct COORowIter<'a> {
633 storage: &'a COOStorage,
634 target_row: IndexType,
635 pos: usize,
636}
637
638impl<'a> Iterator for COORowIter<'a> {
639 type Item = (IndexType, Precision);
640
641 fn next(&mut self) -> Option<Self::Item> {
642 while self.pos < self.storage.values.len() {
643 if self.storage.row_indices[self.pos] == self.target_row {
644 let col = self.storage.col_indices[self.pos];
645 let val = self.storage.values[self.pos];
646 self.pos += 1;
647 return Some((col, val));
648 }
649 self.pos += 1;
650 }
651 None
652 }
653}
654
655pub struct COOColIter<'a> {
657 storage: &'a COOStorage,
658 target_col: IndexType,
659 pos: usize,
660}
661
662impl<'a> Iterator for COOColIter<'a> {
663 type Item = (IndexType, Precision);
664
665 fn next(&mut self) -> Option<Self::Item> {
666 while self.pos < self.storage.values.len() {
667 if self.storage.col_indices[self.pos] == self.target_col {
668 let row = self.storage.row_indices[self.pos];
669 let val = self.storage.values[self.pos];
670 self.pos += 1;
671 return Some((row, val));
672 }
673 self.pos += 1;
674 }
675 None
676 }
677}
678
679impl GraphStorage {
681 pub fn from_triplets(triplets: Vec<(usize, usize, Precision)>, nodes: DimensionType) -> Result<Self> {
683 let mut out_edges = vec![Vec::new(); nodes];
684 let mut in_edges = vec![Vec::new(); nodes];
685 let mut degrees = vec![0.0; nodes];
686
687 for (row, col, weight) in triplets {
688 if weight != 0.0 && row < nodes && col < nodes {
689 out_edges[row].push(GraphEdge {
690 target: col as NodeId,
691 weight,
692 });
693
694 if row != col { in_edges[col].push(GraphEdge {
696 target: row as NodeId,
697 weight,
698 });
699 }
700
701 degrees[row] += weight.abs();
702 }
703 }
704
705 Ok(Self {
706 out_edges,
707 in_edges,
708 degrees,
709 })
710 }
711
712 pub fn get(&self, row: usize, col: usize) -> Option<Precision> {
714 if row >= self.out_edges.len() {
715 return None;
716 }
717
718 for edge in &self.out_edges[row] {
719 if edge.target as usize == col {
720 return Some(edge.weight);
721 }
722 }
723 None
724 }
725
726 pub fn row_iter(&self, row: usize) -> GraphRowIter {
728 if row >= self.out_edges.len() {
729 GraphRowIter {
730 edges: &[],
731 pos: 0,
732 }
733 } else {
734 GraphRowIter {
735 edges: &self.out_edges[row],
736 pos: 0,
737 }
738 }
739 }
740
741 pub fn col_iter(&self, col: usize) -> GraphColIter {
743 if col >= self.in_edges.len() {
744 GraphColIter {
745 edges: &[],
746 pos: 0,
747 }
748 } else {
749 GraphColIter {
750 edges: &self.in_edges[col],
751 pos: 0,
752 }
753 }
754 }
755
756 pub fn multiply_vector(&self, x: &[Precision], result: &mut [Precision]) {
758 result.fill(0.0);
759 self.multiply_vector_add(x, result);
760 }
761
762 pub fn multiply_vector_add(&self, x: &[Precision], result: &mut [Precision]) {
764 for (row, edges) in self.out_edges.iter().enumerate() {
765 for edge in edges {
766 let col = edge.target as usize;
767 if col < x.len() {
768 result[row] += edge.weight * x[col];
769 }
770 }
771 }
772 }
773
774 pub fn nnz(&self) -> usize {
776 self.out_edges.iter().map(|edges| edges.len()).sum()
777 }
778
779 pub fn to_triplets(&self) -> Result<Vec<(usize, usize, Precision)>> {
781 let mut triplets = Vec::new();
782
783 for (row, edges) in self.out_edges.iter().enumerate() {
784 for edge in edges {
785 triplets.push((row, edge.target as usize, edge.weight));
786 }
787 }
788
789 Ok(triplets)
790 }
791
792 pub fn scale(&mut self, factor: Precision) {
794 for edges in &mut self.out_edges {
795 for edge in edges {
796 edge.weight *= factor;
797 }
798 }
799
800 for edges in &mut self.in_edges {
801 for edge in edges {
802 edge.weight *= factor;
803 }
804 }
805
806 for degree in &mut self.degrees {
807 *degree *= factor.abs();
808 }
809 }
810
811 pub fn add_diagonal(&mut self, alpha: Precision) {
813 for (node, edges) in self.out_edges.iter_mut().enumerate() {
814 let mut found = false;
816 for edge in edges.iter_mut() {
817 if edge.target as usize == node {
818 edge.weight += alpha;
819 found = true;
820 break;
821 }
822 }
823
824 if !found && alpha != 0.0 {
826 edges.push(GraphEdge {
827 target: node as NodeId,
828 weight: alpha,
829 });
830 }
831
832 self.degrees[node] += alpha.abs();
834 }
835 }
836
837 pub fn out_neighbors(&self, node: usize) -> &[GraphEdge] {
839 if node < self.out_edges.len() {
840 &self.out_edges[node]
841 } else {
842 &[]
843 }
844 }
845
846 pub fn in_neighbors(&self, node: usize) -> &[GraphEdge] {
848 if node < self.in_edges.len() {
849 &self.in_edges[node]
850 } else {
851 &[]
852 }
853 }
854
855 pub fn degree(&self, node: usize) -> Precision {
857 if node < self.degrees.len() {
858 self.degrees[node]
859 } else {
860 0.0
861 }
862 }
863}
864
865pub struct GraphRowIter<'a> {
867 edges: &'a [GraphEdge],
868 pos: usize,
869}
870
871impl<'a> Iterator for GraphRowIter<'a> {
872 type Item = (IndexType, Precision);
873
874 fn next(&mut self) -> Option<Self::Item> {
875 if self.pos < self.edges.len() {
876 let edge = self.edges[self.pos];
877 self.pos += 1;
878 Some((edge.target, edge.weight))
879 } else {
880 None
881 }
882 }
883}
884
885pub struct GraphColIter<'a> {
887 edges: &'a [GraphEdge],
888 pos: usize,
889}
890
891impl<'a> Iterator for GraphColIter<'a> {
892 type Item = (IndexType, Precision);
893
894 fn next(&mut self) -> Option<Self::Item> {
895 if self.pos < self.edges.len() {
896 let edge = self.edges[self.pos];
897 self.pos += 1;
898 Some((edge.target, edge.weight))
899 } else {
900 None
901 }
902 }
903}
904
905#[cfg(all(test, feature = "std"))]
906mod tests {
907 use super::*;
908
909 #[test]
910 fn test_csr_creation() {
911 let triplets = vec![(0, 0, 1.0), (0, 2, 2.0), (1, 1, 3.0), (2, 0, 4.0), (2, 2, 5.0)];
912 let coo = COOStorage::from_triplets(triplets).unwrap();
913 let csr = CSRStorage::from_coo(&coo, 3, 3).unwrap();
914
915 assert_eq!(csr.nnz(), 5);
916 assert_eq!(csr.get(0, 0), Some(1.0));
917 assert_eq!(csr.get(0, 2), Some(2.0));
918 assert_eq!(csr.get(1, 1), Some(3.0));
919 assert_eq!(csr.get(0, 1), None);
920 }
921
922 #[test]
923 fn test_csr_matrix_vector_multiply() {
924 let triplets = vec![(0, 0, 2.0), (0, 1, 1.0), (1, 0, 1.0), (1, 1, 3.0)];
925 let coo = COOStorage::from_triplets(triplets).unwrap();
926 let csr = CSRStorage::from_coo(&coo, 2, 2).unwrap();
927
928 let x = vec![1.0, 2.0];
929 let mut result = vec![0.0; 2];
930
931 csr.multiply_vector(&x, &mut result);
932 assert_eq!(result, vec![4.0, 7.0]); }
934
935 #[test]
936 fn test_graph_storage() {
937 let triplets = vec![(0, 1, 0.5), (1, 0, 0.3), (1, 2, 0.7), (2, 1, 0.2)];
938 let graph = GraphStorage::from_triplets(triplets, 3).unwrap();
939
940 assert_eq!(graph.nnz(), 4);
941 assert_eq!(graph.out_neighbors(1).len(), 2);
942 assert_eq!(graph.in_neighbors(1).len(), 2);
943 assert!(graph.degree(1) > 0.0);
944 }
945
946 #[test]
947 fn test_format_conversions() {
948 let triplets = vec![(0, 0, 1.0), (0, 2, 2.0), (1, 1, 3.0)];
949
950 let coo1 = COOStorage::from_triplets(triplets.clone()).unwrap();
952 let csr = CSRStorage::from_coo(&coo1, 2, 3).unwrap();
953 let csc = CSCStorage::from_csr(&csr, 2, 3).unwrap();
954 let triplets2 = csc.to_triplets().unwrap();
955
956 let mut t1 = triplets.clone();
958 let mut t2 = triplets2;
959 t1.sort();
960 t2.sort();
961
962 assert_eq!(t1, t2);
963 }
964}