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: 10_000,
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
377 .molecule
378 .clone()
379 .ok_or_else(|| SimulatorError::InvalidConfiguration("Molecule not set".to_string()))?;
380 self.construct_molecular_hamiltonian(&molecule_clone)?;
381 self.stats.hamiltonian_time_ms = hamiltonian_start.elapsed().as_millis() as f64;
382
383 self.run_hartree_fock()?;
385
386 let result = match self.config.method {
388 ElectronicStructureMethod::HartreeFock => self.run_hartree_fock_only(),
389 ElectronicStructureMethod::VQE => self.run_vqe(),
390 ElectronicStructureMethod::QuantumCI => self.run_quantum_ci(),
391 ElectronicStructureMethod::QuantumCC => self.run_quantum_coupled_cluster(),
392 ElectronicStructureMethod::QPE => self.run_quantum_phase_estimation(),
393 }?;
394
395 self.stats.total_time_ms = start_time.elapsed().as_millis() as f64;
396 Ok(result)
397 }
398
399 fn construct_molecular_hamiltonian(&mut self, molecule: &Molecule) -> Result<()> {
401 let num_atoms = molecule.atomic_numbers.len();
402
403 let num_orbitals = if molecule.basis_set == "STO-3G" {
406 num_atoms } else {
408 2 * num_atoms };
410
411 let num_electrons =
412 molecule.atomic_numbers.iter().sum::<u32>() as usize - molecule.charge as usize;
413
414 let one_electron_integrals = self.compute_one_electron_integrals(molecule, num_orbitals)?;
416
417 let two_electron_integrals = self.compute_two_electron_integrals(molecule, num_orbitals)?;
419
420 let nuclear_repulsion = self.compute_nuclear_repulsion(molecule)?;
422
423 let fermionic_hamiltonian = self.create_fermionic_hamiltonian(
425 &one_electron_integrals,
426 &two_electron_integrals,
427 num_orbitals,
428 )?;
429
430 self.fermion_mapper = FermionMapper::new(self.fermion_mapper.method, num_orbitals * 2);
432
433 let pauli_hamiltonian = if self.config.enable_second_quantization_optimization {
435 Some(self.map_to_pauli_operators(&fermionic_hamiltonian, num_orbitals)?)
436 } else {
437 None
438 };
439
440 self.hamiltonian = Some(MolecularHamiltonian {
441 one_electron_integrals,
442 two_electron_integrals,
443 nuclear_repulsion,
444 num_orbitals,
445 num_electrons,
446 fermionic_hamiltonian,
447 pauli_hamiltonian,
448 });
449
450 Ok(())
451 }
452
453 fn compute_one_electron_integrals(
455 &self,
456 molecule: &Molecule,
457 num_orbitals: usize,
458 ) -> Result<Array2<f64>> {
459 let mut integrals = Array2::zeros((num_orbitals, num_orbitals));
460
461 if molecule.atomic_numbers.len() == 2
463 && molecule.atomic_numbers[0] == 1
464 && molecule.atomic_numbers[1] == 1
465 {
466 let bond_length = (molecule.positions[[0, 2]] - molecule.positions[[1, 2]])
467 .mul_add(
468 molecule.positions[[0, 2]] - molecule.positions[[1, 2]],
469 (molecule.positions[[0, 1]] - molecule.positions[[1, 1]]).mul_add(
470 molecule.positions[[0, 1]] - molecule.positions[[1, 1]],
471 (molecule.positions[[0, 0]] - molecule.positions[[1, 0]]).powi(2),
472 ),
473 )
474 .sqrt();
475
476 let overlap = 0.6593 * (-0.1158 * bond_length * bond_length).exp();
478 let kinetic = 1.2266f64.mul_add(-overlap, 0.7618);
479 let nuclear_attraction = -1.2266;
480
481 integrals[[0, 0]] = kinetic + nuclear_attraction;
482 integrals[[1, 1]] = kinetic + nuclear_attraction;
483 integrals[[0, 1]] = overlap * (kinetic + nuclear_attraction);
484 integrals[[1, 0]] = integrals[[0, 1]];
485 } else {
486 for i in 0..num_orbitals {
488 integrals[[i, i]] = -0.5
489 * f64::from(molecule.atomic_numbers[i.min(molecule.atomic_numbers.len() - 1)]);
490 for j in i + 1..num_orbitals {
491 let distance = if i < molecule.positions.nrows()
492 && j < molecule.positions.nrows()
493 {
494 (molecule.positions[[i, 2]] - molecule.positions[[j, 2]])
495 .mul_add(
496 molecule.positions[[i, 2]] - molecule.positions[[j, 2]],
497 (molecule.positions[[i, 1]] - molecule.positions[[j, 1]]).mul_add(
498 molecule.positions[[i, 1]] - molecule.positions[[j, 1]],
499 (molecule.positions[[i, 0]] - molecule.positions[[j, 0]])
500 .powi(2),
501 ),
502 )
503 .sqrt()
504 } else {
505 1.0
506 };
507 let coupling = -0.1 / (1.0 + distance);
508 integrals[[i, j]] = coupling;
509 integrals[[j, i]] = coupling;
510 }
511 }
512 }
513
514 Ok(integrals)
515 }
516
517 fn compute_two_electron_integrals(
519 &self,
520 _molecule: &Molecule,
521 num_orbitals: usize,
522 ) -> Result<Array4<f64>> {
523 let mut integrals = Array4::zeros((num_orbitals, num_orbitals, num_orbitals, num_orbitals));
524
525 for i in 0..num_orbitals {
527 for j in 0..num_orbitals {
528 for k in 0..num_orbitals {
529 for l in 0..num_orbitals {
530 if i == j && k == l && i == k {
531 integrals[[i, j, k, l]] = 0.625; } else if (i == j && k == l) || (i == k && j == l) || (i == l && j == k) {
533 integrals[[i, j, k, l]] = 0.125; }
535 }
536 }
537 }
538 }
539
540 Ok(integrals)
541 }
542
543 fn compute_nuclear_repulsion(&self, molecule: &Molecule) -> Result<f64> {
545 let mut nuclear_repulsion = 0.0;
546
547 for i in 0..molecule.atomic_numbers.len() {
548 for j in i + 1..molecule.atomic_numbers.len() {
549 let distance = (molecule.positions[[i, 2]] - molecule.positions[[j, 2]])
550 .mul_add(
551 molecule.positions[[i, 2]] - molecule.positions[[j, 2]],
552 (molecule.positions[[i, 1]] - molecule.positions[[j, 1]]).mul_add(
553 molecule.positions[[i, 1]] - molecule.positions[[j, 1]],
554 (molecule.positions[[i, 0]] - molecule.positions[[j, 0]]).powi(2),
555 ),
556 )
557 .sqrt();
558
559 if distance > 1e-10 {
560 nuclear_repulsion +=
561 f64::from(molecule.atomic_numbers[i] * molecule.atomic_numbers[j])
562 / distance;
563 } else {
564 return Err(SimulatorError::NumericalError(
565 "Atoms are too close together (distance < 1e-10)".to_string(),
566 ));
567 }
568 }
569 }
570
571 Ok(nuclear_repulsion)
572 }
573
574 fn create_fermionic_hamiltonian(
576 &self,
577 one_electron: &Array2<f64>,
578 two_electron: &Array4<f64>,
579 num_orbitals: usize,
580 ) -> Result<FermionicHamiltonian> {
581 let mut terms = Vec::new();
582
583 for i in 0..num_orbitals {
585 for j in 0..num_orbitals {
586 if one_electron[[i, j]].abs() > 1e-12 {
587 let alpha_term = FermionicString {
589 operators: vec![
590 FermionicOperator::Creation(2 * i),
591 FermionicOperator::Annihilation(2 * j),
592 ],
593 coefficient: Complex64::new(one_electron[[i, j]], 0.0),
594 num_modes: 2 * num_orbitals,
595 };
596 terms.push(alpha_term);
597
598 let beta_term = FermionicString {
600 operators: vec![
601 FermionicOperator::Creation(2 * i + 1),
602 FermionicOperator::Annihilation(2 * j + 1),
603 ],
604 coefficient: Complex64::new(one_electron[[i, j]], 0.0),
605 num_modes: 2 * num_orbitals,
606 };
607 terms.push(beta_term);
608 }
609 }
610 }
611
612 for i in 0..num_orbitals {
614 for j in 0..num_orbitals {
615 for k in 0..num_orbitals {
616 for l in 0..num_orbitals {
617 if two_electron[[i, j, k, l]].abs() > 1e-12 {
618 let coefficient = Complex64::new(0.5 * two_electron[[i, j, k, l]], 0.0);
619
620 if i != j && k != l {
622 let aa_term = FermionicString {
623 operators: vec![
624 FermionicOperator::Creation(2 * i),
625 FermionicOperator::Creation(2 * j),
626 FermionicOperator::Annihilation(2 * l),
627 FermionicOperator::Annihilation(2 * k),
628 ],
629 coefficient,
630 num_modes: 2 * num_orbitals,
631 };
632 terms.push(aa_term);
633 }
634
635 if i != j && k != l {
637 let bb_term = FermionicString {
638 operators: vec![
639 FermionicOperator::Creation(2 * i + 1),
640 FermionicOperator::Creation(2 * j + 1),
641 FermionicOperator::Annihilation(2 * l + 1),
642 FermionicOperator::Annihilation(2 * k + 1),
643 ],
644 coefficient,
645 num_modes: 2 * num_orbitals,
646 };
647 terms.push(bb_term);
648 }
649
650 let ab_term = FermionicString {
652 operators: vec![
653 FermionicOperator::Creation(2 * i),
654 FermionicOperator::Creation(2 * j + 1),
655 FermionicOperator::Annihilation(2 * l + 1),
656 FermionicOperator::Annihilation(2 * k),
657 ],
658 coefficient,
659 num_modes: 2 * num_orbitals,
660 };
661 terms.push(ab_term);
662
663 let ba_term = FermionicString {
665 operators: vec![
666 FermionicOperator::Creation(2 * i + 1),
667 FermionicOperator::Creation(2 * j),
668 FermionicOperator::Annihilation(2 * l),
669 FermionicOperator::Annihilation(2 * k + 1),
670 ],
671 coefficient,
672 num_modes: 2 * num_orbitals,
673 };
674 terms.push(ba_term);
675 }
676 }
677 }
678 }
679 }
680
681 Ok(FermionicHamiltonian {
682 terms,
683 num_modes: 2 * num_orbitals,
684 is_hermitian: true,
685 })
686 }
687
688 fn map_to_pauli_operators(
690 &self,
691 fermionic_ham: &FermionicHamiltonian,
692 num_orbitals: usize,
693 ) -> Result<PauliOperatorSum> {
694 let mut pauli_terms = Vec::new();
695
696 for fermionic_term in &fermionic_ham.terms {
697 let pauli_string = self.fermion_mapper.map_fermionic_string(fermionic_term)?;
698 pauli_terms.push(pauli_string);
699 }
700
701 let num_qubits = num_orbitals * 2;
702 let mut pauli_sum = PauliOperatorSum::new(num_qubits);
703 for term in pauli_terms {
704 pauli_sum.add_term(term)?;
705 }
706 Ok(pauli_sum)
707 }
708
709 fn run_hartree_fock(&mut self) -> Result<()> {
711 let hamiltonian = self.hamiltonian.as_ref().ok_or_else(|| {
712 SimulatorError::InvalidConfiguration("Hamiltonian not constructed".to_string())
713 })?;
714
715 let num_orbitals = hamiltonian.num_orbitals;
716 let num_electrons = hamiltonian.num_electrons;
717
718 let mut density_matrix = Array2::zeros((num_orbitals, num_orbitals));
720 let mut fock_matrix = hamiltonian.one_electron_integrals.clone();
721 let mut scf_energy = 0.0;
722
723 let mut converged = false;
724 let mut iteration = 0;
725
726 while iteration < self.config.max_scf_iterations && !converged {
727 self.build_fock_matrix(&mut fock_matrix, &density_matrix, hamiltonian)?;
729
730 let (_energies, orbitals) = self.diagonalize_fock_matrix(&fock_matrix)?;
732
733 let new_density = self.build_density_matrix(&orbitals, num_electrons)?;
735
736 let new_energy = self.calculate_scf_energy(
738 &new_density,
739 &hamiltonian.one_electron_integrals,
740 &fock_matrix,
741 )?;
742
743 let energy_change = (new_energy - scf_energy).abs();
745 let density_change = (&new_density - &density_matrix).map(|x| x.abs()).sum();
746
747 if energy_change < self.config.convergence_threshold
748 && density_change < self.config.convergence_threshold
749 {
750 converged = true;
751 }
752
753 density_matrix = new_density;
754 scf_energy = new_energy;
755 iteration += 1;
756 }
757
758 let (energies, orbitals) = self.diagonalize_fock_matrix(&fock_matrix)?;
760 let occupations = self.determine_occupations(&energies, num_electrons);
761
762 let molecular_orbitals = MolecularOrbitals {
763 coefficients: orbitals,
764 energies,
765 occupations,
766 num_basis: num_orbitals,
767 num_orbitals,
768 };
769
770 self.hartree_fock = Some(HartreeFockResult {
771 scf_energy: scf_energy + hamiltonian.nuclear_repulsion,
772 molecular_orbitals,
773 density_matrix,
774 fock_matrix,
775 converged,
776 scf_iterations: iteration,
777 });
778
779 Ok(())
780 }
781
782 fn build_fock_matrix(
784 &self,
785 fock: &mut Array2<f64>,
786 density: &Array2<f64>,
787 hamiltonian: &MolecularHamiltonian,
788 ) -> Result<()> {
789 let num_orbitals = hamiltonian.num_orbitals;
790
791 fock.clone_from(&hamiltonian.one_electron_integrals);
793
794 for i in 0..num_orbitals {
796 for j in 0..num_orbitals {
797 let mut two_electron_contribution = 0.0;
798
799 for k in 0..num_orbitals {
800 for l in 0..num_orbitals {
801 two_electron_contribution +=
803 density[[k, l]] * hamiltonian.two_electron_integrals[[i, j, k, l]];
804
805 two_electron_contribution -= 0.5
807 * density[[k, l]]
808 * hamiltonian.two_electron_integrals[[i, k, j, l]];
809 }
810 }
811
812 fock[[i, j]] += two_electron_contribution;
813 }
814 }
815
816 Ok(())
817 }
818
819 fn diagonalize_fock_matrix(&self, fock: &Array2<f64>) -> Result<(Array1<f64>, Array2<f64>)> {
821 if let Some(ref _backend) = self.backend {
822 use crate::scirs2_integration::{Matrix, MemoryPool, LAPACK};
824 use scirs2_core::Complex64;
825
826 let complex_fock: Array2<Complex64> = fock.mapv(|x| Complex64::new(x, 0.0));
828 let pool = MemoryPool::new();
829 let scirs2_matrix = Matrix::from_array2(&complex_fock.view(), &pool).map_err(|e| {
830 SimulatorError::ComputationError(format!("Failed to create SciRS2 matrix: {e}"))
831 })?;
832
833 let eig_result = LAPACK::eig(&scirs2_matrix).map_err(|e| {
835 SimulatorError::ComputationError(format!("Eigenvalue decomposition failed: {e}"))
836 })?;
837
838 let eigenvalues_complex = eig_result.to_array1().map_err(|e| {
840 SimulatorError::ComputationError(format!("Failed to extract eigenvalues: {e}"))
841 })?;
842
843 let eigenvalues: Array1<f64> = eigenvalues_complex.mapv(|c| c.re);
845
846 let eigenvectors = {
848 #[cfg(feature = "advanced_math")]
849 {
850 let eigenvectors_complex_2d = eig_result.eigenvectors().view();
851 eigenvectors_complex_2d.mapv(|c| c.re)
852 }
853 #[cfg(not(feature = "advanced_math"))]
854 {
855 Array2::eye(fock.nrows())
857 }
858 };
859
860 Ok((eigenvalues, eigenvectors))
861 } else {
862 let n = fock.nrows();
864 let mut eigenvalues = Array1::zeros(n);
865 let mut eigenvectors = Array2::eye(n);
866
867 for i in 0..n {
869 eigenvalues[i] = fock[[i, i]];
871
872 let mut v = Array1::zeros(n);
874 v[i] = 1.0;
875
876 for _ in 0..10 {
877 let new_v = fock.dot(&v);
879 let norm = new_v.norm_l2();
880 if norm > 1e-10 {
881 v = new_v / norm;
882 eigenvalues[i] = v.dot(&fock.dot(&v));
883
884 for j in 0..n {
886 eigenvectors[[j, i]] = v[j];
887 }
888 }
889 }
890 }
891
892 Ok((eigenvalues, eigenvectors))
893 }
894 }
895
896 fn build_density_matrix(
898 &self,
899 orbitals: &Array2<f64>,
900 num_electrons: usize,
901 ) -> Result<Array2<f64>> {
902 let num_orbitals = orbitals.nrows();
903 let mut density = Array2::zeros((num_orbitals, num_orbitals));
904
905 let occupied_orbitals = num_electrons / 2; for i in 0..num_orbitals {
908 for j in 0..num_orbitals {
909 for occ in 0..occupied_orbitals {
910 density[[i, j]] += 2.0 * orbitals[[i, occ]] * orbitals[[j, occ]];
911 }
912 }
913 }
914
915 Ok(density)
916 }
917
918 fn calculate_scf_energy(
920 &self,
921 density: &Array2<f64>,
922 one_electron: &Array2<f64>,
923 fock: &Array2<f64>,
924 ) -> Result<f64> {
925 let mut energy = 0.0;
926
927 for i in 0..density.nrows() {
928 for j in 0..density.ncols() {
929 energy += density[[i, j]] * (one_electron[[i, j]] + fock[[i, j]]);
930 }
931 }
932
933 Ok(0.5 * energy)
934 }
935
936 fn determine_occupations(&self, energies: &Array1<f64>, num_electrons: usize) -> Array1<f64> {
938 let mut occupations = Array1::zeros(energies.len());
939 let mut remaining_electrons = num_electrons;
940
941 let mut orbital_indices: Vec<usize> = (0..energies.len()).collect();
943 orbital_indices.sort_by(|&a, &b| {
944 energies[a]
945 .partial_cmp(&energies[b])
946 .unwrap_or(std::cmp::Ordering::Equal)
947 });
948
949 for &orbital in &orbital_indices {
951 if remaining_electrons >= 2 {
952 occupations[orbital] = 2.0; remaining_electrons -= 2;
954 } else if remaining_electrons == 1 {
955 occupations[orbital] = 1.0; remaining_electrons -= 1;
957 } else {
958 break;
959 }
960 }
961
962 occupations
963 }
964
965 fn run_vqe(&mut self) -> Result<ElectronicStructureResult> {
967 let vqe_start = std::time::Instant::now();
968
969 let hamiltonian = self.hamiltonian.as_ref().ok_or_else(|| {
971 SimulatorError::InvalidConfiguration("Hamiltonian not constructed".to_string())
972 })?;
973 let nuclear_repulsion = hamiltonian.nuclear_repulsion;
974
975 let hf = self.hartree_fock.as_ref().ok_or_else(|| {
976 SimulatorError::InvalidConfiguration("Hartree-Fock not converged".to_string())
977 })?;
978
979 let hf_molecular_orbitals = hf.molecular_orbitals.clone();
981 let hf_density_matrix = hf.density_matrix.clone();
982
983 let hf_result = self.hartree_fock.as_ref().ok_or_else(|| {
985 SimulatorError::InvalidConfiguration("Hartree-Fock not converged".to_string())
986 })?;
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().ok_or_else(|| {
1009 SimulatorError::InvalidConfiguration("Hamiltonian not available".to_string())
1010 })?;
1011 self.evaluate_energy_expectation(¶meterized_circuit, hamiltonian)?
1012 };
1013
1014 self.vqe_optimizer.history.push(energy);
1016
1017 if energy < best_energy {
1019 best_energy = energy;
1020 best_state = self.get_circuit_final_state(¶meterized_circuit)?;
1021 }
1022
1023 if iteration > 0 {
1025 let energy_change = (energy - self.vqe_optimizer.history[iteration - 1]).abs();
1026 if energy_change < self.config.vqe_config.energy_threshold {
1027 break;
1028 }
1029 }
1030
1031 let hamiltonian_clone = self.hamiltonian.clone().ok_or_else(|| {
1033 SimulatorError::InvalidConfiguration("Hamiltonian not available".to_string())
1034 })?;
1035 self.update_vqe_parameters(¶meterized_circuit, &hamiltonian_clone)?;
1036
1037 iteration += 1;
1038 }
1039
1040 self.stats.vqe_time_ms = vqe_start.elapsed().as_millis() as f64;
1041 self.stats.circuit_evaluations = iteration;
1042
1043 Ok(ElectronicStructureResult {
1044 ground_state_energy: best_energy + nuclear_repulsion,
1045 molecular_orbitals: hf_molecular_orbitals,
1046 density_matrix: hf_density_matrix.clone(),
1047 dipole_moment: self.calculate_dipole_moment(&hf_density_matrix),
1048 converged: iteration < self.config.vqe_config.max_iterations,
1049 iterations: iteration,
1050 quantum_state: best_state,
1051 vqe_history: self.vqe_optimizer.history.clone(),
1052 stats: self.stats.clone(),
1053 })
1054 }
1055
1056 fn prepare_hartree_fock_state(
1058 &self,
1059 hf_result: &HartreeFockResult,
1060 ) -> Result<Array1<Complex64>> {
1061 let num_qubits = 2 * hf_result.molecular_orbitals.num_orbitals; let mut state = Array1::zeros(1 << num_qubits);
1063
1064 let mut configuration = 0usize;
1066
1067 for i in 0..hf_result.molecular_orbitals.num_orbitals {
1068 if hf_result.molecular_orbitals.occupations[i] >= 1.0 {
1069 configuration |= 1 << (2 * i); }
1071 if hf_result.molecular_orbitals.occupations[i] >= 2.0 {
1072 configuration |= 1 << (2 * i + 1); }
1074 }
1075
1076 state[configuration] = Complex64::new(1.0, 0.0);
1077 Ok(state)
1078 }
1079
1080 fn create_ansatz_circuit(&self, initial_state: &Array1<Complex64>) -> Result<InterfaceCircuit> {
1082 let num_qubits = (initial_state.len() as f64).log2() as usize;
1083 let mut circuit = InterfaceCircuit::new(num_qubits, 0);
1084
1085 match self.config.vqe_config.ansatz {
1086 ChemistryAnsatz::UCCSD => {
1087 self.create_uccsd_ansatz(&mut circuit)?;
1088 }
1089 ChemistryAnsatz::HardwareEfficient => {
1090 self.create_hardware_efficient_ansatz(&mut circuit)?;
1091 }
1092 _ => {
1093 self.create_hardware_efficient_ansatz(&mut circuit)?;
1095 }
1096 }
1097
1098 Ok(circuit)
1099 }
1100
1101 fn create_uccsd_ansatz(&self, circuit: &mut InterfaceCircuit) -> Result<()> {
1103 let num_qubits = circuit.num_qubits;
1104
1105 for i in 0..num_qubits {
1107 for j in i + 1..num_qubits {
1108 let param_idx = self.vqe_optimizer.parameters.len();
1110 let theta = if param_idx < self.vqe_optimizer.parameters.len() {
1111 self.vqe_optimizer.parameters[param_idx]
1112 } else {
1113 (thread_rng().gen::<f64>() - 0.5) * 0.1
1114 };
1115
1116 circuit.add_gate(InterfaceGate::new(InterfaceGateType::RY(theta), vec![i]));
1117 circuit.add_gate(InterfaceGate::new(InterfaceGateType::CNOT, vec![i, j]));
1118 circuit.add_gate(InterfaceGate::new(InterfaceGateType::RY(-theta), vec![j]));
1119 circuit.add_gate(InterfaceGate::new(InterfaceGateType::CNOT, vec![i, j]));
1120 }
1121 }
1122
1123 for i in 0..num_qubits {
1125 for j in i + 1..num_qubits {
1126 for k in j + 1..num_qubits {
1127 for l in k + 1..num_qubits {
1128 circuit.add_gate(InterfaceGate::new(InterfaceGateType::RY(0.0), vec![i]));
1129 circuit.add_gate(InterfaceGate::new(InterfaceGateType::CNOT, vec![i, j]));
1130 circuit.add_gate(InterfaceGate::new(InterfaceGateType::CNOT, vec![j, k]));
1131 circuit.add_gate(InterfaceGate::new(InterfaceGateType::CNOT, vec![k, l]));
1132 circuit.add_gate(InterfaceGate::new(InterfaceGateType::RY(0.0), vec![l]));
1133 circuit.add_gate(InterfaceGate::new(InterfaceGateType::CNOT, vec![k, l]));
1134 circuit.add_gate(InterfaceGate::new(InterfaceGateType::CNOT, vec![j, k]));
1135 circuit.add_gate(InterfaceGate::new(InterfaceGateType::CNOT, vec![i, j]));
1136 }
1137 }
1138 }
1139 }
1140
1141 Ok(())
1142 }
1143
1144 fn create_hardware_efficient_ansatz(&self, circuit: &mut InterfaceCircuit) -> Result<()> {
1146 let num_qubits = circuit.num_qubits;
1147 let num_layers = 3; for layer in 0..num_layers {
1150 for qubit in 0..num_qubits {
1152 circuit.add_gate(InterfaceGate::new(InterfaceGateType::RY(0.0), vec![qubit]));
1153 circuit.add_gate(InterfaceGate::new(InterfaceGateType::RZ(0.0), vec![qubit]));
1154 }
1155
1156 for qubit in 0..num_qubits - 1 {
1158 circuit.add_gate(InterfaceGate::new(
1159 InterfaceGateType::CNOT,
1160 vec![qubit, qubit + 1],
1161 ));
1162 }
1163
1164 if layer % 2 == 1 {
1166 for qubit in 1..num_qubits - 1 {
1167 circuit.add_gate(InterfaceGate::new(
1168 InterfaceGateType::CNOT,
1169 vec![qubit, qubit + 1],
1170 ));
1171 }
1172 }
1173 }
1174
1175 Ok(())
1176 }
1177
1178 fn get_ansatz_parameter_count(&self, circuit: &InterfaceCircuit) -> usize {
1180 let mut count = 0;
1181 for gate in &circuit.gates {
1182 match gate.gate_type {
1183 InterfaceGateType::RX(_) | InterfaceGateType::RY(_) | InterfaceGateType::RZ(_) => {
1184 count += 1;
1185 }
1186 _ => {}
1187 }
1188 }
1189 count
1190 }
1191
1192 fn apply_ansatz_parameters(
1194 &self,
1195 template: &InterfaceCircuit,
1196 parameters: &Array1<f64>,
1197 ) -> Result<InterfaceCircuit> {
1198 let mut circuit = InterfaceCircuit::new(template.num_qubits, 0);
1199 let mut param_index = 0;
1200
1201 for gate in &template.gates {
1202 let new_gate = match gate.gate_type {
1203 InterfaceGateType::RX(_) => {
1204 let param = parameters[param_index];
1205 param_index += 1;
1206 InterfaceGate::new(InterfaceGateType::RX(param), gate.qubits.clone())
1207 }
1208 InterfaceGateType::RY(_) => {
1209 let param = parameters[param_index];
1210 param_index += 1;
1211 InterfaceGate::new(InterfaceGateType::RY(param), gate.qubits.clone())
1212 }
1213 InterfaceGateType::RZ(_) => {
1214 let param = parameters[param_index];
1215 param_index += 1;
1216 InterfaceGate::new(InterfaceGateType::RZ(param), gate.qubits.clone())
1217 }
1218 _ => gate.clone(),
1219 };
1220 circuit.add_gate(new_gate);
1221 }
1222
1223 Ok(circuit)
1224 }
1225
1226 fn evaluate_energy_expectation(
1228 &self,
1229 circuit: &InterfaceCircuit,
1230 hamiltonian: &MolecularHamiltonian,
1231 ) -> Result<f64> {
1232 let final_state = self.get_circuit_final_state(circuit)?;
1234
1235 if let Some(ref pauli_ham) = hamiltonian.pauli_hamiltonian {
1237 self.calculate_pauli_expectation(&final_state, pauli_ham)
1238 } else {
1239 Ok(hamiltonian.one_electron_integrals[[0, 0]])
1241 }
1242 }
1243
1244 fn get_circuit_final_state(&self, circuit: &InterfaceCircuit) -> Result<Array1<Complex64>> {
1246 let mut simulator = StateVectorSimulator::new();
1247 simulator.initialize_state(circuit.num_qubits)?;
1248
1249 simulator.apply_interface_circuit(circuit)?;
1251
1252 Ok(Array1::from_vec(simulator.get_state()))
1253 }
1254
1255 fn calculate_pauli_expectation(
1257 &self,
1258 state: &Array1<Complex64>,
1259 pauli_ham: &PauliOperatorSum,
1260 ) -> Result<f64> {
1261 let mut expectation = 0.0;
1262
1263 for pauli_term in &pauli_ham.terms {
1264 let pauli_expectation = self.calculate_single_pauli_expectation(state, pauli_term)?;
1265 expectation += pauli_expectation.re;
1266 }
1267
1268 Ok(expectation)
1269 }
1270
1271 fn calculate_single_pauli_expectation(
1273 &self,
1274 state: &Array1<Complex64>,
1275 pauli_string: &PauliString,
1276 ) -> Result<Complex64> {
1277 let mut result_state = state.clone();
1280
1281 for (qubit, pauli_op) in pauli_string.operators.iter().enumerate() {
1283 match pauli_op {
1284 PauliOperator::X => {
1285 self.apply_pauli_x(&mut result_state, qubit)?;
1287 }
1288 PauliOperator::Y => {
1289 self.apply_pauli_y(&mut result_state, qubit)?;
1291 }
1292 PauliOperator::Z => {
1293 self.apply_pauli_z(&mut result_state, qubit)?;
1295 }
1296 PauliOperator::I => {
1297 }
1299 }
1300 }
1301
1302 let expectation = state
1304 .iter()
1305 .zip(result_state.iter())
1306 .map(|(a, b)| a.conj() * b)
1307 .sum::<Complex64>();
1308
1309 Ok(expectation * pauli_string.coefficient)
1310 }
1311
1312 fn apply_pauli_x(&self, state: &mut Array1<Complex64>, qubit: usize) -> Result<()> {
1314 let n = state.len();
1315 let bit_mask = 1 << qubit;
1316
1317 for i in 0..n {
1318 if (i & bit_mask) == 0 {
1319 let j = i | bit_mask;
1320 if j < n {
1321 let temp = state[i];
1322 state[i] = state[j];
1323 state[j] = temp;
1324 }
1325 }
1326 }
1327
1328 Ok(())
1329 }
1330
1331 fn apply_pauli_y(&self, state: &mut Array1<Complex64>, qubit: usize) -> Result<()> {
1333 let n = state.len();
1334 let bit_mask = 1 << qubit;
1335
1336 for i in 0..n {
1337 if (i & bit_mask) == 0 {
1338 let j = i | bit_mask;
1339 if j < n {
1340 let temp = state[i];
1341 state[i] = -Complex64::i() * state[j];
1342 state[j] = Complex64::i() * temp;
1343 }
1344 }
1345 }
1346
1347 Ok(())
1348 }
1349
1350 fn apply_pauli_z(&self, state: &mut Array1<Complex64>, qubit: usize) -> Result<()> {
1352 let bit_mask = 1 << qubit;
1353
1354 for i in 0..state.len() {
1355 if (i & bit_mask) != 0 {
1356 state[i] = -state[i];
1357 }
1358 }
1359
1360 Ok(())
1361 }
1362
1363 fn update_vqe_parameters(
1365 &mut self,
1366 circuit: &InterfaceCircuit,
1367 hamiltonian: &MolecularHamiltonian,
1368 ) -> Result<()> {
1369 match self.config.vqe_config.optimizer {
1370 ChemistryOptimizer::GradientDescent => {
1371 self.gradient_descent_update(circuit, hamiltonian)?;
1372 }
1373 ChemistryOptimizer::Adam => {
1374 self.adam_update(circuit, hamiltonian)?;
1375 }
1376 _ => {
1377 self.random_perturbation_update()?;
1379 }
1380 }
1381
1382 self.stats.parameter_updates += 1;
1383 Ok(())
1384 }
1385
1386 fn gradient_descent_update(
1388 &mut self,
1389 circuit: &InterfaceCircuit,
1390 hamiltonian: &MolecularHamiltonian,
1391 ) -> Result<()> {
1392 let gradient = self.compute_parameter_gradient(circuit, hamiltonian)?;
1393
1394 for i in 0..self.vqe_optimizer.parameters.len() {
1395 self.vqe_optimizer.parameters[i] -= self.vqe_optimizer.learning_rate * gradient[i];
1396 }
1397
1398 Ok(())
1399 }
1400
1401 fn compute_parameter_gradient(
1403 &self,
1404 circuit: &InterfaceCircuit,
1405 hamiltonian: &MolecularHamiltonian,
1406 ) -> Result<Array1<f64>> {
1407 let num_params = self.vqe_optimizer.parameters.len();
1408 let mut gradient = Array1::zeros(num_params);
1409 let epsilon = 1e-4;
1410
1411 for i in 0..num_params {
1412 let mut params_plus = self.vqe_optimizer.parameters.clone();
1414 params_plus[i] += epsilon;
1415 let circuit_plus = self.apply_ansatz_parameters(circuit, ¶ms_plus)?;
1416 let energy_plus = self.evaluate_energy_expectation(&circuit_plus, hamiltonian)?;
1417
1418 let mut params_minus = self.vqe_optimizer.parameters.clone();
1420 params_minus[i] -= epsilon;
1421 let circuit_minus = self.apply_ansatz_parameters(circuit, ¶ms_minus)?;
1422 let energy_minus = self.evaluate_energy_expectation(&circuit_minus, hamiltonian)?;
1423
1424 gradient[i] = (energy_plus - energy_minus) / (2.0 * epsilon);
1425 }
1426
1427 Ok(gradient)
1428 }
1429
1430 fn adam_update(
1432 &mut self,
1433 circuit: &InterfaceCircuit,
1434 hamiltonian: &MolecularHamiltonian,
1435 ) -> Result<()> {
1436 let gradient = self.compute_parameter_gradient(circuit, hamiltonian)?;
1437
1438 for i in 0..self.vqe_optimizer.parameters.len() {
1440 self.vqe_optimizer.parameters[i] -= self.vqe_optimizer.learning_rate * gradient[i];
1441 }
1442
1443 Ok(())
1444 }
1445
1446 fn random_perturbation_update(&mut self) -> Result<()> {
1448 for i in 0..self.vqe_optimizer.parameters.len() {
1449 let perturbation = (thread_rng().gen::<f64>() - 0.5) * 0.1;
1450 self.vqe_optimizer.parameters[i] += perturbation;
1451 }
1452 Ok(())
1453 }
1454
1455 fn calculate_dipole_moment(&self, density_matrix: &Array2<f64>) -> Array1<f64> {
1457 let mut dipole = Array1::zeros(3);
1458
1459 if let Some(molecule) = &self.molecule {
1461 for (i, &atomic_number) in molecule.atomic_numbers.iter().enumerate() {
1463 if i < molecule.positions.nrows() {
1464 dipole[0] += f64::from(atomic_number) * molecule.positions[[i, 0]];
1465 dipole[1] += f64::from(atomic_number) * molecule.positions[[i, 1]];
1466 dipole[2] += f64::from(atomic_number) * molecule.positions[[i, 2]];
1467 }
1468 }
1469
1470 let num_orbitals = density_matrix.nrows();
1473 for i in 0..num_orbitals {
1474 for j in 0..num_orbitals {
1475 let density_element = density_matrix[[i, j]];
1476
1477 if i == j {
1480 let orbital_pos = i as f64 / num_orbitals as f64; dipole[0] -= density_element * orbital_pos;
1483 dipole[1] -= density_element * orbital_pos * 0.5;
1484 dipole[2] -= density_element * orbital_pos * 0.3;
1485 }
1486 }
1487 }
1488 }
1489
1490 dipole
1491 }
1492
1493 fn run_hartree_fock_only(&self) -> Result<ElectronicStructureResult> {
1495 let hf_result = self.hartree_fock.as_ref().ok_or_else(|| {
1496 SimulatorError::InvalidConfiguration("Hartree-Fock result not available".to_string())
1497 })?;
1498
1499 Ok(ElectronicStructureResult {
1500 ground_state_energy: hf_result.scf_energy,
1501 molecular_orbitals: hf_result.molecular_orbitals.clone(),
1502 density_matrix: hf_result.density_matrix.clone(),
1503 dipole_moment: self.calculate_dipole_moment(&hf_result.density_matrix),
1504 converged: hf_result.converged,
1505 iterations: hf_result.scf_iterations,
1506 quantum_state: Array1::zeros(1),
1507 vqe_history: Vec::new(),
1508 stats: self.stats.clone(),
1509 })
1510 }
1511
1512 fn run_quantum_ci(&mut self) -> Result<ElectronicStructureResult> {
1513 let original_ansatz = self.config.vqe_config.ansatz;
1515
1516 self.config.vqe_config.ansatz = ChemistryAnsatz::Adaptive;
1518
1519 let original_threshold = self.config.vqe_config.energy_threshold;
1521 self.config.vqe_config.energy_threshold = original_threshold * 0.1; let result = self.run_vqe();
1524
1525 self.config.vqe_config.ansatz = original_ansatz;
1527 self.config.vqe_config.energy_threshold = original_threshold;
1528
1529 result
1530 }
1531
1532 fn run_quantum_coupled_cluster(&mut self) -> Result<ElectronicStructureResult> {
1533 let original_ansatz = self.config.vqe_config.ansatz;
1535 let original_optimizer = self.config.vqe_config.optimizer;
1536
1537 self.config.vqe_config.ansatz = ChemistryAnsatz::UCCSD;
1539 self.config.vqe_config.optimizer = ChemistryOptimizer::Adam;
1540
1541 let num_orbitals = if let Some(hf) = &self.hartree_fock {
1543 hf.molecular_orbitals.num_orbitals
1544 } else {
1545 4 };
1547
1548 let num_singles = num_orbitals * num_orbitals;
1550 let num_doubles = (num_orbitals * (num_orbitals - 1) / 2).pow(2);
1551 let total_params = num_singles + num_doubles;
1552
1553 self.vqe_optimizer.initialize_parameters(total_params);
1554
1555 let result = self.run_vqe();
1556
1557 self.config.vqe_config.ansatz = original_ansatz;
1559 self.config.vqe_config.optimizer = original_optimizer;
1560
1561 result
1562 }
1563
1564 fn run_quantum_phase_estimation(&mut self) -> Result<ElectronicStructureResult> {
1565 if let (Some(hamiltonian), Some(hf)) = (&self.hamiltonian, &self.hartree_fock) {
1569 let num_qubits = hamiltonian.num_orbitals * 2; let ancilla_qubits = 8; let mut qpe_circuit = InterfaceCircuit::new(num_qubits + ancilla_qubits, 0);
1574
1575 for i in 0..ancilla_qubits {
1577 qpe_circuit.add_gate(InterfaceGate::new(InterfaceGateType::Hadamard, vec![i]));
1578 }
1579
1580 self.prepare_qpe_hartree_fock_state(&mut qpe_circuit, ancilla_qubits)?;
1582
1583 for i in 0..ancilla_qubits {
1585 let time_factor = 2.0_f64.powi(i as i32);
1586 self.apply_controlled_hamiltonian_evolution(&mut qpe_circuit, i, time_factor)?;
1587 }
1588
1589 self.apply_inverse_qft(&mut qpe_circuit, 0, ancilla_qubits)?;
1591
1592 let final_state = self.get_circuit_final_state(&qpe_circuit)?;
1594 let energy_estimate =
1595 self.extract_energy_from_qpe_state(&final_state, ancilla_qubits)?;
1596
1597 Ok(ElectronicStructureResult {
1598 ground_state_energy: energy_estimate,
1599 molecular_orbitals: hf.molecular_orbitals.clone(),
1600 density_matrix: hf.density_matrix.clone(),
1601 dipole_moment: self
1602 .fermion_mapper
1603 .calculate_dipole_moment(&hf.density_matrix)?,
1604 converged: true, iterations: 1,
1606 quantum_state: final_state,
1607 vqe_history: Vec::new(),
1608 stats: self.stats.clone(),
1609 })
1610 } else {
1611 self.run_vqe()
1613 }
1614 }
1615
1616 fn prepare_qpe_hartree_fock_state(
1618 &self,
1619 circuit: &mut InterfaceCircuit,
1620 offset: usize,
1621 ) -> Result<()> {
1622 if let Some(hf) = &self.hartree_fock {
1623 let num_electrons = if let Some(molecule) = &self.molecule {
1625 molecule.atomic_numbers.iter().sum::<u32>() as usize - molecule.charge as usize
1626 } else {
1627 2
1628 };
1629 let num_orbitals = hf.molecular_orbitals.num_orbitals;
1630
1631 for i in 0..num_electrons.min(num_orbitals) {
1633 circuit.add_gate(InterfaceGate::new(
1634 InterfaceGateType::PauliX,
1635 vec![offset + i],
1636 ));
1637 }
1638 }
1639 Ok(())
1640 }
1641
1642 fn apply_controlled_hamiltonian_evolution(
1644 &self,
1645 circuit: &mut InterfaceCircuit,
1646 control: usize,
1647 time: f64,
1648 ) -> Result<()> {
1649 if let Some(hamiltonian) = &self.hamiltonian {
1651 for i in 0..hamiltonian.num_orbitals {
1653 let angle = time * hamiltonian.one_electron_integrals[[i, i]];
1654 circuit.add_gate(InterfaceGate::new(
1655 InterfaceGateType::CRZ(angle),
1656 vec![control, circuit.num_qubits - hamiltonian.num_orbitals + i],
1657 ));
1658 }
1659 }
1660 Ok(())
1661 }
1662
1663 fn apply_inverse_qft(
1665 &self,
1666 circuit: &mut InterfaceCircuit,
1667 start: usize,
1668 num_qubits: usize,
1669 ) -> Result<()> {
1670 for i in 0..num_qubits {
1672 let qubit = start + i;
1673
1674 for j in (0..i).rev() {
1676 let control = start + j;
1677 let angle = -PI / 2.0_f64.powi((i - j) as i32);
1678 circuit.add_gate(InterfaceGate::new(
1679 InterfaceGateType::CRZ(angle),
1680 vec![control, qubit],
1681 ));
1682 }
1683
1684 circuit.add_gate(InterfaceGate::new(InterfaceGateType::Hadamard, vec![qubit]));
1686 }
1687
1688 Ok(())
1690 }
1691
1692 fn extract_energy_from_qpe_state(
1694 &self,
1695 state: &Array1<Complex64>,
1696 ancilla_qubits: usize,
1697 ) -> Result<f64> {
1698 let ancilla_states = 1 << ancilla_qubits;
1700 let system_size = state.len() / ancilla_states;
1701
1702 let mut max_prob = 0.0;
1703 let mut most_likely_phase = 0;
1704
1705 for phase_int in 0..ancilla_states {
1706 let mut prob = 0.0;
1707 for sys_state in 0..system_size {
1708 let idx = phase_int * system_size + sys_state;
1709 if idx < state.len() {
1710 prob += state[idx].norm_sqr();
1711 }
1712 }
1713
1714 if prob > max_prob {
1715 max_prob = prob;
1716 most_likely_phase = phase_int;
1717 }
1718 }
1719
1720 let phase = most_likely_phase as f64 / ancilla_states as f64;
1722 let energy = phase * 2.0 * PI; Ok(energy)
1725 }
1726
1727 pub fn construct_molecular_hamiltonian_public(&mut self, molecule: &Molecule) -> Result<()> {
1729 self.construct_molecular_hamiltonian(molecule)
1730 }
1731
1732 #[must_use]
1734 pub const fn get_molecule(&self) -> Option<&Molecule> {
1735 self.molecule.as_ref()
1736 }
1737
1738 pub fn compute_one_electron_integrals_public(
1740 &self,
1741 molecule: &Molecule,
1742 num_orbitals: usize,
1743 ) -> Result<Array2<f64>> {
1744 self.compute_one_electron_integrals(molecule, num_orbitals)
1745 }
1746
1747 pub fn compute_two_electron_integrals_public(
1749 &self,
1750 molecule: &Molecule,
1751 num_orbitals: usize,
1752 ) -> Result<Array4<f64>> {
1753 self.compute_two_electron_integrals(molecule, num_orbitals)
1754 }
1755
1756 pub fn compute_nuclear_repulsion_public(&self, molecule: &Molecule) -> Result<f64> {
1758 self.compute_nuclear_repulsion(molecule)
1759 }
1760
1761 pub fn create_fermionic_hamiltonian_public(
1763 &self,
1764 one_electron: &Array2<f64>,
1765 two_electron: &Array4<f64>,
1766 num_orbitals: usize,
1767 ) -> Result<FermionicHamiltonian> {
1768 self.create_fermionic_hamiltonian(one_electron, two_electron, num_orbitals)
1769 }
1770
1771 #[must_use]
1773 pub fn get_ansatz_parameter_count_public(&self, circuit: &InterfaceCircuit) -> usize {
1774 self.get_ansatz_parameter_count(circuit)
1775 }
1776
1777 pub fn build_density_matrix_public(
1779 &self,
1780 orbitals: &Array2<f64>,
1781 num_electrons: usize,
1782 ) -> Result<Array2<f64>> {
1783 self.build_density_matrix(orbitals, num_electrons)
1784 }
1785
1786 pub fn apply_pauli_x_public(&self, state: &mut Array1<Complex64>, qubit: usize) -> Result<()> {
1788 self.apply_pauli_x(state, qubit)
1789 }
1790
1791 pub fn apply_pauli_z_public(&self, state: &mut Array1<Complex64>, qubit: usize) -> Result<()> {
1793 self.apply_pauli_z(state, qubit)
1794 }
1795}
1796
1797impl FermionMapper {
1798 #[must_use]
1799 pub fn new(method: FermionMapping, num_spin_orbitals: usize) -> Self {
1800 Self {
1801 method,
1802 num_spin_orbitals,
1803 mapping_cache: HashMap::new(),
1804 }
1805 }
1806
1807 fn map_fermionic_string(&self, fermionic_string: &FermionicString) -> Result<PauliString> {
1808 let mut paulis = HashMap::new();
1810
1811 for (i, operator) in fermionic_string.operators.iter().enumerate() {
1812 match operator {
1813 FermionicOperator::Creation(site) => {
1814 paulis.insert(*site, PauliOperator::X);
1816 }
1817 FermionicOperator::Annihilation(site) => {
1818 paulis.insert(*site, PauliOperator::X);
1820 }
1821 _ => {
1822 paulis.insert(i, PauliOperator::Z);
1824 }
1825 }
1826 }
1827
1828 let mut operators_vec = vec![PauliOperator::I; self.num_spin_orbitals];
1830 for (qubit, op) in paulis {
1831 if qubit < operators_vec.len() {
1832 operators_vec[qubit] = op;
1833 }
1834 }
1835
1836 let num_qubits = operators_vec.len();
1837 Ok(PauliString {
1838 operators: operators_vec,
1839 coefficient: fermionic_string.coefficient,
1840 num_qubits,
1841 })
1842 }
1843
1844 fn calculate_dipole_moment(&self, density_matrix: &Array2<f64>) -> Result<Array1<f64>> {
1846 let mut dipole = Array1::zeros(3);
1847
1848 let num_orbitals = density_matrix.nrows();
1851
1852 for i in 0..num_orbitals {
1853 for j in 0..num_orbitals {
1854 let density_element = density_matrix[[i, j]];
1855
1856 if i == j {
1858 let orbital_pos = i as f64 / num_orbitals as f64;
1860 dipole[0] -= density_element * orbital_pos;
1861 dipole[1] -= density_element * orbital_pos * 0.5;
1862 dipole[2] -= density_element * orbital_pos * 0.3;
1863 }
1864 }
1865 }
1866
1867 Ok(dipole)
1868 }
1869
1870 #[must_use]
1872 pub const fn get_method(&self) -> &FermionMapping {
1873 &self.method
1874 }
1875
1876 #[must_use]
1878 pub const fn get_num_spin_orbitals(&self) -> usize {
1879 self.num_spin_orbitals
1880 }
1881}
1882
1883impl VQEOptimizer {
1884 #[must_use]
1885 pub fn new(method: ChemistryOptimizer) -> Self {
1886 Self {
1887 method,
1888 parameters: Array1::zeros(0),
1889 bounds: Vec::new(),
1890 history: Vec::new(),
1891 gradients: Array1::zeros(0),
1892 learning_rate: 0.01,
1893 }
1894 }
1895
1896 fn initialize_parameters(&mut self, num_parameters: usize) {
1897 self.parameters = Array1::from_vec(
1898 (0..num_parameters)
1899 .map(|_| (thread_rng().gen::<f64>() - 0.5) * 0.1)
1900 .collect(),
1901 );
1902 self.bounds = vec![(-PI, PI); num_parameters];
1903 self.gradients = Array1::zeros(num_parameters);
1904 }
1905
1906 pub fn initialize_parameters_public(&mut self, num_parameters: usize) {
1908 self.initialize_parameters(num_parameters);
1909 }
1910
1911 #[must_use]
1913 pub const fn get_parameters(&self) -> &Array1<f64> {
1914 &self.parameters
1915 }
1916
1917 #[must_use]
1919 pub fn get_bounds(&self) -> &[(f64, f64)] {
1920 &self.bounds
1921 }
1922
1923 #[must_use]
1925 pub const fn get_method(&self) -> &ChemistryOptimizer {
1926 &self.method
1927 }
1928}
1929
1930pub fn benchmark_quantum_chemistry() -> Result<()> {
1932 println!("Benchmarking Quantum Chemistry Simulation...");
1933
1934 let h2_molecule = Molecule {
1936 atomic_numbers: vec![1, 1],
1937 positions: Array2::from_shape_vec((2, 3), vec![0.0, 0.0, 0.0, 0.0, 0.0, 1.4])?,
1938 charge: 0,
1939 multiplicity: 1,
1940 basis_set: "STO-3G".to_string(),
1941 };
1942
1943 let config = ElectronicStructureConfig::default();
1944 let mut simulator = QuantumChemistrySimulator::new(config)?;
1945 simulator.set_molecule(h2_molecule)?;
1946
1947 let start_time = std::time::Instant::now();
1948
1949 let result = simulator.run_calculation()?;
1951
1952 let duration = start_time.elapsed();
1953
1954 println!("✅ Quantum Chemistry Results:");
1955 println!(
1956 " Ground State Energy: {:.6} Hartree",
1957 result.ground_state_energy
1958 );
1959 println!(" Converged: {}", result.converged);
1960 println!(" Iterations: {}", result.iterations);
1961 println!(" Hamiltonian Terms: {}", result.stats.hamiltonian_terms);
1962 println!(
1963 " Circuit Evaluations: {}",
1964 result.stats.circuit_evaluations
1965 );
1966 println!(" Total Time: {:.2}ms", duration.as_millis());
1967 println!(" VQE Time: {:.2}ms", result.stats.vqe_time_ms);
1968
1969 Ok(())
1970}
1971
1972#[cfg(test)]
1973mod tests {
1974 use super::*;
1975
1976 #[test]
1977 fn test_quantum_chemistry_simulator_creation() {
1978 let config = ElectronicStructureConfig::default();
1979 let simulator = QuantumChemistrySimulator::new(config);
1980 assert!(simulator.is_ok());
1981 }
1982
1983 #[test]
1984 fn test_h2_molecule_creation() {
1985 let h2 = Molecule {
1986 atomic_numbers: vec![1, 1],
1987 positions: Array2::from_shape_vec((2, 3), vec![0.0, 0.0, 0.0, 0.0, 0.0, 1.4])
1988 .expect("Failed to create H2 molecule positions array"),
1989 charge: 0,
1990 multiplicity: 1,
1991 basis_set: "STO-3G".to_string(),
1992 };
1993
1994 assert_eq!(h2.atomic_numbers, vec![1, 1]);
1995 assert_eq!(h2.charge, 0);
1996 assert_eq!(h2.multiplicity, 1);
1997 }
1998
1999 #[test]
2000 fn test_molecular_hamiltonian_construction() {
2001 let config = ElectronicStructureConfig::default();
2002 let mut simulator = QuantumChemistrySimulator::new(config)
2003 .expect("Failed to create quantum chemistry simulator");
2004
2005 let h2 = Molecule {
2006 atomic_numbers: vec![1, 1],
2007 positions: Array2::from_shape_vec((2, 3), vec![0.0, 0.0, 0.0, 0.0, 0.0, 1.4])
2008 .expect("Failed to create H2 molecule positions array"),
2009 charge: 0,
2010 multiplicity: 1,
2011 basis_set: "STO-3G".to_string(),
2012 };
2013
2014 simulator.set_molecule(h2).expect("Failed to set molecule");
2015 let molecule_clone = simulator.molecule.clone().expect("Molecule should be set");
2016 let result = simulator.construct_molecular_hamiltonian(&molecule_clone);
2017 assert!(result.is_ok());
2018 }
2019
2020 #[test]
2021 fn test_fermion_mapper_creation() {
2022 let mapper = FermionMapper::new(FermionMapping::JordanWigner, 4);
2023 assert_eq!(mapper.method, FermionMapping::JordanWigner);
2024 assert_eq!(mapper.num_spin_orbitals, 4);
2025 }
2026
2027 #[test]
2028 fn test_vqe_optimizer_initialization() {
2029 let mut optimizer = VQEOptimizer::new(ChemistryOptimizer::GradientDescent);
2030 optimizer.initialize_parameters(10);
2031 assert_eq!(optimizer.parameters.len(), 10);
2032 assert_eq!(optimizer.bounds.len(), 10);
2033 }
2034
2035 #[test]
2036 fn test_ansatz_parameter_counting() {
2037 let config = ElectronicStructureConfig::default();
2038 let simulator = QuantumChemistrySimulator::new(config)
2039 .expect("Failed to create quantum chemistry simulator");
2040
2041 let mut circuit = InterfaceCircuit::new(4, 0);
2042 circuit.add_gate(InterfaceGate::new(InterfaceGateType::RY(0.0), vec![0]));
2043 circuit.add_gate(InterfaceGate::new(InterfaceGateType::RZ(0.0), vec![1]));
2044 circuit.add_gate(InterfaceGate::new(InterfaceGateType::CNOT, vec![0, 1]));
2045
2046 let param_count = simulator.get_ansatz_parameter_count(&circuit);
2047 assert_eq!(param_count, 2);
2048 }
2049}