scirs2_sparse/
sym_sparray.rs

1//! Symmetric Sparse Array trait
2//!
3//! This module defines a trait for symmetric sparse array implementations.
4//! It extends the base SparseArray trait with methods specific to symmetric
5//! matrices.
6
7use crate::coo_array::CooArray;
8use crate::csr_array::CsrArray;
9use crate::error::{SparseError, SparseResult};
10use crate::sparray::{SparseArray, SparseSum};
11use crate::sym_coo::SymCooArray;
12use crate::sym_csr::SymCsrArray;
13use scirs2_core::numeric::{Float, SparseElement};
14use std::fmt::Debug;
15use std::ops::{Add, Div, Mul, Sub};
16
17/// Trait for symmetric sparse arrays
18///
19/// Extends the base SparseArray trait with methods specific to
20/// symmetric matrices. Implementations guarantee that the matrix
21/// is kept symmetric throughout all operations.
22pub trait SymSparseArray<T>: SparseArray<T>
23where
24    T: SparseElement + Div<Output = T> + Float + PartialOrd + 'static,
25{
26    /// Get the number of stored non-zero elements
27    ///
28    /// For symmetric formats, this returns the number of elements
29    /// actually stored (typically lower or upper triangular part).
30    ///
31    /// # Returns
32    ///
33    /// Number of stored non-zero elements
34    fn nnz_stored(&self) -> usize;
35
36    /// Check if the matrix is guaranteed to be symmetric
37    ///
38    /// For implementations of this trait, this should always return true.
39    /// It's included for consistency with checking functions.
40    ///
41    /// # Returns
42    ///
43    /// Always true for SymSparseArray implementations
44    fn is_symmetric(&self) -> bool {
45        true
46    }
47
48    /// Convert to standard CSR array
49    ///
50    /// # Returns
51    ///
52    /// A standard CSR array with the full symmetric matrix
53    fn to_csr(&self) -> SparseResult<CsrArray<T>>;
54
55    /// Convert to standard COO array
56    ///
57    /// # Returns
58    ///
59    /// A standard COO array with the full symmetric matrix
60    fn to_coo(&self) -> SparseResult<CooArray<T>>;
61
62    /// Convert to symmetric CSR array
63    ///
64    /// # Returns
65    ///
66    /// A symmetric CSR array
67    fn to_sym_csr(&self) -> SparseResult<SymCsrArray<T>>;
68
69    /// Convert to symmetric COO array
70    ///
71    /// # Returns
72    ///
73    /// A symmetric COO array
74    fn to_sym_coo(&self) -> SparseResult<SymCooArray<T>>;
75}
76
77/// Implementation of SymSparseArray for SymCsrArray
78impl<T> SymSparseArray<T> for SymCsrArray<T>
79where
80    T: Float
81        + SparseElement
82        + Debug
83        + Copy
84        + 'static
85        + Add<Output = T>
86        + Sub<Output = T>
87        + Mul<Output = T>
88        + Div<Output = T>
89        + scirs2_core::simd_ops::SimdUnifiedOps
90        + SparseElement
91        + Send
92        + Sync,
93{
94    fn nnz_stored(&self) -> usize {
95        self.inner().nnz_stored()
96    }
97
98    fn to_csr(&self) -> SparseResult<CsrArray<T>> {
99        self.to_csr_array()
100    }
101
102    fn to_coo(&self) -> SparseResult<CooArray<T>> {
103        // Extract matrix data
104        let csr_inner = self.inner();
105        let shape = csr_inner.shape;
106
107        // Convert to triplets format for full symmetric matrix
108        let mut rows = Vec::new();
109        let mut cols = Vec::new();
110        let mut data = Vec::new();
111
112        for i in 0..shape.0 {
113            for j_ptr in csr_inner.indptr[i]..csr_inner.indptr[i + 1] {
114                let j = csr_inner.indices[j_ptr];
115                let val = csr_inner.data[j_ptr];
116
117                // Add the element itself
118                rows.push(i);
119                cols.push(j);
120                data.push(val);
121
122                // Add its symmetric pair (if not diagonal)
123                if i != j {
124                    rows.push(j);
125                    cols.push(i);
126                    data.push(val);
127                }
128            }
129        }
130
131        // Create a COO array from the triplets
132        CooArray::from_triplets(&rows, &cols, &data, shape, false)
133    }
134
135    fn to_sym_csr(&self) -> SparseResult<SymCsrArray<T>> {
136        // Already a SymCsrArray, return a clone
137        Ok(Clone::clone(self))
138    }
139
140    fn to_sym_coo(&self) -> SparseResult<SymCooArray<T>> {
141        // Convert the internal SymCsrMatrix to SymCooMatrix
142        let csr_inner = self.inner();
143
144        // Extract data from CSR format into COO format (lower triangular part only)
145        let mut data = Vec::new();
146        let mut rows = Vec::new();
147        let mut cols = Vec::new();
148
149        for i in 0..csr_inner.shape.0 {
150            for j in csr_inner.indptr[i]..csr_inner.indptr[i + 1] {
151                let col = csr_inner.indices[j];
152                let val = csr_inner.data[j];
153
154                data.push(val);
155                rows.push(i);
156                cols.push(col);
157            }
158        }
159
160        use crate::sym_coo::SymCooMatrix;
161        let sym_coo = SymCooMatrix::new(data, rows, cols, csr_inner.shape)?;
162
163        Ok(SymCooArray::new(sym_coo))
164    }
165}
166
167/// Implementation of SymSparseArray for SymCooArray
168impl<T> SymSparseArray<T> for SymCooArray<T>
169where
170    T: Float
171        + SparseElement
172        + Debug
173        + Copy
174        + 'static
175        + Add<Output = T>
176        + Sub<Output = T>
177        + Mul<Output = T>
178        + Div<Output = T>
179        + scirs2_core::simd_ops::SimdUnifiedOps
180        + SparseElement
181        + Send
182        + Sync,
183{
184    fn nnz_stored(&self) -> usize {
185        self.inner().nnz_stored()
186    }
187
188    fn to_csr(&self) -> SparseResult<CsrArray<T>> {
189        // Convert to full COO, then to CSR
190        let coo = self.to_coo_array()?;
191        match coo.to_csr() {
192            Ok(boxed_csr) => {
193                // Convert Box<dyn SparseArray<T>> to CsrArray<T>
194                match boxed_csr.as_any().downcast_ref::<CsrArray<T>>() {
195                    Some(csr_array) => Ok(csr_array.clone()),
196                    None => Err(SparseError::ConversionError(
197                        "Failed to downcast to CsrArray".to_string(),
198                    )),
199                }
200            }
201            Err(e) => Err(e),
202        }
203    }
204
205    fn to_coo(&self) -> SparseResult<CooArray<T>> {
206        self.to_coo_array()
207    }
208
209    fn to_sym_csr(&self) -> SparseResult<SymCsrArray<T>> {
210        // We already have a symmetric COO matrix with only the lower triangular part
211        // Let's create a CSR matrix directly from it
212        let coo_inner = self.inner();
213
214        // Extract the triplets data
215        let data = coo_inner.data.clone();
216        let rows = coo_inner.rows.clone();
217        let cols = coo_inner.cols.clone();
218        let shape = coo_inner.shape;
219
220        // Create a new CsrMatrix from these triplets
221        let csr = crate::csr::CsrMatrix::new(data, rows, cols, shape)?;
222
223        // Create a symmetric CSR matrix without checking symmetry (we know it's symmetric)
224        use crate::sym_csr::SymCsrMatrix;
225
226        // Extract the lower triangular part (already in the correct format)
227        let mut sym_data = Vec::new();
228        let mut sym_indices = Vec::new();
229        let mut sym_indptr = vec![0];
230        let n = shape.0;
231
232        for i in 0..n {
233            for j_ptr in csr.indptr[i]..csr.indptr[i + 1] {
234                let j = csr.indices[j_ptr];
235                let val = csr.data[j_ptr];
236
237                // Only include elements in lower triangular part (including diagonal)
238                if j <= i {
239                    sym_data.push(val);
240                    sym_indices.push(j);
241                }
242            }
243
244            sym_indptr.push(sym_data.len());
245        }
246
247        let sym_csr = SymCsrMatrix::new(sym_data, sym_indptr, sym_indices, shape)?;
248
249        Ok(SymCsrArray::new(sym_csr))
250    }
251
252    fn to_sym_coo(&self) -> SparseResult<SymCooArray<T>> {
253        // Already a SymCooArray, return a clone
254        Ok(Clone::clone(self))
255    }
256}
257
258/// Implementation of SparseArray for SymCsrArray
259impl<T> SparseArray<T> for SymCsrArray<T>
260where
261    T: Float
262        + SparseElement
263        + Debug
264        + Copy
265        + 'static
266        + Add<Output = T>
267        + Sub<Output = T>
268        + Mul<Output = T>
269        + Div<Output = T>
270        + scirs2_core::simd_ops::SimdUnifiedOps
271        + SparseElement
272        + Send
273        + Sync,
274{
275    fn to_coo(&self) -> SparseResult<Box<dyn SparseArray<T>>> {
276        let coo_array = <Self as SymSparseArray<T>>::to_coo(self)?;
277        Ok(Box::new(coo_array))
278    }
279
280    fn to_csr(&self) -> SparseResult<Box<dyn SparseArray<T>>> {
281        // Convert to CsrArray (full matrix)
282        let csr = <Self as SymSparseArray<T>>::to_csr(self)?;
283        Ok(Box::new(csr))
284    }
285    fn shape(&self) -> (usize, usize) {
286        self.inner().shape()
287    }
288
289    fn nnz(&self) -> usize {
290        self.inner().nnz()
291    }
292
293    fn dtype(&self) -> &str {
294        if std::any::TypeId::of::<T>() == std::any::TypeId::of::<f32>() {
295            "f32"
296        } else {
297            "f64"
298        }
299    }
300
301    fn get(&self, row: usize, col: usize) -> T {
302        self.inner().get(row, col)
303    }
304
305    fn to_array(&self) -> scirs2_core::ndarray::Array2<T> {
306        // Convert to dense vector of vectors, then to ndarray
307        let dense = self.inner().to_dense();
308        let mut array = scirs2_core::ndarray::Array2::zeros(self.shape());
309
310        for i in 0..dense.len() {
311            for j in 0..dense[i].len() {
312                array[[i, j]] = dense[i][j];
313            }
314        }
315
316        array
317    }
318
319    fn toarray(&self) -> scirs2_core::ndarray::Array2<T> {
320        self.to_array()
321    }
322
323    fn set(&mut self, i: usize, j: usize, value: T) -> SparseResult<()> {
324        Err(SparseError::NotImplemented(
325            "Setting individual elements in SymCsrArray is not supported. Convert to another format first.".to_string()
326        ))
327    }
328
329    fn dot_vector(
330        &self,
331        other: &scirs2_core::ndarray::ArrayView1<T>,
332    ) -> SparseResult<scirs2_core::ndarray::Array1<T>> {
333        // Use optimized symmetric matrix-vector product
334        crate::sym_ops::sym_csr_matvec(self.inner(), other)
335    }
336
337    fn copy(&self) -> Box<dyn SparseArray<T>> {
338        Box::new(Clone::clone(self))
339    }
340
341    fn sub(&self, other: &dyn SparseArray<T>) -> SparseResult<Box<dyn SparseArray<T>>> {
342        // Subtraction will preserve symmetry if other is also symmetric
343        // For simplicity, we'll convert to CSR, perform subtraction, and convert back
344        let self_csr = <Self as SymSparseArray<T>>::to_csr(self)?;
345        let result = self_csr.sub(other)?;
346
347        // For now, we return the CSR result without converting back
348        Ok(result)
349    }
350
351    fn div(&self, other: &dyn SparseArray<T>) -> SparseResult<Box<dyn SparseArray<T>>> {
352        // Division may not preserve symmetry, so we'll just return a CSR result
353        let self_csr = <Self as SymSparseArray<T>>::to_csr(self)?;
354        self_csr.div(other)
355    }
356
357    fn eliminate_zeros(&mut self) {
358        // No-op for SymCsrArray as it already maintains minimal storage
359    }
360
361    fn sort_indices(&mut self) {
362        // No-op for SymCsrArray as indices are already sorted
363    }
364
365    fn sorted_indices(&self) -> Box<dyn SparseArray<T>> {
366        // CSR format typically maintains sorted indices, so return a clone
367        Box::new(Clone::clone(self))
368    }
369
370    fn has_sorted_indices(&self) -> bool {
371        true
372    }
373
374    fn sum(&self, axis: Option<usize>) -> SparseResult<SparseSum<T>> {
375        // Convert to CSR and use its implementation
376        let csr = <Self as SymSparseArray<T>>::to_csr(self)?;
377        SparseArray::<T>::sum(&csr, axis)
378    }
379
380    fn max(&self) -> T {
381        // Convert to CSR and find the maximum value
382        match <Self as SymSparseArray<T>>::to_csr(self) {
383            Ok(csr) => SparseArray::<T>::max(&csr),
384            Err(_) => T::nan(), // Return NaN if conversion fails
385        }
386    }
387
388    fn min(&self) -> T {
389        // Convert to CSR and find the minimum value
390        match <Self as SymSparseArray<T>>::to_csr(self) {
391            Ok(csr) => SparseArray::<T>::min(&csr),
392            Err(_) => T::nan(), // Return NaN if conversion fails
393        }
394    }
395
396    fn slice(
397        &self,
398        rows: (usize, usize),
399        cols: (usize, usize),
400    ) -> SparseResult<Box<dyn SparseArray<T>>> {
401        // Slicing a symmetric matrix may not preserve symmetry
402        // Convert to CSR and use its implementation
403        let csr = <Self as SymSparseArray<T>>::to_csr(self)?;
404        csr.slice(rows, cols)
405    }
406
407    fn as_any(&self) -> &dyn std::any::Any {
408        self
409    }
410
411    fn find(
412        &self,
413    ) -> (
414        scirs2_core::ndarray::Array1<usize>,
415        scirs2_core::ndarray::Array1<usize>,
416        scirs2_core::ndarray::Array1<T>,
417    ) {
418        // To get the full matrix coordinates and values, we need to convert to a full CSR matrix
419        match <Self as SymSparseArray<T>>::to_csr(self) {
420            Ok(csr) => csr.find(),
421            Err(_) => (
422                scirs2_core::ndarray::Array1::from_vec(Vec::new()),
423                scirs2_core::ndarray::Array1::from_vec(Vec::new()),
424                scirs2_core::ndarray::Array1::from_vec(Vec::new()),
425            ),
426        }
427    }
428
429    fn add(&self, other: &dyn SparseArray<T>) -> SparseResult<Box<dyn SparseArray<T>>> {
430        // First check shapes are compatible
431        let selfshape = self.shape();
432        let othershape = other.shape();
433
434        if selfshape != othershape {
435            return Err(crate::error::SparseError::DimensionMismatch {
436                expected: selfshape.0,
437                found: othershape.0,
438            });
439        }
440
441        // Convert both to CSR to perform addition
442        let self_csr = <Self as SymSparseArray<T>>::to_csr(self)?;
443        let other_csr_box = other.to_csr()?;
444
445        // We need to unbox the other_csr to use it directly
446        match other_csr_box.as_any().downcast_ref::<CsrArray<T>>() {
447            Some(other_csr) => {
448                // Add as standard CSR arrays
449                let result = self_csr.add(other_csr)?;
450
451                // Since we expect result to be symmetric (if the other was symmetric),
452                // we can try to convert it back to a SymCsrArray
453
454                // First convert the result to CSR Matrix
455                use crate::csr::CsrMatrix;
456
457                // We need to get the data from the result
458                let (rows, cols, data) = result.find();
459                let csr_matrix =
460                    CsrMatrix::new(data.to_vec(), rows.to_vec(), cols.to_vec(), result.shape())?;
461
462                // Convert to SymCsrMatrix
463                use crate::sym_csr::SymCsrMatrix;
464                let sym_csr = SymCsrMatrix::from_csr(&csr_matrix)?;
465
466                // Create and return SymCsrArray
467                let sym_csr_array = SymCsrArray::new(sym_csr);
468                Ok(Box::new(sym_csr_array) as Box<dyn SparseArray<T>>)
469            }
470            None => {
471                // If we can't downcast, convert both to dense arrays and add them
472                let self_dense = self.to_array();
473                let other_dense = other.to_array();
474
475                // Create the result dense array
476                let mut result_dense = scirs2_core::ndarray::Array2::zeros(selfshape);
477                for i in 0..selfshape.0 {
478                    for j in 0..selfshape.1 {
479                        result_dense[[i, j]] = self_dense[[i, j]] + other_dense[[i, j]];
480                    }
481                }
482
483                // Convert back to CSR
484                // Convert result_dense to triplets
485                let mut rows = Vec::new();
486                let mut cols = Vec::new();
487                let mut values = Vec::new();
488
489                for i in 0..selfshape.0 {
490                    for j in 0..selfshape.1 {
491                        let val = result_dense[[i, j]];
492                        if val != T::sparse_zero() {
493                            rows.push(i);
494                            cols.push(j);
495                            values.push(val);
496                        }
497                    }
498                }
499
500                let csr = CsrArray::from_triplets(&rows, &cols, &values, selfshape, false)?;
501                Ok(Box::new(csr) as Box<dyn SparseArray<T>>)
502            }
503        }
504    }
505
506    fn mul(&self, other: &dyn SparseArray<T>) -> SparseResult<Box<dyn SparseArray<T>>> {
507        // First check shapes are compatible
508        let selfshape = self.shape();
509        let othershape = other.shape();
510
511        if selfshape != othershape {
512            return Err(crate::error::SparseError::DimensionMismatch {
513                expected: selfshape.0,
514                found: othershape.0,
515            });
516        }
517
518        // Convert both to CSR to perform multiplication
519        let self_csr = <Self as SymSparseArray<T>>::to_csr(self)?;
520        let other_csr_box = other.to_csr()?;
521
522        // We need to unbox the other_csr to use it directly
523        match other_csr_box.as_any().downcast_ref::<CsrArray<T>>() {
524            Some(other_csr) => {
525                // Multiply as standard CSR arrays (element-wise)
526                let result = self_csr.mul(other_csr)?;
527
528                // Element-wise multiplication of symmetric matrices preserves symmetry,
529                // so we can convert back to SymCsrArray
530
531                // We need to get the data from the result
532                let (rows, cols, data) = result.find();
533
534                // Convert to CsrMatrix
535                use crate::csr::CsrMatrix;
536                let csr_matrix =
537                    CsrMatrix::new(data.to_vec(), rows.to_vec(), cols.to_vec(), result.shape())?;
538
539                // Convert to SymCsrMatrix
540                use crate::sym_csr::SymCsrMatrix;
541                let sym_csr = SymCsrMatrix::from_csr(&csr_matrix)?;
542
543                // Create and return SymCsrArray
544                let sym_csr_array = SymCsrArray::new(sym_csr);
545                Ok(Box::new(sym_csr_array) as Box<dyn SparseArray<T>>)
546            }
547            None => {
548                // If we can't downcast, convert both to dense arrays and multiply them
549                let self_dense = self.to_array();
550                let other_dense = other.to_array();
551
552                // Create the result dense array
553                let mut result_dense = scirs2_core::ndarray::Array2::zeros(selfshape);
554                for i in 0..selfshape.0 {
555                    for j in 0..selfshape.1 {
556                        result_dense[[i, j]] = self_dense[[i, j]] * other_dense[[i, j]];
557                    }
558                }
559
560                // Convert back to CSR
561                // Convert result_dense to triplets
562                let mut rows = Vec::new();
563                let mut cols = Vec::new();
564                let mut values = Vec::new();
565
566                for i in 0..selfshape.0 {
567                    for j in 0..selfshape.1 {
568                        let val = result_dense[[i, j]];
569                        if val != T::sparse_zero() {
570                            rows.push(i);
571                            cols.push(j);
572                            values.push(val);
573                        }
574                    }
575                }
576
577                let csr = CsrArray::from_triplets(&rows, &cols, &values, selfshape, false)?;
578                Ok(Box::new(csr) as Box<dyn SparseArray<T>>)
579            }
580        }
581    }
582
583    fn dot(&self, other: &dyn SparseArray<T>) -> SparseResult<Box<dyn SparseArray<T>>> {
584        // For matrix multiplication, symmetry is not generally preserved
585        // So we just convert to standard CSR, perform the operation, and return a CSR array
586
587        let self_csr = <Self as SymSparseArray<T>>::to_csr(self)?;
588        let result = self_csr.dot(other)?;
589
590        // For dot product of a symmetric matrix with itself, the result is symmetric
591        // We could check for this case and return a SymCsrArray, but for now we'll
592        // keep it simple and just return the CSR array
593        Ok(result)
594    }
595
596    fn transpose(&self) -> SparseResult<Box<dyn SparseArray<T>>> {
597        // For symmetric matrices, transpose is the same as the original
598        Ok(Box::new(Clone::clone(self)))
599    }
600
601    fn to_csc(&self) -> SparseResult<Box<dyn SparseArray<T>>> {
602        // Convert to CSR, then to CSC
603        let csr = <Self as SymSparseArray<T>>::to_csr(self)?;
604        let csr_box: Box<dyn SparseArray<T>> = Box::new(csr);
605        csr_box.to_csc()
606    }
607
608    fn to_dia(&self) -> SparseResult<Box<dyn SparseArray<T>>> {
609        // Convert to CSR, then to DIA
610        let csr = <Self as SymSparseArray<T>>::to_csr(self)?;
611        let csr_box: Box<dyn SparseArray<T>> = Box::new(csr);
612        csr_box.to_dia()
613    }
614
615    fn to_dok(&self) -> SparseResult<Box<dyn SparseArray<T>>> {
616        // Convert to CSR, then to DOK
617        let csr = <Self as SymSparseArray<T>>::to_csr(self)?;
618        let csr_box: Box<dyn SparseArray<T>> = Box::new(csr);
619        csr_box.to_dok()
620    }
621
622    fn to_lil(&self) -> SparseResult<Box<dyn SparseArray<T>>> {
623        // Convert to CSR, then to LIL
624        let csr = <Self as SymSparseArray<T>>::to_csr(self)?;
625        let csr_box: Box<dyn SparseArray<T>> = Box::new(csr);
626        csr_box.to_lil()
627    }
628
629    fn to_bsr(&self) -> SparseResult<Box<dyn SparseArray<T>>> {
630        // Convert to CSR, then to BSR
631        let csr = <Self as SymSparseArray<T>>::to_csr(self)?;
632        let csr_box: Box<dyn SparseArray<T>> = Box::new(csr);
633        csr_box.to_bsr()
634    }
635}
636
637/// Implementation of SparseArray for SymCooArray
638impl<T> SparseArray<T> for SymCooArray<T>
639where
640    T: Float
641        + SparseElement
642        + Debug
643        + Copy
644        + 'static
645        + Add<Output = T>
646        + Sub<Output = T>
647        + Mul<Output = T>
648        + Div<Output = T>
649        + scirs2_core::simd_ops::SimdUnifiedOps
650        + SparseElement
651        + Send
652        + Sync,
653{
654    fn to_coo(&self) -> SparseResult<Box<dyn SparseArray<T>>> {
655        let coo_array = <Self as SymSparseArray<T>>::to_coo(self)?;
656        Ok(Box::new(coo_array))
657    }
658
659    fn to_csr(&self) -> SparseResult<Box<dyn SparseArray<T>>> {
660        // Convert to CsrArray (full matrix)
661        let csr = <Self as SymSparseArray<T>>::to_csr(self)?;
662        Ok(Box::new(csr))
663    }
664    fn shape(&self) -> (usize, usize) {
665        self.inner().shape()
666    }
667
668    fn nnz(&self) -> usize {
669        self.inner().nnz()
670    }
671
672    fn dtype(&self) -> &str {
673        if std::any::TypeId::of::<T>() == std::any::TypeId::of::<f32>() {
674            "f32"
675        } else {
676            "f64"
677        }
678    }
679
680    fn get(&self, row: usize, col: usize) -> T {
681        self.inner().get(row, col)
682    }
683
684    fn to_array(&self) -> scirs2_core::ndarray::Array2<T> {
685        // Convert to dense vector of vectors, then to ndarray
686        let dense = self.inner().to_dense();
687        let mut array = scirs2_core::ndarray::Array2::zeros(self.shape());
688
689        for i in 0..dense.len() {
690            for j in 0..dense[i].len() {
691                array[[i, j]] = dense[i][j];
692            }
693        }
694
695        array
696    }
697
698    fn toarray(&self) -> scirs2_core::ndarray::Array2<T> {
699        self.to_array()
700    }
701
702    fn set(&mut self, i: usize, j: usize, value: T) -> SparseResult<()> {
703        Err(SparseError::NotImplemented(
704            "Setting individual elements in SymCooArray is not supported. Convert to another format first.".to_string()
705        ))
706    }
707
708    fn dot_vector(
709        &self,
710        other: &scirs2_core::ndarray::ArrayView1<T>,
711    ) -> SparseResult<scirs2_core::ndarray::Array1<T>> {
712        // Use optimized symmetric matrix-vector product
713        crate::sym_ops::sym_coo_matvec(self.inner(), other)
714    }
715
716    fn copy(&self) -> Box<dyn SparseArray<T>> {
717        Box::new(Clone::clone(self))
718    }
719
720    fn sub(&self, other: &dyn SparseArray<T>) -> SparseResult<Box<dyn SparseArray<T>>> {
721        // For simplicity, we'll use the CSR implementation
722        let self_csr = <Self as SymSparseArray<T>>::to_csr(self)?;
723        self_csr.sub(other)
724    }
725
726    fn div(&self, other: &dyn SparseArray<T>) -> SparseResult<Box<dyn SparseArray<T>>> {
727        // For simplicity, we'll use the CSR implementation
728        let self_csr = <Self as SymSparseArray<T>>::to_csr(self)?;
729        self_csr.div(other)
730    }
731
732    fn eliminate_zeros(&mut self) {
733        // Not implemented for SymCooArray
734        // Could be implemented by filtering out zero values in the future
735    }
736
737    fn sort_indices(&mut self) {
738        // Not implemented for SymCooArray
739        // Could be implemented by sorting indices by row, then column in the future
740    }
741
742    fn sorted_indices(&self) -> Box<dyn SparseArray<T>> {
743        // Convert to SymCsrArray which has sorted indices
744        match self.to_sym_csr() {
745            Ok(csr) => Box::new(csr),
746            Err(_) => Box::new(Clone::clone(self)), // Return self if conversion fails
747        }
748    }
749
750    fn has_sorted_indices(&self) -> bool {
751        false
752    }
753
754    fn sum(&self, axis: Option<usize>) -> SparseResult<SparseSum<T>> {
755        // Convert to CSR and use its implementation
756        let csr = <Self as SymSparseArray<T>>::to_csr(self)?;
757        SparseArray::<T>::sum(&csr, axis)
758    }
759
760    fn max(&self) -> T {
761        // Convert to CSR and find the maximum value
762        match <Self as SymSparseArray<T>>::to_csr(self) {
763            Ok(csr) => SparseArray::<T>::max(&csr),
764            Err(_) => T::nan(), // Return NaN if conversion fails
765        }
766    }
767
768    fn min(&self) -> T {
769        // Convert to CSR and find the minimum value
770        match <Self as SymSparseArray<T>>::to_csr(self) {
771            Ok(csr) => SparseArray::<T>::min(&csr),
772            Err(_) => T::nan(), // Return NaN if conversion fails
773        }
774    }
775
776    fn slice(
777        &self,
778        rows: (usize, usize),
779        cols: (usize, usize),
780    ) -> SparseResult<Box<dyn SparseArray<T>>> {
781        // Convert to CSR and use its implementation
782        let csr = <Self as SymSparseArray<T>>::to_csr(self)?;
783        csr.slice(rows, cols)
784    }
785
786    fn as_any(&self) -> &dyn std::any::Any {
787        self
788    }
789
790    fn find(
791        &self,
792    ) -> (
793        scirs2_core::ndarray::Array1<usize>,
794        scirs2_core::ndarray::Array1<usize>,
795        scirs2_core::ndarray::Array1<T>,
796    ) {
797        // To get the full matrix coordinates and values, we need to convert to a full COO matrix
798        match <Self as SymSparseArray<T>>::to_coo(self) {
799            Ok(coo) => coo.find(),
800            Err(_) => (
801                scirs2_core::ndarray::Array1::from_vec(Vec::new()),
802                scirs2_core::ndarray::Array1::from_vec(Vec::new()),
803                scirs2_core::ndarray::Array1::from_vec(Vec::new()),
804            ),
805        }
806    }
807
808    fn add(&self, other: &dyn SparseArray<T>) -> SparseResult<Box<dyn SparseArray<T>>> {
809        // Convert to SymCsrArray and use its implementation
810        self.to_sym_csr()?.add(other)
811    }
812
813    fn mul(&self, other: &dyn SparseArray<T>) -> SparseResult<Box<dyn SparseArray<T>>> {
814        // Convert to SymCsrArray and use its implementation
815        self.to_sym_csr()?.mul(other)
816    }
817
818    fn dot(&self, other: &dyn SparseArray<T>) -> SparseResult<Box<dyn SparseArray<T>>> {
819        // Convert to CSR for dot product
820        <Self as SymSparseArray<T>>::to_csr(self)?.dot(other)
821    }
822
823    fn transpose(&self) -> SparseResult<Box<dyn SparseArray<T>>> {
824        // For symmetric matrices, transpose is the same as the original
825        Ok(Box::new(Clone::clone(self)))
826    }
827
828    fn to_csc(&self) -> SparseResult<Box<dyn SparseArray<T>>> {
829        // Convert to CSR, then to CSC
830        let csr = <Self as SymSparseArray<T>>::to_csr(self)?;
831        let csr_box: Box<dyn SparseArray<T>> = Box::new(csr);
832        csr_box.to_csc()
833    }
834
835    fn to_dia(&self) -> SparseResult<Box<dyn SparseArray<T>>> {
836        // Convert to CSR, then to DIA
837        let csr = <Self as SymSparseArray<T>>::to_csr(self)?;
838        let csr_box: Box<dyn SparseArray<T>> = Box::new(csr);
839        csr_box.to_dia()
840    }
841
842    fn to_dok(&self) -> SparseResult<Box<dyn SparseArray<T>>> {
843        // Convert to CSR, then to DOK
844        let csr = <Self as SymSparseArray<T>>::to_csr(self)?;
845        let csr_box: Box<dyn SparseArray<T>> = Box::new(csr);
846        csr_box.to_dok()
847    }
848
849    fn to_lil(&self) -> SparseResult<Box<dyn SparseArray<T>>> {
850        // Convert to CSR, then to LIL
851        let csr = <Self as SymSparseArray<T>>::to_csr(self)?;
852        let csr_box: Box<dyn SparseArray<T>> = Box::new(csr);
853        csr_box.to_lil()
854    }
855
856    fn to_bsr(&self) -> SparseResult<Box<dyn SparseArray<T>>> {
857        // Convert to CSR, then to BSR
858        let csr = <Self as SymSparseArray<T>>::to_csr(self)?;
859        let csr_box: Box<dyn SparseArray<T>> = Box::new(csr);
860        csr_box.to_bsr()
861    }
862}
863
864#[cfg(test)]
865mod tests {
866    use super::*;
867    use crate::sym_csr::{SymCsrArray, SymCsrMatrix};
868
869    // Create a simple symmetric matrix for testing
870    fn create_test_sym_csr() -> SymCsrArray<f64> {
871        // Create a simple symmetric matrix in CSR format
872        // [2 1 0]
873        // [1 2 3]
874        // [0 3 1]
875
876        // Lower triangular part only:
877        // [2 0 0]
878        // [1 2 0]
879        // [0 3 1]
880
881        let data = vec![2.0, 1.0, 2.0, 3.0, 1.0];
882        let indices = vec![0, 0, 1, 1, 2];
883        let indptr = vec![0, 1, 3, 5];
884
885        let sym_matrix = SymCsrMatrix::new(data, indptr, indices, (3, 3)).unwrap();
886        SymCsrArray::new(sym_matrix)
887    }
888
889    #[test]
890    fn test_sym_sparse_array_trait() {
891        let sym_csr = create_test_sym_csr();
892
893        // Test basic properties
894        assert_eq!(sym_csr.shape(), (3, 3));
895        assert!(sym_csr.is_symmetric());
896
897        // Test SparseArray methods
898        assert_eq!(sym_csr.get(0, 0), 2.0);
899        assert_eq!(sym_csr.get(0, 1), 1.0);
900        assert_eq!(sym_csr.get(1, 0), 1.0); // Through symmetry
901
902        // Test nnz_stored vs nnz
903        assert_eq!(sym_csr.nnz_stored(), 5); // Only stored elements
904        assert_eq!(sym_csr.nnz(), 7); // Including symmetric pairs
905
906        // Test conversion between formats
907        let sym_coo = sym_csr.to_sym_coo().unwrap();
908        assert_eq!(sym_coo.shape(), (3, 3));
909        assert!(sym_coo.is_symmetric());
910
911        let csr = SymSparseArray::<f64>::to_csr(&sym_csr).unwrap();
912        assert_eq!(csr.shape(), (3, 3));
913
914        let coo = SymSparseArray::<f64>::to_coo(&sym_csr).unwrap();
915        assert_eq!(coo.shape(), (3, 3));
916
917        // Test that find() returns the full matrix elements
918        let (rows, _cols, _data) = sym_csr.find();
919        assert!(rows.len() > sym_csr.nnz_stored()); // Should include symmetric pairs
920    }
921
922    #[test]
923    fn test_sym_sparse_array_operations() {
924        let sym_csr = create_test_sym_csr();
925
926        // Create another symmetric matrix for testing operations
927        let sym_csr2 = create_test_sym_csr();
928
929        // Test addition
930        let sum = sym_csr.add(&sym_csr2).unwrap();
931        assert_eq!(sum.shape(), (3, 3));
932        assert_eq!(sum.get(0, 0), 4.0); // 2 + 2
933        assert_eq!(sum.get(0, 1), 2.0); // 1 + 1
934        assert_eq!(sum.get(1, 0), 2.0); // 1 + 1 (symmetric)
935
936        // Test element-wise multiplication
937        let prod = sym_csr.mul(&sym_csr2).unwrap();
938        assert_eq!(prod.shape(), (3, 3));
939        assert_eq!(prod.get(0, 0), 4.0); // 2 * 2
940        assert_eq!(prod.get(0, 1), 1.0); // 1 * 1
941        assert_eq!(prod.get(1, 0), 1.0); // 1 * 1 (symmetric)
942
943        // Test matrix multiplication
944        let dot = sym_csr.dot(&sym_csr2).unwrap();
945        assert_eq!(dot.shape(), (3, 3));
946
947        // Test transpose (should be no change for symmetric matrices)
948        let trans = sym_csr.transpose().unwrap();
949        assert_eq!(trans.shape(), sym_csr.shape());
950        assert_eq!(SparseArray::get(&*trans, 0, 1), sym_csr.get(0, 1));
951        assert_eq!(SparseArray::get(&*trans, 1, 0), sym_csr.get(1, 0));
952    }
953}