1use scirs2_core::ndarray::{Array1, Array2, ArrayView1, ArrayView2};
9use scirs2_core::Complex64;
10use serde::{Deserialize, Serialize};
11use std::collections::HashMap;
12
13use crate::error::{Result, SimulatorError};
14use crate::scirs2_integration::SciRS2Backend;
15use crate::sparse::{apply_sparse_gate, CSRMatrix, SparseMatrixBuilder};
16use crate::statevector::StateVectorSimulator;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum SparseFormat {
21 CSR,
23 CSC,
25 COO,
27 DIA,
29 BSR,
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub enum SparseSolverMethod {
36 LU,
38 QR,
39 Cholesky,
40 CG,
42 GMRES,
43 BiCGSTAB,
44 Arnoldi,
46 Lanczos,
47 LOBPCG,
48 SciRS2Auto,
50 SciRS2Iterative,
51 SciRS2Direct,
52}
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56pub enum Preconditioner {
57 None,
58 Jacobi,
59 ILU,
60 AMG,
61 SciRS2Auto,
62}
63
64#[derive(Debug, Clone)]
66pub struct SparseSolverConfig {
67 pub method: SparseSolverMethod,
69 pub preconditioner: Preconditioner,
71 pub tolerance: f64,
73 pub max_iterations: usize,
75 pub restart: usize,
77 pub parallel: bool,
79 pub memory_limit: usize,
81}
82
83impl Default for SparseSolverConfig {
84 fn default() -> Self {
85 Self {
86 method: SparseSolverMethod::SciRS2Auto,
87 preconditioner: Preconditioner::SciRS2Auto,
88 tolerance: 1e-12,
89 max_iterations: 1000,
90 restart: 30,
91 parallel: true,
92 memory_limit: 8 * 1024 * 1024 * 1024, }
94 }
95}
96
97#[derive(Debug, Clone, Default, Serialize, Deserialize)]
99pub struct SparseSolverStats {
100 pub execution_time_ms: f64,
102 pub iterations: usize,
104 pub residual_norm: f64,
106 pub converged: bool,
108 pub memory_usage_bytes: usize,
110 pub matvec_count: usize,
112 pub method_used: String,
114 pub preconditioner_time_ms: f64,
116}
117
118#[derive(Debug, Clone, Serialize, Deserialize)]
120pub struct SparseEigenResult {
121 pub eigenvalues: Vec<f64>,
123 pub eigenvectors: Array2<Complex64>,
125 pub converged_count: usize,
127 pub stats: SparseSolverStats,
129}
130
131pub struct SciRS2SparseSolver {
133 backend: Option<SciRS2Backend>,
135 config: SparseSolverConfig,
137 stats: SparseSolverStats,
139 matrix_cache: HashMap<String, SparseMatrix>,
141 preconditioner_cache: HashMap<String, Preconditioner>,
143}
144
145#[derive(Debug, Clone)]
147pub struct SparseMatrix {
148 pub shape: (usize, usize),
150 pub format: SparseFormat,
152 pub row_ptr: Vec<usize>,
154 pub col_indices: Vec<usize>,
156 pub values: Vec<Complex64>,
158 pub nnz: usize,
160 pub is_hermitian: bool,
162 pub is_positive_definite: bool,
164}
165
166impl SparseMatrix {
167 #[must_use]
169 pub const fn new(shape: (usize, usize), format: SparseFormat) -> Self {
170 Self {
171 shape,
172 format,
173 row_ptr: Vec::new(),
174 col_indices: Vec::new(),
175 values: Vec::new(),
176 nnz: 0,
177 is_hermitian: false,
178 is_positive_definite: false,
179 }
180 }
181
182 #[must_use]
184 pub fn from_csr(
185 shape: (usize, usize),
186 row_ptr: Vec<usize>,
187 col_indices: Vec<usize>,
188 values: Vec<Complex64>,
189 ) -> Self {
190 let nnz = values.len();
191 Self {
192 shape,
193 format: SparseFormat::CSR,
194 row_ptr,
195 col_indices,
196 values,
197 nnz,
198 is_hermitian: false,
199 is_positive_definite: false,
200 }
201 }
202
203 #[must_use]
205 pub fn identity(n: usize) -> Self {
206 let mut row_ptr = vec![0; n + 1];
207 let mut col_indices = Vec::with_capacity(n);
208 let mut values = Vec::with_capacity(n);
209
210 for i in 0..n {
211 row_ptr[i + 1] = i + 1;
212 col_indices.push(i);
213 values.push(Complex64::new(1.0, 0.0));
214 }
215
216 Self {
217 shape: (n, n),
218 format: SparseFormat::CSR,
219 row_ptr,
220 col_indices,
221 values,
222 nnz: n,
223 is_hermitian: true,
224 is_positive_definite: true,
225 }
226 }
227
228 pub fn matvec(&self, x: &Array1<Complex64>) -> Result<Array1<Complex64>> {
230 if x.len() != self.shape.1 {
231 return Err(SimulatorError::DimensionMismatch(format!(
232 "Vector length {} doesn't match matrix columns {}",
233 x.len(),
234 self.shape.1
235 )));
236 }
237
238 let mut y = Array1::zeros(self.shape.0);
239
240 match self.format {
241 SparseFormat::CSR => {
242 for i in 0..self.shape.0 {
243 let mut sum = Complex64::new(0.0, 0.0);
244 for j in self.row_ptr[i]..self.row_ptr[i + 1] {
245 sum += self.values[j] * x[self.col_indices[j]];
246 }
247 y[i] = sum;
248 }
249 }
250 _ => {
251 return Err(SimulatorError::UnsupportedOperation(format!(
252 "Matrix-vector multiplication not implemented for {:?}",
253 self.format
254 )));
255 }
256 }
257
258 Ok(y)
259 }
260
261 #[must_use]
263 pub fn density(&self) -> f64 {
264 self.nnz as f64 / (self.shape.0 * self.shape.1) as f64
265 }
266
267 #[must_use]
269 pub const fn is_square(&self) -> bool {
270 self.shape.0 == self.shape.1
271 }
272
273 pub fn to_dense(&self) -> Result<Array2<Complex64>> {
275 if self.shape.0 * self.shape.1 > 10_000_000 {
276 return Err(SimulatorError::InvalidInput(
277 "Matrix too large to convert to dense format".to_string(),
278 ));
279 }
280
281 let mut dense = Array2::zeros(self.shape);
282
283 match self.format {
284 SparseFormat::CSR => {
285 for i in 0..self.shape.0 {
286 for j in self.row_ptr[i]..self.row_ptr[i + 1] {
287 dense[[i, self.col_indices[j]]] = self.values[j];
288 }
289 }
290 }
291 _ => {
292 return Err(SimulatorError::UnsupportedOperation(format!(
293 "Dense conversion not implemented for {:?}",
294 self.format
295 )));
296 }
297 }
298
299 Ok(dense)
300 }
301}
302
303impl SciRS2SparseSolver {
304 pub fn new(config: SparseSolverConfig) -> Result<Self> {
306 Ok(Self {
307 backend: None,
308 config,
309 stats: SparseSolverStats::default(),
310 matrix_cache: HashMap::new(),
311 preconditioner_cache: HashMap::new(),
312 })
313 }
314
315 pub fn with_backend(mut self) -> Result<Self> {
317 self.backend = Some(SciRS2Backend::new());
318 Ok(self)
319 }
320
321 pub fn solve_linear_system(
323 &mut self,
324 matrix: &SparseMatrix,
325 rhs: &Array1<Complex64>,
326 ) -> Result<Array1<Complex64>> {
327 let start_time = std::time::Instant::now();
328
329 if !matrix.is_square() {
330 return Err(SimulatorError::InvalidInput(
331 "Matrix must be square for linear system solving".to_string(),
332 ));
333 }
334
335 if rhs.len() != matrix.shape.0 {
336 return Err(SimulatorError::DimensionMismatch(format!(
337 "RHS vector length {} doesn't match matrix size {}",
338 rhs.len(),
339 matrix.shape.0
340 )));
341 }
342
343 let solution = match self.config.method {
344 SparseSolverMethod::SciRS2Auto => self.solve_scirs2_auto(matrix, rhs)?,
345 SparseSolverMethod::SciRS2Direct => self.solve_scirs2_direct(matrix, rhs)?,
346 SparseSolverMethod::SciRS2Iterative => self.solve_scirs2_iterative(matrix, rhs)?,
347 SparseSolverMethod::CG => self.solve_conjugate_gradient(matrix, rhs)?,
348 SparseSolverMethod::GMRES => self.solve_gmres(matrix, rhs)?,
349 SparseSolverMethod::BiCGSTAB => self.solve_bicgstab(matrix, rhs)?,
350 _ => {
351 return Err(SimulatorError::UnsupportedOperation(format!(
352 "Solver method {:?} not implemented",
353 self.config.method
354 )));
355 }
356 };
357
358 self.stats.execution_time_ms = start_time.elapsed().as_secs_f64() * 1000.0;
359
360 Ok(solution)
361 }
362
363 pub fn solve_eigenvalue_problem(
365 &mut self,
366 matrix: &SparseMatrix,
367 num_eigenvalues: usize,
368 which: &str,
369 ) -> Result<SparseEigenResult> {
370 let start_time = std::time::Instant::now();
371
372 if !matrix.is_square() {
373 return Err(SimulatorError::InvalidInput(
374 "Matrix must be square for eigenvalue problems".to_string(),
375 ));
376 }
377
378 if num_eigenvalues >= matrix.shape.0 {
379 return Err(SimulatorError::InvalidInput(
380 "Number of eigenvalues must be less than matrix size".to_string(),
381 ));
382 }
383
384 let (eigenvalues, eigenvectors, converged_count) = match self.config.method {
385 SparseSolverMethod::Arnoldi => self.solve_arnoldi(matrix, num_eigenvalues, which)?,
386 SparseSolverMethod::Lanczos => self.solve_lanczos(matrix, num_eigenvalues, which)?,
387 SparseSolverMethod::LOBPCG => self.solve_lobpcg(matrix, num_eigenvalues, which)?,
388 SparseSolverMethod::SciRS2Auto => {
389 self.solve_eigen_scirs2_auto(matrix, num_eigenvalues, which)?
390 }
391 _ => {
392 return Err(SimulatorError::UnsupportedOperation(format!(
393 "Eigenvalue solver {:?} not implemented",
394 self.config.method
395 )));
396 }
397 };
398
399 self.stats.execution_time_ms = start_time.elapsed().as_secs_f64() * 1000.0;
400 self.stats.converged = converged_count == num_eigenvalues;
401
402 Ok(SparseEigenResult {
403 eigenvalues,
404 eigenvectors,
405 converged_count,
406 stats: self.stats.clone(),
407 })
408 }
409
410 fn solve_scirs2_auto(
412 &mut self,
413 matrix: &SparseMatrix,
414 rhs: &Array1<Complex64>,
415 ) -> Result<Array1<Complex64>> {
416 if let Some(_backend) = &mut self.backend {
417 let density = matrix.density();
419
420 if density > 0.1 {
421 self.solve_scirs2_direct(matrix, rhs)
423 } else if matrix.is_hermitian && matrix.is_positive_definite {
424 self.solve_conjugate_gradient(matrix, rhs)
426 } else {
427 self.solve_gmres(matrix, rhs)
429 }
430 } else {
431 self.solve_gmres(matrix, rhs)
433 }
434 }
435
436 fn solve_scirs2_direct(
438 &mut self,
439 matrix: &SparseMatrix,
440 rhs: &Array1<Complex64>,
441 ) -> Result<Array1<Complex64>> {
442 if let Some(_backend) = &mut self.backend {
443 self.simulate_sparse_lu(matrix, rhs)
445 } else {
446 if matrix.shape.0 <= 1000 {
448 let dense_matrix = matrix.to_dense()?;
449 self.solve_dense_lu(&dense_matrix, rhs)
450 } else {
451 Err(SimulatorError::UnsupportedOperation(
452 "Matrix too large for direct solving without SciRS2 backend".to_string(),
453 ))
454 }
455 }
456 }
457
458 fn solve_scirs2_iterative(
460 &mut self,
461 matrix: &SparseMatrix,
462 rhs: &Array1<Complex64>,
463 ) -> Result<Array1<Complex64>> {
464 if let Some(_backend) = &mut self.backend {
465 if matrix.is_hermitian {
467 self.solve_conjugate_gradient(matrix, rhs)
468 } else {
469 self.solve_gmres(matrix, rhs)
470 }
471 } else {
472 self.solve_gmres(matrix, rhs)
474 }
475 }
476
477 fn solve_conjugate_gradient(
479 &mut self,
480 matrix: &SparseMatrix,
481 rhs: &Array1<Complex64>,
482 ) -> Result<Array1<Complex64>> {
483 let n = matrix.shape.0;
484 let mut x = Array1::zeros(n);
485 let mut r = rhs.clone();
486 let mut p = r.clone();
487 let mut rsold = r.iter().map(|&ri| ri.norm_sqr()).sum::<f64>();
488
489 self.stats.method_used = "ConjugateGradient".to_string();
490
491 for iteration in 0..self.config.max_iterations {
492 let ap = matrix.matvec(&p)?;
493 let alpha = rsold
494 / p.iter()
495 .zip(ap.iter())
496 .map(|(&pi, &api)| (pi.conj() * api).re)
497 .sum::<f64>();
498
499 for i in 0..n {
500 x[i] += alpha * p[i];
501 r[i] -= alpha * ap[i];
502 }
503
504 let rsnew = r.iter().map(|&ri| ri.norm_sqr()).sum::<f64>();
505
506 self.stats.iterations = iteration + 1;
507 self.stats.residual_norm = rsnew.sqrt();
508 self.stats.matvec_count += 1;
509
510 if rsnew.sqrt() < self.config.tolerance {
511 self.stats.converged = true;
512 break;
513 }
514
515 let beta = rsnew / rsold;
516 for i in 0..n {
517 p[i] = r[i] + beta * p[i];
518 }
519
520 rsold = rsnew;
521 }
522
523 Ok(x)
524 }
525
526 fn solve_gmres(
528 &mut self,
529 matrix: &SparseMatrix,
530 rhs: &Array1<Complex64>,
531 ) -> Result<Array1<Complex64>> {
532 let n = matrix.shape.0;
533 let m = self.config.restart.min(n);
534 let mut x = Array1::zeros(n);
535
536 self.stats.method_used = "GMRES".to_string();
537
538 let mut r = rhs.clone();
540 let beta = r.iter().map(|&ri| ri.norm_sqr()).sum::<f64>().sqrt();
541
542 if beta < self.config.tolerance {
543 self.stats.converged = true;
544 return Ok(x);
545 }
546
547 for _restart in 0..(self.config.max_iterations / m) {
548 let mut v = Array2::zeros((n, m + 1));
550 let mut h = Array2::zeros((m + 1, m));
551
552 for i in 0..n {
554 v[[i, 0]] = r[i] / beta;
555 }
556
557 for j in 0..m {
558 let vj = v.column(j).to_owned();
559 let w = matrix.matvec(&vj)?;
560 self.stats.matvec_count += 1;
561
562 for i in 0..=j {
564 let vi = v.column(i);
565 h[[i, j]] = vi
566 .iter()
567 .zip(w.iter())
568 .map(|(&vi_val, &w_val)| vi_val.conj() * w_val)
569 .sum();
570 }
571
572 let mut w_next = w.clone();
573 for i in 0..=j {
574 let vi = v.column(i);
575 for k in 0..n {
576 w_next[k] -= h[[i, j]] * vi[k];
577 }
578 }
579
580 let h_norm = w_next.iter().map(|&wi| wi.norm_sqr()).sum::<f64>().sqrt();
581 h[[j + 1, j]] = Complex64::new(h_norm, 0.0);
582
583 if h_norm > 1e-12 && j + 1 < m {
584 for i in 0..n {
585 v[[i, j + 1]] = w_next[i] / h_norm;
586 }
587 }
588
589 self.stats.iterations += 1;
590 self.stats.residual_norm = h_norm;
591
592 if h_norm < self.config.tolerance {
593 self.stats.converged = true;
594 return Ok(x);
595 }
596 }
597 }
598
599 Ok(x)
600 }
601
602 fn solve_bicgstab(
604 &mut self,
605 matrix: &SparseMatrix,
606 rhs: &Array1<Complex64>,
607 ) -> Result<Array1<Complex64>> {
608 let n = matrix.shape.0;
609 let mut x = Array1::zeros(n);
610 let mut r = rhs.clone();
611 let r0 = r.clone();
612
613 self.stats.method_used = "BiCGSTAB".to_string();
614
615 let mut p = r.clone();
616 let mut alpha = Complex64::new(1.0, 0.0);
617 let mut omega = Complex64::new(1.0, 0.0);
618 let mut rho_old = Complex64::new(1.0, 0.0);
619
620 for iteration in 0..self.config.max_iterations {
621 let rho: Complex64 = r0
622 .iter()
623 .zip(r.iter())
624 .map(|(&r0i, &ri)| r0i.conj() * ri)
625 .sum();
626
627 if rho.norm() < 1e-15 {
628 break;
629 }
630
631 let beta = (rho / rho_old) * (alpha / omega);
632
633 for i in 0..n {
634 p[i] = r[i] + beta * (p[i] - omega * matrix.matvec(&p)?[i]);
635 }
636
637 let ap = matrix.matvec(&p)?;
638 alpha = rho
639 / r0.iter()
640 .zip(ap.iter())
641 .map(|(&r0i, &api)| r0i.conj() * api)
642 .sum::<Complex64>();
643
644 let mut s = r.clone();
645 for i in 0..n {
646 s[i] -= alpha * ap[i];
647 }
648
649 let residual_s = s.iter().map(|&si| si.norm_sqr()).sum::<f64>().sqrt();
650 if residual_s < self.config.tolerance {
651 for i in 0..n {
652 x[i] += alpha * p[i];
653 }
654 self.stats.converged = true;
655 break;
656 }
657
658 let as_vec = matrix.matvec(&s)?;
659 omega = as_vec
660 .iter()
661 .zip(s.iter())
662 .map(|(&asi, &si)| asi.conj() * si)
663 .sum::<Complex64>()
664 / as_vec.iter().map(|&asi| asi.norm_sqr()).sum::<f64>();
665
666 for i in 0..n {
667 x[i] += alpha * p[i] + omega * s[i];
668 r[i] = s[i] - omega * as_vec[i];
669 }
670
671 self.stats.iterations = iteration + 1;
672 self.stats.residual_norm = r.iter().map(|&ri| ri.norm_sqr()).sum::<f64>().sqrt();
673 self.stats.matvec_count += 2;
674
675 if self.stats.residual_norm < self.config.tolerance {
676 self.stats.converged = true;
677 break;
678 }
679
680 rho_old = rho;
681 }
682
683 Ok(x)
684 }
685
686 fn simulate_sparse_lu(
688 &mut self,
689 matrix: &SparseMatrix,
690 rhs: &Array1<Complex64>,
691 ) -> Result<Array1<Complex64>> {
692 self.stats.method_used = "SparseLU".to_string();
696
697 if matrix.shape.0 > 5000 {
699 return self.solve_gmres(matrix, rhs);
700 }
701
702 let dense_matrix = matrix.to_dense()?;
704 self.solve_dense_lu(&dense_matrix, rhs)
705 }
706
707 fn solve_dense_lu(
709 &self,
710 matrix: &Array2<Complex64>,
711 rhs: &Array1<Complex64>,
712 ) -> Result<Array1<Complex64>> {
713 let n = matrix.nrows();
717 if n != matrix.ncols() {
718 return Err(SimulatorError::InvalidInput(
719 "Matrix must be square".to_string(),
720 ));
721 }
722
723 let mut a = matrix.clone();
725 let mut b = rhs.clone();
726
727 for k in 0..n - 1 {
729 for i in k + 1..n {
730 if a[[k, k]].norm() < 1e-15 {
731 return Err(SimulatorError::NumericalError(
732 "Singular matrix".to_string(),
733 ));
734 }
735
736 let factor = a[[i, k]] / a[[k, k]];
737 let b_k = b[k]; for j in k..n {
739 let a_kj = a[[k, j]]; a[[i, j]] -= factor * a_kj;
741 }
742 b[i] -= factor * b_k;
743 }
744 }
745
746 let mut x = Array1::zeros(n);
748 for i in (0..n).rev() {
749 let mut sum = Complex64::new(0.0, 0.0);
750 for j in i + 1..n {
751 sum += a[[i, j]] * x[j];
752 }
753 x[i] = (b[i] - sum) / a[[i, i]];
754 }
755
756 Ok(x)
757 }
758
759 fn diagonalize_projection(
770 matrix: &Array2<Complex64>,
771 hermitian: bool,
772 ) -> Result<(Vec<f64>, Array2<Complex64>)> {
773 let n = matrix.nrows();
774 if hermitian {
775 let mut sym = Array2::<f64>::zeros((n, n));
779 for i in 0..n {
780 for j in 0..n {
781 sym[[i, j]] = 0.5 * (matrix[[i, j]].re + matrix[[j, i]].re);
782 }
783 }
784 let (values, real_vectors) = jacobi_symmetric_eig(&sym);
785 let vectors = real_vectors.mapv(|x| Complex64::new(x, 0.0));
786 Ok((values, vectors))
787 } else {
788 let (complex_values, vectors) = general_complex_eig(matrix)?;
789 let values: Vec<f64> = complex_values.iter().map(|v| v.re).collect();
790 Ok((values, vectors))
791 }
792 }
793
794 fn select_ritz_pairs(
800 krylov_basis: &Array2<Complex64>,
801 ritz_values: &[f64],
802 ritz_vectors: &Array2<Complex64>,
803 num_eigenvalues: usize,
804 which: &str,
805 ) -> (Vec<f64>, Array2<Complex64>) {
806 let m = ritz_values.len();
807 let n = krylov_basis.nrows();
808
809 let v_m = krylov_basis.slice(scirs2_core::ndarray::s![.., ..m]);
811 let full_vectors = v_m.dot(ritz_vectors);
812
813 let mut order: Vec<usize> = (0..m).collect();
816 order.sort_by(|&a, &b| {
817 ritz_values[a]
818 .partial_cmp(&ritz_values[b])
819 .unwrap_or(std::cmp::Ordering::Equal)
820 });
821
822 let count = num_eigenvalues.min(m);
823 let want_largest = matches!(which, "largest" | "LM" | "LA" | "LR");
824 let selected: Vec<usize> = if want_largest {
825 order[m - count..].to_vec()
826 } else {
827 order[..count].to_vec()
828 };
829
830 let mut selected_values = Vec::with_capacity(count);
831 let mut selected_vectors = Array2::<Complex64>::zeros((n, count));
832 for (out_col, &idx) in selected.iter().enumerate() {
833 selected_values.push(ritz_values[idx]);
834 let column = full_vectors.column(idx);
835 let norm = column.iter().map(|c| c.norm_sqr()).sum::<f64>().sqrt();
836 let scale = if norm > 1e-15 { 1.0 / norm } else { 1.0 };
837 for row in 0..n {
838 selected_vectors[[row, out_col]] = column[row] * scale;
839 }
840 }
841 (selected_values, selected_vectors)
842 }
843
844 fn solve_arnoldi(
853 &mut self,
854 matrix: &SparseMatrix,
855 num_eigenvalues: usize,
856 which: &str,
857 ) -> Result<(Vec<f64>, Array2<Complex64>, usize)> {
858 let n = matrix.shape.0;
859 let m = (num_eigenvalues * 2).min(n);
860
861 let mut v = Array2::zeros((n, m + 1));
862 let mut h = Array2::zeros((m + 1, m));
863
864 for i in 0..n {
866 v[[i, 0]] = Complex64::new(fastrand::f64() - 0.5, fastrand::f64() - 0.5);
867 }
868
869 let norm = v
871 .column(0)
872 .iter()
873 .map(|&vi| vi.norm_sqr())
874 .sum::<f64>()
875 .sqrt();
876 for i in 0..n {
877 v[[i, 0]] /= norm;
878 }
879
880 for j in 0..m {
882 let vj = v.column(j).to_owned();
883 let w = matrix.matvec(&vj)?;
884
885 for i in 0..=j {
887 let vi = v.column(i);
888 h[[i, j]] = vi
889 .iter()
890 .zip(w.iter())
891 .map(|(&vi_val, &w_val)| vi_val.conj() * w_val)
892 .sum();
893 }
894
895 let mut w_next = w.clone();
896 for i in 0..=j {
897 let vi = v.column(i);
898 for k in 0..n {
899 w_next[k] -= h[[i, j]] * vi[k];
900 }
901 }
902
903 let h_norm = w_next.iter().map(|&wi| wi.norm_sqr()).sum::<f64>().sqrt();
904 h[[j + 1, j]] = Complex64::new(h_norm, 0.0);
905
906 if h_norm > 1e-12 && j + 1 < m {
907 for i in 0..n {
908 v[[i, j + 1]] = w_next[i] / h_norm;
909 }
910 }
911 }
912
913 let mut h_block = Array2::<Complex64>::zeros((m, m));
918 for i in 0..m {
919 for j in 0..m {
920 h_block[[i, j]] = h[[i, j]];
921 }
922 }
923
924 let (ritz_values, ritz_vectors) = Self::diagonalize_projection(&h_block, false)?;
925 let (eigenvalues, eigenvectors) =
926 Self::select_ritz_pairs(&v, &ritz_values, &ritz_vectors, num_eigenvalues, which);
927 let converged = eigenvalues.len();
928
929 self.stats.method_used = "Arnoldi".to_string();
930
931 Ok((eigenvalues, eigenvectors, converged))
932 }
933
934 fn solve_lanczos(
942 &mut self,
943 matrix: &SparseMatrix,
944 num_eigenvalues: usize,
945 which: &str,
946 ) -> Result<(Vec<f64>, Array2<Complex64>, usize)> {
947 if !matrix.is_hermitian {
948 return self.solve_arnoldi(matrix, num_eigenvalues, which);
949 }
950
951 let n = matrix.shape.0;
952 let m = (num_eigenvalues * 2).min(n);
953
954 let mut v = Array2::zeros((n, m + 1));
955 let mut alpha = vec![0.0; m];
956 let mut beta = vec![0.0; m];
957
958 for i in 0..n {
960 v[[i, 0]] = Complex64::new(fastrand::f64() - 0.5, 0.0);
961 }
962
963 let norm = v
965 .column(0)
966 .iter()
967 .map(|&vi| vi.norm_sqr())
968 .sum::<f64>()
969 .sqrt();
970 for i in 0..n {
971 v[[i, 0]] /= norm;
972 }
973
974 for j in 0..m {
976 let vj = v.column(j).to_owned();
977 let w = matrix.matvec(&vj)?;
978
979 alpha[j] = vj
980 .iter()
981 .zip(w.iter())
982 .map(|(&vji, &wi)| (vji.conj() * wi).re)
983 .sum();
984
985 let mut w_next = w.clone();
986 for i in 0..n {
987 w_next[i] -= alpha[j] * vj[i];
988 if j > 0 {
989 w_next[i] -= beta[j - 1] * v[[i, j - 1]];
990 }
991 }
992
993 beta[j] = w_next.iter().map(|&wi| wi.norm_sqr()).sum::<f64>().sqrt();
994
995 if beta[j] > 1e-12 && j + 1 < m {
996 for i in 0..n {
997 v[[i, j + 1]] = w_next[i] / beta[j];
998 }
999 }
1000 }
1001
1002 let mut tri = Array2::<Complex64>::zeros((m, m));
1008 for i in 0..m {
1009 tri[[i, i]] = Complex64::new(alpha[i], 0.0);
1010 if i + 1 < m {
1011 let off = Complex64::new(beta[i], 0.0);
1012 tri[[i, i + 1]] = off;
1013 tri[[i + 1, i]] = off;
1014 }
1015 }
1016
1017 let (ritz_values, ritz_vectors) = Self::diagonalize_projection(&tri, true)?;
1018 let (eigenvalues, eigenvectors) =
1019 Self::select_ritz_pairs(&v, &ritz_values, &ritz_vectors, num_eigenvalues, which);
1020 let converged = eigenvalues.len();
1021
1022 self.stats.method_used = "Lanczos".to_string();
1023
1024 Ok((eigenvalues, eigenvectors, converged))
1025 }
1026
1027 fn solve_lobpcg(
1035 &mut self,
1036 matrix: &SparseMatrix,
1037 num_eigenvalues: usize,
1038 which: &str,
1039 ) -> Result<(Vec<f64>, Array2<Complex64>, usize)> {
1040 self.solve_lanczos(matrix, num_eigenvalues, which)
1041 }
1042
1043 fn solve_eigen_scirs2_auto(
1045 &mut self,
1046 matrix: &SparseMatrix,
1047 num_eigenvalues: usize,
1048 which: &str,
1049 ) -> Result<(Vec<f64>, Array2<Complex64>, usize)> {
1050 if let Some(_backend) = &mut self.backend {
1051 if matrix.is_hermitian {
1053 self.solve_lanczos(matrix, num_eigenvalues, which)
1054 } else {
1055 self.solve_arnoldi(matrix, num_eigenvalues, which)
1056 }
1057 } else {
1058 if matrix.is_hermitian {
1060 self.solve_lanczos(matrix, num_eigenvalues, which)
1061 } else {
1062 self.solve_arnoldi(matrix, num_eigenvalues, which)
1063 }
1064 }
1065 }
1066
1067 #[must_use]
1069 pub const fn get_stats(&self) -> &SparseSolverStats {
1070 &self.stats
1071 }
1072
1073 pub fn reset_stats(&mut self) {
1075 self.stats = SparseSolverStats::default();
1076 }
1077
1078 pub const fn set_config(&mut self, config: SparseSolverConfig) {
1080 self.config = config;
1081 }
1082}
1083
1084pub(crate) fn jacobi_symmetric_eig(input: &Array2<f64>) -> (Vec<f64>, Array2<f64>) {
1093 let n = input.nrows();
1094 let mut a = input.clone();
1095 let mut v = Array2::<f64>::eye(n);
1096 if n <= 1 {
1097 let values: Vec<f64> = (0..n).map(|i| a[[i, i]]).collect();
1098 return (values, v);
1099 }
1100
1101 for _ in 0..100 {
1102 let mut off = 0.0;
1104 for p in 0..n {
1105 for q in (p + 1)..n {
1106 off += a[[p, q]] * a[[p, q]];
1107 }
1108 }
1109 if off <= 1e-30 {
1110 break;
1111 }
1112
1113 for p in 0..n {
1114 for q in (p + 1)..n {
1115 let apq = a[[p, q]];
1116 if apq.abs() < 1e-300 {
1117 continue;
1118 }
1119 let theta = (a[[q, q]] - a[[p, p]]) / (2.0 * apq);
1120 let t = theta.signum() / (theta.abs() + (theta * theta + 1.0).sqrt());
1123 let cc = 1.0 / (t * t + 1.0).sqrt();
1124 let ss = t * cc;
1125
1126 for k in 0..n {
1128 let akp = a[[k, p]];
1129 let akq = a[[k, q]];
1130 a[[k, p]] = cc * akp - ss * akq;
1131 a[[k, q]] = ss * akp + cc * akq;
1132 }
1133 for k in 0..n {
1134 let apk = a[[p, k]];
1135 let aqk = a[[q, k]];
1136 a[[p, k]] = cc * apk - ss * aqk;
1137 a[[q, k]] = ss * apk + cc * aqk;
1138 }
1139 for k in 0..n {
1141 let vkp = v[[k, p]];
1142 let vkq = v[[k, q]];
1143 v[[k, p]] = cc * vkp - ss * vkq;
1144 v[[k, q]] = ss * vkp + cc * vkq;
1145 }
1146 }
1147 }
1148 }
1149
1150 let eigenvalues: Vec<f64> = (0..n).map(|i| a[[i, i]]).collect();
1151 (eigenvalues, v)
1152}
1153
1154fn qr_decompose_complex(a: &Array2<Complex64>) -> (Array2<Complex64>, Array2<Complex64>) {
1157 let n = a.nrows();
1158 let m = a.ncols();
1159 let mut q = Array2::<Complex64>::zeros((n, m));
1160 let mut r = Array2::<Complex64>::zeros((m, m));
1161
1162 for j in 0..m {
1163 let mut vj: Array1<Complex64> = a.column(j).to_owned();
1164 for i in 0..j {
1165 let rij: Complex64 = (0..n).map(|k| q[[k, i]].conj() * vj[k]).sum();
1166 r[[i, j]] = rij;
1167 for k in 0..n {
1168 vj[k] -= rij * q[[k, i]];
1169 }
1170 }
1171 let norm = vj.iter().map(|c| c.norm_sqr()).sum::<f64>().sqrt();
1172 r[[j, j]] = Complex64::new(norm, 0.0);
1173 if norm > 1e-300 {
1174 for k in 0..n {
1175 q[[k, j]] = vj[k] / norm;
1176 }
1177 }
1178 }
1179
1180 (q, r)
1181}
1182
1183fn solve_linear_dense(
1186 matrix: &Array2<Complex64>,
1187 rhs: &Array1<Complex64>,
1188) -> Result<Array1<Complex64>> {
1189 let n = matrix.nrows();
1190 let mut a = matrix.clone();
1191 let mut b = rhs.clone();
1192
1193 for k in 0..n {
1194 let mut pivot = k;
1196 let mut pivot_mag = a[[k, k]].norm();
1197 for i in (k + 1)..n {
1198 let mag = a[[i, k]].norm();
1199 if mag > pivot_mag {
1200 pivot_mag = mag;
1201 pivot = i;
1202 }
1203 }
1204 if pivot_mag < 1e-300 {
1205 return Err(SimulatorError::NumericalError(
1206 "singular matrix in dense solve".to_string(),
1207 ));
1208 }
1209 if pivot != k {
1210 for j in 0..n {
1211 let tmp = a[[k, j]];
1212 a[[k, j]] = a[[pivot, j]];
1213 a[[pivot, j]] = tmp;
1214 }
1215 b.swap(k, pivot);
1216 }
1217 for i in (k + 1)..n {
1218 let factor = a[[i, k]] / a[[k, k]];
1219 for j in k..n {
1220 let akj = a[[k, j]];
1221 a[[i, j]] -= factor * akj;
1222 }
1223 let bk = b[k];
1224 b[i] -= factor * bk;
1225 }
1226 }
1227
1228 let mut x = Array1::zeros(n);
1229 for i in (0..n).rev() {
1230 let mut sum = Complex64::new(0.0, 0.0);
1231 for j in (i + 1)..n {
1232 sum += a[[i, j]] * x[j];
1233 }
1234 x[i] = (b[i] - sum) / a[[i, i]];
1235 }
1236 Ok(x)
1237}
1238
1239fn inverse_iteration_complex(
1242 a: &Array2<Complex64>,
1243 lambda: Complex64,
1244) -> Result<Array1<Complex64>> {
1245 let n = a.nrows();
1246 let mut shifted = a.clone();
1247 let perturb = Complex64::new(1e-8, 0.0);
1248 for i in 0..n {
1249 shifted[[i, i]] -= lambda + perturb;
1250 }
1251
1252 let mut x = Array1::from_elem(n, Complex64::new(1.0, 0.0));
1253 let mut norm = x.iter().map(|c| c.norm_sqr()).sum::<f64>().sqrt();
1254 if norm > 0.0 {
1255 x.mapv_inplace(|c| c / norm);
1256 }
1257
1258 for _ in 0..8 {
1259 let y = match solve_linear_dense(&shifted, &x) {
1260 Ok(y) => y,
1261 Err(_) => break,
1262 };
1263 norm = y.iter().map(|c| c.norm_sqr()).sum::<f64>().sqrt();
1264 if norm < 1e-300 {
1265 break;
1266 }
1267 for i in 0..n {
1268 x[i] = y[i] / norm;
1269 }
1270 }
1271
1272 Ok(x)
1273}
1274
1275fn general_complex_eig(input: &Array2<Complex64>) -> Result<(Vec<Complex64>, Array2<Complex64>)> {
1283 let n = input.nrows();
1284 if n == 0 {
1285 return Ok((Vec::new(), Array2::zeros((0, 0))));
1286 }
1287 if n == 1 {
1288 let mut vectors = Array2::<Complex64>::zeros((1, 1));
1289 vectors[[0, 0]] = Complex64::new(1.0, 0.0);
1290 return Ok((vec![input[[0, 0]]], vectors));
1291 }
1292
1293 let mut a = input.clone();
1294 let mut eigenvalues: Vec<Complex64> = Vec::with_capacity(n);
1295 let mut p = n;
1296 let max_total_iter = 300 * n;
1297 let mut total_iter = 0;
1298 let two = Complex64::new(2.0, 0.0);
1299 let four = Complex64::new(4.0, 0.0);
1300
1301 while p > 1 {
1302 let sub = a[[p - 1, p - 2]].norm();
1304 let scale = (a[[p - 2, p - 2]].norm() + a[[p - 1, p - 1]].norm()).max(1e-300);
1305 if sub <= 1e-14 * scale {
1306 eigenvalues.push(a[[p - 1, p - 1]]);
1307 p -= 1;
1308 continue;
1309 }
1310 if total_iter >= max_total_iter {
1311 break;
1312 }
1313 total_iter += 1;
1314
1315 let a11 = a[[p - 2, p - 2]];
1317 let a12 = a[[p - 2, p - 1]];
1318 let a21 = a[[p - 1, p - 2]];
1319 let a22 = a[[p - 1, p - 1]];
1320 let trace = a11 + a22;
1321 let det = a11 * a22 - a12 * a21;
1322 let disc = (trace * trace - four * det).sqrt();
1323 let mu1 = (trace + disc) / two;
1324 let mu2 = (trace - disc) / two;
1325 let shift = if (mu1 - a22).norm() <= (mu2 - a22).norm() {
1326 mu1
1327 } else {
1328 mu2
1329 };
1330
1331 let mut block = Array2::<Complex64>::zeros((p, p));
1333 for i in 0..p {
1334 for j in 0..p {
1335 block[[i, j]] = a[[i, j]];
1336 }
1337 }
1338 for i in 0..p {
1339 block[[i, i]] -= shift;
1340 }
1341 let (q, r) = qr_decompose_complex(&block);
1342 let rq = r.dot(&q);
1343 for i in 0..p {
1344 for j in 0..p {
1345 a[[i, j]] = rq[[i, j]];
1346 }
1347 }
1348 for i in 0..p {
1349 a[[i, i]] += shift;
1350 }
1351 }
1352
1353 for i in 0..p {
1355 eigenvalues.push(a[[i, i]]);
1356 }
1357
1358 let mut vectors = Array2::<Complex64>::zeros((n, n));
1360 for (col, &lambda) in eigenvalues.iter().enumerate() {
1361 let vec = inverse_iteration_complex(input, lambda)?;
1362 for i in 0..n {
1363 vectors[[i, col]] = vec[i];
1364 }
1365 }
1366
1367 Ok((eigenvalues, vectors))
1368}
1369
1370pub struct SparseMatrixUtils;
1372
1373impl SparseMatrixUtils {
1374 pub fn hamiltonian_from_pauli_strings(
1376 num_qubits: usize,
1377 pauli_strings: &[(String, f64)],
1378 ) -> Result<SparseMatrix> {
1379 let dim = 1 << num_qubits;
1380 let mut builder = SparseMatrixBuilder::new(dim, dim);
1381
1382 for (pauli_str, coeff) in pauli_strings {
1383 if pauli_str.len() != num_qubits {
1384 return Err(SimulatorError::InvalidInput(format!(
1385 "Pauli string length {} doesn't match num_qubits {}",
1386 pauli_str.len(),
1387 num_qubits
1388 )));
1389 }
1390
1391 for i in 0..dim {
1393 let mut amplitude = Complex64::new(*coeff, 0.0);
1394 let mut target_state = i;
1395
1396 for (qubit, pauli_char) in pauli_str.chars().enumerate() {
1397 let bit_pos = num_qubits - 1 - qubit;
1398 let bit_val = (i >> bit_pos) & 1;
1399
1400 match pauli_char {
1401 'I' => {} 'X' => {
1403 target_state ^= 1 << bit_pos; }
1405 'Y' => {
1406 target_state ^= 1 << bit_pos; if bit_val == 0 {
1408 amplitude *= Complex64::new(0.0, 1.0); } else {
1410 amplitude *= Complex64::new(0.0, -1.0); }
1412 }
1413 'Z' => {
1414 if bit_val == 1 {
1415 amplitude *= Complex64::new(-1.0, 0.0); }
1417 }
1418 _ => {
1419 return Err(SimulatorError::InvalidInput(format!(
1420 "Invalid Pauli character: {pauli_char}"
1421 )));
1422 }
1423 }
1424 }
1425
1426 if amplitude.norm() > 1e-15 {
1427 builder.add(i, target_state, amplitude);
1428 }
1429 }
1430 }
1431
1432 let csr_matrix = builder.build();
1433 let nnz = csr_matrix.values.len();
1434 let mut matrix = SparseMatrix {
1435 shape: (dim, dim),
1436 format: SparseFormat::CSR,
1437 row_ptr: csr_matrix.row_ptr,
1438 col_indices: csr_matrix.col_indices,
1439 values: csr_matrix.values,
1440 nnz,
1441 is_hermitian: true,
1442 is_positive_definite: false,
1443 };
1444 matrix.is_hermitian = true;
1445
1446 Ok(matrix)
1447 }
1448
1449 #[must_use]
1451 pub fn from_dense(dense: &Array2<Complex64>, threshold: f64) -> SparseMatrix {
1452 let (rows, cols) = dense.dim();
1453 let mut row_ptr = vec![0; rows + 1];
1454 let mut col_indices = Vec::new();
1455 let mut values = Vec::new();
1456
1457 let mut nnz = 0;
1458 for i in 0..rows {
1459 row_ptr[i] = nnz;
1460 for j in 0..cols {
1461 if dense[[i, j]].norm() > threshold {
1462 col_indices.push(j);
1463 values.push(dense[[i, j]]);
1464 nnz += 1;
1465 }
1466 }
1467 }
1468 row_ptr[rows] = nnz;
1469
1470 SparseMatrix::from_csr((rows, cols), row_ptr, col_indices, values)
1471 }
1472
1473 #[must_use]
1475 pub fn random_sparse(n: usize, density: f64, hermitian: bool) -> SparseMatrix {
1476 let total_elements = n * n;
1477 let nnz_target = (total_elements as f64 * density) as usize;
1478
1479 let mut row_ptr = vec![0; n + 1];
1480 let mut col_indices = Vec::new();
1481 let mut values = Vec::new();
1482
1483 let mut added_elements = std::collections::HashSet::new();
1484 let mut nnz = 0;
1485
1486 for _ in 0..nnz_target {
1487 let i = fastrand::usize(0..n);
1488 let j = if hermitian && fastrand::f64() < 0.5 && i < n - 1 {
1489 fastrand::usize(i..n) } else {
1491 fastrand::usize(0..n)
1492 };
1493
1494 if added_elements.insert((i, j)) {
1495 let real = fastrand::f64() - 0.5;
1496 let imag = if hermitian && i == j {
1497 0.0
1498 } else {
1499 fastrand::f64() - 0.5
1500 };
1501 let value = Complex64::new(real, imag);
1502
1503 let pos = col_indices
1505 .iter()
1506 .position(|&col| col > j)
1507 .unwrap_or(col_indices.len());
1508 col_indices.insert(pos, j);
1509 values.insert(pos, value);
1510 nnz += 1;
1511
1512 for row in (i + 1)..=n {
1514 row_ptr[row] += 1;
1515 }
1516
1517 if hermitian && i != j {
1519 added_elements.insert((j, i));
1520
1521 let sym_value = value.conj();
1522 let sym_pos = col_indices
1523 .iter()
1524 .position(|&col| col > i)
1525 .unwrap_or(col_indices.len());
1526 col_indices.insert(sym_pos, i);
1527 values.insert(sym_pos, sym_value);
1528 nnz += 1;
1529
1530 for row in (j + 1)..=n {
1531 row_ptr[row] += 1;
1532 }
1533 }
1534 }
1535 }
1536
1537 let mut matrix = SparseMatrix::from_csr((n, n), row_ptr, col_indices, values);
1538 matrix.is_hermitian = hermitian;
1539 matrix
1540 }
1541}
1542
1543pub fn benchmark_sparse_solvers(
1545 matrix_size: usize,
1546 density: f64,
1547) -> Result<HashMap<String, SparseSolverStats>> {
1548 let mut results = HashMap::new();
1549
1550 let matrix = SparseMatrixUtils::random_sparse(matrix_size, density, true);
1552 let mut rhs = Array1::zeros(matrix_size);
1553 for i in 0..matrix_size {
1554 rhs[i] = Complex64::new(fastrand::f64(), 0.0);
1555 }
1556
1557 let methods = vec![
1559 ("CG", SparseSolverMethod::CG),
1560 ("GMRES", SparseSolverMethod::GMRES),
1561 ("BiCGSTAB", SparseSolverMethod::BiCGSTAB),
1562 ("SciRS2Auto", SparseSolverMethod::SciRS2Auto),
1563 ];
1564
1565 for (name, method) in methods {
1566 let config = SparseSolverConfig {
1567 method,
1568 tolerance: 1e-10,
1569 max_iterations: 1000,
1570 ..Default::default()
1571 };
1572
1573 let mut solver = SciRS2SparseSolver::new(config.clone())?;
1574 if method == SparseSolverMethod::SciRS2Auto {
1575 solver = solver.with_backend().unwrap_or_else(|_| {
1576 SciRS2SparseSolver::new(config).expect("fallback solver creation should succeed")
1577 });
1578 }
1579
1580 let _solution = solver.solve_linear_system(&matrix, &rhs)?;
1581 results.insert(name.to_string(), solver.get_stats().clone());
1582 }
1583
1584 Ok(results)
1585}
1586
1587pub fn compare_sparse_solver_accuracy(matrix_size: usize) -> Result<HashMap<String, f64>> {
1589 let mut errors = HashMap::new();
1590
1591 let matrix = SparseMatrix::identity(matrix_size);
1593 let solution = Array1::from_vec(
1594 (0..matrix_size)
1595 .map(|i| Complex64::new(i as f64, 0.0))
1596 .collect(),
1597 );
1598 let rhs = matrix.matvec(&solution)?;
1599
1600 let methods = vec![
1601 ("CG", SparseSolverMethod::CG),
1602 ("GMRES", SparseSolverMethod::GMRES),
1603 ("BiCGSTAB", SparseSolverMethod::BiCGSTAB),
1604 ];
1605
1606 for (name, method) in methods {
1607 let config = SparseSolverConfig {
1608 method,
1609 tolerance: 1e-12,
1610 max_iterations: 1000,
1611 ..Default::default()
1612 };
1613
1614 let mut solver = SciRS2SparseSolver::new(config)?;
1615 let computed_solution = solver.solve_linear_system(&matrix, &rhs)?;
1616
1617 let error = solution
1619 .iter()
1620 .zip(computed_solution.iter())
1621 .map(|(exact, computed)| (exact - computed).norm())
1622 .sum::<f64>()
1623 / matrix_size as f64;
1624
1625 errors.insert(name.to_string(), error);
1626 }
1627
1628 Ok(errors)
1629}
1630
1631#[cfg(test)]
1632mod tests {
1633 use super::*;
1634 use approx::assert_abs_diff_eq;
1635
1636 #[test]
1637 fn test_sparse_matrix_creation() {
1638 let matrix = SparseMatrix::identity(5);
1639 assert_eq!(matrix.shape, (5, 5));
1640 assert_eq!(matrix.nnz, 5);
1641 assert!(matrix.is_hermitian);
1642 assert!(matrix.is_positive_definite);
1643 }
1644
1645 #[test]
1646 fn test_sparse_matrix_matvec() {
1647 let matrix = SparseMatrix::identity(3);
1648 let x = Array1::from_vec(vec![
1649 Complex64::new(1.0, 0.0),
1650 Complex64::new(2.0, 0.0),
1651 Complex64::new(3.0, 0.0),
1652 ]);
1653
1654 let y = matrix.matvec(&x).expect("matvec should succeed");
1655
1656 for i in 0..3 {
1657 assert_abs_diff_eq!(y[i].re, x[i].re, epsilon = 1e-10);
1658 assert_abs_diff_eq!(y[i].im, x[i].im, epsilon = 1e-10);
1659 }
1660 }
1661
1662 #[test]
1663 fn test_sparse_solver_creation() {
1664 let config = SparseSolverConfig::default();
1665 let solver = SciRS2SparseSolver::new(config).expect("solver creation should succeed");
1666 assert!(solver.backend.is_none());
1667 }
1668
1669 #[test]
1670 fn test_identity_solve() {
1671 let matrix = SparseMatrix::identity(5);
1672 let rhs = Array1::from_vec((0..5).map(|i| Complex64::new(i as f64, 0.0)).collect());
1673
1674 let config = SparseSolverConfig {
1675 method: SparseSolverMethod::CG,
1676 tolerance: 1e-10,
1677 max_iterations: 100,
1678 ..Default::default()
1679 };
1680
1681 let mut solver = SciRS2SparseSolver::new(config).expect("solver creation should succeed");
1682 let solution = solver
1683 .solve_linear_system(&matrix, &rhs)
1684 .expect("solve_linear_system should succeed");
1685
1686 for i in 0..5 {
1687 assert_abs_diff_eq!(solution[i].re, rhs[i].re, epsilon = 1e-8);
1688 assert_abs_diff_eq!(solution[i].im, rhs[i].im, epsilon = 1e-8);
1689 }
1690 }
1691
1692 #[test]
1693 fn test_pauli_hamiltonian_creation() {
1694 let pauli_strings = vec![("ZZ".to_string(), 1.0), ("XX".to_string(), 0.5)];
1695
1696 let matrix = SparseMatrixUtils::hamiltonian_from_pauli_strings(2, &pauli_strings)
1697 .expect("hamiltonian_from_pauli_strings should succeed");
1698
1699 assert_eq!(matrix.shape, (4, 4));
1700 assert!(matrix.is_hermitian);
1701 assert!(matrix.nnz > 0);
1702 }
1703
1704 #[test]
1705 fn test_random_sparse_matrix() {
1706 let matrix = SparseMatrixUtils::random_sparse(10, 0.1, true);
1707
1708 assert_eq!(matrix.shape, (10, 10));
1709 assert!(matrix.is_hermitian);
1710 assert!(matrix.density() <= 0.25); }
1712
1713 #[test]
1714 fn test_dense_conversion() {
1715 let matrix = SparseMatrix::identity(3);
1716 let dense = matrix.to_dense().expect("to_dense should succeed");
1717
1718 assert_eq!(dense.shape(), [3, 3]);
1719 for i in 0..3 {
1720 for j in 0..3 {
1721 if i == j {
1722 assert_abs_diff_eq!(dense[[i, j]].re, 1.0, epsilon = 1e-10);
1723 } else {
1724 assert_abs_diff_eq!(dense[[i, j]].norm(), 0.0, epsilon = 1e-10);
1725 }
1726 }
1727 }
1728 }
1729}