1use scirs2_core::ndarray::{Array1, Array2, Array3};
8use scirs2_core::Complex64;
9use std::collections::HashMap;
10
11use crate::error::{Result, SimulatorError};
12use crate::pauli::{PauliOperator, PauliOperatorSum, PauliString};
13use crate::scirs2_integration::SciRS2Backend;
14
15#[derive(Debug, Clone, PartialEq, Eq, Hash)]
17pub enum FermionicOperator {
18 Creation(usize),
20 Annihilation(usize),
22 Number(usize),
24 Hopping { from: usize, to: usize },
26 Interaction { sites: [usize; 4] },
28}
29
30#[derive(Debug, Clone)]
32pub struct FermionicString {
33 pub operators: Vec<FermionicOperator>,
35 pub coefficient: Complex64,
37 pub num_modes: usize,
39}
40
41#[derive(Debug, Clone)]
43pub struct FermionicHamiltonian {
44 pub terms: Vec<FermionicString>,
46 pub num_modes: usize,
48 pub is_hermitian: bool,
50}
51
52pub struct JordanWignerTransform {
54 num_modes: usize,
56 pauli_cache: HashMap<FermionicOperator, PauliString>,
58}
59
60pub struct FermionicSimulator {
62 num_modes: usize,
64 jw_transform: JordanWignerTransform,
66 state: Array1<Complex64>,
68 backend: Option<SciRS2Backend>,
70 stats: FermionicStats,
72}
73
74#[derive(Debug, Clone, Default)]
76pub struct FermionicStats {
77 pub jw_transformations: usize,
79 pub fermionic_ops_applied: usize,
81 pub jw_time_ms: f64,
83 pub operator_memory_bytes: usize,
85 pub max_pauli_string_length: usize,
87}
88
89impl FermionicOperator {
90 #[must_use]
92 pub const fn is_creation(&self) -> bool {
93 matches!(self, Self::Creation(_))
94 }
95
96 #[must_use]
98 pub const fn is_annihilation(&self) -> bool {
99 matches!(self, Self::Annihilation(_))
100 }
101
102 #[must_use]
104 pub const fn site(&self) -> Option<usize> {
105 match self {
106 Self::Creation(i) | Self::Annihilation(i) | Self::Number(i) => Some(*i),
107 _ => None,
108 }
109 }
110
111 #[must_use]
113 pub fn ordering_key(&self) -> (usize, usize) {
114 match self {
115 Self::Creation(i) => (1, *i),
116 Self::Annihilation(i) => (0, *i),
117 Self::Number(i) => (2, *i),
118 Self::Hopping { from, to } => (3, from.min(to) * 1000 + from.max(to)),
119 Self::Interaction { sites } => {
120 let mut sorted_sites = *sites;
121 sorted_sites.sort_unstable();
122 (
123 4,
124 sorted_sites[0] * 1_000_000
125 + sorted_sites[1] * 10_000
126 + sorted_sites[2] * 100
127 + sorted_sites[3],
128 )
129 }
130 }
131 }
132}
133
134impl FermionicString {
135 #[must_use]
137 pub const fn new(
138 operators: Vec<FermionicOperator>,
139 coefficient: Complex64,
140 num_modes: usize,
141 ) -> Self {
142 Self {
143 operators,
144 coefficient,
145 num_modes,
146 }
147 }
148
149 #[must_use]
151 pub fn single_operator(
152 op: FermionicOperator,
153 coefficient: Complex64,
154 num_modes: usize,
155 ) -> Self {
156 Self::new(vec![op], coefficient, num_modes)
157 }
158
159 #[must_use]
161 pub fn creation(site: usize, coefficient: Complex64, num_modes: usize) -> Self {
162 Self::single_operator(FermionicOperator::Creation(site), coefficient, num_modes)
163 }
164
165 #[must_use]
167 pub fn annihilation(site: usize, coefficient: Complex64, num_modes: usize) -> Self {
168 Self::single_operator(
169 FermionicOperator::Annihilation(site),
170 coefficient,
171 num_modes,
172 )
173 }
174
175 #[must_use]
177 pub fn number(site: usize, coefficient: Complex64, num_modes: usize) -> Self {
178 Self::single_operator(FermionicOperator::Number(site), coefficient, num_modes)
179 }
180
181 #[must_use]
183 pub fn hopping(from: usize, to: usize, coefficient: Complex64, num_modes: usize) -> Self {
184 Self::single_operator(
185 FermionicOperator::Hopping { from, to },
186 coefficient,
187 num_modes,
188 )
189 }
190
191 pub fn multiply(&self, other: &Self) -> Result<Self> {
193 if self.num_modes != other.num_modes {
194 return Err(SimulatorError::DimensionMismatch(
195 "Fermionic strings must have same number of modes".to_string(),
196 ));
197 }
198
199 let mut result_ops = self.operators.clone();
200 result_ops.extend(other.operators.clone());
201
202 let (canonical_ops, sign) = self.canonicalize_operators(&result_ops)?;
204
205 Ok(Self {
206 operators: canonical_ops,
207 coefficient: self.coefficient * other.coefficient * sign,
208 num_modes: self.num_modes,
209 })
210 }
211
212 fn canonicalize_operators(
214 &self,
215 ops: &[FermionicOperator],
216 ) -> Result<(Vec<FermionicOperator>, Complex64)> {
217 let mut canonical = ops.to_vec();
218 let mut sign = Complex64::new(1.0, 0.0);
219
220 for i in 0..canonical.len() {
222 for j in (i + 1)..canonical.len() {
223 if canonical[i].ordering_key() > canonical[j].ordering_key() {
224 canonical.swap(i, j);
226 sign *= Complex64::new(-1.0, 0.0);
227 }
228 }
229 }
230
231 let simplified = self.apply_fermionic_algebra(&canonical)?;
233
234 Ok((simplified, sign))
235 }
236
237 fn apply_fermionic_algebra(&self, ops: &[FermionicOperator]) -> Result<Vec<FermionicOperator>> {
239 let mut result = Vec::new();
240 let mut i = 0;
241
242 while i < ops.len() {
243 if i + 1 < ops.len() {
244 match (&ops[i], &ops[i + 1]) {
245 (FermionicOperator::Creation(a), FermionicOperator::Annihilation(b))
247 if a == b =>
248 {
249 result.push(FermionicOperator::Number(*a));
250 i += 2;
251 }
252 (FermionicOperator::Annihilation(a), FermionicOperator::Annihilation(b))
254 if a == b =>
255 {
256 i += 2;
258 }
259 (FermionicOperator::Creation(a), FermionicOperator::Creation(b)) if a == b => {
261 i += 2;
263 }
264 _ => {
265 result.push(ops[i].clone());
266 i += 1;
267 }
268 }
269 } else {
270 result.push(ops[i].clone());
271 i += 1;
272 }
273 }
274
275 Ok(result)
276 }
277
278 #[must_use]
280 pub fn hermitian_conjugate(&self) -> Self {
281 let mut conjugate_ops = Vec::new();
282
283 for op in self.operators.iter().rev() {
285 let conjugate_op = match op {
286 FermionicOperator::Creation(i) => FermionicOperator::Annihilation(*i),
287 FermionicOperator::Annihilation(i) => FermionicOperator::Creation(*i),
288 FermionicOperator::Number(i) => FermionicOperator::Number(*i),
289 FermionicOperator::Hopping { from, to } => FermionicOperator::Hopping {
290 from: *to,
291 to: *from,
292 },
293 FermionicOperator::Interaction { sites } => {
294 let mut rev_sites = *sites;
296 rev_sites.reverse();
297 FermionicOperator::Interaction { sites: rev_sites }
298 }
299 };
300 conjugate_ops.push(conjugate_op);
301 }
302
303 Self {
304 operators: conjugate_ops,
305 coefficient: self.coefficient.conj(),
306 num_modes: self.num_modes,
307 }
308 }
309}
310
311impl FermionicHamiltonian {
312 #[must_use]
314 pub const fn new(num_modes: usize) -> Self {
315 Self {
316 terms: Vec::new(),
317 num_modes,
318 is_hermitian: true,
319 }
320 }
321
322 pub fn add_term(&mut self, term: FermionicString) -> Result<()> {
324 if term.num_modes != self.num_modes {
325 return Err(SimulatorError::DimensionMismatch(
326 "Term must have same number of modes as Hamiltonian".to_string(),
327 ));
328 }
329
330 self.terms.push(term);
331 Ok(())
332 }
333
334 pub fn make_hermitian(&mut self) {
336 let mut conjugate_terms = Vec::new();
337
338 for term in &self.terms {
339 let conjugate = term.hermitian_conjugate();
340 if !self.terms_equal(term, &conjugate) {
342 conjugate_terms.push(conjugate);
343 }
344 }
345
346 self.terms.extend(conjugate_terms);
347 self.is_hermitian = true;
348 }
349
350 fn terms_equal(&self, term1: &FermionicString, term2: &FermionicString) -> bool {
352 term1.operators == term2.operators && (term1.coefficient - term2.coefficient).norm() < 1e-12
353 }
354
355 pub fn molecular_hamiltonian(
357 num_modes: usize,
358 one_body_integrals: &Array2<f64>,
359 two_body_integrals: &Array3<f64>,
360 ) -> Result<Self> {
361 let mut hamiltonian = Self::new(num_modes);
362
363 for i in 0..num_modes {
365 for j in 0..num_modes {
366 if one_body_integrals[[i, j]].abs() > 1e-12 {
367 let coeff = Complex64::new(one_body_integrals[[i, j]], 0.0);
368 let term = FermionicString::new(
369 vec![
370 FermionicOperator::Creation(i),
371 FermionicOperator::Annihilation(j),
372 ],
373 coeff,
374 num_modes,
375 );
376 hamiltonian.add_term(term)?;
377 }
378 }
379 }
380
381 for i in 0..num_modes {
383 for j in 0..num_modes {
384 for k in 0..num_modes {
385 if two_body_integrals[[i, j, k]].abs() > 1e-12 {
386 for l in 0..num_modes {
387 let coeff = Complex64::new(0.5 * two_body_integrals[[i, j, k]], 0.0);
388 let term = FermionicString::new(
389 vec![
390 FermionicOperator::Creation(i),
391 FermionicOperator::Creation(j),
392 FermionicOperator::Annihilation(l),
393 FermionicOperator::Annihilation(k),
394 ],
395 coeff,
396 num_modes,
397 );
398 hamiltonian.add_term(term)?;
399 }
400 }
401 }
402 }
403 }
404
405 hamiltonian.make_hermitian();
406 Ok(hamiltonian)
407 }
408
409 pub fn hubbard_model(
411 sites: usize,
412 hopping: f64,
413 interaction: f64,
414 chemical_potential: f64,
415 ) -> Result<Self> {
416 let num_modes = 2 * sites; let mut hamiltonian = Self::new(num_modes);
418
419 for i in 0..sites {
421 for sigma in 0..2 {
422 let site_i = 2 * i + sigma;
423
424 if i + 1 < sites {
426 let site_j = 2 * (i + 1) + sigma;
427
428 let hopping_term = FermionicString::hopping(
430 site_i,
431 site_j,
432 Complex64::new(-hopping, 0.0),
433 num_modes,
434 );
435 hamiltonian.add_term(hopping_term)?;
436
437 let back_hopping_term = FermionicString::hopping(
439 site_j,
440 site_i,
441 Complex64::new(-hopping, 0.0),
442 num_modes,
443 );
444 hamiltonian.add_term(back_hopping_term)?;
445 }
446 }
447 }
448
449 for i in 0..sites {
451 let up_site = 2 * i;
452 let down_site = 2 * i + 1;
453
454 let interaction_term = FermionicString::new(
455 vec![
456 FermionicOperator::Number(up_site),
457 FermionicOperator::Number(down_site),
458 ],
459 Complex64::new(interaction, 0.0),
460 num_modes,
461 );
462 hamiltonian.add_term(interaction_term)?;
463 }
464
465 for i in 0..num_modes {
467 let mu_term =
468 FermionicString::number(i, Complex64::new(-chemical_potential, 0.0), num_modes);
469 hamiltonian.add_term(mu_term)?;
470 }
471
472 Ok(hamiltonian)
473 }
474}
475
476impl JordanWignerTransform {
477 #[must_use]
479 pub fn new(num_modes: usize) -> Self {
480 Self {
481 num_modes,
482 pauli_cache: HashMap::new(),
483 }
484 }
485
486 pub fn transform_operator_to_sum(
499 &mut self,
500 op: &FermionicOperator,
501 ) -> Result<PauliOperatorSum> {
502 let mut sum = PauliOperatorSum::new(self.num_modes);
503 match op {
504 FermionicOperator::Number(site) => {
505 let identity = PauliString::new(self.num_modes); let mut id = identity;
508 id.coefficient = Complex64::new(0.5, 0.0);
509 sum.add_term(id)?;
510
511 let z_term =
512 self.single_site_pauli(*site, PauliOperator::Z, Complex64::new(-0.5, 0.0))?;
513 sum.add_term(z_term)?;
514 }
515 FermionicOperator::Creation(site) => {
516 let x_term =
518 self.jw_pauli_string(*site, PauliOperator::X, Complex64::new(0.5, 0.0))?;
519 let y_term =
520 self.jw_pauli_string(*site, PauliOperator::Y, Complex64::new(0.0, -0.5))?;
521 sum.add_term(x_term)?;
522 sum.add_term(y_term)?;
523 }
524 FermionicOperator::Annihilation(site) => {
525 let x_term =
527 self.jw_pauli_string(*site, PauliOperator::X, Complex64::new(0.5, 0.0))?;
528 let y_term =
529 self.jw_pauli_string(*site, PauliOperator::Y, Complex64::new(0.0, 0.5))?;
530 sum.add_term(x_term)?;
531 sum.add_term(y_term)?;
532 }
533 FermionicOperator::Hopping { from, to } => {
534 let creation_sum =
536 self.transform_operator_to_sum(&FermionicOperator::Creation(*from))?;
537 let annihilation_sum =
538 self.transform_operator_to_sum(&FermionicOperator::Annihilation(*to))?;
539 for ca in &creation_sum.terms {
540 for an in &annihilation_sum.terms {
541 let product = ca.multiply(an)?;
542 if product.coefficient.norm() > 1e-15 {
544 sum.add_term(product)?;
545 }
546 }
547 }
548 }
549 FermionicOperator::Interaction { sites } => {
550 let sums: Vec<PauliOperatorSum> = [
552 FermionicOperator::Creation(sites[0]),
553 FermionicOperator::Creation(sites[1]),
554 FermionicOperator::Annihilation(sites[2]),
555 FermionicOperator::Annihilation(sites[3]),
556 ]
557 .iter()
558 .map(|fop| self.transform_operator_to_sum(fop))
559 .collect::<Result<Vec<_>>>()?;
560
561 let mut current: Vec<PauliString> = sums[0].terms.clone();
563 for next_sum in sums.iter().skip(1) {
564 let mut new_current = Vec::new();
565 for ca in ¤t {
566 for nb in &next_sum.terms {
567 let product = ca.multiply(nb)?;
568 if product.coefficient.norm() > 1e-15 {
569 new_current.push(product);
570 }
571 }
572 }
573 current = new_current;
574 }
575 for term in current {
576 sum.add_term(term)?;
577 }
578 }
579 }
580 Ok(sum)
581 }
582
583 fn jw_pauli_string(
585 &self,
586 site: usize,
587 op: PauliOperator,
588 coeff: Complex64,
589 ) -> Result<PauliString> {
590 if site >= self.num_modes {
591 return Err(SimulatorError::IndexOutOfBounds(site));
592 }
593 let mut paulis = vec![PauliOperator::I; self.num_modes];
594 paulis[..site].fill(PauliOperator::Z);
595 paulis[site] = op;
596 let ops: Vec<(usize, PauliOperator)> = paulis
597 .iter()
598 .enumerate()
599 .filter(|(_, &p)| p != PauliOperator::I)
600 .map(|(i, &p)| (i, p))
601 .collect();
602 PauliString::from_ops(self.num_modes, &ops, coeff)
603 }
604
605 fn single_site_pauli(
607 &self,
608 site: usize,
609 op: PauliOperator,
610 coeff: Complex64,
611 ) -> Result<PauliString> {
612 if site >= self.num_modes {
613 return Err(SimulatorError::IndexOutOfBounds(site));
614 }
615 PauliString::from_ops(self.num_modes, &[(site, op)], coeff)
616 }
617
618 pub fn transform_operator(&mut self, op: &FermionicOperator) -> Result<PauliString> {
625 if let Some(cached) = self.pauli_cache.get(op) {
626 return Ok(cached.clone());
627 }
628
629 let pauli_string = match op {
630 FermionicOperator::Creation(i) => {
631 self.jw_pauli_string(*i, PauliOperator::X, Complex64::new(0.5, 0.0))?
632 }
633 FermionicOperator::Annihilation(i) => {
634 self.jw_pauli_string(*i, PauliOperator::X, Complex64::new(0.5, 0.0))?
635 }
636 FermionicOperator::Number(i) => {
637 self.single_site_pauli(*i, PauliOperator::Z, Complex64::new(-0.5, 0.0))?
639 }
640 FermionicOperator::Hopping { from, to } => self
641 .creation_to_pauli(*from)?
642 .multiply(&self.annihilation_to_pauli(*to)?)?,
643 FermionicOperator::Interaction { sites } => self
644 .creation_to_pauli(sites[0])?
645 .multiply(&self.creation_to_pauli(sites[1])?)?
646 .multiply(&self.annihilation_to_pauli(sites[2])?)?
647 .multiply(&self.annihilation_to_pauli(sites[3])?)?,
648 };
649
650 self.pauli_cache.insert(op.clone(), pauli_string.clone());
651 Ok(pauli_string)
652 }
653
654 fn creation_to_pauli(&self, site: usize) -> Result<PauliString> {
656 if site >= self.num_modes {
657 return Err(SimulatorError::IndexOutOfBounds(site));
658 }
659 self.jw_pauli_string(site, PauliOperator::X, Complex64::new(0.5, 0.0))
660 }
661
662 fn annihilation_to_pauli(&self, site: usize) -> Result<PauliString> {
664 if site >= self.num_modes {
665 return Err(SimulatorError::IndexOutOfBounds(site));
666 }
667 self.jw_pauli_string(site, PauliOperator::X, Complex64::new(0.5, 0.0))
668 }
669
670 fn hopping_to_pauli(&self, from: usize, to: usize) -> Result<PauliString> {
672 self.creation_to_pauli(from)?
673 .multiply(&self.annihilation_to_pauli(to)?)
674 }
675
676 fn interaction_to_pauli(&self, sites: [usize; 4]) -> Result<PauliString> {
678 self.creation_to_pauli(sites[0])?
679 .multiply(&self.creation_to_pauli(sites[1])?)?
680 .multiply(&self.annihilation_to_pauli(sites[2])?)?
681 .multiply(&self.annihilation_to_pauli(sites[3])?)
682 }
683
684 pub fn transform_string(
690 &mut self,
691 fermionic_string: &FermionicString,
692 ) -> Result<PauliOperatorSum> {
693 let mut pauli_sum = PauliOperatorSum::new(self.num_modes);
694
695 if fermionic_string.operators.is_empty() {
696 let mut identity_string = PauliString::new(self.num_modes);
697 identity_string.coefficient = fermionic_string.coefficient;
698 pauli_sum.add_term(identity_string)?;
699 return Ok(pauli_sum);
700 }
701
702 let mut current: Vec<PauliString> = {
705 let first_sum = self.transform_operator_to_sum(&fermionic_string.operators[0])?;
706 first_sum.terms
707 };
708
709 for op in fermionic_string.operators.iter().skip(1) {
710 let next_sum = self.transform_operator_to_sum(op)?;
711 let mut new_current = Vec::new();
712 for ca in ¤t {
713 for nb in &next_sum.terms {
714 let product = ca.multiply(nb)?;
715 if product.coefficient.norm() > 1e-15 {
716 new_current.push(product);
717 }
718 }
719 }
720 current = new_current;
721 }
722
723 for mut term in current {
725 term.coefficient *= fermionic_string.coefficient;
726 pauli_sum.add_term(term)?;
727 }
728
729 Ok(pauli_sum)
730 }
731
732 pub fn transform_hamiltonian(
734 &mut self,
735 hamiltonian: &FermionicHamiltonian,
736 ) -> Result<PauliOperatorSum> {
737 let mut pauli_hamiltonian = PauliOperatorSum::new(self.num_modes);
738
739 for term in &hamiltonian.terms {
740 let pauli_terms = self.transform_string(term)?;
741 for pauli_term in pauli_terms.terms {
742 let _ = pauli_hamiltonian.add_term(pauli_term);
743 }
744 }
745
746 Ok(pauli_hamiltonian)
747 }
748}
749
750impl FermionicSimulator {
751 pub fn new(num_modes: usize) -> Result<Self> {
753 let dim = 1 << num_modes;
754 let mut state = Array1::zeros(dim);
755 state[0] = Complex64::new(1.0, 0.0); Ok(Self {
758 num_modes,
759 jw_transform: JordanWignerTransform::new(num_modes),
760 state,
761 backend: None,
762 stats: FermionicStats::default(),
763 })
764 }
765
766 pub fn with_scirs2_backend(mut self) -> Result<Self> {
768 self.backend = Some(SciRS2Backend::new());
769 Ok(self)
770 }
771
772 pub fn set_initial_state(&mut self, occupation: &[bool]) -> Result<()> {
774 if occupation.len() != self.num_modes {
775 return Err(SimulatorError::DimensionMismatch(
776 "Occupation must match number of modes".to_string(),
777 ));
778 }
779
780 let mut index = 0;
782 for (i, &occupied) in occupation.iter().enumerate() {
783 if occupied {
784 index |= 1 << (self.num_modes - 1 - i);
785 }
786 }
787
788 self.state.fill(Complex64::new(0.0, 0.0));
789 self.state[index] = Complex64::new(1.0, 0.0);
790
791 Ok(())
792 }
793
794 pub fn apply_fermionic_operator(&mut self, op: &FermionicOperator) -> Result<()> {
796 let start_time = std::time::Instant::now();
797
798 let pauli_string = self.jw_transform.transform_operator(op)?;
800
801 self.apply_pauli_string(&pauli_string)?;
803
804 self.stats.fermionic_ops_applied += 1;
805 self.stats.jw_transformations += 1;
806 self.stats.jw_time_ms += start_time.elapsed().as_secs_f64() * 1000.0;
807
808 Ok(())
809 }
810
811 pub fn apply_fermionic_string(&mut self, fermionic_string: &FermionicString) -> Result<()> {
813 let pauli_sum = self.jw_transform.transform_string(fermionic_string)?;
814
815 for pauli_term in &pauli_sum.terms {
817 self.apply_pauli_string(pauli_term)?;
818 }
819
820 Ok(())
821 }
822
823 fn apply_pauli_string(&mut self, pauli_string: &PauliString) -> Result<()> {
837 let n = self.num_modes;
838 let size = self.state.len();
839
840 for (q, &op) in pauli_string.operators.iter().enumerate() {
841 let q_bit = n - 1 - q;
842 match op {
843 PauliOperator::I => {}
844 PauliOperator::Z => {
845 for i in 0..size {
846 if (i >> q_bit) & 1 == 1 {
847 self.state[i] = -self.state[i];
848 }
849 }
850 }
851 PauliOperator::X => {
852 for i in 0..size {
853 if (i >> q_bit) & 1 == 0 {
854 let j = i | (1 << q_bit);
855 self.state.swap(i, j);
856 }
857 }
858 }
859 PauliOperator::Y => {
860 for i in 0..size {
863 if (i >> q_bit) & 1 == 0 {
864 let j = i | (1 << q_bit);
865 let a = self.state[i];
866 let b = self.state[j];
867 self.state[i] = Complex64::new(0.0, 1.0) * b;
868 self.state[j] = Complex64::new(0.0, -1.0) * a;
869 }
870 }
871 }
872 }
873 }
874
875 let coeff = pauli_string.coefficient;
877 for amp in self.state.iter_mut() {
878 *amp *= coeff;
879 }
880
881 Ok(())
882 }
883
884 pub fn expectation_value(&mut self, op: &FermionicOperator) -> Result<Complex64> {
890 let pauli_sum = self.jw_transform.transform_operator_to_sum(op)?;
891
892 let mut total = Complex64::new(0.0, 0.0);
893 for term in &pauli_sum.terms {
894 total += self.compute_pauli_expectation(term)?;
895 }
896 Ok(total)
897 }
898
899 fn compute_pauli_expectation(&self, pauli_string: &PauliString) -> Result<Complex64> {
904 let n = self.num_modes;
905 let size = self.state.len();
906 let mut psi_prime = self.state.clone();
907
908 for (q, &op) in pauli_string.operators.iter().enumerate() {
910 let q_bit = n - 1 - q;
911 match op {
912 PauliOperator::I => {}
913 PauliOperator::Z => {
914 for i in 0..size {
915 if (i >> q_bit) & 1 == 1 {
916 psi_prime[i] = -psi_prime[i];
917 }
918 }
919 }
920 PauliOperator::X => {
921 for i in 0..size {
922 if (i >> q_bit) & 1 == 0 {
923 let j = i | (1 << q_bit);
924 psi_prime.swap(i, j);
925 }
926 }
927 }
928 PauliOperator::Y => {
929 for i in 0..size {
930 if (i >> q_bit) & 1 == 0 {
931 let j = i | (1 << q_bit);
932 let a = psi_prime[i];
933 let b = psi_prime[j];
934 psi_prime[i] = Complex64::new(0.0, 1.0) * b;
935 psi_prime[j] = Complex64::new(0.0, -1.0) * a;
936 }
937 }
938 }
939 }
940 }
941
942 let raw: Complex64 = self
944 .state
945 .iter()
946 .zip(psi_prime.iter())
947 .map(|(&a, &b)| a.conj() * b)
948 .sum();
949
950 Ok(pauli_string.coefficient * raw)
951 }
952
953 pub fn evolve_hamiltonian(
955 &mut self,
956 hamiltonian: &FermionicHamiltonian,
957 time: f64,
958 ) -> Result<()> {
959 let pauli_hamiltonian = self.jw_transform.transform_hamiltonian(hamiltonian)?;
961
962 self.evolve_pauli_hamiltonian(&pauli_hamiltonian, time)?;
964
965 Ok(())
966 }
967
968 fn apply_pauli_operators(
972 operators: &[PauliOperator],
973 num_modes: usize,
974 target: &mut Array1<Complex64>,
975 ) {
976 let size = target.len();
977 for (q, &op) in operators.iter().enumerate() {
978 let q_bit = num_modes - 1 - q;
979 match op {
980 PauliOperator::I => {}
981 PauliOperator::Z => {
982 for i in 0..size {
983 if (i >> q_bit) & 1 == 1 {
984 target[i] = -target[i];
985 }
986 }
987 }
988 PauliOperator::X => {
989 for i in 0..size {
990 if (i >> q_bit) & 1 == 0 {
991 let j = i | (1 << q_bit);
992 target.swap(i, j);
993 }
994 }
995 }
996 PauliOperator::Y => {
997 for i in 0..size {
998 if (i >> q_bit) & 1 == 0 {
999 let j = i | (1 << q_bit);
1000 let a = target[i];
1001 let b = target[j];
1002 target[i] = Complex64::new(0.0, 1.0) * b;
1003 target[j] = Complex64::new(0.0, -1.0) * a;
1004 }
1005 }
1006 }
1007 }
1008 }
1009 }
1010
1011 fn evolve_pauli_hamiltonian(
1024 &mut self,
1025 hamiltonian: &PauliOperatorSum,
1026 time: f64,
1027 ) -> Result<()> {
1028 let mut merged: HashMap<Vec<PauliOperator>, Complex64> = HashMap::new();
1030 for term in &hamiltonian.terms {
1031 *merged
1032 .entry(term.operators.clone())
1033 .or_insert(Complex64::new(0.0, 0.0)) += term.coefficient;
1034 }
1035
1036 let n = self.num_modes;
1037 let size = self.state.len();
1038
1039 for (operators, coeff) in &merged {
1040 let c_re = coeff.re;
1041 let c_im = coeff.im;
1042 if c_re.abs() < 1e-14 && c_im.abs() < 1e-14 {
1044 continue;
1045 }
1046
1047 let mut p_psi = self.state.clone();
1049 Self::apply_pauli_operators(operators, n, &mut p_psi);
1050
1051 let magnitude = (c_re * c_re + c_im * c_im).sqrt();
1057 let theta = magnitude * time;
1058 let cos_t = theta.cos();
1059 let sin_t = theta.sin();
1060
1061 let phase = Complex64::new(c_im * sin_t / magnitude, -c_re * sin_t / magnitude);
1064
1065 for i in 0..size {
1066 self.state[i] = cos_t * self.state[i] + phase * p_psi[i];
1067 }
1068 }
1069
1070 Ok(())
1071 }
1072
1073 #[must_use]
1075 pub const fn get_state(&self) -> &Array1<Complex64> {
1076 &self.state
1077 }
1078
1079 #[must_use]
1081 pub fn get_particle_number(&self) -> f64 {
1082 let mut total_number = 0.0;
1083
1084 for (index, amplitude) in self.state.iter().enumerate() {
1085 let prob = amplitude.norm_sqr();
1086 let popcount = f64::from(index.count_ones());
1087 total_number += prob * popcount;
1088 }
1089
1090 total_number
1091 }
1092
1093 #[must_use]
1095 pub const fn get_stats(&self) -> &FermionicStats {
1096 &self.stats
1097 }
1098
1099 pub fn particle_correlation(&mut self, site1: usize, site2: usize) -> Result<f64> {
1104 let n1 = self
1106 .expectation_value(&FermionicOperator::Number(site1))?
1107 .re;
1108 let n2 = self
1109 .expectation_value(&FermionicOperator::Number(site2))?
1110 .re;
1111
1112 let n1n2_string = FermionicString {
1115 operators: vec![
1116 FermionicOperator::Number(site1),
1117 FermionicOperator::Number(site2),
1118 ],
1119 coefficient: Complex64::new(1.0, 0.0),
1120 num_modes: self.num_modes,
1121 };
1122 let pauli_sum = self.jw_transform.transform_string(&n1n2_string)?;
1123
1124 let mut n1n2 = 0.0_f64;
1125 for term in &pauli_sum.terms {
1126 n1n2 += self.compute_pauli_expectation(term)?.re;
1127 }
1128
1129 Ok(n1n2 - n1 * n2)
1130 }
1131}
1132
1133pub fn benchmark_fermionic_simulation(num_modes: usize) -> Result<FermionicStats> {
1135 let mut simulator = FermionicSimulator::new(num_modes)?;
1136
1137 let hamiltonian = FermionicHamiltonian::hubbard_model(num_modes / 2, 1.0, 2.0, 0.5)?;
1139
1140 let creation_op = FermionicOperator::Creation(0);
1142 simulator.apply_fermionic_operator(&creation_op)?;
1143
1144 let annihilation_op = FermionicOperator::Annihilation(1);
1145 simulator.apply_fermionic_operator(&annihilation_op)?;
1146
1147 simulator.evolve_hamiltonian(&hamiltonian, 0.1)?;
1149
1150 Ok(simulator.get_stats().clone())
1151}
1152
1153#[cfg(test)]
1154mod tests {
1155 use super::*;
1156
1157 #[test]
1158 fn test_fermionic_operator_creation() {
1159 let op = FermionicOperator::Creation(0);
1160 assert!(op.is_creation());
1161 assert!(!op.is_annihilation());
1162 assert_eq!(op.site(), Some(0));
1163 }
1164
1165 #[test]
1166 fn test_fermionic_string() {
1167 let ops = vec![
1168 FermionicOperator::Creation(0),
1169 FermionicOperator::Annihilation(1),
1170 ];
1171 let string = FermionicString::new(ops, Complex64::new(1.0, 0.0), 4);
1172 assert_eq!(string.operators.len(), 2);
1173 assert_eq!(string.num_modes, 4);
1174 }
1175
1176 #[test]
1177 fn test_hubbard_hamiltonian() {
1178 let hamiltonian = FermionicHamiltonian::hubbard_model(2, 1.0, 2.0, 0.5)
1179 .expect("Failed to create Hubbard model Hamiltonian");
1180 assert_eq!(hamiltonian.num_modes, 4); assert!(!hamiltonian.terms.is_empty());
1182 }
1183
1184 #[test]
1185 fn test_jordan_wigner_transform() {
1186 let mut jw = JordanWignerTransform::new(4);
1187 let creation_op = FermionicOperator::Creation(1);
1188 let pauli_string = jw
1189 .transform_operator(&creation_op)
1190 .expect("Failed to transform creation operator via Jordan-Wigner");
1191
1192 assert_eq!(pauli_string.num_qubits, 4);
1193 assert_eq!(pauli_string.operators[0], PauliOperator::Z); assert_eq!(pauli_string.operators[1], PauliOperator::X);
1195 }
1196
1197 #[test]
1198 fn test_fermionic_simulator() {
1199 let mut simulator =
1200 FermionicSimulator::new(4).expect("Failed to create fermionic simulator");
1201
1202 simulator
1204 .set_initial_state(&[true, false, false, false])
1205 .expect("Failed to set initial fermionic state");
1206
1207 let particle_number = simulator.get_particle_number();
1208 assert!((particle_number - 1.0).abs() < 1e-10);
1209 }
1210
1211 #[test]
1212 fn test_fermionic_string_multiplication() {
1213 let string1 = FermionicString::creation(0, Complex64::new(1.0, 0.0), 4);
1214 let string2 = FermionicString::annihilation(1, Complex64::new(1.0, 0.0), 4);
1215
1216 let product = string1
1217 .multiply(&string2)
1218 .expect("Failed to multiply fermionic strings");
1219 assert!(!product.operators.is_empty());
1220 }
1221}