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