1use 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#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct SpectralAnalysisResult {
23 pub eigenvalues: Vec<f64>,
25 pub eigenvectors: Array2<Complex64>,
27 pub energy_gaps: Vec<f64>,
29 pub ground_state_energy: f64,
31 pub spectral_gap: f64,
33 pub participation_ratios: Vec<f64>,
35 pub entanglement_entropies: Vec<f64>,
37 pub spectral_stats: SpectralStatistics,
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct PhaseTransitionResult {
44 pub parameters: Vec<f64>,
46 pub ground_state_energies: Vec<f64>,
48 pub energy_gaps: Vec<f64>,
50 pub order_parameters: Vec<f64>,
52 pub fidelity_susceptibility: Vec<f64>,
54 pub critical_points: Vec<f64>,
56 pub phase_boundaries: Vec<(f64, f64)>,
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct SpectralDensityResult {
63 pub energy_grid: Vec<f64>,
65 pub density: Vec<f64>,
67 pub local_dos: Array2<f64>,
69 pub integrated_dos: Vec<f64>,
71 pub mobility_edges: Vec<f64>,
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize)]
77pub struct EntanglementSpectrumResult {
78 pub eigenvalues: Vec<f64>,
80 pub entropy: f64,
82 pub renyi_entropies: HashMap<String, f64>,
84 pub entanglement_gap: f64,
86 pub participation_ratio: f64,
88 pub bipartition: Vec<usize>,
90}
91
92#[derive(Debug, Clone, Default, Serialize, Deserialize)]
94pub struct SpectralStatistics {
95 pub level_spacing_mean: f64,
97 pub level_spacing_std: f64,
98 pub nn_spacing_ratio: f64,
100 pub spectral_rigidity: f64,
102 pub number_variance: f64,
104 pub spectral_form_factor: Vec<Complex64>,
106 pub thouless_time: f64,
108}
109
110#[derive(Debug, Clone, Serialize, Deserialize)]
112pub struct BandStructureResult {
113 pub k_points: Vec<Array1<f64>>,
115 pub energy_bands: Array2<f64>,
117 pub band_gaps: Vec<f64>,
119 pub dos: SpectralDensityResult,
121 pub effective_masses: Vec<f64>,
123}
124
125#[derive(Debug, Clone)]
127pub struct SpectralConfig {
128 pub num_eigenvalues: usize,
130 pub which: String,
132 pub tolerance: f64,
134 pub max_iterations: usize,
136 pub parallel: bool,
138 pub energy_resolution: f64,
140 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
158pub struct SciRS2SpectralAnalyzer {
160 backend: Option<SciRS2Backend>,
162 config: SpectralConfig,
164 eigenvalue_cache: HashMap<String, SparseEigenResult>,
166}
167
168impl SciRS2SpectralAnalyzer {
169 pub fn new(config: SpectralConfig) -> Result<Self> {
171 Ok(Self {
172 backend: None,
173 config,
174 eigenvalue_cache: HashMap::new(),
175 })
176 }
177
178 pub fn with_backend(mut self) -> Result<Self> {
180 self.backend = Some(SciRS2Backend::new());
181 Ok(self)
182 }
183
184 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 let eigen_result = self.compute_eigenvalues(hamiltonian)?;
197
198 let eigenvalues = eigen_result.eigenvalues;
199 let eigenvectors = eigen_result.eigenvectors;
200
201 let energy_gaps = self.calculate_energy_gaps(&eigenvalues);
203
204 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 let participation_ratios = self.calculate_participation_ratios(&eigenvectors)?;
214
215 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 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 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 let results: Result<Vec<_>> = if self.config.parallel {
256 parameters
257 .par_iter()
258 .map(|¶m| {
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(|¶m| {
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 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 let fidelity_susceptibility =
306 self.calculate_fidelity_susceptibility(&ground_states, ¶meters)?;
307
308 let critical_points =
310 self.detect_critical_points(&energy_gaps, &fidelity_susceptibility, ¶meters)?;
311
312 let phase_boundaries = self.identify_phase_boundaries(&order_parameters, ¶meters)?;
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 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 let eigen_result = self.compute_eigenvalues(hamiltonian)?;
343 let eigenvalues = eigen_result.eigenvalues;
344
345 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 let local_dos = Array2::zeros((hamiltonian.shape.0.min(100), num_points));
363
364 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 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 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 let subsystem_dim = 1 << subsystem_size;
408 let env_dim = 1 << env_size;
409
410 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 let eigenvalues = self.diagonalize_hermitian_matrix(&rho_reduced)?;
427
428 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 let entropy = -nonzero_eigenvalues.iter().map(|&p| p * p.ln()).sum::<f64>();
435
436 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 let entanglement_gap = if nonzero_eigenvalues.len() > 1 {
454 nonzero_eigenvalues[0] - nonzero_eigenvalues[1]
455 } else {
456 0.0
457 };
458
459 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 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 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 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 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 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 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 let window = energy_step * 2.0; let count = all_energies
567 .iter()
568 .filter(|&&e| (e - energy).abs() < window)
569 .count() as f64;
570
571 let dos_value = count / (window * k_path.len() as f64);
573 density.push(dos_value);
574 }
575
576 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 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(), };
594
595 let mut effective_masses = Vec::new();
597
598 for band in 0..num_bands {
599 let mut band_effective_masses = Vec::new();
600
601 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 let dk = 0.1; let second_derivative = (e_next - 2.0 * e_curr + e_prev) / (dk * dk);
611
612 let effective_mass = if second_derivative.abs() > 1e-10 {
615 1.0 / second_derivative.abs()
616 } else {
617 f64::INFINITY };
619
620 band_effective_masses.push(effective_mass);
621 }
622
623 let avg_effective_mass = if band_effective_masses.is_empty() {
625 1.0 } 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 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 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 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 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 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 fn calculate_spectral_statistics(&self, eigenvalues: &[f64]) -> Result<SpectralStatistics> {
722 if eigenvalues.len() < 3 {
723 return Ok(SpectralStatistics::default());
724 }
725
726 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 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 let spectral_rigidity = level_spacing_std / level_spacing_mean;
755
756 let number_variance = eigenvalues.len() as f64 * level_spacing_std.powi(2);
758
759 let max_time_steps = 100;
761 let max_time = 10.0 * level_spacing_mean; 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; for t_idx in 0..max_time_steps {
768 let t = t_idx as f64 * dt;
769
770 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 if t_idx >= 5 && t_idx < spectral_form_factor.len().saturating_sub(5) {
782 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 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 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 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 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 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 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 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 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 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 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 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; 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 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 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 if n <= 8 {
951 let mut eigenvalues = Vec::new();
952
953 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 let new_x = matrix.dot(&x);
961
962 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 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 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 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 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 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 pub fn get_config(&self) -> &SpectralConfig {
1023 &self.config
1024 }
1025
1026 pub fn set_config(&mut self, config: SpectralConfig) {
1028 self.config = config;
1029 }
1030
1031 pub fn clear_cache(&mut self) {
1033 self.eigenvalue_cache.clear();
1034 }
1035}
1036
1037pub struct QuantumHamiltonianLibrary;
1039
1040impl QuantumHamiltonianLibrary {
1041 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 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 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 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 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 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 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 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
1101trait 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
1125pub fn benchmark_spectral_analysis(system_size: usize) -> Result<HashMap<String, f64>> {
1127 let mut results = HashMap::new();
1128
1129 let hamiltonian = QuantumHamiltonianLibrary::transverse_field_ising(system_size, 1.0, 0.5)?;
1131
1132 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 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 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); assert!(ratios[1] > 1.0); }
1247}