quantrs2_core/
fermionic.rs

1//! Fermionic operations and Jordan-Wigner transformations
2//!
3//! This module provides support for fermionic operators and their mapping to qubit operators
4//! using the Jordan-Wigner transformation. This enables quantum simulation of fermionic systems
5//! such as molecules and condensed matter systems.
6
7use crate::{
8    error::{QuantRS2Error, QuantRS2Result},
9    gate::{single, GateOp},
10    qubit::QubitId,
11};
12use ndarray::Array2;
13use num_complex::Complex;
14use rustc_hash::FxHashMap;
15
16/// Type alias for complex numbers
17type Complex64 = Complex<f64>;
18
19/// Fermionic operator types
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
21pub enum FermionOperatorType {
22    /// Creation operator a†
23    Creation,
24    /// Annihilation operator a
25    Annihilation,
26    /// Number operator n = a†a
27    Number,
28    /// Identity operator
29    Identity,
30}
31
32/// A single fermionic operator acting on a specific mode
33#[derive(Debug, Clone, PartialEq)]
34pub struct FermionOperator {
35    /// Type of the operator
36    pub op_type: FermionOperatorType,
37    /// Mode (site) index
38    pub mode: usize,
39    /// Coefficient
40    pub coefficient: Complex64,
41}
42
43impl FermionOperator {
44    /// Create a new fermionic operator
45    pub fn new(op_type: FermionOperatorType, mode: usize, coefficient: Complex64) -> Self {
46        Self {
47            op_type,
48            mode,
49            coefficient,
50        }
51    }
52
53    /// Create a creation operator
54    pub fn creation(mode: usize) -> Self {
55        Self::new(
56            FermionOperatorType::Creation,
57            mode,
58            Complex64::new(1.0, 0.0),
59        )
60    }
61
62    /// Create an annihilation operator
63    pub fn annihilation(mode: usize) -> Self {
64        Self::new(
65            FermionOperatorType::Annihilation,
66            mode,
67            Complex64::new(1.0, 0.0),
68        )
69    }
70
71    /// Create a number operator
72    pub fn number(mode: usize) -> Self {
73        Self::new(FermionOperatorType::Number, mode, Complex64::new(1.0, 0.0))
74    }
75
76    /// Get the Hermitian conjugate
77    pub fn dagger(&self) -> Self {
78        let conj_coeff = self.coefficient.conj();
79        match self.op_type {
80            FermionOperatorType::Creation => {
81                Self::new(FermionOperatorType::Annihilation, self.mode, conj_coeff)
82            }
83            FermionOperatorType::Annihilation => {
84                Self::new(FermionOperatorType::Creation, self.mode, conj_coeff)
85            }
86            FermionOperatorType::Number => {
87                Self::new(FermionOperatorType::Number, self.mode, conj_coeff)
88            }
89            FermionOperatorType::Identity => {
90                Self::new(FermionOperatorType::Identity, self.mode, conj_coeff)
91            }
92        }
93    }
94}
95
96/// A product of fermionic operators
97#[derive(Debug, Clone, PartialEq)]
98pub struct FermionTerm {
99    /// Ordered list of operators in the term
100    pub operators: Vec<FermionOperator>,
101    /// Overall coefficient
102    pub coefficient: Complex64,
103}
104
105impl FermionTerm {
106    /// Create a new fermionic term
107    pub fn new(operators: Vec<FermionOperator>, coefficient: Complex64) -> Self {
108        Self {
109            operators,
110            coefficient,
111        }
112    }
113
114    /// Create an identity term
115    pub fn identity() -> Self {
116        Self {
117            operators: vec![],
118            coefficient: Complex64::new(1.0, 0.0),
119        }
120    }
121
122    /// Normal order the operators using anticommutation relations
123    pub fn normal_order(&mut self) -> QuantRS2Result<()> {
124        // Bubble sort with anticommutation
125        let n = self.operators.len();
126        for i in 0..n {
127            for j in 0..n.saturating_sub(i + 1) {
128                if self.should_swap(j) {
129                    self.swap_operators(j)?;
130                }
131            }
132        }
133        Ok(())
134    }
135
136    /// Check if two adjacent operators should be swapped
137    fn should_swap(&self, idx: usize) -> bool {
138        if idx + 1 >= self.operators.len() {
139            return false;
140        }
141
142        let op1 = &self.operators[idx];
143        let op2 = &self.operators[idx + 1];
144
145        // Normal ordering: creation operators before annihilation
146        match (op1.op_type, op2.op_type) {
147            (FermionOperatorType::Annihilation, FermionOperatorType::Creation) => {
148                op1.mode > op2.mode
149            }
150            (FermionOperatorType::Creation, FermionOperatorType::Creation) => op1.mode > op2.mode,
151            (FermionOperatorType::Annihilation, FermionOperatorType::Annihilation) => {
152                op1.mode < op2.mode
153            }
154            _ => false,
155        }
156    }
157
158    /// Swap two adjacent operators with anticommutation
159    fn swap_operators(&mut self, idx: usize) -> QuantRS2Result<()> {
160        if idx + 1 >= self.operators.len() {
161            return Err(QuantRS2Error::InvalidInput("Index out of bounds".into()));
162        }
163
164        let op1 = &self.operators[idx];
165        let op2 = &self.operators[idx + 1];
166
167        // Check anticommutation relation
168        if op1.mode != op2.mode {
169            // Different modes anticommute: {a_i, a_j†} = 0 for i ≠ j
170            self.coefficient *= -1.0;
171        } else {
172            // Same mode: {a_i, a_i†} = 1
173            match (op1.op_type, op2.op_type) {
174                (FermionOperatorType::Annihilation, FermionOperatorType::Creation) => {
175                    // a_i a_i† = 1 - a_i† a_i
176                    // This requires splitting into two terms
177                    return Err(QuantRS2Error::UnsupportedOperation(
178                        "Anticommutation that produces multiple terms not yet supported".into(),
179                    ));
180                }
181                _ => {
182                    self.coefficient *= -1.0;
183                }
184            }
185        }
186
187        self.operators.swap(idx, idx + 1);
188        Ok(())
189    }
190
191    /// Get the Hermitian conjugate
192    pub fn dagger(&self) -> Self {
193        let mut conj_ops = self.operators.clone();
194        conj_ops.reverse();
195        conj_ops = conj_ops.into_iter().map(|op| op.dagger()).collect();
196
197        Self {
198            operators: conj_ops,
199            coefficient: self.coefficient.conj(),
200        }
201    }
202}
203
204/// A sum of fermionic terms (second-quantized Hamiltonian)
205#[derive(Debug, Clone)]
206pub struct FermionHamiltonian {
207    /// Terms in the Hamiltonian
208    pub terms: Vec<FermionTerm>,
209    /// Number of fermionic modes
210    pub n_modes: usize,
211}
212
213impl FermionHamiltonian {
214    /// Create a new fermionic Hamiltonian
215    pub fn new(n_modes: usize) -> Self {
216        Self {
217            terms: Vec::new(),
218            n_modes,
219        }
220    }
221
222    /// Add a term to the Hamiltonian
223    pub fn add_term(&mut self, term: FermionTerm) {
224        self.terms.push(term);
225    }
226
227    /// Add a one-body term: h_ij a†_i a_j
228    pub fn add_one_body(&mut self, i: usize, j: usize, coefficient: Complex64) {
229        let term = FermionTerm::new(
230            vec![
231                FermionOperator::creation(i),
232                FermionOperator::annihilation(j),
233            ],
234            coefficient,
235        );
236        self.add_term(term);
237    }
238
239    /// Add a two-body term: g_ijkl a†_i a†_j a_k a_l
240    pub fn add_two_body(&mut self, i: usize, j: usize, k: usize, l: usize, coefficient: Complex64) {
241        let term = FermionTerm::new(
242            vec![
243                FermionOperator::creation(i),
244                FermionOperator::creation(j),
245                FermionOperator::annihilation(k),
246                FermionOperator::annihilation(l),
247            ],
248            coefficient,
249        );
250        self.add_term(term);
251    }
252
253    /// Add a chemical potential term: μ n_i
254    pub fn add_chemical_potential(&mut self, i: usize, mu: f64) {
255        let term = FermionTerm::new(vec![FermionOperator::number(i)], Complex64::new(mu, 0.0));
256        self.add_term(term);
257    }
258
259    /// Get the Hermitian conjugate
260    pub fn dagger(&self) -> Self {
261        let conj_terms = self.terms.iter().map(|t| t.dagger()).collect();
262        Self {
263            terms: conj_terms,
264            n_modes: self.n_modes,
265        }
266    }
267
268    /// Check if the Hamiltonian is Hermitian
269    pub fn is_hermitian(&self, _tolerance: f64) -> bool {
270        let conj = self.dagger();
271
272        // Compare terms (this is simplified - proper implementation would canonicalize first)
273        if self.terms.len() != conj.terms.len() {
274            return false;
275        }
276
277        // Check if all terms match within tolerance
278        true // Placeholder
279    }
280}
281
282/// Jordan-Wigner transformation
283pub struct JordanWigner {
284    /// Number of fermionic modes (qubits)
285    n_modes: usize,
286}
287
288impl JordanWigner {
289    /// Create a new Jordan-Wigner transformer
290    pub fn new(n_modes: usize) -> Self {
291        Self { n_modes }
292    }
293
294    /// Transform a fermionic operator to qubit operators
295    pub fn transform_operator(&self, op: &FermionOperator) -> QuantRS2Result<Vec<QubitOperator>> {
296        match op.op_type {
297            FermionOperatorType::Creation => self.transform_creation(op.mode, op.coefficient),
298            FermionOperatorType::Annihilation => {
299                self.transform_annihilation(op.mode, op.coefficient)
300            }
301            FermionOperatorType::Number => self.transform_number(op.mode, op.coefficient),
302            FermionOperatorType::Identity => {
303                Ok(vec![QubitOperator::identity(self.n_modes, op.coefficient)])
304            }
305        }
306    }
307
308    /// Transform creation operator a†_j = (X_j - iY_j)/2 ⊗ Z_{<j}
309    fn transform_creation(
310        &self,
311        mode: usize,
312        coeff: Complex64,
313    ) -> QuantRS2Result<Vec<QubitOperator>> {
314        if mode >= self.n_modes {
315            return Err(QuantRS2Error::InvalidInput(format!(
316                "Mode {} out of bounds",
317                mode
318            )));
319        }
320
321        let mut operators = Vec::new();
322
323        // (X_j - iY_j)/2 = σ^-_j
324        let sigma_minus = QubitTerm {
325            operators: vec![(mode, PauliOperator::Minus)],
326            coefficient: coeff,
327        };
328
329        // Apply Z string to all qubits before mode j
330        let z_string: Vec<_> = (0..mode).map(|i| (i, PauliOperator::Z)).collect();
331
332        let mut term = sigma_minus;
333        term.operators.extend(z_string);
334
335        operators.push(QubitOperator {
336            terms: vec![term],
337            n_qubits: self.n_modes,
338        });
339
340        Ok(operators)
341    }
342
343    /// Transform annihilation operator a_j = (X_j + iY_j)/2 ⊗ Z_{<j}
344    fn transform_annihilation(
345        &self,
346        mode: usize,
347        coeff: Complex64,
348    ) -> QuantRS2Result<Vec<QubitOperator>> {
349        if mode >= self.n_modes {
350            return Err(QuantRS2Error::InvalidInput(format!(
351                "Mode {} out of bounds",
352                mode
353            )));
354        }
355
356        let mut operators = Vec::new();
357
358        // (X_j + iY_j)/2 = σ^+_j
359        let sigma_plus = QubitTerm {
360            operators: vec![(mode, PauliOperator::Plus)],
361            coefficient: coeff,
362        };
363
364        // Apply Z string to all qubits before mode j
365        let z_string: Vec<_> = (0..mode).map(|i| (i, PauliOperator::Z)).collect();
366
367        let mut term = sigma_plus;
368        term.operators.extend(z_string);
369
370        operators.push(QubitOperator {
371            terms: vec![term],
372            n_qubits: self.n_modes,
373        });
374
375        Ok(operators)
376    }
377
378    /// Transform number operator n_j = a†_j a_j = (I - Z_j)/2
379    fn transform_number(
380        &self,
381        mode: usize,
382        coeff: Complex64,
383    ) -> QuantRS2Result<Vec<QubitOperator>> {
384        if mode >= self.n_modes {
385            return Err(QuantRS2Error::InvalidInput(format!(
386                "Mode {} out of bounds",
387                mode
388            )));
389        }
390
391        let mut operators = Vec::new();
392
393        // Identity term
394        operators.push(QubitOperator {
395            terms: vec![QubitTerm {
396                operators: vec![],
397                coefficient: coeff * 0.5,
398            }],
399            n_qubits: self.n_modes,
400        });
401
402        // -Z term
403        operators.push(QubitOperator {
404            terms: vec![QubitTerm {
405                operators: vec![(mode, PauliOperator::Z)],
406                coefficient: -coeff * 0.5,
407            }],
408            n_qubits: self.n_modes,
409        });
410
411        Ok(operators)
412    }
413
414    /// Transform a fermionic term to qubit operators
415    pub fn transform_term(&self, term: &FermionTerm) -> QuantRS2Result<QubitOperator> {
416        if term.operators.is_empty() {
417            return Ok(QubitOperator::identity(self.n_modes, term.coefficient));
418        }
419
420        // Transform each operator and combine
421        let mut result = QubitOperator::identity(self.n_modes, term.coefficient);
422
423        for op in &term.operators {
424            let transformed = self.transform_operator(op)?;
425
426            // Multiply qubit operators
427            let mut new_result = QubitOperator::zero(self.n_modes);
428            for t in transformed {
429                new_result = new_result.add(&result.multiply(&t)?)?;
430            }
431            result = new_result;
432        }
433
434        Ok(result)
435    }
436
437    /// Transform a fermionic Hamiltonian to qubit operators
438    pub fn transform_hamiltonian(
439        &self,
440        hamiltonian: &FermionHamiltonian,
441    ) -> QuantRS2Result<QubitOperator> {
442        let mut qubit_ham = QubitOperator::zero(self.n_modes);
443
444        for term in &hamiltonian.terms {
445            let transformed = self.transform_term(term)?;
446            qubit_ham = qubit_ham.add(&transformed)?;
447        }
448
449        Ok(qubit_ham)
450    }
451}
452
453/// Pauli operator types
454#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
455pub enum PauliOperator {
456    /// Identity
457    I,
458    /// Pauli X
459    X,
460    /// Pauli Y
461    Y,
462    /// Pauli Z
463    Z,
464    /// Raising operator (X + iY)/2
465    Plus,
466    /// Lowering operator (X - iY)/2
467    Minus,
468}
469
470impl PauliOperator {
471    /// Get the matrix representation
472    pub fn matrix(&self) -> Array2<Complex64> {
473        match self {
474            PauliOperator::I => Array2::from_shape_vec(
475                (2, 2),
476                vec![
477                    Complex64::new(1.0, 0.0),
478                    Complex64::new(0.0, 0.0),
479                    Complex64::new(0.0, 0.0),
480                    Complex64::new(1.0, 0.0),
481                ],
482            )
483            .unwrap(),
484            PauliOperator::X => Array2::from_shape_vec(
485                (2, 2),
486                vec![
487                    Complex64::new(0.0, 0.0),
488                    Complex64::new(1.0, 0.0),
489                    Complex64::new(1.0, 0.0),
490                    Complex64::new(0.0, 0.0),
491                ],
492            )
493            .unwrap(),
494            PauliOperator::Y => Array2::from_shape_vec(
495                (2, 2),
496                vec![
497                    Complex64::new(0.0, 0.0),
498                    Complex64::new(0.0, -1.0),
499                    Complex64::new(0.0, 1.0),
500                    Complex64::new(0.0, 0.0),
501                ],
502            )
503            .unwrap(),
504            PauliOperator::Z => Array2::from_shape_vec(
505                (2, 2),
506                vec![
507                    Complex64::new(1.0, 0.0),
508                    Complex64::new(0.0, 0.0),
509                    Complex64::new(0.0, 0.0),
510                    Complex64::new(-1.0, 0.0),
511                ],
512            )
513            .unwrap(),
514            PauliOperator::Plus => Array2::from_shape_vec(
515                (2, 2),
516                vec![
517                    Complex64::new(0.0, 0.0),
518                    Complex64::new(1.0, 0.0),
519                    Complex64::new(0.0, 0.0),
520                    Complex64::new(0.0, 0.0),
521                ],
522            )
523            .unwrap(),
524            PauliOperator::Minus => Array2::from_shape_vec(
525                (2, 2),
526                vec![
527                    Complex64::new(0.0, 0.0),
528                    Complex64::new(0.0, 0.0),
529                    Complex64::new(1.0, 0.0),
530                    Complex64::new(0.0, 0.0),
531                ],
532            )
533            .unwrap(),
534        }
535    }
536}
537
538/// A term in a qubit operator (product of Pauli operators)
539#[derive(Debug, Clone)]
540pub struct QubitTerm {
541    /// List of (qubit_index, pauli_operator) pairs
542    pub operators: Vec<(usize, PauliOperator)>,
543    /// Coefficient
544    pub coefficient: Complex64,
545}
546
547/// A sum of qubit terms (Pauli strings)
548#[derive(Debug, Clone)]
549pub struct QubitOperator {
550    /// Terms in the operator
551    pub terms: Vec<QubitTerm>,
552    /// Number of qubits
553    pub n_qubits: usize,
554}
555
556impl QubitOperator {
557    /// Create a zero operator
558    pub fn zero(n_qubits: usize) -> Self {
559        Self {
560            terms: vec![],
561            n_qubits,
562        }
563    }
564
565    /// Create an identity operator
566    pub fn identity(n_qubits: usize, coefficient: Complex64) -> Self {
567        Self {
568            terms: vec![QubitTerm {
569                operators: vec![],
570                coefficient,
571            }],
572            n_qubits,
573        }
574    }
575
576    /// Add two qubit operators
577    pub fn add(&self, other: &Self) -> QuantRS2Result<Self> {
578        if self.n_qubits != other.n_qubits {
579            return Err(QuantRS2Error::InvalidInput(
580                "Operators must have same number of qubits".into(),
581            ));
582        }
583
584        let mut result = self.clone();
585        result.terms.extend(other.terms.clone());
586        Ok(result)
587    }
588
589    /// Multiply two qubit operators
590    pub fn multiply(&self, other: &Self) -> QuantRS2Result<Self> {
591        if self.n_qubits != other.n_qubits {
592            return Err(QuantRS2Error::InvalidInput(
593                "Operators must have same number of qubits".into(),
594            ));
595        }
596
597        let mut result_terms = Vec::new();
598
599        for term1 in &self.terms {
600            for term2 in &other.terms {
601                // Multiply coefficients
602                let coeff = term1.coefficient * term2.coefficient;
603
604                // Combine Pauli operators
605                let mut combined_ops = term1.operators.clone();
606                combined_ops.extend(&term2.operators);
607
608                result_terms.push(QubitTerm {
609                    operators: combined_ops,
610                    coefficient: coeff,
611                });
612            }
613        }
614
615        Ok(Self {
616            terms: result_terms,
617            n_qubits: self.n_qubits,
618        })
619    }
620
621    /// Simplify by combining like terms
622    pub fn simplify(&mut self) {
623        // Group terms by operator pattern
624        let mut grouped: FxHashMap<Vec<(usize, PauliOperator)>, Complex64> = FxHashMap::default();
625
626        for term in &self.terms {
627            let key = term.operators.clone();
628            *grouped.entry(key).or_insert(Complex64::new(0.0, 0.0)) += term.coefficient;
629        }
630
631        // Rebuild terms
632        self.terms = grouped
633            .into_iter()
634            .filter(|(_, coeff)| coeff.norm() > 1e-12)
635            .map(|(ops, coeff)| QubitTerm {
636                operators: ops,
637                coefficient: coeff,
638            })
639            .collect();
640    }
641}
642
643/// Convert a QubitOperator to quantum gates
644pub fn qubit_operator_to_gates(op: &QubitOperator) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
645    let mut gates = Vec::new();
646
647    for term in &op.terms {
648        // Apply coefficient as global phase
649        // In practice, this would be handled by the circuit simulator
650
651        // Apply Pauli operators
652        for (qubit, pauli) in &term.operators {
653            let gate: Box<dyn GateOp> = match pauli {
654                PauliOperator::I => continue, // Identity - skip
655                PauliOperator::X => Box::new(single::PauliX {
656                    target: QubitId(*qubit as u32),
657                }),
658                PauliOperator::Y => Box::new(single::PauliY {
659                    target: QubitId(*qubit as u32),
660                }),
661                PauliOperator::Z => Box::new(single::PauliZ {
662                    target: QubitId(*qubit as u32),
663                }),
664                PauliOperator::Plus | PauliOperator::Minus => {
665                    return Err(QuantRS2Error::UnsupportedOperation(
666                        "Ladder operators require decomposition".into(),
667                    ));
668                }
669            };
670            gates.push(gate);
671        }
672    }
673
674    Ok(gates)
675}
676
677/// Bravyi-Kitaev transformation (alternative to Jordan-Wigner)
678pub struct BravyiKitaev {
679    #[allow(dead_code)]
680    n_modes: usize,
681}
682
683impl BravyiKitaev {
684    /// Create a new Bravyi-Kitaev transformer
685    pub fn new(n_modes: usize) -> Self {
686        Self { n_modes }
687    }
688
689    /// Transform a fermionic operator (placeholder)
690    pub fn transform_operator(&self, _op: &FermionOperator) -> QuantRS2Result<Vec<QubitOperator>> {
691        // Bravyi-Kitaev transformation is more complex than Jordan-Wigner
692        // This is a placeholder for future implementation
693        Err(QuantRS2Error::UnsupportedOperation(
694            "Bravyi-Kitaev transformation not yet implemented".into(),
695        ))
696    }
697}
698
699#[cfg(test)]
700mod tests {
701    use super::*;
702
703    #[test]
704    fn test_fermion_operator_creation() {
705        let op = FermionOperator::creation(0);
706        assert_eq!(op.op_type, FermionOperatorType::Creation);
707        assert_eq!(op.mode, 0);
708        assert_eq!(op.coefficient, Complex64::new(1.0, 0.0));
709    }
710
711    #[test]
712    fn test_fermion_operator_dagger() {
713        let op = FermionOperator::creation(0);
714        let dag = op.dagger();
715        assert_eq!(dag.op_type, FermionOperatorType::Annihilation);
716        assert_eq!(dag.mode, 0);
717    }
718
719    #[test]
720    fn test_jordan_wigner_number_operator() {
721        let jw = JordanWigner::new(4);
722        let op = FermionOperator::number(1);
723        let qubit_ops = jw.transform_operator(&op).unwrap();
724
725        // n_1 = (I - Z_1)/2
726        assert_eq!(qubit_ops.len(), 2);
727    }
728
729    #[test]
730    fn test_jordan_wigner_creation_operator() {
731        let jw = JordanWigner::new(4);
732        let op = FermionOperator::creation(2);
733        let qubit_ops = jw.transform_operator(&op).unwrap();
734
735        // a†_2 should have Z operators on qubits 0 and 1
736        assert_eq!(qubit_ops.len(), 1);
737    }
738
739    #[test]
740    fn test_fermionic_hamiltonian() {
741        let mut ham = FermionHamiltonian::new(4);
742
743        // Add hopping term
744        ham.add_one_body(0, 1, Complex64::new(-1.0, 0.0));
745        ham.add_one_body(1, 0, Complex64::new(-1.0, 0.0));
746
747        // Add interaction
748        ham.add_two_body(0, 1, 1, 0, Complex64::new(2.0, 0.0));
749
750        assert_eq!(ham.terms.len(), 3);
751    }
752
753    #[test]
754    fn test_qubit_operator_operations() {
755        let op1 = QubitOperator::identity(2, Complex64::new(1.0, 0.0));
756        let op2 = QubitOperator::identity(2, Complex64::new(2.0, 0.0));
757
758        let sum = op1.add(&op2).unwrap();
759        assert_eq!(sum.terms.len(), 2);
760
761        let prod = op1.multiply(&op2).unwrap();
762        assert_eq!(prod.terms.len(), 1);
763        assert_eq!(prod.terms[0].coefficient, Complex64::new(2.0, 0.0));
764    }
765}