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