1use scirs2_core::ndarray::ndarray_linalg::Norm; use scirs2_core::ndarray::{Array1, Array2, Array4};
20use scirs2_core::random::prelude::*;
21use scirs2_core::Complex64;
22use std::collections::HashMap;
23use std::f64::consts::PI;
24
25use crate::circuit_interfaces::{InterfaceCircuit, InterfaceGate, InterfaceGateType};
26use crate::error::{Result, SimulatorError};
27use crate::fermionic_simulation::{FermionicHamiltonian, FermionicOperator, FermionicString};
28use crate::pauli::{PauliOperator, PauliOperatorSum, PauliString};
29use crate::scirs2_integration::SciRS2Backend;
30use crate::statevector::StateVectorSimulator;
31
32#[derive(Debug, Clone)]
34pub struct Molecule {
35 pub atomic_numbers: Vec<u32>,
37 pub positions: Array2<f64>,
39 pub charge: i32,
41 pub multiplicity: u32,
43 pub basis_set: String,
45}
46
47#[derive(Debug, Clone)]
49pub struct ElectronicStructureConfig {
50 pub method: ElectronicStructureMethod,
52 pub convergence_threshold: f64,
54 pub max_scf_iterations: usize,
56 pub active_space: Option<ActiveSpace>,
58 pub enable_second_quantization_optimization: bool,
60 pub fermion_mapping: FermionMapping,
62 pub enable_orbital_optimization: bool,
64 pub vqe_config: VQEConfig,
66}
67
68impl Default for ElectronicStructureConfig {
69 fn default() -> Self {
70 Self {
71 method: ElectronicStructureMethod::VQE,
72 convergence_threshold: 1e-6,
73 max_scf_iterations: 100,
74 active_space: None,
75 enable_second_quantization_optimization: true,
76 fermion_mapping: FermionMapping::JordanWigner,
77 enable_orbital_optimization: true,
78 vqe_config: VQEConfig::default(),
79 }
80 }
81}
82
83#[derive(Debug, Clone, Copy, PartialEq, Eq)]
85pub enum ElectronicStructureMethod {
86 HartreeFock,
88 VQE,
90 QuantumCI,
92 QuantumCC,
94 QPE,
96}
97
98#[derive(Debug, Clone)]
100pub struct ActiveSpace {
101 pub num_electrons: usize,
103 pub num_orbitals: usize,
105 pub orbital_indices: Vec<usize>,
107 pub frozen_core: Vec<usize>,
109 pub frozen_virtual: Vec<usize>,
111}
112
113#[derive(Debug, Clone, Copy, PartialEq, Eq)]
115pub enum FermionMapping {
116 JordanWigner,
118 Parity,
120 BravyiKitaev,
122 SymmetryConservingBK,
124 FenwickTree,
126}
127
128#[derive(Debug, Clone)]
130pub struct VQEConfig {
131 pub ansatz: ChemistryAnsatz,
133 pub optimizer: ChemistryOptimizer,
135 pub max_iterations: usize,
137 pub energy_threshold: f64,
139 pub gradient_threshold: f64,
141 pub shots: usize,
143 pub enable_noise_mitigation: bool,
145}
146
147impl Default for VQEConfig {
148 fn default() -> Self {
149 Self {
150 ansatz: ChemistryAnsatz::UCCSD,
151 optimizer: ChemistryOptimizer::COBYLA,
152 max_iterations: 100,
153 energy_threshold: 1e-6,
154 gradient_threshold: 1e-4,
155 shots: 10000,
156 enable_noise_mitigation: true,
157 }
158 }
159}
160
161#[derive(Debug, Clone, Copy, PartialEq, Eq)]
163pub enum ChemistryAnsatz {
164 UCCSD,
166 HardwareEfficient,
168 SymmetryPreserving,
170 LowDepth,
172 Adaptive,
174}
175
176#[derive(Debug, Clone, Copy, PartialEq, Eq)]
178pub enum ChemistryOptimizer {
179 COBYLA,
181 SLSQP,
183 Powell,
185 GradientDescent,
187 Adam,
189 QuantumNaturalGradient,
191}
192
193#[derive(Debug, Clone)]
195pub struct MolecularOrbitals {
196 pub coefficients: Array2<f64>,
198 pub energies: Array1<f64>,
200 pub occupations: Array1<f64>,
202 pub num_basis: usize,
204 pub num_orbitals: usize,
206}
207
208#[derive(Debug, Clone)]
210pub struct ElectronicStructureResult {
211 pub ground_state_energy: f64,
213 pub molecular_orbitals: MolecularOrbitals,
215 pub density_matrix: Array2<f64>,
217 pub dipole_moment: Array1<f64>,
219 pub converged: bool,
221 pub iterations: usize,
223 pub quantum_state: Array1<Complex64>,
225 pub vqe_history: Vec<f64>,
227 pub stats: ChemistryStats,
229}
230
231#[derive(Debug, Clone, Default)]
233pub struct ChemistryStats {
234 pub total_time_ms: f64,
236 pub hamiltonian_time_ms: f64,
238 pub vqe_time_ms: f64,
240 pub circuit_evaluations: usize,
242 pub parameter_updates: usize,
244 pub memory_usage_mb: f64,
246 pub hamiltonian_terms: usize,
248}
249
250#[derive(Debug, Clone)]
252pub struct MolecularHamiltonian {
253 pub one_electron_integrals: Array2<f64>,
255 pub two_electron_integrals: Array4<f64>,
257 pub nuclear_repulsion: f64,
259 pub num_orbitals: usize,
261 pub num_electrons: usize,
263 pub fermionic_hamiltonian: FermionicHamiltonian,
265 pub pauli_hamiltonian: Option<PauliOperatorSum>,
267}
268
269pub struct QuantumChemistrySimulator {
271 config: ElectronicStructureConfig,
273 backend: Option<SciRS2Backend>,
275 molecule: Option<Molecule>,
277 hamiltonian: Option<MolecularHamiltonian>,
279 hartree_fock: Option<HartreeFockResult>,
281 fermion_mapper: FermionMapper,
283 vqe_optimizer: VQEOptimizer,
285 stats: ChemistryStats,
287}
288
289#[derive(Debug, Clone)]
291pub struct HartreeFockResult {
292 pub scf_energy: f64,
294 pub molecular_orbitals: MolecularOrbitals,
296 pub density_matrix: Array2<f64>,
298 pub fock_matrix: Array2<f64>,
300 pub converged: bool,
302 pub scf_iterations: usize,
304}
305
306#[derive(Debug, Clone)]
308pub struct FermionMapper {
309 method: FermionMapping,
311 num_spin_orbitals: usize,
313 mapping_cache: HashMap<String, PauliString>,
315}
316
317#[derive(Debug, Clone)]
319pub struct VQEOptimizer {
320 method: ChemistryOptimizer,
322 parameters: Array1<f64>,
324 bounds: Vec<(f64, f64)>,
326 history: Vec<f64>,
328 gradients: Array1<f64>,
330 learning_rate: f64,
332}
333
334impl QuantumChemistrySimulator {
335 pub fn new(config: ElectronicStructureConfig) -> Result<Self> {
337 let backend = if config.enable_second_quantization_optimization {
338 Some(SciRS2Backend::new())
339 } else {
340 None
341 };
342
343 Ok(Self {
344 config: config.clone(),
345 backend,
346 molecule: None,
347 hamiltonian: None,
348 hartree_fock: None,
349 fermion_mapper: FermionMapper::new(config.fermion_mapping, 0),
350 vqe_optimizer: VQEOptimizer::new(config.vqe_config.optimizer),
351 stats: ChemistryStats::default(),
352 })
353 }
354
355 pub fn set_molecule(&mut self, molecule: Molecule) -> Result<()> {
357 self.molecule = Some(molecule);
358 self.hamiltonian = None; self.hartree_fock = None; Ok(())
361 }
362
363 pub fn run_calculation(&mut self) -> Result<ElectronicStructureResult> {
365 let start_time = std::time::Instant::now();
366
367 if self.molecule.is_none() {
369 return Err(SimulatorError::InvalidConfiguration(
370 "Molecule not set".to_string(),
371 ));
372 }
373
374 let hamiltonian_start = std::time::Instant::now();
376 let molecule_clone = self.molecule.clone().unwrap();
377 self.construct_molecular_hamiltonian(&molecule_clone)?;
378 self.stats.hamiltonian_time_ms = hamiltonian_start.elapsed().as_millis() as f64;
379
380 self.run_hartree_fock()?;
382
383 let result = match self.config.method {
385 ElectronicStructureMethod::HartreeFock => self.run_hartree_fock_only(),
386 ElectronicStructureMethod::VQE => self.run_vqe(),
387 ElectronicStructureMethod::QuantumCI => self.run_quantum_ci(),
388 ElectronicStructureMethod::QuantumCC => self.run_quantum_coupled_cluster(),
389 ElectronicStructureMethod::QPE => self.run_quantum_phase_estimation(),
390 }?;
391
392 self.stats.total_time_ms = start_time.elapsed().as_millis() as f64;
393 Ok(result)
394 }
395
396 fn construct_molecular_hamiltonian(&mut self, molecule: &Molecule) -> Result<()> {
398 let num_atoms = molecule.atomic_numbers.len();
399
400 let num_orbitals = if molecule.basis_set == "STO-3G" {
403 num_atoms } else {
405 2 * num_atoms };
407
408 let num_electrons =
409 molecule.atomic_numbers.iter().sum::<u32>() as usize - molecule.charge as usize;
410
411 let one_electron_integrals = self.compute_one_electron_integrals(molecule, num_orbitals)?;
413
414 let two_electron_integrals = self.compute_two_electron_integrals(molecule, num_orbitals)?;
416
417 let nuclear_repulsion = self.compute_nuclear_repulsion(molecule)?;
419
420 let fermionic_hamiltonian = self.create_fermionic_hamiltonian(
422 &one_electron_integrals,
423 &two_electron_integrals,
424 num_orbitals,
425 )?;
426
427 self.fermion_mapper = FermionMapper::new(self.fermion_mapper.method, num_orbitals * 2);
429
430 let pauli_hamiltonian = if self.config.enable_second_quantization_optimization {
432 Some(self.map_to_pauli_operators(&fermionic_hamiltonian, num_orbitals)?)
433 } else {
434 None
435 };
436
437 self.hamiltonian = Some(MolecularHamiltonian {
438 one_electron_integrals,
439 two_electron_integrals,
440 nuclear_repulsion,
441 num_orbitals,
442 num_electrons,
443 fermionic_hamiltonian,
444 pauli_hamiltonian,
445 });
446
447 Ok(())
448 }
449
450 fn compute_one_electron_integrals(
452 &self,
453 molecule: &Molecule,
454 num_orbitals: usize,
455 ) -> Result<Array2<f64>> {
456 let mut integrals = Array2::zeros((num_orbitals, num_orbitals));
457
458 if molecule.atomic_numbers.len() == 2
460 && molecule.atomic_numbers[0] == 1
461 && molecule.atomic_numbers[1] == 1
462 {
463 let bond_length = (molecule.positions[[0, 2]] - molecule.positions[[1, 2]])
464 .mul_add(
465 molecule.positions[[0, 2]] - molecule.positions[[1, 2]],
466 (molecule.positions[[0, 1]] - molecule.positions[[1, 1]]).mul_add(
467 molecule.positions[[0, 1]] - molecule.positions[[1, 1]],
468 (molecule.positions[[0, 0]] - molecule.positions[[1, 0]]).powi(2),
469 ),
470 )
471 .sqrt();
472
473 let overlap = 0.6593 * (-0.1158 * bond_length * bond_length).exp();
475 let kinetic = 1.2266f64.mul_add(-overlap, 0.7618);
476 let nuclear_attraction = -1.2266;
477
478 integrals[[0, 0]] = kinetic + nuclear_attraction;
479 integrals[[1, 1]] = kinetic + nuclear_attraction;
480 integrals[[0, 1]] = overlap * (kinetic + nuclear_attraction);
481 integrals[[1, 0]] = integrals[[0, 1]];
482 } else {
483 for i in 0..num_orbitals {
485 integrals[[i, i]] =
486 -0.5 * molecule.atomic_numbers[i.min(molecule.atomic_numbers.len() - 1)] as f64;
487 for j in i + 1..num_orbitals {
488 let distance = if i < molecule.positions.nrows()
489 && j < molecule.positions.nrows()
490 {
491 (molecule.positions[[i, 2]] - molecule.positions[[j, 2]])
492 .mul_add(
493 molecule.positions[[i, 2]] - molecule.positions[[j, 2]],
494 (molecule.positions[[i, 1]] - molecule.positions[[j, 1]]).mul_add(
495 molecule.positions[[i, 1]] - molecule.positions[[j, 1]],
496 (molecule.positions[[i, 0]] - molecule.positions[[j, 0]])
497 .powi(2),
498 ),
499 )
500 .sqrt()
501 } else {
502 1.0
503 };
504 let coupling = -0.1 / (1.0 + distance);
505 integrals[[i, j]] = coupling;
506 integrals[[j, i]] = coupling;
507 }
508 }
509 }
510
511 Ok(integrals)
512 }
513
514 fn compute_two_electron_integrals(
516 &self,
517 _molecule: &Molecule,
518 num_orbitals: usize,
519 ) -> Result<Array4<f64>> {
520 let mut integrals = Array4::zeros((num_orbitals, num_orbitals, num_orbitals, num_orbitals));
521
522 for i in 0..num_orbitals {
524 for j in 0..num_orbitals {
525 for k in 0..num_orbitals {
526 for l in 0..num_orbitals {
527 if i == j && k == l && i == k {
528 integrals[[i, j, k, l]] = 0.625; } else if (i == j && k == l) || (i == k && j == l) || (i == l && j == k) {
530 integrals[[i, j, k, l]] = 0.125; }
532 }
533 }
534 }
535 }
536
537 Ok(integrals)
538 }
539
540 fn compute_nuclear_repulsion(&self, molecule: &Molecule) -> Result<f64> {
542 let mut nuclear_repulsion = 0.0;
543
544 for i in 0..molecule.atomic_numbers.len() {
545 for j in i + 1..molecule.atomic_numbers.len() {
546 let distance = (molecule.positions[[i, 2]] - molecule.positions[[j, 2]])
547 .mul_add(
548 molecule.positions[[i, 2]] - molecule.positions[[j, 2]],
549 (molecule.positions[[i, 1]] - molecule.positions[[j, 1]]).mul_add(
550 molecule.positions[[i, 1]] - molecule.positions[[j, 1]],
551 (molecule.positions[[i, 0]] - molecule.positions[[j, 0]]).powi(2),
552 ),
553 )
554 .sqrt();
555
556 if distance > 1e-10 {
557 nuclear_repulsion +=
558 (molecule.atomic_numbers[i] * molecule.atomic_numbers[j]) as f64 / distance;
559 } else {
560 return Err(SimulatorError::NumericalError(
561 "Atoms are too close together (distance < 1e-10)".to_string(),
562 ));
563 }
564 }
565 }
566
567 Ok(nuclear_repulsion)
568 }
569
570 fn create_fermionic_hamiltonian(
572 &self,
573 one_electron: &Array2<f64>,
574 two_electron: &Array4<f64>,
575 num_orbitals: usize,
576 ) -> Result<FermionicHamiltonian> {
577 let mut terms = Vec::new();
578
579 for i in 0..num_orbitals {
581 for j in 0..num_orbitals {
582 if one_electron[[i, j]].abs() > 1e-12 {
583 let alpha_term = FermionicString {
585 operators: vec![
586 FermionicOperator::Creation(2 * i),
587 FermionicOperator::Annihilation(2 * j),
588 ],
589 coefficient: Complex64::new(one_electron[[i, j]], 0.0),
590 num_modes: 2 * num_orbitals,
591 };
592 terms.push(alpha_term);
593
594 let beta_term = FermionicString {
596 operators: vec![
597 FermionicOperator::Creation(2 * i + 1),
598 FermionicOperator::Annihilation(2 * j + 1),
599 ],
600 coefficient: Complex64::new(one_electron[[i, j]], 0.0),
601 num_modes: 2 * num_orbitals,
602 };
603 terms.push(beta_term);
604 }
605 }
606 }
607
608 for i in 0..num_orbitals {
610 for j in 0..num_orbitals {
611 for k in 0..num_orbitals {
612 for l in 0..num_orbitals {
613 if two_electron[[i, j, k, l]].abs() > 1e-12 {
614 let coefficient = Complex64::new(0.5 * two_electron[[i, j, k, l]], 0.0);
615
616 if i != j && k != l {
618 let aa_term = FermionicString {
619 operators: vec![
620 FermionicOperator::Creation(2 * i),
621 FermionicOperator::Creation(2 * j),
622 FermionicOperator::Annihilation(2 * l),
623 FermionicOperator::Annihilation(2 * k),
624 ],
625 coefficient,
626 num_modes: 2 * num_orbitals,
627 };
628 terms.push(aa_term);
629 }
630
631 if i != j && k != l {
633 let bb_term = FermionicString {
634 operators: vec![
635 FermionicOperator::Creation(2 * i + 1),
636 FermionicOperator::Creation(2 * j + 1),
637 FermionicOperator::Annihilation(2 * l + 1),
638 FermionicOperator::Annihilation(2 * k + 1),
639 ],
640 coefficient,
641 num_modes: 2 * num_orbitals,
642 };
643 terms.push(bb_term);
644 }
645
646 let ab_term = FermionicString {
648 operators: vec![
649 FermionicOperator::Creation(2 * i),
650 FermionicOperator::Creation(2 * j + 1),
651 FermionicOperator::Annihilation(2 * l + 1),
652 FermionicOperator::Annihilation(2 * k),
653 ],
654 coefficient,
655 num_modes: 2 * num_orbitals,
656 };
657 terms.push(ab_term);
658
659 let ba_term = FermionicString {
661 operators: vec![
662 FermionicOperator::Creation(2 * i + 1),
663 FermionicOperator::Creation(2 * j),
664 FermionicOperator::Annihilation(2 * l),
665 FermionicOperator::Annihilation(2 * k + 1),
666 ],
667 coefficient,
668 num_modes: 2 * num_orbitals,
669 };
670 terms.push(ba_term);
671 }
672 }
673 }
674 }
675 }
676
677 Ok(FermionicHamiltonian {
678 terms,
679 num_modes: 2 * num_orbitals,
680 is_hermitian: true,
681 })
682 }
683
684 fn map_to_pauli_operators(
686 &self,
687 fermionic_ham: &FermionicHamiltonian,
688 num_orbitals: usize,
689 ) -> Result<PauliOperatorSum> {
690 let mut pauli_terms = Vec::new();
691
692 for fermionic_term in &fermionic_ham.terms {
693 let pauli_string = self.fermion_mapper.map_fermionic_string(fermionic_term)?;
694 pauli_terms.push(pauli_string);
695 }
696
697 let num_qubits = num_orbitals * 2;
698 let mut pauli_sum = PauliOperatorSum::new(num_qubits);
699 for term in pauli_terms {
700 pauli_sum.add_term(term)?;
701 }
702 Ok(pauli_sum)
703 }
704
705 fn run_hartree_fock(&mut self) -> Result<()> {
707 let hamiltonian = self.hamiltonian.as_ref().ok_or_else(|| {
708 SimulatorError::InvalidConfiguration("Hamiltonian not constructed".to_string())
709 })?;
710
711 let num_orbitals = hamiltonian.num_orbitals;
712 let num_electrons = hamiltonian.num_electrons;
713
714 let mut density_matrix = Array2::zeros((num_orbitals, num_orbitals));
716 let mut fock_matrix = hamiltonian.one_electron_integrals.clone();
717 let mut scf_energy = 0.0;
718
719 let mut converged = false;
720 let mut iteration = 0;
721
722 while iteration < self.config.max_scf_iterations && !converged {
723 self.build_fock_matrix(&mut fock_matrix, &density_matrix, hamiltonian)?;
725
726 let (_energies, orbitals) = self.diagonalize_fock_matrix(&fock_matrix)?;
728
729 let new_density = self.build_density_matrix(&orbitals, num_electrons)?;
731
732 let new_energy = self.calculate_scf_energy(
734 &new_density,
735 &hamiltonian.one_electron_integrals,
736 &fock_matrix,
737 )?;
738
739 let energy_change = (new_energy - scf_energy).abs();
741 let density_change = (&new_density - &density_matrix).map(|x| x.abs()).sum();
742
743 if energy_change < self.config.convergence_threshold
744 && density_change < self.config.convergence_threshold
745 {
746 converged = true;
747 }
748
749 density_matrix = new_density;
750 scf_energy = new_energy;
751 iteration += 1;
752 }
753
754 let (energies, orbitals) = self.diagonalize_fock_matrix(&fock_matrix)?;
756 let occupations = self.determine_occupations(&energies, num_electrons);
757
758 let molecular_orbitals = MolecularOrbitals {
759 coefficients: orbitals,
760 energies,
761 occupations,
762 num_basis: num_orbitals,
763 num_orbitals,
764 };
765
766 self.hartree_fock = Some(HartreeFockResult {
767 scf_energy: scf_energy + hamiltonian.nuclear_repulsion,
768 molecular_orbitals,
769 density_matrix,
770 fock_matrix,
771 converged,
772 scf_iterations: iteration,
773 });
774
775 Ok(())
776 }
777
778 fn build_fock_matrix(
780 &self,
781 fock: &mut Array2<f64>,
782 density: &Array2<f64>,
783 hamiltonian: &MolecularHamiltonian,
784 ) -> Result<()> {
785 let num_orbitals = hamiltonian.num_orbitals;
786
787 *fock = hamiltonian.one_electron_integrals.clone();
789
790 for i in 0..num_orbitals {
792 for j in 0..num_orbitals {
793 let mut two_electron_contribution = 0.0;
794
795 for k in 0..num_orbitals {
796 for l in 0..num_orbitals {
797 two_electron_contribution +=
799 density[[k, l]] * hamiltonian.two_electron_integrals[[i, j, k, l]];
800
801 two_electron_contribution -= 0.5
803 * density[[k, l]]
804 * hamiltonian.two_electron_integrals[[i, k, j, l]];
805 }
806 }
807
808 fock[[i, j]] += two_electron_contribution;
809 }
810 }
811
812 Ok(())
813 }
814
815 fn diagonalize_fock_matrix(&self, fock: &Array2<f64>) -> Result<(Array1<f64>, Array2<f64>)> {
817 if let Some(ref _backend) = self.backend {
818 use crate::scirs2_integration::{Matrix, MemoryPool, LAPACK};
820 use scirs2_core::Complex64;
821
822 let complex_fock: Array2<Complex64> = fock.mapv(|x| Complex64::new(x, 0.0));
824 let pool = MemoryPool::new();
825 let scirs2_matrix = Matrix::from_array2(&complex_fock.view(), &pool).map_err(|e| {
826 SimulatorError::ComputationError(format!("Failed to create SciRS2 matrix: {e}"))
827 })?;
828
829 let eig_result = LAPACK::eig(&scirs2_matrix).map_err(|e| {
831 SimulatorError::ComputationError(format!("Eigenvalue decomposition failed: {e}"))
832 })?;
833
834 let eigenvalues_complex = eig_result.to_array1().map_err(|e| {
836 SimulatorError::ComputationError(format!("Failed to extract eigenvalues: {e}"))
837 })?;
838
839 let eigenvalues: Array1<f64> = eigenvalues_complex.mapv(|c| c.re);
841
842 let eigenvectors = {
844 #[cfg(feature = "advanced_math")]
845 {
846 let eigenvectors_complex_2d = eig_result.eigenvectors().view();
847 eigenvectors_complex_2d.mapv(|c| c.re)
848 }
849 #[cfg(not(feature = "advanced_math"))]
850 {
851 Array2::eye(fock.nrows())
853 }
854 };
855
856 Ok((eigenvalues, eigenvectors))
857 } else {
858 let n = fock.nrows();
860 let mut eigenvalues = Array1::zeros(n);
861 let mut eigenvectors = Array2::eye(n);
862
863 for i in 0..n {
865 eigenvalues[i] = fock[[i, i]];
867
868 let mut v = Array1::zeros(n);
870 v[i] = 1.0;
871
872 for _ in 0..10 {
873 let new_v = fock.dot(&v);
875 let norm = new_v.norm_l2();
876 if norm > 1e-10 {
877 v = new_v / norm;
878 eigenvalues[i] = v.dot(&fock.dot(&v));
879
880 for j in 0..n {
882 eigenvectors[[j, i]] = v[j];
883 }
884 }
885 }
886 }
887
888 Ok((eigenvalues, eigenvectors))
889 }
890 }
891
892 fn build_density_matrix(
894 &self,
895 orbitals: &Array2<f64>,
896 num_electrons: usize,
897 ) -> Result<Array2<f64>> {
898 let num_orbitals = orbitals.nrows();
899 let mut density = Array2::zeros((num_orbitals, num_orbitals));
900
901 let occupied_orbitals = num_electrons / 2; for i in 0..num_orbitals {
904 for j in 0..num_orbitals {
905 for occ in 0..occupied_orbitals {
906 density[[i, j]] += 2.0 * orbitals[[i, occ]] * orbitals[[j, occ]];
907 }
908 }
909 }
910
911 Ok(density)
912 }
913
914 fn calculate_scf_energy(
916 &self,
917 density: &Array2<f64>,
918 one_electron: &Array2<f64>,
919 fock: &Array2<f64>,
920 ) -> Result<f64> {
921 let mut energy = 0.0;
922
923 for i in 0..density.nrows() {
924 for j in 0..density.ncols() {
925 energy += density[[i, j]] * (one_electron[[i, j]] + fock[[i, j]]);
926 }
927 }
928
929 Ok(0.5 * energy)
930 }
931
932 fn determine_occupations(&self, energies: &Array1<f64>, num_electrons: usize) -> Array1<f64> {
934 let mut occupations = Array1::zeros(energies.len());
935 let mut remaining_electrons = num_electrons;
936
937 let mut orbital_indices: Vec<usize> = (0..energies.len()).collect();
939 orbital_indices.sort_by(|&a, &b| energies[a].partial_cmp(&energies[b]).unwrap());
940
941 for &orbital in &orbital_indices {
943 if remaining_electrons >= 2 {
944 occupations[orbital] = 2.0; remaining_electrons -= 2;
946 } else if remaining_electrons == 1 {
947 occupations[orbital] = 1.0; remaining_electrons -= 1;
949 } else {
950 break;
951 }
952 }
953
954 occupations
955 }
956
957 fn run_vqe(&mut self) -> Result<ElectronicStructureResult> {
959 let vqe_start = std::time::Instant::now();
960
961 if self.hamiltonian.is_none() {
962 return Err(SimulatorError::InvalidConfiguration(
963 "Hamiltonian not constructed".to_string(),
964 ));
965 }
966
967 let nuclear_repulsion = self.hamiltonian.as_ref().unwrap().nuclear_repulsion;
969
970 if self.hartree_fock.is_none() {
971 return Err(SimulatorError::InvalidConfiguration(
972 "Hartree-Fock not converged".to_string(),
973 ));
974 }
975
976 let hf_molecular_orbitals = self
978 .hartree_fock
979 .as_ref()
980 .unwrap()
981 .molecular_orbitals
982 .clone();
983 let hf_density_matrix = self.hartree_fock.as_ref().unwrap().density_matrix.clone();
984
985 let hf_result = self.hartree_fock.as_ref().unwrap();
987 let initial_state = self.prepare_hartree_fock_state(hf_result)?;
988
989 let ansatz_circuit = self.create_ansatz_circuit(&initial_state)?;
991
992 let num_parameters = self.get_ansatz_parameter_count(&ansatz_circuit);
994 self.vqe_optimizer.initialize_parameters(num_parameters);
995
996 let mut best_energy = std::f64::INFINITY;
998 let mut best_state = initial_state;
999 let mut iteration = 0;
1000
1001 while iteration < self.config.vqe_config.max_iterations {
1002 let parameterized_circuit =
1004 self.apply_ansatz_parameters(&ansatz_circuit, &self.vqe_optimizer.parameters)?;
1005
1006 let energy = {
1008 let hamiltonian = self.hamiltonian.as_ref().unwrap();
1009 self.evaluate_energy_expectation(¶meterized_circuit, hamiltonian)?
1010 };
1011
1012 self.vqe_optimizer.history.push(energy);
1014
1015 if energy < best_energy {
1017 best_energy = energy;
1018 best_state = self.get_circuit_final_state(¶meterized_circuit)?;
1019 }
1020
1021 if iteration > 0 {
1023 let energy_change = (energy - self.vqe_optimizer.history[iteration - 1]).abs();
1024 if energy_change < self.config.vqe_config.energy_threshold {
1025 break;
1026 }
1027 }
1028
1029 let hamiltonian_clone = self.hamiltonian.clone().unwrap();
1031 self.update_vqe_parameters(¶meterized_circuit, &hamiltonian_clone)?;
1032
1033 iteration += 1;
1034 }
1035
1036 self.stats.vqe_time_ms = vqe_start.elapsed().as_millis() as f64;
1037 self.stats.circuit_evaluations = iteration;
1038
1039 Ok(ElectronicStructureResult {
1040 ground_state_energy: best_energy + nuclear_repulsion,
1041 molecular_orbitals: hf_molecular_orbitals,
1042 density_matrix: hf_density_matrix.clone(),
1043 dipole_moment: self.calculate_dipole_moment(&hf_density_matrix),
1044 converged: iteration < self.config.vqe_config.max_iterations,
1045 iterations: iteration,
1046 quantum_state: best_state,
1047 vqe_history: self.vqe_optimizer.history.clone(),
1048 stats: self.stats.clone(),
1049 })
1050 }
1051
1052 fn prepare_hartree_fock_state(
1054 &self,
1055 hf_result: &HartreeFockResult,
1056 ) -> Result<Array1<Complex64>> {
1057 let num_qubits = 2 * hf_result.molecular_orbitals.num_orbitals; let mut state = Array1::zeros(1 << num_qubits);
1059
1060 let mut configuration = 0usize;
1062
1063 for i in 0..hf_result.molecular_orbitals.num_orbitals {
1064 if hf_result.molecular_orbitals.occupations[i] >= 1.0 {
1065 configuration |= 1 << (2 * i); }
1067 if hf_result.molecular_orbitals.occupations[i] >= 2.0 {
1068 configuration |= 1 << (2 * i + 1); }
1070 }
1071
1072 state[configuration] = Complex64::new(1.0, 0.0);
1073 Ok(state)
1074 }
1075
1076 fn create_ansatz_circuit(&self, initial_state: &Array1<Complex64>) -> Result<InterfaceCircuit> {
1078 let num_qubits = (initial_state.len() as f64).log2() as usize;
1079 let mut circuit = InterfaceCircuit::new(num_qubits, 0);
1080
1081 match self.config.vqe_config.ansatz {
1082 ChemistryAnsatz::UCCSD => {
1083 self.create_uccsd_ansatz(&mut circuit)?;
1084 }
1085 ChemistryAnsatz::HardwareEfficient => {
1086 self.create_hardware_efficient_ansatz(&mut circuit)?;
1087 }
1088 _ => {
1089 self.create_hardware_efficient_ansatz(&mut circuit)?;
1091 }
1092 }
1093
1094 Ok(circuit)
1095 }
1096
1097 fn create_uccsd_ansatz(&self, circuit: &mut InterfaceCircuit) -> Result<()> {
1099 let num_qubits = circuit.num_qubits;
1100
1101 for i in 0..num_qubits {
1103 for j in i + 1..num_qubits {
1104 let param_idx = self.vqe_optimizer.parameters.len();
1106 let theta = if param_idx < self.vqe_optimizer.parameters.len() {
1107 self.vqe_optimizer.parameters[param_idx]
1108 } else {
1109 (thread_rng().gen::<f64>() - 0.5) * 0.1
1110 };
1111
1112 circuit.add_gate(InterfaceGate::new(InterfaceGateType::RY(theta), vec![i]));
1113 circuit.add_gate(InterfaceGate::new(InterfaceGateType::CNOT, vec![i, j]));
1114 circuit.add_gate(InterfaceGate::new(InterfaceGateType::RY(-theta), vec![j]));
1115 circuit.add_gate(InterfaceGate::new(InterfaceGateType::CNOT, vec![i, j]));
1116 }
1117 }
1118
1119 for i in 0..num_qubits {
1121 for j in i + 1..num_qubits {
1122 for k in j + 1..num_qubits {
1123 for l in k + 1..num_qubits {
1124 circuit.add_gate(InterfaceGate::new(InterfaceGateType::RY(0.0), vec![i]));
1125 circuit.add_gate(InterfaceGate::new(InterfaceGateType::CNOT, vec![i, j]));
1126 circuit.add_gate(InterfaceGate::new(InterfaceGateType::CNOT, vec![j, k]));
1127 circuit.add_gate(InterfaceGate::new(InterfaceGateType::CNOT, vec![k, l]));
1128 circuit.add_gate(InterfaceGate::new(InterfaceGateType::RY(0.0), vec![l]));
1129 circuit.add_gate(InterfaceGate::new(InterfaceGateType::CNOT, vec![k, l]));
1130 circuit.add_gate(InterfaceGate::new(InterfaceGateType::CNOT, vec![j, k]));
1131 circuit.add_gate(InterfaceGate::new(InterfaceGateType::CNOT, vec![i, j]));
1132 }
1133 }
1134 }
1135 }
1136
1137 Ok(())
1138 }
1139
1140 fn create_hardware_efficient_ansatz(&self, circuit: &mut InterfaceCircuit) -> Result<()> {
1142 let num_qubits = circuit.num_qubits;
1143 let num_layers = 3; for layer in 0..num_layers {
1146 for qubit in 0..num_qubits {
1148 circuit.add_gate(InterfaceGate::new(InterfaceGateType::RY(0.0), vec![qubit]));
1149 circuit.add_gate(InterfaceGate::new(InterfaceGateType::RZ(0.0), vec![qubit]));
1150 }
1151
1152 for qubit in 0..num_qubits - 1 {
1154 circuit.add_gate(InterfaceGate::new(
1155 InterfaceGateType::CNOT,
1156 vec![qubit, qubit + 1],
1157 ));
1158 }
1159
1160 if layer % 2 == 1 {
1162 for qubit in 1..num_qubits - 1 {
1163 circuit.add_gate(InterfaceGate::new(
1164 InterfaceGateType::CNOT,
1165 vec![qubit, qubit + 1],
1166 ));
1167 }
1168 }
1169 }
1170
1171 Ok(())
1172 }
1173
1174 fn get_ansatz_parameter_count(&self, circuit: &InterfaceCircuit) -> usize {
1176 let mut count = 0;
1177 for gate in &circuit.gates {
1178 match gate.gate_type {
1179 InterfaceGateType::RX(_) | InterfaceGateType::RY(_) | InterfaceGateType::RZ(_) => {
1180 count += 1;
1181 }
1182 _ => {}
1183 }
1184 }
1185 count
1186 }
1187
1188 fn apply_ansatz_parameters(
1190 &self,
1191 template: &InterfaceCircuit,
1192 parameters: &Array1<f64>,
1193 ) -> Result<InterfaceCircuit> {
1194 let mut circuit = InterfaceCircuit::new(template.num_qubits, 0);
1195 let mut param_index = 0;
1196
1197 for gate in &template.gates {
1198 let new_gate = match gate.gate_type {
1199 InterfaceGateType::RX(_) => {
1200 let param = parameters[param_index];
1201 param_index += 1;
1202 InterfaceGate::new(InterfaceGateType::RX(param), gate.qubits.clone())
1203 }
1204 InterfaceGateType::RY(_) => {
1205 let param = parameters[param_index];
1206 param_index += 1;
1207 InterfaceGate::new(InterfaceGateType::RY(param), gate.qubits.clone())
1208 }
1209 InterfaceGateType::RZ(_) => {
1210 let param = parameters[param_index];
1211 param_index += 1;
1212 InterfaceGate::new(InterfaceGateType::RZ(param), gate.qubits.clone())
1213 }
1214 _ => gate.clone(),
1215 };
1216 circuit.add_gate(new_gate);
1217 }
1218
1219 Ok(circuit)
1220 }
1221
1222 fn evaluate_energy_expectation(
1224 &self,
1225 circuit: &InterfaceCircuit,
1226 hamiltonian: &MolecularHamiltonian,
1227 ) -> Result<f64> {
1228 let final_state = self.get_circuit_final_state(circuit)?;
1230
1231 if let Some(ref pauli_ham) = hamiltonian.pauli_hamiltonian {
1233 self.calculate_pauli_expectation(&final_state, pauli_ham)
1234 } else {
1235 Ok(hamiltonian.one_electron_integrals[[0, 0]])
1237 }
1238 }
1239
1240 fn get_circuit_final_state(&self, circuit: &InterfaceCircuit) -> Result<Array1<Complex64>> {
1242 let mut simulator = StateVectorSimulator::new();
1243 simulator.initialize_state(circuit.num_qubits)?;
1244
1245 simulator.apply_interface_circuit(circuit)?;
1247
1248 Ok(Array1::from_vec(simulator.get_state()))
1249 }
1250
1251 fn calculate_pauli_expectation(
1253 &self,
1254 state: &Array1<Complex64>,
1255 pauli_ham: &PauliOperatorSum,
1256 ) -> Result<f64> {
1257 let mut expectation = 0.0;
1258
1259 for pauli_term in &pauli_ham.terms {
1260 let pauli_expectation = self.calculate_single_pauli_expectation(state, pauli_term)?;
1261 expectation += pauli_expectation.re;
1262 }
1263
1264 Ok(expectation)
1265 }
1266
1267 fn calculate_single_pauli_expectation(
1269 &self,
1270 state: &Array1<Complex64>,
1271 pauli_string: &PauliString,
1272 ) -> Result<Complex64> {
1273 let mut result_state = state.clone();
1276
1277 for (qubit, pauli_op) in pauli_string.operators.iter().enumerate() {
1279 match pauli_op {
1280 PauliOperator::X => {
1281 self.apply_pauli_x(&mut result_state, qubit)?;
1283 }
1284 PauliOperator::Y => {
1285 self.apply_pauli_y(&mut result_state, qubit)?;
1287 }
1288 PauliOperator::Z => {
1289 self.apply_pauli_z(&mut result_state, qubit)?;
1291 }
1292 PauliOperator::I => {
1293 }
1295 }
1296 }
1297
1298 let expectation = state
1300 .iter()
1301 .zip(result_state.iter())
1302 .map(|(a, b)| a.conj() * b)
1303 .sum::<Complex64>();
1304
1305 Ok(expectation * pauli_string.coefficient)
1306 }
1307
1308 fn apply_pauli_x(&self, state: &mut Array1<Complex64>, qubit: usize) -> Result<()> {
1310 let n = state.len();
1311 let bit_mask = 1 << qubit;
1312
1313 for i in 0..n {
1314 if (i & bit_mask) == 0 {
1315 let j = i | bit_mask;
1316 if j < n {
1317 let temp = state[i];
1318 state[i] = state[j];
1319 state[j] = temp;
1320 }
1321 }
1322 }
1323
1324 Ok(())
1325 }
1326
1327 fn apply_pauli_y(&self, state: &mut Array1<Complex64>, qubit: usize) -> Result<()> {
1329 let n = state.len();
1330 let bit_mask = 1 << qubit;
1331
1332 for i in 0..n {
1333 if (i & bit_mask) == 0 {
1334 let j = i | bit_mask;
1335 if j < n {
1336 let temp = state[i];
1337 state[i] = -Complex64::i() * state[j];
1338 state[j] = Complex64::i() * temp;
1339 }
1340 }
1341 }
1342
1343 Ok(())
1344 }
1345
1346 fn apply_pauli_z(&self, state: &mut Array1<Complex64>, qubit: usize) -> Result<()> {
1348 let bit_mask = 1 << qubit;
1349
1350 for i in 0..state.len() {
1351 if (i & bit_mask) != 0 {
1352 state[i] = -state[i];
1353 }
1354 }
1355
1356 Ok(())
1357 }
1358
1359 fn update_vqe_parameters(
1361 &mut self,
1362 circuit: &InterfaceCircuit,
1363 hamiltonian: &MolecularHamiltonian,
1364 ) -> Result<()> {
1365 match self.config.vqe_config.optimizer {
1366 ChemistryOptimizer::GradientDescent => {
1367 self.gradient_descent_update(circuit, hamiltonian)?;
1368 }
1369 ChemistryOptimizer::Adam => {
1370 self.adam_update(circuit, hamiltonian)?;
1371 }
1372 _ => {
1373 self.random_perturbation_update()?;
1375 }
1376 }
1377
1378 self.stats.parameter_updates += 1;
1379 Ok(())
1380 }
1381
1382 fn gradient_descent_update(
1384 &mut self,
1385 circuit: &InterfaceCircuit,
1386 hamiltonian: &MolecularHamiltonian,
1387 ) -> Result<()> {
1388 let gradient = self.compute_parameter_gradient(circuit, hamiltonian)?;
1389
1390 for i in 0..self.vqe_optimizer.parameters.len() {
1391 self.vqe_optimizer.parameters[i] -= self.vqe_optimizer.learning_rate * gradient[i];
1392 }
1393
1394 Ok(())
1395 }
1396
1397 fn compute_parameter_gradient(
1399 &self,
1400 circuit: &InterfaceCircuit,
1401 hamiltonian: &MolecularHamiltonian,
1402 ) -> Result<Array1<f64>> {
1403 let num_params = self.vqe_optimizer.parameters.len();
1404 let mut gradient = Array1::zeros(num_params);
1405 let epsilon = 1e-4;
1406
1407 for i in 0..num_params {
1408 let mut params_plus = self.vqe_optimizer.parameters.clone();
1410 params_plus[i] += epsilon;
1411 let circuit_plus = self.apply_ansatz_parameters(circuit, ¶ms_plus)?;
1412 let energy_plus = self.evaluate_energy_expectation(&circuit_plus, hamiltonian)?;
1413
1414 let mut params_minus = self.vqe_optimizer.parameters.clone();
1416 params_minus[i] -= epsilon;
1417 let circuit_minus = self.apply_ansatz_parameters(circuit, ¶ms_minus)?;
1418 let energy_minus = self.evaluate_energy_expectation(&circuit_minus, hamiltonian)?;
1419
1420 gradient[i] = (energy_plus - energy_minus) / (2.0 * epsilon);
1421 }
1422
1423 Ok(gradient)
1424 }
1425
1426 fn adam_update(
1428 &mut self,
1429 circuit: &InterfaceCircuit,
1430 hamiltonian: &MolecularHamiltonian,
1431 ) -> Result<()> {
1432 let gradient = self.compute_parameter_gradient(circuit, hamiltonian)?;
1433
1434 for i in 0..self.vqe_optimizer.parameters.len() {
1436 self.vqe_optimizer.parameters[i] -= self.vqe_optimizer.learning_rate * gradient[i];
1437 }
1438
1439 Ok(())
1440 }
1441
1442 fn random_perturbation_update(&mut self) -> Result<()> {
1444 for i in 0..self.vqe_optimizer.parameters.len() {
1445 let perturbation = (thread_rng().gen::<f64>() - 0.5) * 0.1;
1446 self.vqe_optimizer.parameters[i] += perturbation;
1447 }
1448 Ok(())
1449 }
1450
1451 fn calculate_dipole_moment(&self, density_matrix: &Array2<f64>) -> Array1<f64> {
1453 let mut dipole = Array1::zeros(3);
1454
1455 if let Some(molecule) = &self.molecule {
1457 for (i, &atomic_number) in molecule.atomic_numbers.iter().enumerate() {
1459 if i < molecule.positions.nrows() {
1460 dipole[0] += atomic_number as f64 * molecule.positions[[i, 0]];
1461 dipole[1] += atomic_number as f64 * molecule.positions[[i, 1]];
1462 dipole[2] += atomic_number as f64 * molecule.positions[[i, 2]];
1463 }
1464 }
1465
1466 let num_orbitals = density_matrix.nrows();
1469 for i in 0..num_orbitals {
1470 for j in 0..num_orbitals {
1471 let density_element = density_matrix[[i, j]];
1472
1473 if i == j {
1476 let orbital_pos = i as f64 / num_orbitals as f64; dipole[0] -= density_element * orbital_pos;
1479 dipole[1] -= density_element * orbital_pos * 0.5;
1480 dipole[2] -= density_element * orbital_pos * 0.3;
1481 }
1482 }
1483 }
1484 }
1485
1486 dipole
1487 }
1488
1489 fn run_hartree_fock_only(&self) -> Result<ElectronicStructureResult> {
1491 let hf_result = self.hartree_fock.as_ref().unwrap();
1492
1493 Ok(ElectronicStructureResult {
1494 ground_state_energy: hf_result.scf_energy,
1495 molecular_orbitals: hf_result.molecular_orbitals.clone(),
1496 density_matrix: hf_result.density_matrix.clone(),
1497 dipole_moment: self.calculate_dipole_moment(&hf_result.density_matrix),
1498 converged: hf_result.converged,
1499 iterations: hf_result.scf_iterations,
1500 quantum_state: Array1::zeros(1),
1501 vqe_history: Vec::new(),
1502 stats: self.stats.clone(),
1503 })
1504 }
1505
1506 fn run_quantum_ci(&mut self) -> Result<ElectronicStructureResult> {
1507 let original_ansatz = self.config.vqe_config.ansatz;
1509
1510 self.config.vqe_config.ansatz = ChemistryAnsatz::Adaptive;
1512
1513 let original_threshold = self.config.vqe_config.energy_threshold;
1515 self.config.vqe_config.energy_threshold = original_threshold * 0.1; let result = self.run_vqe();
1518
1519 self.config.vqe_config.ansatz = original_ansatz;
1521 self.config.vqe_config.energy_threshold = original_threshold;
1522
1523 result
1524 }
1525
1526 fn run_quantum_coupled_cluster(&mut self) -> Result<ElectronicStructureResult> {
1527 let original_ansatz = self.config.vqe_config.ansatz;
1529 let original_optimizer = self.config.vqe_config.optimizer;
1530
1531 self.config.vqe_config.ansatz = ChemistryAnsatz::UCCSD;
1533 self.config.vqe_config.optimizer = ChemistryOptimizer::Adam;
1534
1535 let num_orbitals = if let Some(hf) = &self.hartree_fock {
1537 hf.molecular_orbitals.num_orbitals
1538 } else {
1539 4 };
1541
1542 let num_singles = num_orbitals * num_orbitals;
1544 let num_doubles = (num_orbitals * (num_orbitals - 1) / 2).pow(2);
1545 let total_params = num_singles + num_doubles;
1546
1547 self.vqe_optimizer.initialize_parameters(total_params);
1548
1549 let result = self.run_vqe();
1550
1551 self.config.vqe_config.ansatz = original_ansatz;
1553 self.config.vqe_config.optimizer = original_optimizer;
1554
1555 result
1556 }
1557
1558 fn run_quantum_phase_estimation(&mut self) -> Result<ElectronicStructureResult> {
1559 if let (Some(hamiltonian), Some(hf)) = (&self.hamiltonian, &self.hartree_fock) {
1563 let num_qubits = hamiltonian.num_orbitals * 2; let ancilla_qubits = 8; let mut qpe_circuit = InterfaceCircuit::new(num_qubits + ancilla_qubits, 0);
1568
1569 for i in 0..ancilla_qubits {
1571 qpe_circuit.add_gate(InterfaceGate::new(InterfaceGateType::Hadamard, vec![i]));
1572 }
1573
1574 self.prepare_qpe_hartree_fock_state(&mut qpe_circuit, ancilla_qubits)?;
1576
1577 for i in 0..ancilla_qubits {
1579 let time_factor = 2.0_f64.powi(i as i32);
1580 self.apply_controlled_hamiltonian_evolution(&mut qpe_circuit, i, time_factor)?;
1581 }
1582
1583 self.apply_inverse_qft(&mut qpe_circuit, 0, ancilla_qubits)?;
1585
1586 let final_state = self.get_circuit_final_state(&qpe_circuit)?;
1588 let energy_estimate =
1589 self.extract_energy_from_qpe_state(&final_state, ancilla_qubits)?;
1590
1591 Ok(ElectronicStructureResult {
1592 ground_state_energy: energy_estimate,
1593 molecular_orbitals: hf.molecular_orbitals.clone(),
1594 density_matrix: hf.density_matrix.clone(),
1595 dipole_moment: self
1596 .fermion_mapper
1597 .calculate_dipole_moment(&hf.density_matrix)?,
1598 converged: true, iterations: 1,
1600 quantum_state: final_state,
1601 vqe_history: Vec::new(),
1602 stats: self.stats.clone(),
1603 })
1604 } else {
1605 self.run_vqe()
1607 }
1608 }
1609
1610 fn prepare_qpe_hartree_fock_state(
1612 &self,
1613 circuit: &mut InterfaceCircuit,
1614 offset: usize,
1615 ) -> Result<()> {
1616 if let Some(hf) = &self.hartree_fock {
1617 let num_electrons = if let Some(molecule) = &self.molecule {
1619 molecule.atomic_numbers.iter().sum::<u32>() as usize - molecule.charge as usize
1620 } else {
1621 2
1622 };
1623 let num_orbitals = hf.molecular_orbitals.num_orbitals;
1624
1625 for i in 0..num_electrons.min(num_orbitals) {
1627 circuit.add_gate(InterfaceGate::new(
1628 InterfaceGateType::PauliX,
1629 vec![offset + i],
1630 ));
1631 }
1632 }
1633 Ok(())
1634 }
1635
1636 fn apply_controlled_hamiltonian_evolution(
1638 &self,
1639 circuit: &mut InterfaceCircuit,
1640 control: usize,
1641 time: f64,
1642 ) -> Result<()> {
1643 if let Some(hamiltonian) = &self.hamiltonian {
1645 for i in 0..hamiltonian.num_orbitals {
1647 let angle = time * hamiltonian.one_electron_integrals[[i, i]];
1648 circuit.add_gate(InterfaceGate::new(
1649 InterfaceGateType::CRZ(angle),
1650 vec![control, circuit.num_qubits - hamiltonian.num_orbitals + i],
1651 ));
1652 }
1653 }
1654 Ok(())
1655 }
1656
1657 fn apply_inverse_qft(
1659 &self,
1660 circuit: &mut InterfaceCircuit,
1661 start: usize,
1662 num_qubits: usize,
1663 ) -> Result<()> {
1664 for i in 0..num_qubits {
1666 let qubit = start + i;
1667
1668 for j in (0..i).rev() {
1670 let control = start + j;
1671 let angle = -PI / 2.0_f64.powi((i - j) as i32);
1672 circuit.add_gate(InterfaceGate::new(
1673 InterfaceGateType::CRZ(angle),
1674 vec![control, qubit],
1675 ));
1676 }
1677
1678 circuit.add_gate(InterfaceGate::new(InterfaceGateType::Hadamard, vec![qubit]));
1680 }
1681
1682 Ok(())
1684 }
1685
1686 fn extract_energy_from_qpe_state(
1688 &self,
1689 state: &Array1<Complex64>,
1690 ancilla_qubits: usize,
1691 ) -> Result<f64> {
1692 let ancilla_states = 1 << ancilla_qubits;
1694 let system_size = state.len() / ancilla_states;
1695
1696 let mut max_prob = 0.0;
1697 let mut most_likely_phase = 0;
1698
1699 for phase_int in 0..ancilla_states {
1700 let mut prob = 0.0;
1701 for sys_state in 0..system_size {
1702 let idx = phase_int * system_size + sys_state;
1703 if idx < state.len() {
1704 prob += state[idx].norm_sqr();
1705 }
1706 }
1707
1708 if prob > max_prob {
1709 max_prob = prob;
1710 most_likely_phase = phase_int;
1711 }
1712 }
1713
1714 let phase = most_likely_phase as f64 / ancilla_states as f64;
1716 let energy = phase * 2.0 * PI; Ok(energy)
1719 }
1720
1721 pub fn construct_molecular_hamiltonian_public(&mut self, molecule: &Molecule) -> Result<()> {
1723 self.construct_molecular_hamiltonian(molecule)
1724 }
1725
1726 pub const fn get_molecule(&self) -> Option<&Molecule> {
1728 self.molecule.as_ref()
1729 }
1730
1731 pub fn compute_one_electron_integrals_public(
1733 &self,
1734 molecule: &Molecule,
1735 num_orbitals: usize,
1736 ) -> Result<Array2<f64>> {
1737 self.compute_one_electron_integrals(molecule, num_orbitals)
1738 }
1739
1740 pub fn compute_two_electron_integrals_public(
1742 &self,
1743 molecule: &Molecule,
1744 num_orbitals: usize,
1745 ) -> Result<Array4<f64>> {
1746 self.compute_two_electron_integrals(molecule, num_orbitals)
1747 }
1748
1749 pub fn compute_nuclear_repulsion_public(&self, molecule: &Molecule) -> Result<f64> {
1751 self.compute_nuclear_repulsion(molecule)
1752 }
1753
1754 pub fn create_fermionic_hamiltonian_public(
1756 &self,
1757 one_electron: &Array2<f64>,
1758 two_electron: &Array4<f64>,
1759 num_orbitals: usize,
1760 ) -> Result<FermionicHamiltonian> {
1761 self.create_fermionic_hamiltonian(one_electron, two_electron, num_orbitals)
1762 }
1763
1764 pub fn get_ansatz_parameter_count_public(&self, circuit: &InterfaceCircuit) -> usize {
1766 self.get_ansatz_parameter_count(circuit)
1767 }
1768
1769 pub fn build_density_matrix_public(
1771 &self,
1772 orbitals: &Array2<f64>,
1773 num_electrons: usize,
1774 ) -> Result<Array2<f64>> {
1775 self.build_density_matrix(orbitals, num_electrons)
1776 }
1777
1778 pub fn apply_pauli_x_public(&self, state: &mut Array1<Complex64>, qubit: usize) -> Result<()> {
1780 self.apply_pauli_x(state, qubit)
1781 }
1782
1783 pub fn apply_pauli_z_public(&self, state: &mut Array1<Complex64>, qubit: usize) -> Result<()> {
1785 self.apply_pauli_z(state, qubit)
1786 }
1787}
1788
1789impl FermionMapper {
1790 pub fn new(method: FermionMapping, num_spin_orbitals: usize) -> Self {
1791 Self {
1792 method,
1793 num_spin_orbitals,
1794 mapping_cache: HashMap::new(),
1795 }
1796 }
1797
1798 fn map_fermionic_string(&self, fermionic_string: &FermionicString) -> Result<PauliString> {
1799 let mut paulis = HashMap::new();
1801
1802 for (i, operator) in fermionic_string.operators.iter().enumerate() {
1803 match operator {
1804 FermionicOperator::Creation(site) => {
1805 paulis.insert(*site, PauliOperator::X);
1807 }
1808 FermionicOperator::Annihilation(site) => {
1809 paulis.insert(*site, PauliOperator::X);
1811 }
1812 _ => {
1813 paulis.insert(i, PauliOperator::Z);
1815 }
1816 }
1817 }
1818
1819 let mut operators_vec = vec![PauliOperator::I; self.num_spin_orbitals];
1821 for (qubit, op) in paulis {
1822 if qubit < operators_vec.len() {
1823 operators_vec[qubit] = op;
1824 }
1825 }
1826
1827 let num_qubits = operators_vec.len();
1828 Ok(PauliString {
1829 operators: operators_vec,
1830 coefficient: fermionic_string.coefficient,
1831 num_qubits,
1832 })
1833 }
1834
1835 fn calculate_dipole_moment(&self, density_matrix: &Array2<f64>) -> Result<Array1<f64>> {
1837 let mut dipole = Array1::zeros(3);
1838
1839 let num_orbitals = density_matrix.nrows();
1842
1843 for i in 0..num_orbitals {
1844 for j in 0..num_orbitals {
1845 let density_element = density_matrix[[i, j]];
1846
1847 if i == j {
1849 let orbital_pos = i as f64 / num_orbitals as f64;
1851 dipole[0] -= density_element * orbital_pos;
1852 dipole[1] -= density_element * orbital_pos * 0.5;
1853 dipole[2] -= density_element * orbital_pos * 0.3;
1854 }
1855 }
1856 }
1857
1858 Ok(dipole)
1859 }
1860
1861 pub const fn get_method(&self) -> &FermionMapping {
1863 &self.method
1864 }
1865
1866 pub const fn get_num_spin_orbitals(&self) -> usize {
1868 self.num_spin_orbitals
1869 }
1870}
1871
1872impl VQEOptimizer {
1873 pub fn new(method: ChemistryOptimizer) -> Self {
1874 Self {
1875 method,
1876 parameters: Array1::zeros(0),
1877 bounds: Vec::new(),
1878 history: Vec::new(),
1879 gradients: Array1::zeros(0),
1880 learning_rate: 0.01,
1881 }
1882 }
1883
1884 fn initialize_parameters(&mut self, num_parameters: usize) {
1885 self.parameters = Array1::from_vec(
1886 (0..num_parameters)
1887 .map(|_| (thread_rng().gen::<f64>() - 0.5) * 0.1)
1888 .collect(),
1889 );
1890 self.bounds = vec![(-PI, PI); num_parameters];
1891 self.gradients = Array1::zeros(num_parameters);
1892 }
1893
1894 pub fn initialize_parameters_public(&mut self, num_parameters: usize) {
1896 self.initialize_parameters(num_parameters);
1897 }
1898
1899 pub const fn get_parameters(&self) -> &Array1<f64> {
1901 &self.parameters
1902 }
1903
1904 pub fn get_bounds(&self) -> &[(f64, f64)] {
1906 &self.bounds
1907 }
1908
1909 pub const fn get_method(&self) -> &ChemistryOptimizer {
1911 &self.method
1912 }
1913}
1914
1915pub fn benchmark_quantum_chemistry() -> Result<()> {
1917 println!("Benchmarking Quantum Chemistry Simulation...");
1918
1919 let h2_molecule = Molecule {
1921 atomic_numbers: vec![1, 1],
1922 positions: Array2::from_shape_vec((2, 3), vec![0.0, 0.0, 0.0, 0.0, 0.0, 1.4])?,
1923 charge: 0,
1924 multiplicity: 1,
1925 basis_set: "STO-3G".to_string(),
1926 };
1927
1928 let config = ElectronicStructureConfig::default();
1929 let mut simulator = QuantumChemistrySimulator::new(config)?;
1930 simulator.set_molecule(h2_molecule)?;
1931
1932 let start_time = std::time::Instant::now();
1933
1934 let result = simulator.run_calculation()?;
1936
1937 let duration = start_time.elapsed();
1938
1939 println!("✅ Quantum Chemistry Results:");
1940 println!(
1941 " Ground State Energy: {:.6} Hartree",
1942 result.ground_state_energy
1943 );
1944 println!(" Converged: {}", result.converged);
1945 println!(" Iterations: {}", result.iterations);
1946 println!(" Hamiltonian Terms: {}", result.stats.hamiltonian_terms);
1947 println!(
1948 " Circuit Evaluations: {}",
1949 result.stats.circuit_evaluations
1950 );
1951 println!(" Total Time: {:.2}ms", duration.as_millis());
1952 println!(" VQE Time: {:.2}ms", result.stats.vqe_time_ms);
1953
1954 Ok(())
1955}
1956
1957#[cfg(test)]
1958mod tests {
1959 use super::*;
1960
1961 #[test]
1962 fn test_quantum_chemistry_simulator_creation() {
1963 let config = ElectronicStructureConfig::default();
1964 let simulator = QuantumChemistrySimulator::new(config);
1965 assert!(simulator.is_ok());
1966 }
1967
1968 #[test]
1969 fn test_h2_molecule_creation() {
1970 let h2 = Molecule {
1971 atomic_numbers: vec![1, 1],
1972 positions: Array2::from_shape_vec((2, 3), vec![0.0, 0.0, 0.0, 0.0, 0.0, 1.4]).unwrap(),
1973 charge: 0,
1974 multiplicity: 1,
1975 basis_set: "STO-3G".to_string(),
1976 };
1977
1978 assert_eq!(h2.atomic_numbers, vec![1, 1]);
1979 assert_eq!(h2.charge, 0);
1980 assert_eq!(h2.multiplicity, 1);
1981 }
1982
1983 #[test]
1984 fn test_molecular_hamiltonian_construction() {
1985 let config = ElectronicStructureConfig::default();
1986 let mut simulator = QuantumChemistrySimulator::new(config).unwrap();
1987
1988 let h2 = Molecule {
1989 atomic_numbers: vec![1, 1],
1990 positions: Array2::from_shape_vec((2, 3), vec![0.0, 0.0, 0.0, 0.0, 0.0, 1.4]).unwrap(),
1991 charge: 0,
1992 multiplicity: 1,
1993 basis_set: "STO-3G".to_string(),
1994 };
1995
1996 simulator.set_molecule(h2).unwrap();
1997 let molecule_clone = simulator.molecule.clone().unwrap();
1998 let result = simulator.construct_molecular_hamiltonian(&molecule_clone);
1999 assert!(result.is_ok());
2000 }
2001
2002 #[test]
2003 fn test_fermion_mapper_creation() {
2004 let mapper = FermionMapper::new(FermionMapping::JordanWigner, 4);
2005 assert_eq!(mapper.method, FermionMapping::JordanWigner);
2006 assert_eq!(mapper.num_spin_orbitals, 4);
2007 }
2008
2009 #[test]
2010 fn test_vqe_optimizer_initialization() {
2011 let mut optimizer = VQEOptimizer::new(ChemistryOptimizer::GradientDescent);
2012 optimizer.initialize_parameters(10);
2013 assert_eq!(optimizer.parameters.len(), 10);
2014 assert_eq!(optimizer.bounds.len(), 10);
2015 }
2016
2017 #[test]
2018 fn test_ansatz_parameter_counting() {
2019 let config = ElectronicStructureConfig::default();
2020 let simulator = QuantumChemistrySimulator::new(config).unwrap();
2021
2022 let mut circuit = InterfaceCircuit::new(4, 0);
2023 circuit.add_gate(InterfaceGate::new(InterfaceGateType::RY(0.0), vec![0]));
2024 circuit.add_gate(InterfaceGate::new(InterfaceGateType::RZ(0.0), vec![1]));
2025 circuit.add_gate(InterfaceGate::new(InterfaceGateType::CNOT, vec![0, 1]));
2026
2027 let param_count = simulator.get_ansatz_parameter_count(&circuit);
2028 assert_eq!(param_count, 2);
2029 }
2030}