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 pub fn is_creation(&self) -> bool {
92 matches!(self, FermionicOperator::Creation(_))
93 }
94
95 pub fn is_annihilation(&self) -> bool {
97 matches!(self, FermionicOperator::Annihilation(_))
98 }
99
100 pub fn site(&self) -> Option<usize> {
102 match self {
103 FermionicOperator::Creation(i)
104 | FermionicOperator::Annihilation(i)
105 | FermionicOperator::Number(i) => Some(*i),
106 _ => None,
107 }
108 }
109
110 pub fn ordering_key(&self) -> (usize, usize) {
112 match self {
113 FermionicOperator::Creation(i) => (1, *i),
114 FermionicOperator::Annihilation(i) => (0, *i),
115 FermionicOperator::Number(i) => (2, *i),
116 FermionicOperator::Hopping { from, to } => (3, from.min(to) * 1000 + from.max(to)),
117 FermionicOperator::Interaction { sites } => {
118 let mut sorted_sites = *sites;
119 sorted_sites.sort();
120 (
121 4,
122 sorted_sites[0] * 1000000
123 + sorted_sites[1] * 10000
124 + sorted_sites[2] * 100
125 + sorted_sites[3],
126 )
127 }
128 }
129 }
130}
131
132impl FermionicString {
133 pub fn new(
135 operators: Vec<FermionicOperator>,
136 coefficient: Complex64,
137 num_modes: usize,
138 ) -> Self {
139 Self {
140 operators,
141 coefficient,
142 num_modes,
143 }
144 }
145
146 pub fn single_operator(
148 op: FermionicOperator,
149 coefficient: Complex64,
150 num_modes: usize,
151 ) -> Self {
152 Self::new(vec![op], coefficient, num_modes)
153 }
154
155 pub fn creation(site: usize, coefficient: Complex64, num_modes: usize) -> Self {
157 Self::single_operator(FermionicOperator::Creation(site), coefficient, num_modes)
158 }
159
160 pub fn annihilation(site: usize, coefficient: Complex64, num_modes: usize) -> Self {
162 Self::single_operator(
163 FermionicOperator::Annihilation(site),
164 coefficient,
165 num_modes,
166 )
167 }
168
169 pub fn number(site: usize, coefficient: Complex64, num_modes: usize) -> Self {
171 Self::single_operator(FermionicOperator::Number(site), coefficient, num_modes)
172 }
173
174 pub fn hopping(from: usize, to: usize, coefficient: Complex64, num_modes: usize) -> Self {
176 Self::single_operator(
177 FermionicOperator::Hopping { from, to },
178 coefficient,
179 num_modes,
180 )
181 }
182
183 pub fn multiply(&self, other: &FermionicString) -> Result<FermionicString> {
185 if self.num_modes != other.num_modes {
186 return Err(SimulatorError::DimensionMismatch(
187 "Fermionic strings must have same number of modes".to_string(),
188 ));
189 }
190
191 let mut result_ops = self.operators.clone();
192 result_ops.extend(other.operators.clone());
193
194 let (canonical_ops, sign) = self.canonicalize_operators(&result_ops)?;
196
197 Ok(FermionicString {
198 operators: canonical_ops,
199 coefficient: self.coefficient * other.coefficient * sign,
200 num_modes: self.num_modes,
201 })
202 }
203
204 fn canonicalize_operators(
206 &self,
207 ops: &[FermionicOperator],
208 ) -> Result<(Vec<FermionicOperator>, Complex64)> {
209 let mut canonical = ops.to_vec();
210 let mut sign = Complex64::new(1.0, 0.0);
211
212 for i in 0..canonical.len() {
214 for j in (i + 1)..canonical.len() {
215 if canonical[i].ordering_key() > canonical[j].ordering_key() {
216 canonical.swap(i, j);
218 sign *= Complex64::new(-1.0, 0.0);
219 }
220 }
221 }
222
223 let simplified = self.apply_fermionic_algebra(&canonical)?;
225
226 Ok((simplified, sign))
227 }
228
229 fn apply_fermionic_algebra(&self, ops: &[FermionicOperator]) -> Result<Vec<FermionicOperator>> {
231 let mut result = Vec::new();
232 let mut i = 0;
233
234 while i < ops.len() {
235 if i + 1 < ops.len() {
236 match (&ops[i], &ops[i + 1]) {
237 (FermionicOperator::Creation(a), FermionicOperator::Annihilation(b))
239 if a == b =>
240 {
241 result.push(FermionicOperator::Number(*a));
242 i += 2;
243 continue;
244 }
245 (FermionicOperator::Annihilation(a), FermionicOperator::Annihilation(b))
247 if a == b =>
248 {
249 i += 2;
251 continue;
252 }
253 (FermionicOperator::Creation(a), FermionicOperator::Creation(b)) if a == b => {
255 i += 2;
257 continue;
258 }
259 _ => {
260 result.push(ops[i].clone());
261 i += 1;
262 }
263 }
264 } else {
265 result.push(ops[i].clone());
266 i += 1;
267 }
268 }
269
270 Ok(result)
271 }
272
273 pub fn hermitian_conjugate(&self) -> FermionicString {
275 let mut conjugate_ops = Vec::new();
276
277 for op in self.operators.iter().rev() {
279 let conjugate_op = match op {
280 FermionicOperator::Creation(i) => FermionicOperator::Annihilation(*i),
281 FermionicOperator::Annihilation(i) => FermionicOperator::Creation(*i),
282 FermionicOperator::Number(i) => FermionicOperator::Number(*i),
283 FermionicOperator::Hopping { from, to } => FermionicOperator::Hopping {
284 from: *to,
285 to: *from,
286 },
287 FermionicOperator::Interaction { sites } => {
288 let mut rev_sites = *sites;
290 rev_sites.reverse();
291 FermionicOperator::Interaction { sites: rev_sites }
292 }
293 };
294 conjugate_ops.push(conjugate_op);
295 }
296
297 FermionicString {
298 operators: conjugate_ops,
299 coefficient: self.coefficient.conj(),
300 num_modes: self.num_modes,
301 }
302 }
303}
304
305impl FermionicHamiltonian {
306 pub fn new(num_modes: usize) -> Self {
308 Self {
309 terms: Vec::new(),
310 num_modes,
311 is_hermitian: true,
312 }
313 }
314
315 pub fn add_term(&mut self, term: FermionicString) -> Result<()> {
317 if term.num_modes != self.num_modes {
318 return Err(SimulatorError::DimensionMismatch(
319 "Term must have same number of modes as Hamiltonian".to_string(),
320 ));
321 }
322
323 self.terms.push(term);
324 Ok(())
325 }
326
327 pub fn make_hermitian(&mut self) {
329 let mut conjugate_terms = Vec::new();
330
331 for term in &self.terms {
332 let conjugate = term.hermitian_conjugate();
333 if !self.terms_equal(term, &conjugate) {
335 conjugate_terms.push(conjugate);
336 }
337 }
338
339 self.terms.extend(conjugate_terms);
340 self.is_hermitian = true;
341 }
342
343 fn terms_equal(&self, term1: &FermionicString, term2: &FermionicString) -> bool {
345 term1.operators == term2.operators && (term1.coefficient - term2.coefficient).norm() < 1e-12
346 }
347
348 pub fn molecular_hamiltonian(
350 num_modes: usize,
351 one_body_integrals: &Array2<f64>,
352 two_body_integrals: &Array3<f64>,
353 ) -> Result<Self> {
354 let mut hamiltonian = Self::new(num_modes);
355
356 for i in 0..num_modes {
358 for j in 0..num_modes {
359 if one_body_integrals[[i, j]].abs() > 1e-12 {
360 let coeff = Complex64::new(one_body_integrals[[i, j]], 0.0);
361 let term = FermionicString::new(
362 vec![
363 FermionicOperator::Creation(i),
364 FermionicOperator::Annihilation(j),
365 ],
366 coeff,
367 num_modes,
368 );
369 hamiltonian.add_term(term)?;
370 }
371 }
372 }
373
374 for i in 0..num_modes {
376 for j in 0..num_modes {
377 for k in 0..num_modes {
378 if two_body_integrals[[i, j, k]].abs() > 1e-12 {
379 for l in 0..num_modes {
380 let coeff = Complex64::new(0.5 * two_body_integrals[[i, j, k]], 0.0);
381 let term = FermionicString::new(
382 vec![
383 FermionicOperator::Creation(i),
384 FermionicOperator::Creation(j),
385 FermionicOperator::Annihilation(l),
386 FermionicOperator::Annihilation(k),
387 ],
388 coeff,
389 num_modes,
390 );
391 hamiltonian.add_term(term)?;
392 }
393 }
394 }
395 }
396 }
397
398 hamiltonian.make_hermitian();
399 Ok(hamiltonian)
400 }
401
402 pub fn hubbard_model(
404 sites: usize,
405 hopping: f64,
406 interaction: f64,
407 chemical_potential: f64,
408 ) -> Result<Self> {
409 let num_modes = 2 * sites; let mut hamiltonian = Self::new(num_modes);
411
412 for i in 0..sites {
414 for sigma in 0..2 {
415 let site_i = 2 * i + sigma;
416
417 if i + 1 < sites {
419 let site_j = 2 * (i + 1) + sigma;
420
421 let hopping_term = FermionicString::hopping(
423 site_i,
424 site_j,
425 Complex64::new(-hopping, 0.0),
426 num_modes,
427 );
428 hamiltonian.add_term(hopping_term)?;
429
430 let back_hopping_term = FermionicString::hopping(
432 site_j,
433 site_i,
434 Complex64::new(-hopping, 0.0),
435 num_modes,
436 );
437 hamiltonian.add_term(back_hopping_term)?;
438 }
439 }
440 }
441
442 for i in 0..sites {
444 let up_site = 2 * i;
445 let down_site = 2 * i + 1;
446
447 let interaction_term = FermionicString::new(
448 vec![
449 FermionicOperator::Number(up_site),
450 FermionicOperator::Number(down_site),
451 ],
452 Complex64::new(interaction, 0.0),
453 num_modes,
454 );
455 hamiltonian.add_term(interaction_term)?;
456 }
457
458 for i in 0..num_modes {
460 let mu_term =
461 FermionicString::number(i, Complex64::new(-chemical_potential, 0.0), num_modes);
462 hamiltonian.add_term(mu_term)?;
463 }
464
465 Ok(hamiltonian)
466 }
467}
468
469impl JordanWignerTransform {
470 pub fn new(num_modes: usize) -> Self {
472 Self {
473 num_modes,
474 pauli_cache: HashMap::new(),
475 }
476 }
477
478 pub fn transform_operator(&mut self, op: &FermionicOperator) -> Result<PauliString> {
480 if let Some(cached) = self.pauli_cache.get(op) {
481 return Ok(cached.clone());
482 }
483
484 let pauli_string = match op {
485 FermionicOperator::Creation(i) => self.creation_to_pauli(*i)?,
486 FermionicOperator::Annihilation(i) => self.annihilation_to_pauli(*i)?,
487 FermionicOperator::Number(i) => self.number_to_pauli(*i)?,
488 FermionicOperator::Hopping { from, to } => self.hopping_to_pauli(*from, *to)?,
489 FermionicOperator::Interaction { sites } => self.interaction_to_pauli(*sites)?,
490 };
491
492 self.pauli_cache.insert(op.clone(), pauli_string.clone());
493 Ok(pauli_string)
494 }
495
496 fn creation_to_pauli(&self, site: usize) -> Result<PauliString> {
498 if site >= self.num_modes {
499 return Err(SimulatorError::IndexOutOfBounds(site));
500 }
501
502 let mut paulis = vec![PauliOperator::I; self.num_modes];
503
504 for j in 0..site {
506 paulis[j] = PauliOperator::Z;
507 }
508
509 paulis[site] = PauliOperator::X;
513
514 let ops: Vec<(usize, PauliOperator)> = paulis
515 .iter()
516 .enumerate()
517 .filter(|(_, &op)| op != PauliOperator::I)
518 .map(|(i, &op)| (i, op))
519 .collect();
520
521 PauliString::from_ops(self.num_modes, &ops, Complex64::new(0.5, 0.0))
522 }
523
524 fn annihilation_to_pauli(&self, site: usize) -> Result<PauliString> {
526 if site >= self.num_modes {
527 return Err(SimulatorError::IndexOutOfBounds(site));
528 }
529
530 let mut paulis = vec![PauliOperator::I; self.num_modes];
531
532 for j in 0..site {
534 paulis[j] = PauliOperator::Z;
535 }
536
537 paulis[site] = PauliOperator::X;
538
539 let ops: Vec<(usize, PauliOperator)> = paulis
540 .iter()
541 .enumerate()
542 .filter(|(_, &op)| op != PauliOperator::I)
543 .map(|(i, &op)| (i, op))
544 .collect();
545
546 PauliString::from_ops(self.num_modes, &ops, Complex64::new(0.5, 0.0))
547 }
548
549 fn number_to_pauli(&self, site: usize) -> Result<PauliString> {
551 if site >= self.num_modes {
552 return Err(SimulatorError::IndexOutOfBounds(site));
553 }
554
555 let mut paulis = vec![PauliOperator::I; self.num_modes];
556
557 paulis[site] = PauliOperator::Z;
559
560 let ops: Vec<(usize, PauliOperator)> = paulis
561 .iter()
562 .enumerate()
563 .filter(|(_, &op)| op != PauliOperator::I)
564 .map(|(i, &op)| (i, op))
565 .collect();
566
567 PauliString::from_ops(self.num_modes, &ops, Complex64::new(-0.5, 0.0))
568 }
569
570 fn hopping_to_pauli(&self, from: usize, to: usize) -> Result<PauliString> {
572 let mut paulis = vec![PauliOperator::I; self.num_modes];
575
576 let min_site = from.min(to);
577 let max_site = from.max(to);
578
579 for j in min_site..max_site {
581 paulis[j] = PauliOperator::Z;
582 }
583
584 paulis[from] = PauliOperator::X;
585 paulis[to] = PauliOperator::X;
586
587 let ops: Vec<(usize, PauliOperator)> = paulis
588 .iter()
589 .enumerate()
590 .filter(|(_, &op)| op != PauliOperator::I)
591 .map(|(i, &op)| (i, op))
592 .collect();
593
594 PauliString::from_ops(self.num_modes, &ops, Complex64::new(0.25, 0.0))
595 }
596
597 fn interaction_to_pauli(&self, sites: [usize; 4]) -> Result<PauliString> {
599 let mut paulis = vec![PauliOperator::I; self.num_modes];
601
602 for &site in &sites {
603 paulis[site] = PauliOperator::Z;
604 }
605
606 let ops: Vec<(usize, PauliOperator)> = paulis
607 .iter()
608 .enumerate()
609 .filter(|(_, &op)| op != PauliOperator::I)
610 .map(|(i, &op)| (i, op))
611 .collect();
612
613 PauliString::from_ops(self.num_modes, &ops, Complex64::new(0.0625, 0.0))
614 }
615
616 pub fn transform_string(
618 &mut self,
619 fermionic_string: &FermionicString,
620 ) -> Result<PauliOperatorSum> {
621 let mut pauli_sum = PauliOperatorSum::new(self.num_modes);
622
623 if fermionic_string.operators.is_empty() {
624 let mut identity_string = PauliString::new(self.num_modes);
626 identity_string.coefficient = fermionic_string.coefficient;
627 let _ = pauli_sum.add_term(identity_string);
628 return Ok(pauli_sum);
629 }
630
631 if fermionic_string.operators.len() == 1 {
633 let pauli_string = self.transform_operator(&fermionic_string.operators[0])?;
634 let mut scaled_string = pauli_string.clone();
635 scaled_string.coefficient = pauli_string.coefficient * fermionic_string.coefficient;
636 let _ = pauli_sum.add_term(scaled_string);
637 } else {
638 let mut identity_string = PauliString::new(self.num_modes);
641 identity_string.coefficient = fermionic_string.coefficient;
642 let _ = pauli_sum.add_term(identity_string);
643 }
644
645 Ok(pauli_sum)
646 }
647
648 pub fn transform_hamiltonian(
650 &mut self,
651 hamiltonian: &FermionicHamiltonian,
652 ) -> Result<PauliOperatorSum> {
653 let mut pauli_hamiltonian = PauliOperatorSum::new(self.num_modes);
654
655 for term in &hamiltonian.terms {
656 let pauli_terms = self.transform_string(term)?;
657 for pauli_term in pauli_terms.terms {
658 let _ = pauli_hamiltonian.add_term(pauli_term);
659 }
660 }
661
662 Ok(pauli_hamiltonian)
663 }
664}
665
666impl FermionicSimulator {
667 pub fn new(num_modes: usize) -> Result<Self> {
669 let dim = 1 << num_modes;
670 let mut state = Array1::zeros(dim);
671 state[0] = Complex64::new(1.0, 0.0); Ok(Self {
674 num_modes,
675 jw_transform: JordanWignerTransform::new(num_modes),
676 state,
677 backend: None,
678 stats: FermionicStats::default(),
679 })
680 }
681
682 pub fn with_scirs2_backend(mut self) -> Result<Self> {
684 self.backend = Some(SciRS2Backend::new());
685 Ok(self)
686 }
687
688 pub fn set_initial_state(&mut self, occupation: &[bool]) -> Result<()> {
690 if occupation.len() != self.num_modes {
691 return Err(SimulatorError::DimensionMismatch(
692 "Occupation must match number of modes".to_string(),
693 ));
694 }
695
696 let mut index = 0;
698 for (i, &occupied) in occupation.iter().enumerate() {
699 if occupied {
700 index |= 1 << (self.num_modes - 1 - i);
701 }
702 }
703
704 self.state.fill(Complex64::new(0.0, 0.0));
705 self.state[index] = Complex64::new(1.0, 0.0);
706
707 Ok(())
708 }
709
710 pub fn apply_fermionic_operator(&mut self, op: &FermionicOperator) -> Result<()> {
712 let start_time = std::time::Instant::now();
713
714 let pauli_string = self.jw_transform.transform_operator(op)?;
716
717 self.apply_pauli_string(&pauli_string)?;
719
720 self.stats.fermionic_ops_applied += 1;
721 self.stats.jw_transformations += 1;
722 self.stats.jw_time_ms += start_time.elapsed().as_secs_f64() * 1000.0;
723
724 Ok(())
725 }
726
727 pub fn apply_fermionic_string(&mut self, fermionic_string: &FermionicString) -> Result<()> {
729 let pauli_sum = self.jw_transform.transform_string(fermionic_string)?;
730
731 for pauli_term in &pauli_sum.terms {
733 self.apply_pauli_string(pauli_term)?;
734 }
735
736 Ok(())
737 }
738
739 fn apply_pauli_string(&mut self, pauli_string: &PauliString) -> Result<()> {
741 Ok(())
744 }
745
746 pub fn expectation_value(&mut self, op: &FermionicOperator) -> Result<Complex64> {
748 let pauli_string = self.jw_transform.transform_operator(op)?;
749
750 let expectation = self.compute_pauli_expectation(&pauli_string)?;
752
753 Ok(expectation)
754 }
755
756 fn compute_pauli_expectation(&self, pauli_string: &PauliString) -> Result<Complex64> {
758 Ok(Complex64::new(0.0, 0.0))
761 }
762
763 pub fn evolve_hamiltonian(
765 &mut self,
766 hamiltonian: &FermionicHamiltonian,
767 time: f64,
768 ) -> Result<()> {
769 let pauli_hamiltonian = self.jw_transform.transform_hamiltonian(hamiltonian)?;
771
772 self.evolve_pauli_hamiltonian(&pauli_hamiltonian, time)?;
774
775 Ok(())
776 }
777
778 fn evolve_pauli_hamiltonian(
780 &mut self,
781 _hamiltonian: &PauliOperatorSum,
782 _time: f64,
783 ) -> Result<()> {
784 Ok(())
787 }
788
789 pub fn get_state(&self) -> &Array1<Complex64> {
791 &self.state
792 }
793
794 pub fn get_particle_number(&self) -> f64 {
796 let mut total_number = 0.0;
797
798 for (index, amplitude) in self.state.iter().enumerate() {
799 let prob = amplitude.norm_sqr();
800 let popcount = index.count_ones() as f64;
801 total_number += prob * popcount;
802 }
803
804 total_number
805 }
806
807 pub fn get_stats(&self) -> &FermionicStats {
809 &self.stats
810 }
811
812 pub fn particle_correlation(&mut self, site1: usize, site2: usize) -> Result<f64> {
814 let n1_op = FermionicOperator::Number(site1);
815 let n2_op = FermionicOperator::Number(site2);
816
817 let n1_exp = self.expectation_value(&n1_op)?.re;
818 let n2_exp = self.expectation_value(&n2_op)?.re;
819
820 let n1_n2_exp = 0.0; Ok(n1_n2_exp - n1_exp * n2_exp)
824 }
825}
826
827pub fn benchmark_fermionic_simulation(num_modes: usize) -> Result<FermionicStats> {
829 let mut simulator = FermionicSimulator::new(num_modes)?;
830
831 let hamiltonian = FermionicHamiltonian::hubbard_model(num_modes / 2, 1.0, 2.0, 0.5)?;
833
834 let creation_op = FermionicOperator::Creation(0);
836 simulator.apply_fermionic_operator(&creation_op)?;
837
838 let annihilation_op = FermionicOperator::Annihilation(1);
839 simulator.apply_fermionic_operator(&annihilation_op)?;
840
841 simulator.evolve_hamiltonian(&hamiltonian, 0.1)?;
843
844 Ok(simulator.get_stats().clone())
845}
846
847#[cfg(test)]
848mod tests {
849 use super::*;
850
851 #[test]
852 fn test_fermionic_operator_creation() {
853 let op = FermionicOperator::Creation(0);
854 assert!(op.is_creation());
855 assert!(!op.is_annihilation());
856 assert_eq!(op.site(), Some(0));
857 }
858
859 #[test]
860 fn test_fermionic_string() {
861 let ops = vec![
862 FermionicOperator::Creation(0),
863 FermionicOperator::Annihilation(1),
864 ];
865 let string = FermionicString::new(ops, Complex64::new(1.0, 0.0), 4);
866 assert_eq!(string.operators.len(), 2);
867 assert_eq!(string.num_modes, 4);
868 }
869
870 #[test]
871 fn test_hubbard_hamiltonian() {
872 let hamiltonian = FermionicHamiltonian::hubbard_model(2, 1.0, 2.0, 0.5).unwrap();
873 assert_eq!(hamiltonian.num_modes, 4); assert!(!hamiltonian.terms.is_empty());
875 }
876
877 #[test]
878 fn test_jordan_wigner_transform() {
879 let mut jw = JordanWignerTransform::new(4);
880 let creation_op = FermionicOperator::Creation(1);
881 let pauli_string = jw.transform_operator(&creation_op).unwrap();
882
883 assert_eq!(pauli_string.num_qubits, 4);
884 assert_eq!(pauli_string.operators[0], PauliOperator::Z); assert_eq!(pauli_string.operators[1], PauliOperator::X);
886 }
887
888 #[test]
889 fn test_fermionic_simulator() {
890 let mut simulator = FermionicSimulator::new(4).unwrap();
891
892 simulator
894 .set_initial_state(&[true, false, false, false])
895 .unwrap();
896
897 let particle_number = simulator.get_particle_number();
898 assert!((particle_number - 1.0).abs() < 1e-10);
899 }
900
901 #[test]
902 fn test_fermionic_string_multiplication() {
903 let string1 = FermionicString::creation(0, Complex64::new(1.0, 0.0), 4);
904 let string2 = FermionicString::annihilation(1, Complex64::new(1.0, 0.0), 4);
905
906 let product = string1.multiply(&string2).unwrap();
907 assert!(!product.operators.is_empty());
908 }
909}