1use std::collections::HashMap;
13
14use crate::error::SparseError;
15
16type CsrTriple = (Vec<usize>, Vec<usize>, Vec<f64>);
18
19#[derive(Debug, Clone)]
25pub struct MatrixPowerConfig {
26 pub max_nnz: Option<usize>,
29 pub reuse_structure: bool,
32 pub power: usize,
34}
35
36#[derive(Debug, Clone)]
38pub struct MatrixPowerResult {
39 pub row_offsets: Vec<usize>,
41 pub col_indices: Vec<usize>,
43 pub values: Vec<f64>,
45 pub rows: usize,
47 pub cols: usize,
49 pub nnz: usize,
51 pub multiplications_performed: usize,
53 pub nnz_growth: Vec<usize>,
55}
56
57pub fn sparse_matrix_power(
81 row_offsets: &[usize],
82 col_indices: &[usize],
83 values: &[f64],
84 rows: usize,
85 cols: usize,
86 power: usize,
87 config: &MatrixPowerConfig,
88) -> Result<MatrixPowerResult, SparseError> {
89 validate_csr(row_offsets, col_indices, values, rows)?;
90
91 if power > 1 && rows != cols {
92 return Err(SparseError::DimensionMismatch(format!(
93 "matrix must be square for power > 1, got {}x{}",
94 rows, cols
95 )));
96 }
97
98 if power == 0 {
100 let (id_offsets, id_indices, id_values) = sparse_identity(rows);
101 return Ok(MatrixPowerResult {
102 nnz: id_indices.len(),
103 row_offsets: id_offsets,
104 col_indices: id_indices,
105 values: id_values,
106 rows,
107 cols: rows,
108 multiplications_performed: 0,
109 nnz_growth: vec![],
110 });
111 }
112
113 if power == 1 {
115 return Ok(MatrixPowerResult {
116 row_offsets: row_offsets.to_vec(),
117 col_indices: col_indices.to_vec(),
118 values: values.to_vec(),
119 rows,
120 cols,
121 nnz: col_indices.len(),
122 multiplications_performed: 0,
123 nnz_growth: vec![col_indices.len()],
124 });
125 }
126
127 let mut base_offsets = row_offsets.to_vec();
130 let mut base_indices = col_indices.to_vec();
131 let mut base_values = values.to_vec();
132
133 let mut result_offsets: Option<Vec<usize>> = None;
134 let mut result_indices: Option<Vec<usize>> = None;
135 let mut result_values: Option<Vec<f64>> = None;
136
137 let mut mults = 0usize;
138 let mut nnz_growth = vec![col_indices.len()];
139 let mut exp = power;
140
141 let mut prev_structure: Option<(Vec<usize>, Vec<usize>)> = None;
143
144 while exp > 0 {
145 if exp & 1 == 1 {
146 if let (Some(r_off), Some(r_idx), Some(r_val)) =
148 (&result_offsets, &result_indices, &result_values)
149 {
150 let (new_off, new_idx, new_val) = host_spgemm(
151 r_off,
152 r_idx,
153 r_val,
154 rows,
155 rows,
156 &base_offsets,
157 &base_indices,
158 &base_values,
159 rows,
160 rows,
161 )?;
162 mults += 1;
163
164 check_max_nnz(new_idx.len(), config.max_nnz)?;
165 nnz_growth.push(new_idx.len());
166
167 if config.reuse_structure {
168 if let Some((ref ps_off, ref ps_idx)) = prev_structure {
169 if ps_off == &new_off && ps_idx == &new_idx {
170 }
172 }
173 prev_structure = Some((new_off.clone(), new_idx.clone()));
174 }
175
176 result_offsets = Some(new_off);
177 result_indices = Some(new_idx);
178 result_values = Some(new_val);
179 } else {
180 result_offsets = Some(base_offsets.clone());
181 result_indices = Some(base_indices.clone());
182 result_values = Some(base_values.clone());
183 }
184 }
185 exp >>= 1;
186 if exp > 0 {
187 let (new_off, new_idx, new_val) = host_spgemm(
189 &base_offsets,
190 &base_indices,
191 &base_values,
192 rows,
193 rows,
194 &base_offsets.clone(),
195 &base_indices.clone(),
196 &base_values.clone(),
197 rows,
198 rows,
199 )?;
200 mults += 1;
201
202 check_max_nnz(new_idx.len(), config.max_nnz)?;
203 nnz_growth.push(new_idx.len());
204
205 if config.reuse_structure {
206 prev_structure = Some((new_off.clone(), new_idx.clone()));
207 }
208
209 base_offsets = new_off;
210 base_indices = new_idx;
211 base_values = new_val;
212 }
213 }
214
215 let r_offsets = result_offsets
216 .ok_or_else(|| SparseError::InternalError("result not computed".to_string()))?;
217 let r_indices = result_indices
218 .ok_or_else(|| SparseError::InternalError("result not computed".to_string()))?;
219 let r_values = result_values
220 .ok_or_else(|| SparseError::InternalError("result not computed".to_string()))?;
221
222 Ok(MatrixPowerResult {
223 nnz: r_indices.len(),
224 row_offsets: r_offsets,
225 col_indices: r_indices,
226 values: r_values,
227 rows,
228 cols: rows,
229 multiplications_performed: mults,
230 nnz_growth,
231 })
232}
233
234#[allow(clippy::too_many_arguments)]
251pub fn host_spgemm(
252 a_row_offsets: &[usize],
253 a_col_indices: &[usize],
254 a_values: &[f64],
255 a_rows: usize,
256 a_cols: usize,
257 b_row_offsets: &[usize],
258 b_col_indices: &[usize],
259 b_values: &[f64],
260 b_rows: usize,
261 b_cols: usize,
262) -> Result<CsrTriple, SparseError> {
263 if a_cols != b_rows {
264 return Err(SparseError::DimensionMismatch(format!(
265 "A.cols ({}) != B.rows ({}) in SpGEMM",
266 a_cols, b_rows
267 )));
268 }
269
270 let _ = b_cols; let mut c_row_offsets = Vec::with_capacity(a_rows + 1);
273 let mut c_col_indices = Vec::new();
274 let mut c_values = Vec::new();
275
276 c_row_offsets.push(0usize);
277
278 let mut accum: HashMap<usize, f64> = HashMap::new();
279
280 for i in 0..a_rows {
281 accum.clear();
282 let a_start = a_row_offsets[i];
283 let a_end = a_row_offsets[i + 1];
284
285 for idx in a_start..a_end {
286 let j = a_col_indices[idx];
287 let a_ij = a_values[idx];
288
289 let b_start = b_row_offsets[j];
290 let b_end = b_row_offsets[j + 1];
291
292 for b_idx in b_start..b_end {
293 let k = b_col_indices[b_idx];
294 let b_jk = b_values[b_idx];
295 *accum.entry(k).or_insert(0.0) += a_ij * b_jk;
296 }
297 }
298
299 let mut entries: Vec<(usize, f64)> = accum.drain().collect();
301 entries.sort_unstable_by_key(|&(col, _)| col);
302
303 for (col, val) in entries {
304 c_col_indices.push(col);
305 c_values.push(val);
306 }
307
308 c_row_offsets.push(c_col_indices.len());
309 }
310
311 Ok((c_row_offsets, c_col_indices, c_values))
312}
313
314pub fn sparse_identity(n: usize) -> (Vec<usize>, Vec<usize>, Vec<f64>) {
318 let row_offsets: Vec<usize> = (0..=n).collect();
319 let col_indices: Vec<usize> = (0..n).collect();
320 let values = vec![1.0; n];
321 (row_offsets, col_indices, values)
322}
323
324pub fn estimate_power_nnz(
329 row_offsets: &[usize],
330 _col_indices: &[usize],
331 rows: usize,
332 power: usize,
333) -> usize {
334 if rows == 0 || power == 0 {
335 return rows; }
337
338 let nnz = if row_offsets.len() > rows {
339 row_offsets[rows]
340 } else {
341 return 0;
342 };
343
344 if nnz == 0 {
345 return 0;
346 }
347
348 let avg_degree = nnz as f64 / rows as f64;
349 let estimated_degree = avg_degree.powi(power as i32);
350 let per_row = estimated_degree.min(rows as f64);
351 let total = (rows as f64 * per_row).min((rows * rows) as f64);
352 total as usize
353}
354
355pub fn sparse_matrix_polynomial(
373 row_offsets: &[usize],
374 col_indices: &[usize],
375 values: &[f64],
376 rows: usize,
377 cols: usize,
378 coefficients: &[f64],
379) -> Result<MatrixPowerResult, SparseError> {
380 validate_csr(row_offsets, col_indices, values, rows)?;
381
382 if rows != cols {
383 return Err(SparseError::DimensionMismatch(format!(
384 "matrix must be square for polynomial evaluation, got {}x{}",
385 rows, cols
386 )));
387 }
388
389 if coefficients.is_empty() {
390 return Err(SparseError::InvalidArgument(
391 "polynomial coefficients must not be empty".to_string(),
392 ));
393 }
394
395 let n = rows;
396 let degree = coefficients.len() - 1;
397
398 if degree == 0 {
400 let (id_off, id_idx, id_val) = sparse_identity(n);
401 let (s_off, s_idx, s_val) = scalar_multiply_csr(&id_off, &id_idx, &id_val, coefficients[0]);
402 return Ok(MatrixPowerResult {
403 nnz: s_idx.len(),
404 row_offsets: s_off,
405 col_indices: s_idx,
406 values: s_val,
407 rows: n,
408 cols: n,
409 multiplications_performed: 0,
410 nnz_growth: vec![],
411 });
412 }
413
414 let mut mults = 0usize;
420 let mut nnz_growth = Vec::new();
421
422 let (id_off, id_idx, id_val) = sparse_identity(n);
424 let (mut r_off, mut r_idx, mut r_val) =
425 scalar_multiply_csr(&id_off, &id_idx, &id_val, coefficients[degree]);
426
427 for i in (0..degree).rev() {
428 let (prod_off, prod_idx, prod_val) = host_spgemm(
430 &r_off,
431 &r_idx,
432 &r_val,
433 n,
434 n,
435 row_offsets,
436 col_indices,
437 values,
438 n,
439 n,
440 )?;
441 mults += 1;
442 nnz_growth.push(prod_idx.len());
443
444 let (ci_off, ci_idx, ci_val) =
446 scalar_multiply_csr(&id_off, &id_idx, &id_val, coefficients[i]);
447 let (sum_off, sum_idx, sum_val) = add_csr(
448 &prod_off, &prod_idx, &prod_val, &ci_off, &ci_idx, &ci_val, n,
449 )?;
450
451 r_off = sum_off;
452 r_idx = sum_idx;
453 r_val = sum_val;
454 }
455
456 Ok(MatrixPowerResult {
457 nnz: r_idx.len(),
458 row_offsets: r_off,
459 col_indices: r_idx,
460 values: r_val,
461 rows: n,
462 cols: n,
463 multiplications_performed: mults,
464 nnz_growth,
465 })
466}
467
468pub fn scalar_multiply_csr(
474 row_offsets: &[usize],
475 col_indices: &[usize],
476 values: &[f64],
477 scalar: f64,
478) -> (Vec<usize>, Vec<usize>, Vec<f64>) {
479 let scaled: Vec<f64> = values.iter().map(|&v| v * scalar).collect();
480 (row_offsets.to_vec(), col_indices.to_vec(), scaled)
481}
482
483pub fn add_csr(
493 a_offsets: &[usize],
494 a_indices: &[usize],
495 a_values: &[f64],
496 b_offsets: &[usize],
497 b_indices: &[usize],
498 b_values: &[f64],
499 rows: usize,
500) -> Result<CsrTriple, SparseError> {
501 if a_offsets.len() != rows + 1 || b_offsets.len() != rows + 1 {
502 return Err(SparseError::InvalidFormat(format!(
503 "row_offsets length mismatch: expected {}, got A={} B={}",
504 rows + 1,
505 a_offsets.len(),
506 b_offsets.len()
507 )));
508 }
509
510 let mut c_offsets = Vec::with_capacity(rows + 1);
511 let mut c_indices = Vec::new();
512 let mut c_values = Vec::new();
513 c_offsets.push(0usize);
514
515 for i in 0..rows {
516 let mut ai = a_offsets[i];
517 let ae = a_offsets[i + 1];
518 let mut bi = b_offsets[i];
519 let be = b_offsets[i + 1];
520
521 while ai < ae && bi < be {
522 let ac = a_indices[ai];
523 let bc = b_indices[bi];
524 match ac.cmp(&bc) {
525 std::cmp::Ordering::Less => {
526 c_indices.push(ac);
527 c_values.push(a_values[ai]);
528 ai += 1;
529 }
530 std::cmp::Ordering::Greater => {
531 c_indices.push(bc);
532 c_values.push(b_values[bi]);
533 bi += 1;
534 }
535 std::cmp::Ordering::Equal => {
536 c_indices.push(ac);
537 c_values.push(a_values[ai] + b_values[bi]);
538 ai += 1;
539 bi += 1;
540 }
541 }
542 }
543 while ai < ae {
544 c_indices.push(a_indices[ai]);
545 c_values.push(a_values[ai]);
546 ai += 1;
547 }
548 while bi < be {
549 c_indices.push(b_indices[bi]);
550 c_values.push(b_values[bi]);
551 bi += 1;
552 }
553
554 c_offsets.push(c_indices.len());
555 }
556
557 Ok((c_offsets, c_indices, c_values))
558}
559
560fn validate_csr(
566 row_offsets: &[usize],
567 col_indices: &[usize],
568 values: &[f64],
569 rows: usize,
570) -> Result<(), SparseError> {
571 if row_offsets.len() != rows + 1 {
572 return Err(SparseError::InvalidFormat(format!(
573 "row_offsets length should be {} but is {}",
574 rows + 1,
575 row_offsets.len()
576 )));
577 }
578 if col_indices.len() != values.len() {
579 return Err(SparseError::InvalidFormat(format!(
580 "col_indices length ({}) != values length ({})",
581 col_indices.len(),
582 values.len()
583 )));
584 }
585 let nnz = row_offsets.get(rows).copied().unwrap_or(0);
586 if col_indices.len() != nnz {
587 return Err(SparseError::InvalidFormat(format!(
588 "col_indices length ({}) != nnz from row_offsets ({})",
589 col_indices.len(),
590 nnz
591 )));
592 }
593 Ok(())
594}
595
596fn check_max_nnz(nnz: usize, max: Option<usize>) -> Result<(), SparseError> {
598 if let Some(limit) = max {
599 if nnz > limit {
600 return Err(SparseError::InvalidArgument(format!(
601 "nnz ({}) exceeds max_nnz limit ({})",
602 nnz, limit
603 )));
604 }
605 }
606 Ok(())
607}
608
609#[cfg(test)]
614mod tests {
615 use super::*;
616
617 fn default_config(power: usize) -> MatrixPowerConfig {
618 MatrixPowerConfig {
619 max_nnz: None,
620 reuse_structure: false,
621 power,
622 }
623 }
624
625 #[test]
628 fn test_sparse_identity() {
629 let (off, idx, val) = sparse_identity(4);
630 assert_eq!(off, vec![0, 1, 2, 3, 4]);
631 assert_eq!(idx, vec![0, 1, 2, 3]);
632 assert_eq!(val, vec![1.0, 1.0, 1.0, 1.0]);
633 }
634
635 #[test]
636 fn test_sparse_identity_zero() {
637 let (off, idx, val) = sparse_identity(0);
638 assert_eq!(off, vec![0]);
639 assert!(idx.is_empty());
640 assert!(val.is_empty());
641 }
642
643 #[test]
646 fn test_power_zero_returns_identity() {
647 let off = vec![0, 2, 3];
649 let idx = vec![0, 1, 1];
650 let val = vec![1.0, 2.0, 3.0];
651 let config = default_config(0);
652 let res = sparse_matrix_power(&off, &idx, &val, 2, 2, 0, &config);
653 let r = res.expect("test: power 0 should succeed");
654 assert_eq!(r.rows, 2);
655 assert_eq!(r.cols, 2);
656 assert_eq!(r.nnz, 2);
657 assert_eq!(r.row_offsets, vec![0, 1, 2]);
658 assert_eq!(r.col_indices, vec![0, 1]);
659 assert_eq!(r.values, vec![1.0, 1.0]);
660 assert_eq!(r.multiplications_performed, 0);
661 }
662
663 #[test]
666 fn test_power_one_returns_copy() {
667 let off = vec![0, 2, 3];
668 let idx = vec![0, 1, 1];
669 let val = vec![1.0, 2.0, 3.0];
670 let config = default_config(1);
671 let r = sparse_matrix_power(&off, &idx, &val, 2, 2, 1, &config)
672 .expect("test: power 1 should succeed");
673 assert_eq!(r.row_offsets, off);
674 assert_eq!(r.col_indices, idx);
675 assert_eq!(r.values, val);
676 assert_eq!(r.multiplications_performed, 0);
677 }
678
679 #[test]
682 fn test_power_two_3x3() {
683 let off = vec![0, 1, 2, 3];
685 let idx = vec![0, 1, 2];
686 let val = vec![1.0, 2.0, 3.0];
687 let config = default_config(2);
688 let r = sparse_matrix_power(&off, &idx, &val, 3, 3, 2, &config)
689 .expect("test: power 2 should succeed");
690 assert_eq!(r.col_indices, vec![0, 1, 2]);
692 assert_eq!(r.values, vec![1.0, 4.0, 9.0]);
693 }
694
695 #[test]
698 fn test_binary_vs_sequential_power4() {
699 let off = vec![0, 2, 3];
702 let idx = vec![0, 1, 1];
703 let val = vec![1.0, 1.0, 1.0];
704
705 let config = default_config(4);
707 let r = sparse_matrix_power(&off, &idx, &val, 2, 2, 4, &config)
708 .expect("test: binary exp power 4");
709
710 assert_eq!(r.row_offsets, vec![0, 2, 3]);
712 assert_eq!(r.col_indices, vec![0, 1, 1]);
713 assert!((r.values[0] - 1.0).abs() < 1e-12);
714 assert!((r.values[1] - 4.0).abs() < 1e-12);
715 assert!((r.values[2] - 1.0).abs() < 1e-12);
716 }
717
718 #[test]
721 fn test_max_nnz_abort() {
722 let off = vec![0, 3, 6, 9];
724 let idx = vec![0, 1, 2, 0, 1, 2, 0, 1, 2];
725 let val = vec![1.0; 9];
726 let config = MatrixPowerConfig {
727 max_nnz: Some(5), reuse_structure: false,
729 power: 2,
730 };
731 let result = sparse_matrix_power(&off, &idx, &val, 3, 3, 2, &config);
732 assert!(result.is_err());
733 let err = result.unwrap_err();
734 let msg = err.to_string();
735 assert!(
736 msg.contains("max_nnz"),
737 "error should mention max_nnz: {}",
738 msg
739 );
740 }
741
742 #[test]
745 fn test_nnz_growth_tracking() {
746 let off = vec![0, 1, 2, 3];
748 let idx = vec![0, 1, 2];
749 let val = vec![2.0, 3.0, 4.0];
750 let config = default_config(4);
751 let r = sparse_matrix_power(&off, &idx, &val, 3, 3, 4, &config).expect("test: nnz growth");
752 assert!(!r.nnz_growth.is_empty());
754 for &g in &r.nnz_growth {
756 assert_eq!(g, 3);
757 }
758 }
759
760 #[test]
763 fn test_host_spgemm_2x2() {
764 let a_off = vec![0, 2, 4];
767 let a_idx = vec![0, 1, 0, 1];
768 let a_val = vec![1.0, 2.0, 3.0, 4.0];
769 let b_off = vec![0, 2, 4];
770 let b_idx = vec![0, 1, 0, 1];
771 let b_val = vec![5.0, 6.0, 7.0, 8.0];
772
773 let (c_off, c_idx, c_val) =
774 host_spgemm(&a_off, &a_idx, &a_val, 2, 2, &b_off, &b_idx, &b_val, 2, 2)
775 .expect("test: spgemm 2x2");
776
777 assert_eq!(c_off, vec![0, 2, 4]);
778 assert_eq!(c_idx, vec![0, 1, 0, 1]);
779 assert!((c_val[0] - 19.0).abs() < 1e-12);
780 assert!((c_val[1] - 22.0).abs() < 1e-12);
781 assert!((c_val[2] - 43.0).abs() < 1e-12);
782 assert!((c_val[3] - 50.0).abs() < 1e-12);
783 }
784
785 #[test]
788 fn test_polynomial_identity_plus_a() {
789 let off = vec![0, 1, 2];
791 let idx = vec![0, 1];
792 let val = vec![2.0, 3.0];
793 let coeffs = [1.0, 1.0]; let r = sparse_matrix_polynomial(&off, &idx, &val, 2, 2, &coeffs)
796 .expect("test: polynomial I+A");
797 assert_eq!(r.col_indices, vec![0, 1]);
798 assert!((r.values[0] - 3.0).abs() < 1e-12);
799 assert!((r.values[1] - 4.0).abs() < 1e-12);
800 }
801
802 #[test]
805 fn test_scalar_multiply() {
806 let off = vec![0, 2, 3];
807 let idx = vec![0, 1, 1];
808 let val = vec![1.0, 2.0, 3.0];
809 let (s_off, s_idx, s_val) = scalar_multiply_csr(&off, &idx, &val, 3.0);
810 assert_eq!(s_off, off);
811 assert_eq!(s_idx, idx);
812 assert_eq!(s_val, vec![3.0, 6.0, 9.0]);
813 }
814
815 #[test]
818 fn test_add_csr() {
819 let a_off = vec![0, 1, 2];
821 let a_idx = vec![0, 1];
822 let a_val = vec![1.0, 2.0];
823 let b_off = vec![0, 1, 2];
824 let b_idx = vec![1, 0];
825 let b_val = vec![3.0, 4.0];
826
827 let (c_off, c_idx, c_val) =
828 add_csr(&a_off, &a_idx, &a_val, &b_off, &b_idx, &b_val, 2).expect("test: add_csr");
829 assert_eq!(c_off, vec![0, 2, 4]);
831 assert_eq!(c_idx, vec![0, 1, 0, 1]);
832 assert!((c_val[0] - 1.0).abs() < 1e-12);
833 assert!((c_val[1] - 3.0).abs() < 1e-12);
834 assert!((c_val[2] - 4.0).abs() < 1e-12);
835 assert!((c_val[3] - 2.0).abs() < 1e-12);
836 }
837
838 #[test]
841 fn test_estimate_power_nnz() {
842 let off = vec![0, 2, 4, 6, 8];
844 let idx = vec![0, 1, 1, 2, 2, 3, 0, 3];
845 let est = estimate_power_nnz(&off, &idx, 4, 2);
846 assert_eq!(est, 16);
848 }
849
850 #[test]
851 fn test_estimate_power_nnz_zero() {
852 let off = vec![0, 0, 0];
853 let idx: Vec<usize> = vec![];
854 let est = estimate_power_nnz(&off, &idx, 2, 3);
855 assert_eq!(est, 0);
856 }
857
858 #[test]
861 fn test_diagonal_power() {
862 let off = vec![0, 1, 2, 3];
864 let idx = vec![0, 1, 2];
865 let val = vec![2.0, 3.0, 5.0];
866 let config = default_config(3);
867 let r =
868 sparse_matrix_power(&off, &idx, &val, 3, 3, 3, &config).expect("test: diagonal power");
869 assert_eq!(r.col_indices, vec![0, 1, 2]);
870 assert!((r.values[0] - 8.0).abs() < 1e-12);
871 assert!((r.values[1] - 27.0).abs() < 1e-12);
872 assert!((r.values[2] - 125.0).abs() < 1e-12);
873 }
874
875 #[test]
878 fn test_horner_vs_direct() {
879 let off = vec![0, 1, 2];
883 let idx = vec![0, 1];
884 let val = vec![2.0, 3.0];
885 let coeffs = [1.0, 2.0, 3.0];
886
887 let r = sparse_matrix_polynomial(&off, &idx, &val, 2, 2, &coeffs)
888 .expect("test: Horner polynomial");
889 assert_eq!(r.col_indices, vec![0, 1]);
890 assert!((r.values[0] - 17.0).abs() < 1e-12);
891 assert!((r.values[1] - 34.0).abs() < 1e-12);
892 }
893
894 #[test]
897 fn test_empty_matrix_power() {
898 let off = vec![0];
899 let idx: Vec<usize> = vec![];
900 let val: Vec<f64> = vec![];
901 let config = default_config(5);
902 let r =
904 sparse_matrix_power(&off, &idx, &val, 0, 0, 0, &config).expect("test: empty power 0");
905 assert_eq!(r.rows, 0);
906 assert_eq!(r.cols, 0);
907 assert_eq!(r.nnz, 0);
908 }
909
910 #[test]
913 fn test_reuse_structure_flag() {
914 let off = vec![0, 1, 2];
916 let idx = vec![0, 1];
917 let val = vec![2.0, 3.0];
918 let config = MatrixPowerConfig {
919 max_nnz: None,
920 reuse_structure: true,
921 power: 4,
922 };
923 let r =
924 sparse_matrix_power(&off, &idx, &val, 2, 2, 4, &config).expect("test: reuse structure");
925 assert!((r.values[0] - 16.0).abs() < 1e-12);
927 assert!((r.values[1] - 81.0).abs() < 1e-12);
928 }
929}