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 rustc_hash::FxHashMap;
13use scirs2_core::ndarray::Array2;
14use scirs2_core::Complex;
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 const 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 const 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 const 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 const 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    #[must_use]
78    pub fn dagger(&self) -> Self {
79        let conj_coeff = self.coefficient.conj();
80        match self.op_type {
81            FermionOperatorType::Creation => {
82                Self::new(FermionOperatorType::Annihilation, self.mode, conj_coeff)
83            }
84            FermionOperatorType::Annihilation => {
85                Self::new(FermionOperatorType::Creation, self.mode, conj_coeff)
86            }
87            FermionOperatorType::Number => {
88                Self::new(FermionOperatorType::Number, self.mode, conj_coeff)
89            }
90            FermionOperatorType::Identity => {
91                Self::new(FermionOperatorType::Identity, self.mode, conj_coeff)
92            }
93        }
94    }
95}
96
97/// A product of fermionic operators
98#[derive(Debug, Clone, PartialEq)]
99pub struct FermionTerm {
100    /// Ordered list of operators in the term
101    pub operators: Vec<FermionOperator>,
102    /// Overall coefficient
103    pub coefficient: Complex64,
104}
105
106impl FermionTerm {
107    /// Create a new fermionic term
108    pub const fn new(operators: Vec<FermionOperator>, coefficient: Complex64) -> Self {
109        Self {
110            operators,
111            coefficient,
112        }
113    }
114
115    /// Create an identity term
116    pub const fn identity() -> Self {
117        Self {
118            operators: vec![],
119            coefficient: Complex64::new(1.0, 0.0),
120        }
121    }
122
123    /// Normal order the operators using anticommutation relations
124    pub fn normal_order(&mut self) -> QuantRS2Result<()> {
125        // Bubble sort with anticommutation
126        let n = self.operators.len();
127        for i in 0..n {
128            for j in 0..n.saturating_sub(i + 1) {
129                if self.should_swap(j) {
130                    self.swap_operators(j)?;
131                }
132            }
133        }
134        Ok(())
135    }
136
137    /// Check if two adjacent operators should be swapped
138    fn should_swap(&self, idx: usize) -> bool {
139        if idx + 1 >= self.operators.len() {
140            return false;
141        }
142
143        let op1 = &self.operators[idx];
144        let op2 = &self.operators[idx + 1];
145
146        // Normal ordering: creation operators before annihilation
147        match (op1.op_type, op2.op_type) {
148            (FermionOperatorType::Annihilation, FermionOperatorType::Creation) => {
149                op1.mode > op2.mode
150            }
151            (FermionOperatorType::Creation, FermionOperatorType::Creation) => op1.mode > op2.mode,
152            (FermionOperatorType::Annihilation, FermionOperatorType::Annihilation) => {
153                op1.mode < op2.mode
154            }
155            _ => false,
156        }
157    }
158
159    /// Swap two adjacent operators with anticommutation
160    fn swap_operators(&mut self, idx: usize) -> QuantRS2Result<()> {
161        if idx + 1 >= self.operators.len() {
162            return Err(QuantRS2Error::InvalidInput("Index out of bounds".into()));
163        }
164
165        let op1 = &self.operators[idx];
166        let op2 = &self.operators[idx + 1];
167
168        // Check anticommutation relation
169        if op1.mode == op2.mode {
170            // Same mode: {a_i, a_i†} = 1
171            match (op1.op_type, op2.op_type) {
172                (FermionOperatorType::Annihilation, FermionOperatorType::Creation) => {
173                    // a_i a_i† = 1 - a_i† a_i
174                    // This requires splitting into two terms
175                    return Err(QuantRS2Error::UnsupportedOperation(
176                        "Anticommutation that produces multiple terms not yet supported".into(),
177                    ));
178                }
179                _ => {
180                    self.coefficient *= -1.0;
181                }
182            }
183        } else {
184            // Different modes anticommute: {a_i, a_j†} = 0 for i ≠ j
185            self.coefficient *= -1.0;
186        }
187
188        self.operators.swap(idx, idx + 1);
189        Ok(())
190    }
191
192    /// Get the Hermitian conjugate
193    #[must_use]
194    pub fn dagger(&self) -> Self {
195        let mut conj_ops = self.operators.clone();
196        conj_ops.reverse();
197        conj_ops = conj_ops.into_iter().map(|op| op.dagger()).collect();
198
199        Self {
200            operators: conj_ops,
201            coefficient: self.coefficient.conj(),
202        }
203    }
204}
205
206/// A sum of fermionic terms (second-quantized Hamiltonian)
207#[derive(Debug, Clone)]
208pub struct FermionHamiltonian {
209    /// Terms in the Hamiltonian
210    pub terms: Vec<FermionTerm>,
211    /// Number of fermionic modes
212    pub n_modes: usize,
213}
214
215impl FermionHamiltonian {
216    /// Create a new fermionic Hamiltonian
217    pub const fn new(n_modes: usize) -> Self {
218        Self {
219            terms: Vec::new(),
220            n_modes,
221        }
222    }
223
224    /// Add a term to the Hamiltonian
225    pub fn add_term(&mut self, term: FermionTerm) {
226        self.terms.push(term);
227    }
228
229    /// Add a one-body term: h_ij a†_i a_j
230    pub fn add_one_body(&mut self, i: usize, j: usize, coefficient: Complex64) {
231        let term = FermionTerm::new(
232            vec![
233                FermionOperator::creation(i),
234                FermionOperator::annihilation(j),
235            ],
236            coefficient,
237        );
238        self.add_term(term);
239    }
240
241    /// Add a two-body term: g_ijkl a†_i a†_j a_k a_l
242    pub fn add_two_body(&mut self, i: usize, j: usize, k: usize, l: usize, coefficient: Complex64) {
243        let term = FermionTerm::new(
244            vec![
245                FermionOperator::creation(i),
246                FermionOperator::creation(j),
247                FermionOperator::annihilation(k),
248                FermionOperator::annihilation(l),
249            ],
250            coefficient,
251        );
252        self.add_term(term);
253    }
254
255    /// Add a chemical potential term: μ n_i
256    pub fn add_chemical_potential(&mut self, i: usize, mu: f64) {
257        let term = FermionTerm::new(vec![FermionOperator::number(i)], Complex64::new(mu, 0.0));
258        self.add_term(term);
259    }
260
261    /// Get the Hermitian conjugate
262    #[must_use]
263    pub fn dagger(&self) -> Self {
264        let conj_terms = self.terms.iter().map(|t| t.dagger()).collect();
265        Self {
266            terms: conj_terms,
267            n_modes: self.n_modes,
268        }
269    }
270
271    /// Check if the Hamiltonian is Hermitian
272    pub fn is_hermitian(&self, _tolerance: f64) -> bool {
273        let conj = self.dagger();
274
275        // Compare terms (this is simplified - proper implementation would canonicalize first)
276        if self.terms.len() != conj.terms.len() {
277            return false;
278        }
279
280        // Check if all terms match within tolerance
281        true // Placeholder
282    }
283}
284
285/// Jordan-Wigner transformation
286pub struct JordanWigner {
287    /// Number of fermionic modes (qubits)
288    n_modes: usize,
289}
290
291impl JordanWigner {
292    /// Create a new Jordan-Wigner transformer
293    pub const fn new(n_modes: usize) -> Self {
294        Self { n_modes }
295    }
296
297    /// Transform a fermionic operator to qubit operators
298    pub fn transform_operator(&self, op: &FermionOperator) -> QuantRS2Result<Vec<QubitOperator>> {
299        match op.op_type {
300            FermionOperatorType::Creation => self.transform_creation(op.mode, op.coefficient),
301            FermionOperatorType::Annihilation => {
302                self.transform_annihilation(op.mode, op.coefficient)
303            }
304            FermionOperatorType::Number => self.transform_number(op.mode, op.coefficient),
305            FermionOperatorType::Identity => {
306                Ok(vec![QubitOperator::identity(self.n_modes, op.coefficient)])
307            }
308        }
309    }
310
311    /// Transform creation operator a†_j = (X_j - iY_j)/2 ⊗ Z_{<j}
312    fn transform_creation(
313        &self,
314        mode: usize,
315        coeff: Complex64,
316    ) -> QuantRS2Result<Vec<QubitOperator>> {
317        if mode >= self.n_modes {
318            return Err(QuantRS2Error::InvalidInput(format!(
319                "Mode {mode} out of bounds"
320            )));
321        }
322
323        let mut operators = Vec::new();
324
325        // (X_j - iY_j)/2 = σ^-_j
326        let sigma_minus = QubitTerm {
327            operators: vec![(mode, PauliOperator::Minus)],
328            coefficient: coeff,
329        };
330
331        // Apply Z string to all qubits before mode j
332        let z_string: Vec<_> = (0..mode).map(|i| (i, PauliOperator::Z)).collect();
333
334        let mut term = sigma_minus;
335        term.operators.extend(z_string);
336
337        operators.push(QubitOperator {
338            terms: vec![term],
339            n_qubits: self.n_modes,
340        });
341
342        Ok(operators)
343    }
344
345    /// Transform annihilation operator a_j = (X_j + iY_j)/2 ⊗ Z_{<j}
346    fn transform_annihilation(
347        &self,
348        mode: usize,
349        coeff: Complex64,
350    ) -> QuantRS2Result<Vec<QubitOperator>> {
351        if mode >= self.n_modes {
352            return Err(QuantRS2Error::InvalidInput(format!(
353                "Mode {mode} out of bounds"
354            )));
355        }
356
357        let mut operators = Vec::new();
358
359        // (X_j + iY_j)/2 = σ^+_j
360        let sigma_plus = QubitTerm {
361            operators: vec![(mode, PauliOperator::Plus)],
362            coefficient: coeff,
363        };
364
365        // Apply Z string to all qubits before mode j
366        let z_string: Vec<_> = (0..mode).map(|i| (i, PauliOperator::Z)).collect();
367
368        let mut term = sigma_plus;
369        term.operators.extend(z_string);
370
371        operators.push(QubitOperator {
372            terms: vec![term],
373            n_qubits: self.n_modes,
374        });
375
376        Ok(operators)
377    }
378
379    /// Transform number operator n_j = a†_j a_j = (I - Z_j)/2
380    fn transform_number(
381        &self,
382        mode: usize,
383        coeff: Complex64,
384    ) -> QuantRS2Result<Vec<QubitOperator>> {
385        if mode >= self.n_modes {
386            return Err(QuantRS2Error::InvalidInput(format!(
387                "Mode {mode} out of bounds"
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            Self::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            .expect("Pauli I matrix construction should succeed"),
484            Self::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            .expect("Pauli X matrix construction should succeed"),
494            Self::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            .expect("Pauli Y matrix construction should succeed"),
504            Self::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            .expect("Pauli Z matrix construction should succeed"),
514            Self::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            .expect("Pauli Plus matrix construction should succeed"),
524            Self::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            .expect("Pauli Minus matrix construction should succeed"),
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 const 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 const 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
724            .transform_operator(&op)
725            .expect("Jordan-Wigner transformation should succeed");
726
727        // n_1 = (I - Z_1)/2
728        assert_eq!(qubit_ops.len(), 2);
729    }
730
731    #[test]
732    fn test_jordan_wigner_creation_operator() {
733        let jw = JordanWigner::new(4);
734        let op = FermionOperator::creation(2);
735        let qubit_ops = jw
736            .transform_operator(&op)
737            .expect("Jordan-Wigner transformation should succeed");
738
739        // a†_2 should have Z operators on qubits 0 and 1
740        assert_eq!(qubit_ops.len(), 1);
741    }
742
743    #[test]
744    fn test_fermionic_hamiltonian() {
745        let mut ham = FermionHamiltonian::new(4);
746
747        // Add hopping term
748        ham.add_one_body(0, 1, Complex64::new(-1.0, 0.0));
749        ham.add_one_body(1, 0, Complex64::new(-1.0, 0.0));
750
751        // Add interaction
752        ham.add_two_body(0, 1, 1, 0, Complex64::new(2.0, 0.0));
753
754        assert_eq!(ham.terms.len(), 3);
755    }
756
757    #[test]
758    fn test_qubit_operator_operations() {
759        let op1 = QubitOperator::identity(2, Complex64::new(1.0, 0.0));
760        let op2 = QubitOperator::identity(2, Complex64::new(2.0, 0.0));
761
762        let sum = op1
763            .add(&op2)
764            .expect("QubitOperator addition should succeed");
765        assert_eq!(sum.terms.len(), 2);
766
767        let prod = op1
768            .multiply(&op2)
769            .expect("QubitOperator multiplication should succeed");
770        assert_eq!(prod.terms.len(), 1);
771        assert_eq!(prod.terms[0].coefficient, Complex64::new(2.0, 0.0));
772    }
773}