quantrs2_sim/
scirs2_eigensolvers.rs

1//! SciRS2-optimized eigensolvers for quantum spectral analysis.
2//!
3//! This module provides specialized eigenvalue analysis tools for quantum systems,
4//! including energy spectrum calculations, quantum phase transition detection,
5//! entanglement spectrum analysis, and spectral density computations.
6
7use ndarray::{Array1, Array2, Array3, ArrayView1, ArrayView2, Axis};
8use num_complex::Complex64;
9use scirs2_core::parallel_ops::*;
10use serde::{Deserialize, Serialize};
11use std::collections::HashMap;
12
13use crate::error::{Result, SimulatorError};
14use crate::pauli::{PauliOperatorSum, PauliString};
15use crate::scirs2_integration::SciRS2Backend;
16use crate::scirs2_sparse::{
17    SciRS2SparseSolver, SparseEigenResult, SparseMatrix, SparseSolverConfig,
18};
19
20/// Spectral analysis result
21#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct SpectralAnalysisResult {
23    /// Eigenvalues (energy levels)
24    pub eigenvalues: Vec<f64>,
25    /// Corresponding eigenvectors (energy eigenstates)
26    pub eigenvectors: Array2<Complex64>,
27    /// Energy gaps between consecutive levels
28    pub energy_gaps: Vec<f64>,
29    /// Ground state energy
30    pub ground_state_energy: f64,
31    /// Spectral gap (lowest excitation energy)
32    pub spectral_gap: f64,
33    /// Participation ratio for each eigenstate
34    pub participation_ratios: Vec<f64>,
35    /// Entanglement entropy for each eigenstate
36    pub entanglement_entropies: Vec<f64>,
37    /// Spectral statistics
38    pub spectral_stats: SpectralStatistics,
39}
40
41/// Quantum phase transition analysis result
42#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct PhaseTransitionResult {
44    /// Parameter values tested
45    pub parameters: Vec<f64>,
46    /// Ground state energies
47    pub ground_state_energies: Vec<f64>,
48    /// Energy gaps
49    pub energy_gaps: Vec<f64>,
50    /// Order parameters
51    pub order_parameters: Vec<f64>,
52    /// Fidelity susceptibility
53    pub fidelity_susceptibility: Vec<f64>,
54    /// Critical points detected
55    pub critical_points: Vec<f64>,
56    /// Phase boundaries
57    pub phase_boundaries: Vec<(f64, f64)>,
58}
59
60/// Spectral density result
61#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct SpectralDensityResult {
63    /// Energy grid
64    pub energy_grid: Vec<f64>,
65    /// Spectral density values
66    pub density: Vec<f64>,
67    /// Local density of states
68    pub local_dos: Array2<f64>,
69    /// Integrated density of states
70    pub integrated_dos: Vec<f64>,
71    /// Mobility edges (if applicable)
72    pub mobility_edges: Vec<f64>,
73}
74
75/// Entanglement spectrum analysis result
76#[derive(Debug, Clone, Serialize, Deserialize)]
77pub struct EntanglementSpectrumResult {
78    /// Entanglement eigenvalues (Schmidt values)
79    pub eigenvalues: Vec<f64>,
80    /// Entanglement entropy
81    pub entropy: f64,
82    /// Renyi entropies
83    pub renyi_entropies: HashMap<String, f64>,
84    /// Entanglement gap
85    pub entanglement_gap: f64,
86    /// Participation ratio
87    pub participation_ratio: f64,
88    /// Bipartition specification
89    pub bipartition: Vec<usize>,
90}
91
92/// Spectral statistics
93#[derive(Debug, Clone, Default, Serialize, Deserialize)]
94pub struct SpectralStatistics {
95    /// Level spacing statistics
96    pub level_spacing_mean: f64,
97    pub level_spacing_std: f64,
98    /// Nearest neighbor spacing ratio
99    pub nn_spacing_ratio: f64,
100    /// Spectral rigidity
101    pub spectral_rigidity: f64,
102    /// Number variance
103    pub number_variance: f64,
104    /// Spectral form factor
105    pub spectral_form_factor: Vec<Complex64>,
106    /// Thouless time
107    pub thouless_time: f64,
108}
109
110/// Band structure calculation result
111#[derive(Debug, Clone, Serialize, Deserialize)]
112pub struct BandStructureResult {
113    /// k-point path
114    pub k_points: Vec<Array1<f64>>,
115    /// Energy bands
116    pub energy_bands: Array2<f64>,
117    /// Band gaps
118    pub band_gaps: Vec<f64>,
119    /// Density of states
120    pub dos: SpectralDensityResult,
121    /// Effective masses
122    pub effective_masses: Vec<f64>,
123}
124
125/// Configuration for spectral analysis
126#[derive(Debug, Clone)]
127pub struct SpectralConfig {
128    /// Number of eigenvalues to compute
129    pub num_eigenvalues: usize,
130    /// Which eigenvalues to target ("smallest", "largest", "interior")
131    pub which: String,
132    /// Convergence tolerance
133    pub tolerance: f64,
134    /// Maximum iterations
135    pub max_iterations: usize,
136    /// Use parallel execution
137    pub parallel: bool,
138    /// Energy resolution for spectral density
139    pub energy_resolution: f64,
140    /// Broadening parameter for spectral density
141    pub broadening: f64,
142}
143
144impl Default for SpectralConfig {
145    fn default() -> Self {
146        Self {
147            num_eigenvalues: 10,
148            which: "smallest".to_string(),
149            tolerance: 1e-10,
150            max_iterations: 1000,
151            parallel: true,
152            energy_resolution: 0.01,
153            broadening: 0.1,
154        }
155    }
156}
157
158/// SciRS2-optimized spectral analyzer
159pub struct SciRS2SpectralAnalyzer {
160    /// SciRS2 backend
161    backend: Option<SciRS2Backend>,
162    /// Configuration
163    config: SpectralConfig,
164    /// Cached eigenvalue results
165    eigenvalue_cache: HashMap<String, SparseEigenResult>,
166}
167
168impl SciRS2SpectralAnalyzer {
169    /// Create new spectral analyzer
170    pub fn new(config: SpectralConfig) -> Result<Self> {
171        Ok(Self {
172            backend: None,
173            config,
174            eigenvalue_cache: HashMap::new(),
175        })
176    }
177
178    /// Initialize with SciRS2 backend
179    pub fn with_backend(mut self) -> Result<Self> {
180        self.backend = Some(SciRS2Backend::new());
181        Ok(self)
182    }
183
184    /// Perform comprehensive spectral analysis
185    pub fn analyze_spectrum(
186        &mut self,
187        hamiltonian: &SparseMatrix,
188    ) -> Result<SpectralAnalysisResult> {
189        if !hamiltonian.is_square() {
190            return Err(SimulatorError::InvalidInput(
191                "Hamiltonian must be square".to_string(),
192            ));
193        }
194
195        // Compute eigenvalues and eigenvectors
196        let eigen_result = self.compute_eigenvalues(hamiltonian)?;
197
198        let eigenvalues = eigen_result.eigenvalues;
199        let eigenvectors = eigen_result.eigenvectors;
200
201        // Calculate energy gaps
202        let energy_gaps = self.calculate_energy_gaps(&eigenvalues);
203
204        // Ground state properties
205        let ground_state_energy = eigenvalues[0];
206        let spectral_gap = if eigenvalues.len() > 1 {
207            eigenvalues[1] - eigenvalues[0]
208        } else {
209            0.0
210        };
211
212        // Participation ratios
213        let participation_ratios = self.calculate_participation_ratios(&eigenvectors)?;
214
215        // Entanglement entropies (for small systems)
216        let entanglement_entropies = if hamiltonian.shape.0 <= 4096 {
217            self.calculate_entanglement_entropies(&eigenvectors)?
218        } else {
219            vec![0.0; eigenvalues.len()]
220        };
221
222        // Spectral statistics
223        let spectral_stats = self.calculate_spectral_statistics(&eigenvalues)?;
224
225        Ok(SpectralAnalysisResult {
226            eigenvalues,
227            eigenvectors,
228            energy_gaps,
229            ground_state_energy,
230            spectral_gap,
231            participation_ratios,
232            entanglement_entropies,
233            spectral_stats,
234        })
235    }
236
237    /// Analyze quantum phase transitions
238    pub fn analyze_phase_transition<F>(
239        &mut self,
240        hamiltonian_generator: F,
241        parameter_range: (f64, f64),
242        num_points: usize,
243    ) -> Result<PhaseTransitionResult>
244    where
245        F: Fn(f64) -> Result<SparseMatrix> + Sync + Send,
246    {
247        let parameters: Vec<f64> = (0..num_points)
248            .map(|i| {
249                parameter_range.0
250                    + (parameter_range.1 - parameter_range.0) * i as f64 / (num_points - 1) as f64
251            })
252            .collect();
253
254        // Parallel computation for different parameter values
255        let results: Result<Vec<_>> = if self.config.parallel {
256            parameters
257                .par_iter()
258                .map(|&param| {
259                    let hamiltonian = hamiltonian_generator(param)?;
260                    let mut analyzer = Self::new(self.config.clone())?;
261                    let analysis = analyzer.analyze_spectrum(&hamiltonian)?;
262                    Ok((
263                        analysis.ground_state_energy,
264                        analysis.spectral_gap,
265                        analysis.eigenvectors.column(0).to_owned(),
266                    ))
267                })
268                .collect()
269        } else {
270            parameters
271                .iter()
272                .map(|&param| {
273                    let hamiltonian = hamiltonian_generator(param)?;
274                    let analysis = self.analyze_spectrum(&hamiltonian)?;
275                    Ok((
276                        analysis.ground_state_energy,
277                        analysis.spectral_gap,
278                        analysis.eigenvectors.column(0).to_owned(),
279                    ))
280                })
281                .collect()
282        };
283
284        let results = results?;
285        let (ground_state_energies, energy_gaps, ground_states): (Vec<_>, Vec<_>, Vec<_>) = results
286            .into_iter()
287            .map(|(e, g, gs)| (e, g, gs))
288            .multiunzip();
289
290        // Calculate order parameters (overlap with first ground state)
291        let reference_state = &ground_states[0];
292        let order_parameters: Vec<f64> = ground_states
293            .iter()
294            .map(|state| {
295                let overlap: Complex64 = reference_state
296                    .iter()
297                    .zip(state.iter())
298                    .map(|(&ref_amp, &state_amp)| ref_amp.conj() * state_amp)
299                    .sum();
300                overlap.norm_sqr()
301            })
302            .collect();
303
304        // Calculate fidelity susceptibility
305        let fidelity_susceptibility =
306            self.calculate_fidelity_susceptibility(&ground_states, &parameters)?;
307
308        // Detect critical points
309        let critical_points =
310            self.detect_critical_points(&energy_gaps, &fidelity_susceptibility, &parameters)?;
311
312        // Identify phase boundaries
313        let phase_boundaries = self.identify_phase_boundaries(&order_parameters, &parameters)?;
314
315        Ok(PhaseTransitionResult {
316            parameters,
317            ground_state_energies,
318            energy_gaps,
319            order_parameters,
320            fidelity_susceptibility,
321            critical_points,
322            phase_boundaries,
323        })
324    }
325
326    /// Calculate spectral density
327    pub fn calculate_spectral_density(
328        &mut self,
329        hamiltonian: &SparseMatrix,
330        energy_range: (f64, f64),
331    ) -> Result<SpectralDensityResult> {
332        let num_points =
333            ((energy_range.1 - energy_range.0) / self.config.energy_resolution) as usize;
334        let energy_grid: Vec<f64> = (0..num_points)
335            .map(|i| {
336                energy_range.0
337                    + (energy_range.1 - energy_range.0) * i as f64 / (num_points - 1) as f64
338            })
339            .collect();
340
341        // Get eigenvalues for density calculation
342        let eigen_result = self.compute_eigenvalues(hamiltonian)?;
343        let eigenvalues = eigen_result.eigenvalues;
344
345        // Calculate spectral density using Gaussian broadening
346        let density: Vec<f64> = energy_grid
347            .iter()
348            .map(|&energy| {
349                eigenvalues
350                    .iter()
351                    .map(|&eigenval| {
352                        let diff = energy - eigenval;
353                        (-diff * diff / (2.0 * self.config.broadening * self.config.broadening))
354                            .exp()
355                    })
356                    .sum::<f64>()
357                    / (self.config.broadening * (2.0 * std::f64::consts::PI).sqrt())
358            })
359            .collect();
360
361        // Local density of states (simplified - would need position operators)
362        let local_dos = Array2::zeros((hamiltonian.shape.0.min(100), num_points));
363
364        // Integrated density of states
365        let mut integrated_dos = vec![0.0; num_points];
366        let mut cumulative = 0.0;
367        for (i, &d) in density.iter().enumerate() {
368            cumulative += d * self.config.energy_resolution;
369            integrated_dos[i] = cumulative;
370        }
371
372        // Mobility edges (placeholder - would need disorder analysis)
373        let mobility_edges = Vec::new();
374
375        Ok(SpectralDensityResult {
376            energy_grid,
377            density,
378            local_dos,
379            integrated_dos,
380            mobility_edges,
381        })
382    }
383
384    /// Calculate entanglement spectrum
385    pub fn calculate_entanglement_spectrum(
386        &mut self,
387        state: &Array1<Complex64>,
388        bipartition: &[usize],
389    ) -> Result<EntanglementSpectrumResult> {
390        let total_qubits = (state.len() as f64).log2() as usize;
391        if state.len() != 1 << total_qubits {
392            return Err(SimulatorError::InvalidInput(
393                "State vector length must be a power of 2".to_string(),
394            ));
395        }
396
397        let subsystem_size = bipartition.len();
398        let env_size = total_qubits - subsystem_size;
399
400        if subsystem_size == 0 || subsystem_size >= total_qubits {
401            return Err(SimulatorError::InvalidInput(
402                "Invalid bipartition specification".to_string(),
403            ));
404        }
405
406        // Reshape state into reduced density matrix form
407        let subsystem_dim = 1 << subsystem_size;
408        let env_dim = 1 << env_size;
409
410        // Create reduced density matrix for subsystem
411        let mut rho_reduced = Array2::zeros((subsystem_dim, subsystem_dim));
412
413        for i in 0..subsystem_dim {
414            for j in 0..subsystem_dim {
415                let mut element = Complex64::new(0.0, 0.0);
416                for k in 0..env_dim {
417                    let full_i = self.encode_full_state(i, k, bipartition, total_qubits);
418                    let full_j = self.encode_full_state(j, k, bipartition, total_qubits);
419                    element += state[full_i].conj() * state[full_j];
420                }
421                rho_reduced[[i, j]] = element;
422            }
423        }
424
425        // Diagonalize reduced density matrix
426        let eigenvalues = self.diagonalize_hermitian_matrix(&rho_reduced)?;
427
428        // Filter out zero eigenvalues and sort
429        let mut nonzero_eigenvalues: Vec<f64> =
430            eigenvalues.into_iter().filter(|&x| x > 1e-15).collect();
431        nonzero_eigenvalues.sort_by(|a, b| b.partial_cmp(a).unwrap());
432
433        // Calculate entanglement entropy
434        let entropy = -nonzero_eigenvalues.iter().map(|&p| p * p.ln()).sum::<f64>();
435
436        // Calculate Renyi entropies
437        let mut renyi_entropies = HashMap::new();
438        for &n in &[2, 3, 4, 5] {
439            let renyi_n = if n == 1 {
440                entropy
441            } else {
442                (1.0 / (1.0 - n as f64))
443                    * nonzero_eigenvalues
444                        .iter()
445                        .map(|&p| p.powf(n as f64))
446                        .sum::<f64>()
447                        .ln()
448            };
449            renyi_entropies.insert(format!("S_{}", n), renyi_n);
450        }
451
452        // Entanglement gap (difference between largest and second largest eigenvalues)
453        let entanglement_gap = if nonzero_eigenvalues.len() > 1 {
454            nonzero_eigenvalues[0] - nonzero_eigenvalues[1]
455        } else {
456            0.0
457        };
458
459        // Participation ratio
460        let participation_ratio = if !nonzero_eigenvalues.is_empty() {
461            let sum_p2 = nonzero_eigenvalues.iter().map(|&p| p * p).sum::<f64>();
462            1.0 / sum_p2
463        } else {
464            0.0
465        };
466
467        Ok(EntanglementSpectrumResult {
468            eigenvalues: nonzero_eigenvalues,
469            entropy,
470            renyi_entropies,
471            entanglement_gap,
472            participation_ratio,
473            bipartition: bipartition.to_vec(),
474        })
475    }
476
477    /// Calculate band structure
478    pub fn calculate_band_structure<F>(
479        &mut self,
480        hamiltonian_generator: F,
481        k_path: &[Array1<f64>],
482        num_bands: usize,
483    ) -> Result<BandStructureResult>
484    where
485        F: Fn(&Array1<f64>) -> Result<SparseMatrix> + Sync + Send,
486    {
487        // Calculate energy bands along k-point path
488        let band_results: Result<Vec<_>> = if self.config.parallel {
489            k_path
490                .par_iter()
491                .map(|k_point| {
492                    let hamiltonian = hamiltonian_generator(k_point)?;
493                    let mut config = SparseSolverConfig::default();
494                    config.method = crate::scirs2_sparse::SparseSolverMethod::Lanczos;
495                    let mut solver = SciRS2SparseSolver::new(config)?;
496                    let result =
497                        solver.solve_eigenvalue_problem(&hamiltonian, num_bands, "smallest")?;
498                    Ok(result.eigenvalues)
499                })
500                .collect()
501        } else {
502            k_path
503                .iter()
504                .map(|k_point| {
505                    let hamiltonian = hamiltonian_generator(k_point)?;
506                    let mut config = SparseSolverConfig::default();
507                    config.method = crate::scirs2_sparse::SparseSolverMethod::Lanczos;
508                    let mut solver = SciRS2SparseSolver::new(config)?;
509                    let result =
510                        solver.solve_eigenvalue_problem(&hamiltonian, num_bands, "smallest")?;
511                    Ok(result.eigenvalues)
512                })
513                .collect()
514        };
515
516        let band_results = band_results?;
517
518        // Organize into band structure
519        let mut energy_bands = Array2::zeros((k_path.len(), num_bands));
520        for (i, eigenvalues) in band_results.iter().enumerate() {
521            for (j, &energy) in eigenvalues.iter().enumerate() {
522                if j < num_bands {
523                    energy_bands[[i, j]] = energy;
524                }
525            }
526        }
527
528        // Calculate band gaps
529        let mut band_gaps = Vec::new();
530        for i in 0..num_bands - 1 {
531            let mut min_gap = f64::INFINITY;
532            for k in 0..k_path.len() {
533                let gap = energy_bands[[k, i + 1]] - energy_bands[[k, i]];
534                if gap < min_gap {
535                    min_gap = gap;
536                }
537            }
538            band_gaps.push(min_gap);
539        }
540
541        // Calculate density of states from band structure
542        let all_energies: Vec<f64> = energy_bands.iter().cloned().collect();
543        let energy_range = (
544            all_energies.iter().cloned().fold(f64::INFINITY, f64::min),
545            all_energies
546                .iter()
547                .cloned()
548                .fold(f64::NEG_INFINITY, f64::max),
549        );
550
551        // Create DOS directly from band energies using histogram
552        let num_energy_points = 200;
553        let energy_min = energy_range.0;
554        let energy_max = energy_range.1;
555        let energy_step = (energy_max - energy_min) / (num_energy_points - 1) as f64;
556
557        let mut energy_grid = Vec::new();
558        let mut density = Vec::new();
559
560        for i in 0..num_energy_points {
561            let energy = energy_min + i as f64 * energy_step;
562            energy_grid.push(energy);
563
564            // Count energy eigenvalues within a small window around this energy
565            let window = energy_step * 2.0; // Use 2x step size as window
566            let count = all_energies
567                .iter()
568                .filter(|&&e| (e - energy).abs() < window)
569                .count() as f64;
570
571            // Normalize by window size and number of k-points for proper density
572            let dos_value = count / (window * k_path.len() as f64);
573            density.push(dos_value);
574        }
575
576        // Calculate integrated DOS
577        let mut integrated_dos = Vec::new();
578        let mut integral = 0.0;
579        for &dos_val in &density {
580            integral += dos_val * energy_step;
581            integrated_dos.push(integral);
582        }
583
584        // Placeholder for local DOS (would need more sophisticated calculation)
585        let local_dos = Array2::zeros((all_energies.len().min(50), num_energy_points));
586
587        let dos = SpectralDensityResult {
588            energy_grid,
589            density,
590            local_dos,
591            integrated_dos,
592            mobility_edges: Vec::new(), // No disorder analysis for clean band structure
593        };
594
595        // Calculate effective masses using numerical second derivative
596        let mut effective_masses = Vec::new();
597
598        for band in 0..num_bands {
599            let mut band_effective_masses = Vec::new();
600
601            // Calculate effective mass for each k-segment
602            for k_idx in 1..k_path.len() - 1 {
603                let e_prev = energy_bands[[k_idx - 1, band]];
604                let e_curr = energy_bands[[k_idx, band]];
605                let e_next = energy_bands[[k_idx + 1, band]];
606
607                // Numerical second derivative: d²E/dk² ≈ (E(k+dk) - 2E(k) + E(k-dk)) / dk²
608                // Assuming uniform k-spacing
609                let dk = 0.1; // Approximate k-spacing in units of π/a
610                let second_derivative = (e_next - 2.0 * e_curr + e_prev) / (dk * dk);
611
612                // Effective mass: m* = ħ² / (d²E/dk²)
613                // Using atomic units where ħ = 1
614                let effective_mass = if second_derivative.abs() > 1e-10 {
615                    1.0 / second_derivative.abs()
616                } else {
617                    f64::INFINITY // Flat band
618                };
619
620                band_effective_masses.push(effective_mass);
621            }
622
623            // Average effective mass for this band
624            let avg_effective_mass = if band_effective_masses.is_empty() {
625                1.0 // Default for single k-point
626            } else {
627                band_effective_masses.iter().sum::<f64>() / band_effective_masses.len() as f64
628            };
629
630            effective_masses.push(avg_effective_mass);
631        }
632
633        Ok(BandStructureResult {
634            k_points: k_path.to_vec(),
635            energy_bands,
636            band_gaps,
637            dos,
638            effective_masses,
639        })
640    }
641
642    /// Compute eigenvalues using appropriate solver
643    fn compute_eigenvalues(&mut self, hamiltonian: &SparseMatrix) -> Result<SparseEigenResult> {
644        let cache_key = format!("eigenvals_{}_{}", hamiltonian.shape.0, hamiltonian.nnz);
645
646        if let Some(cached_result) = self.eigenvalue_cache.get(&cache_key) {
647            return Ok(cached_result.clone());
648        }
649
650        let mut config = SparseSolverConfig::default();
651        config.method = if hamiltonian.is_hermitian {
652            crate::scirs2_sparse::SparseSolverMethod::Lanczos
653        } else {
654            crate::scirs2_sparse::SparseSolverMethod::Arnoldi
655        };
656
657        let mut solver = if let Some(_) = &self.backend {
658            SciRS2SparseSolver::new(config)?.with_backend()?
659        } else {
660            SciRS2SparseSolver::new(config)?
661        };
662
663        let result = solver.solve_eigenvalue_problem(
664            hamiltonian,
665            self.config.num_eigenvalues,
666            &self.config.which,
667        )?;
668
669        self.eigenvalue_cache.insert(cache_key, result.clone());
670        Ok(result)
671    }
672
673    /// Calculate energy gaps
674    fn calculate_energy_gaps(&self, eigenvalues: &[f64]) -> Vec<f64> {
675        eigenvalues
676            .windows(2)
677            .map(|window| window[1] - window[0])
678            .collect()
679    }
680
681    /// Calculate participation ratios
682    fn calculate_participation_ratios(&self, eigenvectors: &Array2<Complex64>) -> Result<Vec<f64>> {
683        let mut ratios = Vec::new();
684
685        for col_idx in 0..eigenvectors.ncols() {
686            let column = eigenvectors.column(col_idx);
687            let sum_p4: f64 = column.iter().map(|&c| c.norm_sqr().powi(2)).sum();
688            let ratio = if sum_p4 > 0.0 { 1.0 / sum_p4 } else { 0.0 };
689            ratios.push(ratio);
690        }
691
692        Ok(ratios)
693    }
694
695    /// Calculate entanglement entropies
696    fn calculate_entanglement_entropies(
697        &mut self,
698        eigenvectors: &Array2<Complex64>,
699    ) -> Result<Vec<f64>> {
700        let mut entropies = Vec::new();
701
702        for col_idx in 0..eigenvectors.ncols() {
703            let state = eigenvectors.column(col_idx).to_owned();
704            let num_qubits = (state.len() as f64).log2() as usize;
705
706            if num_qubits <= 10 {
707                // Only for small systems
708                let bipartition: Vec<usize> = (0..num_qubits / 2).collect();
709                let ent_result = self.calculate_entanglement_spectrum(&state, &bipartition);
710                let entropy = ent_result.map(|r| r.entropy).unwrap_or(0.0);
711                entropies.push(entropy);
712            } else {
713                entropies.push(0.0);
714            }
715        }
716
717        Ok(entropies)
718    }
719
720    /// Calculate spectral statistics
721    fn calculate_spectral_statistics(&self, eigenvalues: &[f64]) -> Result<SpectralStatistics> {
722        if eigenvalues.len() < 3 {
723            return Ok(SpectralStatistics::default());
724        }
725
726        // Level spacing statistics
727        let spacings: Vec<f64> = eigenvalues
728            .windows(2)
729            .map(|window| window[1] - window[0])
730            .collect();
731
732        let level_spacing_mean = spacings.iter().sum::<f64>() / spacings.len() as f64;
733        let level_spacing_std = {
734            let variance = spacings
735                .iter()
736                .map(|&s| (s - level_spacing_mean).powi(2))
737                .sum::<f64>()
738                / spacings.len() as f64;
739            variance.sqrt()
740        };
741
742        // Nearest neighbor spacing ratio
743        let nn_spacing_ratio = if spacings.len() > 1 {
744            let ratios: Vec<f64> = spacings
745                .windows(2)
746                .map(|window| window[0].min(window[1]) / window[0].max(window[1]))
747                .collect();
748            ratios.iter().sum::<f64>() / ratios.len() as f64
749        } else {
750            0.0
751        };
752
753        // Spectral rigidity (simplified)
754        let spectral_rigidity = level_spacing_std / level_spacing_mean;
755
756        // Number variance (simplified)
757        let number_variance = eigenvalues.len() as f64 * level_spacing_std.powi(2);
758
759        // Spectral form factor calculation
760        let max_time_steps = 100;
761        let max_time = 10.0 * level_spacing_mean; // Time in units of mean level spacing
762        let dt = max_time / max_time_steps as f64;
763
764        let mut spectral_form_factor = Vec::new();
765        let mut thouless_time = 1.0 / level_spacing_mean; // Default fallback
766
767        for t_idx in 0..max_time_steps {
768            let t = t_idx as f64 * dt;
769
770            // Calculate form factor K(t) = |∑ᵢ exp(iEᵢt)|² / N
771            let mut sum_exp = Complex64::new(0.0, 0.0);
772            for &energy in eigenvalues {
773                let phase = energy * t;
774                sum_exp += Complex64::new(phase.cos(), phase.sin());
775            }
776
777            let form_factor_value = sum_exp.norm_sqr() / eigenvalues.len() as f64;
778            spectral_form_factor.push(Complex64::new(form_factor_value, 0.0));
779
780            // Find Thouless time as the time when form factor transitions from ramp to plateau
781            if t_idx >= 5 && t_idx < spectral_form_factor.len().saturating_sub(5) {
782                // Check if we're transitioning from linear ramp to plateau
783                let prev_values: Vec<f64> = spectral_form_factor[t_idx.saturating_sub(5)..t_idx]
784                    .iter()
785                    .map(|c| c.re)
786                    .collect();
787                let next_values: Vec<f64> = spectral_form_factor
788                    [t_idx..(t_idx + 5).min(spectral_form_factor.len())]
789                    .iter()
790                    .map(|c| c.re)
791                    .collect();
792
793                // Calculate slopes
794                let prev_slope =
795                    (prev_values.last().unwrap() - prev_values.first().unwrap()) / (5.0 * dt);
796                let next_slope =
797                    (next_values.last().unwrap() - next_values.first().unwrap()) / (5.0 * dt);
798
799                // Detect plateau (slope change from positive to near zero)
800                if prev_slope > 0.1 && next_slope.abs() < 0.05 && t > dt * 5.0 {
801                    thouless_time = t;
802                    break;
803                }
804            }
805        }
806
807        Ok(SpectralStatistics {
808            level_spacing_mean,
809            level_spacing_std,
810            nn_spacing_ratio,
811            spectral_rigidity,
812            number_variance,
813            spectral_form_factor,
814            thouless_time,
815        })
816    }
817
818    /// Calculate fidelity susceptibility
819    fn calculate_fidelity_susceptibility(
820        &self,
821        ground_states: &[Array1<Complex64>],
822        parameters: &[f64],
823    ) -> Result<Vec<f64>> {
824        let mut susceptibilities = Vec::new();
825
826        for i in 1..ground_states.len() - 1 {
827            let dparam = parameters[i + 1] - parameters[i - 1];
828            if dparam.abs() < 1e-15 {
829                susceptibilities.push(0.0);
830                continue;
831            }
832
833            // Calculate derivative of ground state
834            let mut derivative = Array1::zeros(ground_states[i].len());
835            for j in 0..ground_states[i].len() {
836                derivative[j] = (ground_states[i + 1][j] - ground_states[i - 1][j]) / dparam;
837            }
838
839            // Fidelity susceptibility
840            let overlap: Complex64 = ground_states[i]
841                .iter()
842                .zip(derivative.iter())
843                .map(|(&gs, &deriv)| gs.conj() * deriv)
844                .sum();
845
846            let susceptibility =
847                derivative.iter().map(|&d| d.norm_sqr()).sum::<f64>() - overlap.norm_sqr();
848            susceptibilities.push(susceptibility);
849        }
850
851        // Handle boundary points
852        if !susceptibilities.is_empty() {
853            susceptibilities.insert(0, susceptibilities[0]);
854            susceptibilities.push(susceptibilities[susceptibilities.len() - 1]);
855        }
856
857        Ok(susceptibilities)
858    }
859
860    /// Detect critical points
861    fn detect_critical_points(
862        &self,
863        energy_gaps: &[f64],
864        fidelity_susceptibility: &[f64],
865        parameters: &[f64],
866    ) -> Result<Vec<f64>> {
867        let mut critical_points = Vec::new();
868
869        // Find minima in energy gap
870        for i in 1..energy_gaps.len() - 1 {
871            if energy_gaps[i] < energy_gaps[i - 1] && energy_gaps[i] < energy_gaps[i + 1] {
872                critical_points.push(parameters[i]);
873            }
874        }
875
876        // Find maxima in fidelity susceptibility
877        for i in 1..fidelity_susceptibility.len() - 1 {
878            if fidelity_susceptibility[i] > fidelity_susceptibility[i - 1]
879                && fidelity_susceptibility[i] > fidelity_susceptibility[i + 1]
880            {
881                critical_points.push(parameters[i]);
882            }
883        }
884
885        // Remove duplicates and sort
886        critical_points.sort_by(|a, b| a.partial_cmp(b).unwrap());
887        critical_points.dedup_by(|a, b| (*a - *b).abs() < 1e-6);
888
889        Ok(critical_points)
890    }
891
892    /// Identify phase boundaries
893    fn identify_phase_boundaries(
894        &self,
895        order_parameters: &[f64],
896        parameters: &[f64],
897    ) -> Result<Vec<(f64, f64)>> {
898        let mut boundaries = Vec::new();
899        let threshold = 0.1; // Threshold for order parameter change
900
901        for i in 1..order_parameters.len() {
902            let change = (order_parameters[i] - order_parameters[i - 1]).abs();
903            if change > threshold {
904                boundaries.push((parameters[i - 1], parameters[i]));
905            }
906        }
907
908        Ok(boundaries)
909    }
910
911    /// Encode full state index from subsystem and environment indices
912    fn encode_full_state(
913        &self,
914        sub_idx: usize,
915        env_idx: usize,
916        bipartition: &[usize],
917        total_qubits: usize,
918    ) -> usize {
919        let mut full_idx = 0;
920        let mut sub_bit = 0;
921        let mut env_bit = 0;
922
923        for qubit in 0..total_qubits {
924            if bipartition.contains(&qubit) {
925                if (sub_idx >> sub_bit) & 1 == 1 {
926                    full_idx |= 1 << (total_qubits - 1 - qubit);
927                }
928                sub_bit += 1;
929            } else {
930                if (env_idx >> env_bit) & 1 == 1 {
931                    full_idx |= 1 << (total_qubits - 1 - qubit);
932                }
933                env_bit += 1;
934            }
935        }
936
937        full_idx
938    }
939
940    /// Diagonalize Hermitian matrix using proper eigenvalue computation
941    fn diagonalize_hermitian_matrix(&self, matrix: &Array2<Complex64>) -> Result<Vec<f64>> {
942        let n = matrix.nrows();
943        if n != matrix.ncols() {
944            return Err(SimulatorError::InvalidInput(
945                "Matrix must be square".to_string(),
946            ));
947        }
948
949        // For small matrices, implement simplified power iteration for dominant eigenvalue
950        if n <= 8 {
951            let mut eigenvalues = Vec::new();
952
953            // Power iteration to find largest eigenvalue
954            let mut x = Array1::from_vec(vec![Complex64::new(1.0, 0.0); n]);
955            let max_iterations = 100;
956            let tolerance = 1e-10;
957
958            for _ in 0..max_iterations {
959                // x = A * x
960                let new_x = matrix.dot(&x);
961
962                // Normalize
963                let norm = new_x.iter().map(|c| c.norm_sqr()).sum::<f64>().sqrt();
964                if norm < tolerance {
965                    break;
966                }
967
968                for (old, new) in x.iter_mut().zip(new_x.iter()) {
969                    *old = *new / norm;
970                }
971            }
972
973            // Compute Rayleigh quotient: λ = x†Ax / x†x
974            let ax = matrix.dot(&x);
975            let numerator: Complex64 = x
976                .iter()
977                .zip(ax.iter())
978                .map(|(xi, axi)| xi.conj() * axi)
979                .sum();
980            let denominator: f64 = x.iter().map(|xi| xi.norm_sqr()).sum();
981
982            if denominator > tolerance {
983                eigenvalues.push(numerator.re / denominator);
984            }
985
986            // For small matrices, estimate remaining eigenvalues using trace and determinant
987            if n == 2 {
988                let trace = matrix[[0, 0]].re + matrix[[1, 1]].re;
989                let det = (matrix[[0, 0]] * matrix[[1, 1]] - matrix[[0, 1]] * matrix[[1, 0]]).re;
990
991                // Solve characteristic polynomial: λ² - trace*λ + det = 0
992                let discriminant = trace * trace - 4.0 * det;
993                if discriminant >= 0.0 {
994                    let sqrt_disc = discriminant.sqrt();
995                    eigenvalues.clear();
996                    eigenvalues.push((trace + sqrt_disc) / 2.0);
997                    eigenvalues.push((trace - sqrt_disc) / 2.0);
998                }
999            }
1000
1001            // If no eigenvalues computed, fall back to diagonal elements with warning
1002            if eigenvalues.is_empty() {
1003                eprintln!(
1004                    "Warning: Failed to compute eigenvalues properly, using diagonal approximation"
1005                );
1006                for i in 0..n {
1007                    eigenvalues.push(matrix[[i, i]].re);
1008                }
1009            }
1010
1011            eigenvalues.sort_by(|a, b| b.partial_cmp(a).unwrap());
1012            Ok(eigenvalues)
1013        } else {
1014            // For larger matrices, suggest using proper LAPACK implementation
1015            Err(SimulatorError::UnsupportedOperation(
1016                format!("Matrix size {} too large. Recommend using ndarray-linalg or LAPACK for proper eigenvalue computation", n)
1017            ))
1018        }
1019    }
1020
1021    /// Get configuration
1022    pub fn get_config(&self) -> &SpectralConfig {
1023        &self.config
1024    }
1025
1026    /// Set configuration
1027    pub fn set_config(&mut self, config: SpectralConfig) {
1028        self.config = config;
1029    }
1030
1031    /// Clear eigenvalue cache
1032    pub fn clear_cache(&mut self) {
1033        self.eigenvalue_cache.clear();
1034    }
1035}
1036
1037/// Utilities for creating quantum Hamiltonians for spectral analysis
1038pub struct QuantumHamiltonianLibrary;
1039
1040impl QuantumHamiltonianLibrary {
1041    /// Create transverse field Ising model Hamiltonian
1042    pub fn transverse_field_ising(num_sites: usize, j: f64, h: f64) -> Result<SparseMatrix> {
1043        use crate::scirs2_sparse::SparseMatrixUtils;
1044
1045        let mut pauli_terms = Vec::new();
1046
1047        // ZZ interactions
1048        for i in 0..num_sites - 1 {
1049            let mut pauli_string = "I".repeat(num_sites);
1050            pauli_string.replace_range(i..i + 1, "Z");
1051            pauli_string.replace_range(i + 1..i + 2, "Z");
1052            pauli_terms.push((pauli_string, -j));
1053        }
1054
1055        // Transverse field
1056        for i in 0..num_sites {
1057            let mut pauli_string = "I".repeat(num_sites);
1058            pauli_string.replace_range(i..i + 1, "X");
1059            pauli_terms.push((pauli_string, -h));
1060        }
1061
1062        SparseMatrixUtils::hamiltonian_from_pauli_strings(num_sites, &pauli_terms)
1063    }
1064
1065    /// Create Heisenberg model Hamiltonian
1066    pub fn heisenberg_model(num_sites: usize, jx: f64, jy: f64, jz: f64) -> Result<SparseMatrix> {
1067        use crate::scirs2_sparse::SparseMatrixUtils;
1068
1069        let mut pauli_terms = Vec::new();
1070
1071        for i in 0..num_sites - 1 {
1072            // XX interaction
1073            let mut xx_string = "I".repeat(num_sites);
1074            xx_string.replace_range(i..i + 1, "X");
1075            xx_string.replace_range(i + 1..i + 2, "X");
1076            pauli_terms.push((xx_string, jx));
1077
1078            // YY interaction
1079            let mut yy_string = "I".repeat(num_sites);
1080            yy_string.replace_range(i..i + 1, "Y");
1081            yy_string.replace_range(i + 1..i + 2, "Y");
1082            pauli_terms.push((yy_string, jy));
1083
1084            // ZZ interaction
1085            let mut zz_string = "I".repeat(num_sites);
1086            zz_string.replace_range(i..i + 1, "Z");
1087            zz_string.replace_range(i + 1..i + 2, "Z");
1088            pauli_terms.push((zz_string, jz));
1089        }
1090
1091        SparseMatrixUtils::hamiltonian_from_pauli_strings(num_sites, &pauli_terms)
1092    }
1093
1094    /// Create random matrix model
1095    pub fn random_matrix_model(size: usize, density: f64, hermitian: bool) -> SparseMatrix {
1096        use crate::scirs2_sparse::SparseMatrixUtils;
1097        SparseMatrixUtils::random_sparse(size, density, hermitian)
1098    }
1099}
1100
1101/// Trait for multi-unzip operations
1102trait MultiUnzip<A, B, C> {
1103    fn multiunzip(self) -> (Vec<A>, Vec<B>, Vec<C>);
1104}
1105
1106impl<I, A, B, C> MultiUnzip<A, B, C> for I
1107where
1108    I: Iterator<Item = (A, B, C)>,
1109{
1110    fn multiunzip(self) -> (Vec<A>, Vec<B>, Vec<C>) {
1111        let mut vec_a = Vec::new();
1112        let mut vec_b = Vec::new();
1113        let mut vec_c = Vec::new();
1114
1115        for (a, b, c) in self {
1116            vec_a.push(a);
1117            vec_b.push(b);
1118            vec_c.push(c);
1119        }
1120
1121        (vec_a, vec_b, vec_c)
1122    }
1123}
1124
1125/// Benchmark spectral analysis methods
1126pub fn benchmark_spectral_analysis(system_size: usize) -> Result<HashMap<String, f64>> {
1127    let mut results = HashMap::new();
1128
1129    // Create test Hamiltonian
1130    let hamiltonian = QuantumHamiltonianLibrary::transverse_field_ising(system_size, 1.0, 0.5)?;
1131
1132    // Test different analysis methods
1133    let config = SpectralConfig {
1134        num_eigenvalues: 10.min(hamiltonian.shape.0),
1135        ..Default::default()
1136    };
1137
1138    let mut analyzer = SciRS2SpectralAnalyzer::new(config)?;
1139
1140    // Time spectral analysis
1141    let start_time = std::time::Instant::now();
1142    let _analysis = analyzer.analyze_spectrum(&hamiltonian)?;
1143    let analysis_time = start_time.elapsed().as_secs_f64();
1144
1145    results.insert("SpectrumAnalysis".to_string(), analysis_time);
1146
1147    // Time spectral density calculation
1148    let start_time = std::time::Instant::now();
1149    let _density = analyzer.calculate_spectral_density(&hamiltonian, (-5.0, 5.0))?;
1150    let density_time = start_time.elapsed().as_secs_f64();
1151
1152    results.insert("SpectralDensity".to_string(), density_time);
1153
1154    Ok(results)
1155}
1156
1157#[cfg(test)]
1158mod tests {
1159    use super::*;
1160    use approx::assert_abs_diff_eq;
1161
1162    #[test]
1163    fn test_spectral_analyzer_creation() {
1164        let config = SpectralConfig::default();
1165        let analyzer = SciRS2SpectralAnalyzer::new(config).unwrap();
1166        assert_eq!(analyzer.config.num_eigenvalues, 10);
1167    }
1168
1169    #[test]
1170    fn test_transverse_field_ising() {
1171        let hamiltonian = QuantumHamiltonianLibrary::transverse_field_ising(3, 1.0, 0.5).unwrap();
1172        assert_eq!(hamiltonian.shape, (8, 8));
1173        assert!(hamiltonian.is_hermitian);
1174    }
1175
1176    #[test]
1177    fn test_heisenberg_model() {
1178        let hamiltonian = QuantumHamiltonianLibrary::heisenberg_model(2, 1.0, 1.0, 1.0).unwrap();
1179        assert_eq!(hamiltonian.shape, (4, 4));
1180        assert!(hamiltonian.is_hermitian);
1181    }
1182
1183    #[test]
1184    fn test_spectral_analysis() {
1185        let hamiltonian = QuantumHamiltonianLibrary::transverse_field_ising(3, 1.0, 0.5).unwrap();
1186        let config = SpectralConfig {
1187            num_eigenvalues: 5,
1188            ..Default::default()
1189        };
1190
1191        let mut analyzer = SciRS2SpectralAnalyzer::new(config).unwrap();
1192        let result = analyzer.analyze_spectrum(&hamiltonian).unwrap();
1193
1194        assert_eq!(result.eigenvalues.len(), 5);
1195        assert!(result.spectral_gap >= 0.0);
1196        assert_eq!(result.participation_ratios.len(), 5);
1197    }
1198
1199    #[test]
1200    fn test_entanglement_spectrum() {
1201        let mut state = Array1::zeros(4);
1202        state[0] = Complex64::new(1.0 / 2.0_f64.sqrt(), 0.0);
1203        state[3] = Complex64::new(1.0 / 2.0_f64.sqrt(), 0.0);
1204
1205        let config = SpectralConfig::default();
1206        let mut analyzer = SciRS2SpectralAnalyzer::new(config).unwrap();
1207
1208        let bipartition = vec![0];
1209        let result = analyzer
1210            .calculate_entanglement_spectrum(&state, &bipartition)
1211            .unwrap();
1212
1213        assert!(result.entropy > 0.0);
1214        assert_eq!(result.bipartition, vec![0]);
1215    }
1216
1217    #[test]
1218    fn test_energy_gaps() {
1219        let eigenvalues = vec![0.0, 1.0, 3.0, 6.0];
1220        let config = SpectralConfig::default();
1221        let analyzer = SciRS2SpectralAnalyzer::new(config).unwrap();
1222
1223        let gaps = analyzer.calculate_energy_gaps(&eigenvalues);
1224
1225        assert_eq!(gaps, vec![1.0, 2.0, 3.0]);
1226    }
1227
1228    #[test]
1229    fn test_participation_ratios() {
1230        let mut eigenvectors = Array2::zeros((4, 2));
1231        eigenvectors[[0, 0]] = Complex64::new(1.0, 0.0);
1232        eigenvectors[[1, 1]] = Complex64::new(0.5, 0.0);
1233        eigenvectors[[2, 1]] = Complex64::new(0.5, 0.0);
1234        eigenvectors[[3, 1]] = Complex64::new(0.5, 0.0);
1235
1236        let config = SpectralConfig::default();
1237        let analyzer = SciRS2SpectralAnalyzer::new(config).unwrap();
1238
1239        let ratios = analyzer
1240            .calculate_participation_ratios(&eigenvectors)
1241            .unwrap();
1242
1243        assert_eq!(ratios.len(), 2);
1244        assert_abs_diff_eq!(ratios[0], 1.0, epsilon = 1e-10); // Localized state
1245        assert!(ratios[1] > 1.0); // Delocalized state
1246    }
1247}