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(&mut self, op: &FermionicOperator) -> Result<PauliString> {
488 if let Some(cached) = self.pauli_cache.get(op) {
489 return Ok(cached.clone());
490 }
491
492 let pauli_string = match op {
493 FermionicOperator::Creation(i) => self.creation_to_pauli(*i)?,
494 FermionicOperator::Annihilation(i) => self.annihilation_to_pauli(*i)?,
495 FermionicOperator::Number(i) => self.number_to_pauli(*i)?,
496 FermionicOperator::Hopping { from, to } => self.hopping_to_pauli(*from, *to)?,
497 FermionicOperator::Interaction { sites } => self.interaction_to_pauli(*sites)?,
498 };
499
500 self.pauli_cache.insert(op.clone(), pauli_string.clone());
501 Ok(pauli_string)
502 }
503
504 fn creation_to_pauli(&self, site: usize) -> Result<PauliString> {
506 if site >= self.num_modes {
507 return Err(SimulatorError::IndexOutOfBounds(site));
508 }
509
510 let mut paulis = vec![PauliOperator::I; self.num_modes];
511
512 paulis[..site].fill(PauliOperator::Z);
514
515 paulis[site] = PauliOperator::X;
519
520 let ops: Vec<(usize, PauliOperator)> = paulis
521 .iter()
522 .enumerate()
523 .filter(|(_, &op)| op != PauliOperator::I)
524 .map(|(i, &op)| (i, op))
525 .collect();
526
527 PauliString::from_ops(self.num_modes, &ops, Complex64::new(0.5, 0.0))
528 }
529
530 fn annihilation_to_pauli(&self, site: usize) -> Result<PauliString> {
532 if site >= self.num_modes {
533 return Err(SimulatorError::IndexOutOfBounds(site));
534 }
535
536 let mut paulis = vec![PauliOperator::I; self.num_modes];
537
538 paulis[..site].fill(PauliOperator::Z);
540
541 paulis[site] = PauliOperator::X;
542
543 let ops: Vec<(usize, PauliOperator)> = paulis
544 .iter()
545 .enumerate()
546 .filter(|(_, &op)| op != PauliOperator::I)
547 .map(|(i, &op)| (i, op))
548 .collect();
549
550 PauliString::from_ops(self.num_modes, &ops, Complex64::new(0.5, 0.0))
551 }
552
553 fn number_to_pauli(&self, site: usize) -> Result<PauliString> {
555 if site >= self.num_modes {
556 return Err(SimulatorError::IndexOutOfBounds(site));
557 }
558
559 let mut paulis = vec![PauliOperator::I; self.num_modes];
560
561 paulis[site] = PauliOperator::Z;
563
564 let ops: Vec<(usize, PauliOperator)> = paulis
565 .iter()
566 .enumerate()
567 .filter(|(_, &op)| op != PauliOperator::I)
568 .map(|(i, &op)| (i, op))
569 .collect();
570
571 PauliString::from_ops(self.num_modes, &ops, Complex64::new(-0.5, 0.0))
572 }
573
574 fn hopping_to_pauli(&self, from: usize, to: usize) -> Result<PauliString> {
576 let mut paulis = vec![PauliOperator::I; self.num_modes];
579
580 let min_site = from.min(to);
581 let max_site = from.max(to);
582
583 paulis[min_site..max_site].fill(PauliOperator::Z);
585
586 paulis[from] = PauliOperator::X;
587 paulis[to] = PauliOperator::X;
588
589 let ops: Vec<(usize, PauliOperator)> = paulis
590 .iter()
591 .enumerate()
592 .filter(|(_, &op)| op != PauliOperator::I)
593 .map(|(i, &op)| (i, op))
594 .collect();
595
596 PauliString::from_ops(self.num_modes, &ops, Complex64::new(0.25, 0.0))
597 }
598
599 fn interaction_to_pauli(&self, sites: [usize; 4]) -> Result<PauliString> {
601 let mut paulis = vec![PauliOperator::I; self.num_modes];
603
604 for &site in &sites {
605 paulis[site] = PauliOperator::Z;
606 }
607
608 let ops: Vec<(usize, PauliOperator)> = paulis
609 .iter()
610 .enumerate()
611 .filter(|(_, &op)| op != PauliOperator::I)
612 .map(|(i, &op)| (i, op))
613 .collect();
614
615 PauliString::from_ops(self.num_modes, &ops, Complex64::new(0.0625, 0.0))
616 }
617
618 pub fn transform_string(
620 &mut self,
621 fermionic_string: &FermionicString,
622 ) -> Result<PauliOperatorSum> {
623 let mut pauli_sum = PauliOperatorSum::new(self.num_modes);
624
625 if fermionic_string.operators.is_empty() {
626 let mut identity_string = PauliString::new(self.num_modes);
628 identity_string.coefficient = fermionic_string.coefficient;
629 let _ = pauli_sum.add_term(identity_string);
630 return Ok(pauli_sum);
631 }
632
633 if fermionic_string.operators.len() == 1 {
635 let pauli_string = self.transform_operator(&fermionic_string.operators[0])?;
636 let mut scaled_string = pauli_string.clone();
637 scaled_string.coefficient = pauli_string.coefficient * fermionic_string.coefficient;
638 let _ = pauli_sum.add_term(scaled_string);
639 } else {
640 let mut identity_string = PauliString::new(self.num_modes);
643 identity_string.coefficient = fermionic_string.coefficient;
644 let _ = pauli_sum.add_term(identity_string);
645 }
646
647 Ok(pauli_sum)
648 }
649
650 pub fn transform_hamiltonian(
652 &mut self,
653 hamiltonian: &FermionicHamiltonian,
654 ) -> Result<PauliOperatorSum> {
655 let mut pauli_hamiltonian = PauliOperatorSum::new(self.num_modes);
656
657 for term in &hamiltonian.terms {
658 let pauli_terms = self.transform_string(term)?;
659 for pauli_term in pauli_terms.terms {
660 let _ = pauli_hamiltonian.add_term(pauli_term);
661 }
662 }
663
664 Ok(pauli_hamiltonian)
665 }
666}
667
668impl FermionicSimulator {
669 pub fn new(num_modes: usize) -> Result<Self> {
671 let dim = 1 << num_modes;
672 let mut state = Array1::zeros(dim);
673 state[0] = Complex64::new(1.0, 0.0); Ok(Self {
676 num_modes,
677 jw_transform: JordanWignerTransform::new(num_modes),
678 state,
679 backend: None,
680 stats: FermionicStats::default(),
681 })
682 }
683
684 pub fn with_scirs2_backend(mut self) -> Result<Self> {
686 self.backend = Some(SciRS2Backend::new());
687 Ok(self)
688 }
689
690 pub fn set_initial_state(&mut self, occupation: &[bool]) -> Result<()> {
692 if occupation.len() != self.num_modes {
693 return Err(SimulatorError::DimensionMismatch(
694 "Occupation must match number of modes".to_string(),
695 ));
696 }
697
698 let mut index = 0;
700 for (i, &occupied) in occupation.iter().enumerate() {
701 if occupied {
702 index |= 1 << (self.num_modes - 1 - i);
703 }
704 }
705
706 self.state.fill(Complex64::new(0.0, 0.0));
707 self.state[index] = Complex64::new(1.0, 0.0);
708
709 Ok(())
710 }
711
712 pub fn apply_fermionic_operator(&mut self, op: &FermionicOperator) -> Result<()> {
714 let start_time = std::time::Instant::now();
715
716 let pauli_string = self.jw_transform.transform_operator(op)?;
718
719 self.apply_pauli_string(&pauli_string)?;
721
722 self.stats.fermionic_ops_applied += 1;
723 self.stats.jw_transformations += 1;
724 self.stats.jw_time_ms += start_time.elapsed().as_secs_f64() * 1000.0;
725
726 Ok(())
727 }
728
729 pub fn apply_fermionic_string(&mut self, fermionic_string: &FermionicString) -> Result<()> {
731 let pauli_sum = self.jw_transform.transform_string(fermionic_string)?;
732
733 for pauli_term in &pauli_sum.terms {
735 self.apply_pauli_string(pauli_term)?;
736 }
737
738 Ok(())
739 }
740
741 const fn apply_pauli_string(&self, pauli_string: &PauliString) -> Result<()> {
743 Ok(())
746 }
747
748 pub fn expectation_value(&mut self, op: &FermionicOperator) -> Result<Complex64> {
750 let pauli_string = self.jw_transform.transform_operator(op)?;
751
752 let expectation = self.compute_pauli_expectation(&pauli_string)?;
754
755 Ok(expectation)
756 }
757
758 const fn compute_pauli_expectation(&self, pauli_string: &PauliString) -> Result<Complex64> {
760 Ok(Complex64::new(0.0, 0.0))
763 }
764
765 pub fn evolve_hamiltonian(
767 &mut self,
768 hamiltonian: &FermionicHamiltonian,
769 time: f64,
770 ) -> Result<()> {
771 let pauli_hamiltonian = self.jw_transform.transform_hamiltonian(hamiltonian)?;
773
774 self.evolve_pauli_hamiltonian(&pauli_hamiltonian, time)?;
776
777 Ok(())
778 }
779
780 const fn evolve_pauli_hamiltonian(
782 &self,
783 _hamiltonian: &PauliOperatorSum,
784 _time: f64,
785 ) -> Result<()> {
786 Ok(())
789 }
790
791 #[must_use]
793 pub const fn get_state(&self) -> &Array1<Complex64> {
794 &self.state
795 }
796
797 #[must_use]
799 pub fn get_particle_number(&self) -> f64 {
800 let mut total_number = 0.0;
801
802 for (index, amplitude) in self.state.iter().enumerate() {
803 let prob = amplitude.norm_sqr();
804 let popcount = f64::from(index.count_ones());
805 total_number += prob * popcount;
806 }
807
808 total_number
809 }
810
811 #[must_use]
813 pub const fn get_stats(&self) -> &FermionicStats {
814 &self.stats
815 }
816
817 pub fn particle_correlation(&mut self, site1: usize, site2: usize) -> Result<f64> {
819 let n1_op = FermionicOperator::Number(site1);
820 let n2_op = FermionicOperator::Number(site2);
821
822 let n1_exp = self.expectation_value(&n1_op)?.re;
823 let n2_exp = self.expectation_value(&n2_op)?.re;
824
825 let n1_n2_exp = 0.0; Ok(n1_exp.mul_add(-n2_exp, n1_n2_exp))
829 }
830}
831
832pub fn benchmark_fermionic_simulation(num_modes: usize) -> Result<FermionicStats> {
834 let mut simulator = FermionicSimulator::new(num_modes)?;
835
836 let hamiltonian = FermionicHamiltonian::hubbard_model(num_modes / 2, 1.0, 2.0, 0.5)?;
838
839 let creation_op = FermionicOperator::Creation(0);
841 simulator.apply_fermionic_operator(&creation_op)?;
842
843 let annihilation_op = FermionicOperator::Annihilation(1);
844 simulator.apply_fermionic_operator(&annihilation_op)?;
845
846 simulator.evolve_hamiltonian(&hamiltonian, 0.1)?;
848
849 Ok(simulator.get_stats().clone())
850}
851
852#[cfg(test)]
853mod tests {
854 use super::*;
855
856 #[test]
857 fn test_fermionic_operator_creation() {
858 let op = FermionicOperator::Creation(0);
859 assert!(op.is_creation());
860 assert!(!op.is_annihilation());
861 assert_eq!(op.site(), Some(0));
862 }
863
864 #[test]
865 fn test_fermionic_string() {
866 let ops = vec![
867 FermionicOperator::Creation(0),
868 FermionicOperator::Annihilation(1),
869 ];
870 let string = FermionicString::new(ops, Complex64::new(1.0, 0.0), 4);
871 assert_eq!(string.operators.len(), 2);
872 assert_eq!(string.num_modes, 4);
873 }
874
875 #[test]
876 fn test_hubbard_hamiltonian() {
877 let hamiltonian = FermionicHamiltonian::hubbard_model(2, 1.0, 2.0, 0.5)
878 .expect("Failed to create Hubbard model Hamiltonian");
879 assert_eq!(hamiltonian.num_modes, 4); assert!(!hamiltonian.terms.is_empty());
881 }
882
883 #[test]
884 fn test_jordan_wigner_transform() {
885 let mut jw = JordanWignerTransform::new(4);
886 let creation_op = FermionicOperator::Creation(1);
887 let pauli_string = jw
888 .transform_operator(&creation_op)
889 .expect("Failed to transform creation operator via Jordan-Wigner");
890
891 assert_eq!(pauli_string.num_qubits, 4);
892 assert_eq!(pauli_string.operators[0], PauliOperator::Z); assert_eq!(pauli_string.operators[1], PauliOperator::X);
894 }
895
896 #[test]
897 fn test_fermionic_simulator() {
898 let mut simulator =
899 FermionicSimulator::new(4).expect("Failed to create fermionic simulator");
900
901 simulator
903 .set_initial_state(&[true, false, false, false])
904 .expect("Failed to set initial fermionic state");
905
906 let particle_number = simulator.get_particle_number();
907 assert!((particle_number - 1.0).abs() < 1e-10);
908 }
909
910 #[test]
911 fn test_fermionic_string_multiplication() {
912 let string1 = FermionicString::creation(0, Complex64::new(1.0, 0.0), 4);
913 let string2 = FermionicString::annihilation(1, Complex64::new(1.0, 0.0), 4);
914
915 let product = string1
916 .multiply(&string2)
917 .expect("Failed to multiply fermionic strings");
918 assert!(!product.operators.is_empty());
919 }
920}