Repository: HQSquantumsimulations/qoqo (language: python)

Issue:
fixing building docs



Discussion / hints:

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b9e408b2..cc3d7f43 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,7 +6,7 @@ This changelog track changes to the qoqo project starting at version v0.5.0
 
 ### Added in 1.6.0-alpha.0
 
-* Added first bosonic operations traits and derives in `roqoqo` and `roqoqo-derive`.
+* Added first bosonic operations traits in `roqoqo` and `qoqo`.
 
 ## 1.5.0
 
diff --git a/qoqo-macros/src/lib.rs b/qoqo-macros/src/lib.rs
index 462e554f..e0559f5e 100644
--- a/qoqo-macros/src/lib.rs
+++ b/qoqo-macros/src/lib.rs
@@ -27,13 +27,12 @@ mod operate;
 // mod operate_unitary;
 
 /// Array of field names that are reserved for use with specific traits
-const RESERVED_FIELDS: &[&str; 13] = &[
+const RESERVED_FIELDS: &[&str; 15] = &[
     "qubit",
     "control",
     "control_0",
     "control_1",
     "target",
-    "theta",
     "qubits",
     "global_phase",
     "alpha_r",
@@ -41,6 +40,9 @@ const RESERVED_FIELDS: &[&str; 13] = &[
     "beta_r",
     "beta_i",
     "name",
+    "mode",
+    "mode_0",
+    "mode_1",
 ];
 
 // Struct for parsed derive macro arguments. Used to identify structs belonging to enums
@@ -90,10 +92,6 @@ pub fn wrap(
     };
     let rotate_quote = if attribute_arguments.contains("Rotate") {
         quote! {
-            /// Returns angle of rotation
-            pub fn theta(&self) -> CalculatorFloatWrapper{
-                CalculatorFloatWrapper{internal: self.internal.theta().clone()}
-            }
             /// Returns Rotated gate raised to power
             ///
             /// Args:
@@ -417,6 +415,118 @@ pub fn wrap(
     } else {
         TokenStream::new()
     };
+
+    let involve_modes_quote = if attribute_arguments.contains("InvolveModes") {
+        quote! {
+        /// List of modes the operation acts on.
+        ///
+        /// Returns:
+        ///     Union[set[int], str]: The involved qubits as a set or 'ALL' if all qubits are involved
+        pub fn involved_modes(&self) -> PyObject {
+            Python::with_gil(|py| -> PyObject {
+                let involved = self.internal.involved_modes();
+                match involved {
+                    InvolvedModes::All => {
+                        let pyref: &PySet = PySet::new(py, &["All"]).unwrap();
+                        let pyobject: PyObject = pyref.to_object(py);
+                        pyobject
+                    },
+                    InvolvedModes::None => {
+                        let pyref: &PySet = PySet::empty(py).unwrap();
+                        let pyobject: PyObject = pyref.to_object(py);
+                        pyobject
+                    },
+                    InvolvedModes::Set(x) => {
+                        let mut vector: Vec<usize> = Vec::new();
+                        for mode in x {
+                            vector.push(mode)
+                        }
+                        let pyref: &PySet = PySet::new(py, &vector[..]).unwrap();
+                        let pyobject: PyObject = pyref.to_object(py);
+                        pyobject
+                    },
+                }
+            })
+            }
+        }
+    } else {
+        TokenStream::new()
+    };
+
+    let substitute_modes_quote = if attribute_arguments.contains("SubstituteModes") {
+        quote! {
+        /// Remap the bosonic modes in copy of the operation.
+        ///
+        /// Args:
+        ///     mapping (dict[int, int]): Mapping for bosonic modes in operation.
+        ///
+        /// Returns:
+        ///     self
+        ///
+        /// Raises:
+        ///     PyValueError: Remapping could not be performed
+        pub fn remap_modes(&self, mapping: HashMap<usize, usize>) -> PyResult<Self> {
+            let new_internal = self.internal.remap_modes(&mapping).map_err(|x|
+                PyRuntimeError::new_err(format!("Mode remapping failed: {:?}",x))
+            )?;
+            Ok(Self{internal: new_internal})
+            }
+        }
+    } else {
+        TokenStream::new()
+    };
+
+    let operate_mode_gate_quote = if attribute_arguments.contains("OperateModeGate") {
+        quote! {}
+    } else {
+        TokenStream::new()
+    };
+
+    let operate_single_mode_quote = if attribute_arguments.contains("OperateSingleMode") {
+        quote! {
+        /// Return `mode` the bosonic Operation acts on.
+        ///
+        /// Returns:
+        ///     int
+        pub fn mode(&self) -> usize {
+                self.internal.mode().clone()
+            }
+        }
+    } else {
+        TokenStream::new()
+    };
+
+    let operate_two_mode_quote = if attribute_arguments.contains("OperateTwoMode") {
+        quote! {
+        /// Return `mode_0` bosonic mode of two bosonic mode Operation.
+        ///
+        /// Returns:
+        ///     int
+        pub fn mode_0(&self) -> usize {
+                self.internal.mode_0().clone()
+            }
+        /// Return `mode_1` bosonic mode of two bosonic mode Operation.
+        ///
+        /// Returns:
+        ///     int
+        pub fn mode_1(&self) -> usize {
+                self.internal.mode_1().clone()
+            }
+        }
+    } else {
+        TokenStream::new()
+    };
+    let operate_single_mode_gate_quote = if attribute_arguments.contains("OperateSingleModeGate") {
+        quote! {}
+    } else {
+        TokenStream::new()
+    };
+    let operate_two_mode_gate_quote = if attribute_arguments.contains("OperateTwoModeGate") {
+        quote! {}
+    } else {
+        TokenStream::new()
+    };
+
     let msg = format!("Internal storage of {} object", ident);
     let q = quote! {
         #[automatically_derived]
@@ -446,6 +556,13 @@ pub fn wrap(
             #operate_pragma_noise_proba_quote
             #define_quote
             #operate_constant_gate_quote
+            #involve_modes_quote
+            #substitute_modes_quote
+            #operate_mode_gate_quote
+            #operate_single_mode_quote
+            #operate_two_mode_quote
+            #operate_single_mode_gate_quote
+            #operate_two_mode_gate_quote
             fn __format__(&self, _format_spec: &str) -> PyResult<String> {
                 Ok(format!("{:?}", self.internal))
             }
@@ -841,32 +958,32 @@ pub fn devicewrapper(
                 GenericDeviceWrapper{ internal: self.internal.to_generic_device()}
             }
 
-    /// Returns the names of a single qubit operations available on the device.
-    ///
-    /// Returns:
-    ///     List[strt]: The list of gate names.
-    pub fn single_qubit_gate_names(&self) -> Vec<String>{
-        self.internal.single_qubit_gate_names()
-    }
+            /// Returns the names of a single qubit operations available on the device.
+            ///
+            /// Returns:
+            ///     List[strt]: The list of gate names.
+            pub fn single_qubit_gate_names(&self) -> Vec<String>{
+                self.internal.single_qubit_gate_names()
+            }
 
-    /// Returns the names of a two qubit operations available on the device.
-    ///
-    /// Returns:
-    ///     List[strt]: The list of gate names.
-    pub fn two_qubit_gate_names(&self) -> Vec<String>{
-        self.internal.two_qubit_gate_names()
-    }
+            /// Returns the names of a two qubit operations available on the device.
+            ///
+            /// Returns:
+            ///     List[strt]: The list of gate names.
+            pub fn two_qubit_gate_names(&self) -> Vec<String>{
+                self.internal.two_qubit_gate_names()
+            }
 
-    /// Returns the names of a mutli qubit operations available on the device.
-    ///
-    /// The list of names also includes the three qubit gate operations.
-    ///
-    /// Returns:
-    ///     List[strt]: The list of gate names.
-    ///
-    pub fn multi_qubit_gate_names(&self) -> Vec<String>{
-        self.internal.multi_qubit_gate_names()
-    }
+            /// Returns the names of a mutli qubit operations available on the device.
+            ///
+            /// The list of names also includes the three qubit gate operations.
+            ///
+            /// Returns:
+            ///     List[strt]: The list of gate names.
+            ///
+            pub fn multi_qubit_gate_names(&self) -> Vec<String>{
+                self.internal.multi_qubit_gate_names()
+            }
 
             /// Returns a copy of the device (copy here produces a deepcopy).
             ///
diff --git a/qoqo/build.rs b/qoqo/build.rs
index 22248cd2..a773203f 100644
--- a/qoqo/build.rs
+++ b/qoqo/build.rs
@@ -123,6 +123,7 @@ const SOURCE_FILES: &[&str] = &[
     "src/operations/multi_qubit_gate_operations.rs",
     "src/operations/measurement_operations.rs",
     "src/operations/define_operations.rs",
+    "src/operations/bosonic_operations.rs",
 ];
 
 fn main() {
diff --git a/qoqo/src/operations/bosonic_operations.rs b/qoqo/src/operations/bosonic_operations.rs
new file mode 100644
index 00000000..72054b79
--- /dev/null
+++ b/qoqo/src/operations/bosonic_operations.rs
@@ -0,0 +1,107 @@
+// Copyright © 2021-2023 HQS Quantum Simulations GmbH. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+// in compliance with the License. You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software distributed under the
+// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+// express or implied. See the License for the specific language governing permissions and
+// limitations under the License.
+
+use pyo3::exceptions::PyRuntimeError;
+use pyo3::prelude::*;
+use pyo3::types::PySet;
+use qoqo_calculator::CalculatorFloat;
+use qoqo_calculator_pyo3::{convert_into_calculator_float, CalculatorFloatWrapper};
+use qoqo_macros::*;
+use roqoqo::operations::*;
+use std::collections::HashMap;
+
+#[wrap(
+    Operate,
+    OperateModeGate,
+    Substitute,
+    SubstituteModes,
+    InvolveModes,
+    OperateSingleMode,
+    InvolveQubits,
+    OperateSingleModeGate
+)]
+/// The single-mode squeezing gate with tunable squeezing.
+///
+/// The squeezing gate is a quantum operation that allows for precise manipulation of quantum states,
+/// by reducing the uncertainty in one variable and therefore increasing the uncertainty of another.
+/// https://arxiv.org/pdf/quant-ph/0106157.pdf
+///
+/// Args:
+///     mode (int): The mode the squeezing gate is applied to.
+///     squeezing (CalculatorFloat): The coefficient of the squeezing operation.
+///     phase (CalculatorFloat): The squeezing phase angle of the squeezing operation.
+pub struct Squeezing {
+    mode: usize,
+    squeezing: CalculatorFloat,
+    phase: CalculatorFloat,
+}
+
+#[wrap(
+    Operate,
+    OperateModeGate,
+    Substitute,
+    SubstituteModes,
+    InvolveModes,
+    OperateSingleMode,
+    InvolveQubits,
+    OperateSingleModeGate
+)]
+/// The single-mode phase-shift gate with variable phase, given by R(θ) = eexp(i * θ * 𝑁̂).
+///
+/// https://arxiv.org/pdf/2104.03241.pdf
+///
+/// Args:
+///     mode (int): The mode the phase-shift gate is applied to.
+///     phase (CalculatorFloat): The phase by which to shift the mode.
+pub struct PhaseShift {
+    mode: usize,
+    phase: CalculatorFloat,
+}
+
+#[wrap(
+    OperateModeGate,
+    OperateTwoModeGate,
+    Operate,
+    Substitute,
+    InvolveModes,
+    SubstituteModes,
+    OperateTwoMode
+)]
+/// The 2-mode beam splitter which splits a beam with a transmission amplitude cos(θ) and a reflection amplitude exp(i * φ) * sin(θ).
+///
+/// Args:
+///     mode_0 (int): The first mode the beam-splitter is applied to.
+///     mode_1 (int): The second mode the beam-splitter is applied to.
+///     theta (CalculatorFloat): The transmittivity angle of the beam-splitter.
+///     phi (CalculatorFloat): The phase angle of the beam-splitter.
+pub struct BeamSplitter {
+    mode_0: usize,
+    mode_1: usize,
+    theta: CalculatorFloat,
+    phi: CalculatorFloat,
+}
+
+#[wrap(Operate, Substitute, InvolveModes, SubstituteModes, OperateSingleMode)]
+/// The photon number-resolving detector measurement for bosons.
+///
+/// This can be used as a single-shot measurement of the photon number.
+/// https://arxiv.org/pdf/0902.4824.pdf
+///
+/// Args:
+///     mode (int): The mode the detector (measurement) is applied to.
+///     readout (str): The register for the readout.
+///     readout_index (int): The index in the readout the result is saved to.
+pub struct PNRDetection {
+    mode: usize,
+    readout: String,
+    readout_index: usize,
+}
diff --git a/qoqo/src/operations/mod.rs b/qoqo/src/operations/mod.rs
index b9d381ab..2b6cd00a 100644
--- a/qoqo/src/operations/mod.rs
+++ b/qoqo/src/operations/mod.rs
@@ -28,6 +28,8 @@ mod three_qubit_gate_operations;
 pub use three_qubit_gate_operations::*;
 mod multi_qubit_gate_operations;
 pub use multi_qubit_gate_operations::*;
+mod bosonic_operations;
+pub use bosonic_operations::*;
 include!(concat!(
     env!("OUT_DIR"),
     "/_auto_generated_operation_conversion.rs"
@@ -134,5 +136,10 @@ pub fn operations(_py: Python, m: &PyModule) -> PyResult<()> {
     m.add_class::<GPi2Wrapper>()?;
     // 1.5
     m.add_class::<PragmaControlledCircuitWrapper>()?;
+    // 1.6
+    m.add_class::<SqueezingWrapper>()?;
+    m.add_class::<PhaseShiftWrapper>()?;
+    m.add_class::<BeamSplitterWrapper>()?;
+    m.add_class::<PNRDetectionWrapper>()?;
     Ok(())
 }
diff --git a/roqoqo/src/operations/bosonic_operations.rs b/roqoqo/src/operations/bosonic_operations.rs
index 5d9367c9..bb4c03a8 100644
--- a/roqoqo/src/operations/bosonic_operations.rs
+++ b/roqoqo/src/operations/bosonic_operations.rs
@@ -44,6 +44,8 @@ pub struct Squeezing {
     mode: usize,
     /// The coefficient of the squeezing operation.
     squeezing: CalculatorFloat,
+    /// The squeezing phase angle of the squeezing operation.
+    phase: CalculatorFloat,
 }
 
 #[allow(non_upper_case_globals)]
diff --git a/roqoqo/src/operations/mod.rs b/roqoqo/src/operations/mod.rs
index cd856e2a..de83d8d1 100644
--- a/roqoqo/src/operations/mod.rs
+++ b/roqoqo/src/operations/mod.rs
@@ -962,7 +962,7 @@ where
 /// ```
 /// use roqoqo::operations::{OperateModeGate, Squeezing};
 ///
-/// let _op = Squeezing::new(0, 0.1.into());
+/// let _op = Squeezing::new(0, 0.1.into(), 0.0.into());
 /// ```
 ///
 pub trait OperateModeGate:


Test patch:
diff --git a/qoqo/tests/integration/operations/bosonic_operations.rs b/qoqo/tests/integration/operations/bosonic_operations.rs
new file mode 100644
index 00000000..2f5c76dd
--- /dev/null
+++ b/qoqo/tests/integration/operations/bosonic_operations.rs
@@ -0,0 +1,816 @@
+// Copyright © 2021-2023 HQS Quantum Simulations GmbH. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+// in compliance with the License. You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software distributed under the
+// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+// express or implied. See the License for the specific language governing permissions and
+// limitations under the License.
+
+use pyo3::prelude::*;
+use pyo3::Python;
+use qoqo::operations::convert_operation_to_pyobject;
+use qoqo::operations::{
+    BeamSplitterWrapper, PNRDetectionWrapper, PhaseShiftWrapper, SqueezingWrapper,
+};
+use qoqo_calculator::Calculator;
+use qoqo_calculator::CalculatorFloat;
+use qoqo_calculator_pyo3::CalculatorFloatWrapper;
+use roqoqo::operations::Operation;
+use roqoqo::operations::*;
+use std::collections::{HashMap, HashSet};
+use test_case::test_case;
+
+// helper function to convert CalculatorFloat into a python object
+fn convert_cf_to_pyobject(
+    py: Python,
+    parameter: CalculatorFloat,
+) -> &PyCell<CalculatorFloatWrapper> {
+    let parameter_type = py.get_type::<CalculatorFloatWrapper>();
+    match parameter {
+        CalculatorFloat::Float(x) => parameter_type
+            .call1((x,))
+            .unwrap()
+            .downcast::<PyCell<CalculatorFloatWrapper>>()
+            .unwrap(),
+        CalculatorFloat::Str(x) => parameter_type
+            .call1((x,))
+            .unwrap()
+            .downcast::<PyCell<CalculatorFloatWrapper>>()
+            .unwrap(),
+    }
+}
+
+/// Test new() function for Squeezing
+#[test_case(Operation::from(Squeezing::new(1, 0.1.into(), 0.1.into())), (1, 0.1, 0.1,), "__eq__"; "Squeezing_eq")]
+#[test_case(Operation::from(Squeezing::new(1, 0.1.into(), 0.1.into())), (0, 0.1, 0.1,), "__ne__"; "Squeezing_ne")]
+fn test_new_squeezing(input_operation: Operation, arguments: (u32, f64, f64), method: &str) {
+    let operation = convert_operation_to_pyobject(input_operation).unwrap();
+    pyo3::prepare_freethreaded_python();
+    Python::with_gil(|py| {
+        let operation_type = py.get_type::<SqueezingWrapper>();
+        let operation_py = operation_type
+            .call1(arguments)
+            .unwrap()
+            .downcast::<PyCell<SqueezingWrapper>>()
+            .unwrap();
+
+        let comparison = bool::extract(
+            operation
+                .as_ref(py)
+                .call_method1(method, (operation_py,))
+                .unwrap(),
+        )
+        .unwrap();
+        assert!(comparison);
+
+        let def_wrapper = operation_py.extract::<SqueezingWrapper>().unwrap();
+        let new_op_diff = operation_type
+            .call1((2, 0.1, 0.0))
+            .unwrap()
+            .downcast::<PyCell<SqueezingWrapper>>()
+            .unwrap();
+        let def_wrapper_diff = new_op_diff.extract::<SqueezingWrapper>().unwrap();
+        let helper_ne: bool = def_wrapper_diff != def_wrapper;
+        assert!(helper_ne);
+        let helper_eq: bool = def_wrapper == def_wrapper.clone();
+        assert!(helper_eq);
+
+        assert_eq!(
+            format!("{:?}", def_wrapper_diff),
+            "SqueezingWrapper { internal: Squeezing { mode: 2, squeezing: Float(0.1), phase: Float(0.0) } }"
+        );
+
+        let comparison_copy = bool::extract(
+            operation
+                .call_method0(py, "squeezing")
+                .unwrap()
+                .as_ref(py)
+                .call_method1(
+                    "__eq__",
+                    (convert_cf_to_pyobject(py, CalculatorFloat::Float(0.1)),),
+                )
+                .unwrap(),
+        )
+        .unwrap();
+        assert!(comparison_copy);
+
+        let comparison_copy = bool::extract(
+            operation
+                .call_method0(py, "phase")
+                .unwrap()
+                .as_ref(py)
+                .call_method1(
+                    "__eq__",
+                    (convert_cf_to_pyobject(py, CalculatorFloat::Float(0.1)),),
+                )
+                .unwrap(),
+        )
+        .unwrap();
+        assert!(comparison_copy);
+    })
+}
+
+/// Test new() function for PhaseShift
+#[test_case(Operation::from(PhaseShift::new(1, 0.1.into())), (1, 0.1,), "__eq__"; "PhaseShift_eq")]
+#[test_case(Operation::from(PhaseShift::new(1, 0.1.into())), (0, 0.1,), "__ne__"; "PhaseShift_ne")]
+fn test_new_phaseshift(input_operation: Operation, arguments: (u32, f64), method: &str) {
+    let operation = convert_operation_to_pyobject(input_operation).unwrap();
+    pyo3::prepare_freethreaded_python();
+    Python::with_gil(|py| {
+        let operation_type = py.get_type::<PhaseShiftWrapper>();
+        let operation_py = operation_type
+            .call1(arguments)
+            .unwrap()
+            .downcast::<PyCell<PhaseShiftWrapper>>()
+            .unwrap();
+
+        let comparison = bool::extract(
+            operation
+                .as_ref(py)
+                .call_method1(method, (operation_py,))
+                .unwrap(),
+        )
+        .unwrap();
+        assert!(comparison);
+
+        let def_wrapper = operation_py.extract::<PhaseShiftWrapper>().unwrap();
+        let new_op_diff = operation_type
+            .call1((2, 0.1))
+            .unwrap()
+            .downcast::<PyCell<PhaseShiftWrapper>>()
+            .unwrap();
+        let def_wrapper_diff = new_op_diff.extract::<PhaseShiftWrapper>().unwrap();
+        let helper_ne: bool = def_wrapper_diff != def_wrapper;
+        assert!(helper_ne);
+        let helper_eq: bool = def_wrapper == def_wrapper.clone();
+        assert!(helper_eq);
+
+        assert_eq!(
+            format!("{:?}", def_wrapper_diff),
+            "PhaseShiftWrapper { internal: PhaseShift { mode: 2, phase: Float(0.1) } }"
+        );
+
+        let comparison_copy = bool::extract(
+            operation
+                .call_method0(py, "phase")
+                .unwrap()
+                .as_ref(py)
+                .call_method1(
+                    "__eq__",
+                    (convert_cf_to_pyobject(py, CalculatorFloat::Float(0.1)),),
+                )
+                .unwrap(),
+        )
+        .unwrap();
+        assert!(comparison_copy);
+    })
+}
+
+/// Test new() function for BeamSplitter
+#[test_case(Operation::from(BeamSplitter::new(0, 1, 0.1.into(), 0.5.into())), (0, 1, 0.1, 0.5,), "__eq__"; "BeamSplitter_eq")]
+#[test_case(Operation::from(BeamSplitter::new(1, 0, 0.1.into(), 0.5.into())), (0, 1, 0.1, 0.5,), "__ne__"; "BeamSplitter_ne")]
+fn test_new_beamsplitter(
+    input_operation: Operation,
+    arguments: (u32, u32, f64, f64),
+    method: &str,
+) {
+    let operation = convert_operation_to_pyobject(input_operation).unwrap();
+    pyo3::prepare_freethreaded_python();
+    Python::with_gil(|py| {
+        let operation_type = py.get_type::<BeamSplitterWrapper>();
+        let operation_py = operation_type
+            .call1(arguments)
+            .unwrap()
+            .downcast::<PyCell<BeamSplitterWrapper>>()
+            .unwrap();
+
+        let comparison = bool::extract(
+            operation
+                .as_ref(py)
+                .call_method1(method, (operation_py,))
+                .unwrap(),
+        )
+        .unwrap();
+        assert!(comparison);
+
+        let def_wrapper = operation_py.extract::<BeamSplitterWrapper>().unwrap();
+        let new_op_diff = operation_type
+            .call1((2, 1, 0.1, 0.1))
+            .unwrap()
+            .downcast::<PyCell<BeamSplitterWrapper>>()
+            .unwrap();
+        let def_wrapper_diff = new_op_diff.extract::<BeamSplitterWrapper>().unwrap();
+        let helper_ne: bool = def_wrapper_diff != def_wrapper;
+        assert!(helper_ne);
+        let helper_eq: bool = def_wrapper == def_wrapper.clone();
+        assert!(helper_eq);
+
+        assert_eq!(
+            format!("{:?}", def_wrapper_diff),
+            "BeamSplitterWrapper { internal: BeamSplitter { mode_0: 2, mode_1: 1, theta: Float(0.1), phi: Float(0.1) } }"
+        );
+
+        let comparison_copy: bool = bool::extract(
+            operation
+                .call_method0(py, "theta")
+                .unwrap()
+                .as_ref(py)
+                .call_method1(
+                    "__eq__",
+                    (convert_cf_to_pyobject(py, CalculatorFloat::Float(0.1)),),
+                )
+                .unwrap(),
+        )
+        .unwrap();
+        assert!(comparison_copy);
+        let comparison_copy: bool = bool::extract(
+            operation
+                .call_method0(py, "phi")
+                .unwrap()
+                .as_ref(py)
+                .call_method1(
+                    "__eq__",
+                    (convert_cf_to_pyobject(py, CalculatorFloat::Float(0.5)),),
+                )
+                .unwrap(),
+        )
+        .unwrap();
+        assert!(comparison_copy);
+    })
+}
+
+/// Test new() function for PNRDetection
+#[test_case(Operation::from(PNRDetection::new(1, "ro".into(), 0)), (1, "ro".into(), 0,), "__eq__"; "PNRDetection_eq")]
+#[test_case(Operation::from(PNRDetection::new(1, "ro".into(), 0)), (0, "ro".into(), 0,), "__ne__"; "PNRDetection_ne")]
+fn test_new_pnrdetection(input_operation: Operation, arguments: (u32, String, u32), method: &str) {
+    let operation = convert_operation_to_pyobject(input_operation).unwrap();
+    pyo3::prepare_freethreaded_python();
+    Python::with_gil(|py| {
+        let operation_type = py.get_type::<PNRDetectionWrapper>();
+        let operation_py = operation_type
+            .call1(arguments)
+            .unwrap()
+            .downcast::<PyCell<PNRDetectionWrapper>>()
+            .unwrap();
+
+        let comparison = bool::extract(
+            operation
+                .as_ref(py)
+                .call_method1(method, (operation_py,))
+                .unwrap(),
+        )
+        .unwrap();
+        assert!(comparison);
+
+        let def_wrapper = operation_py.extract::<PNRDetectionWrapper>().unwrap();
+        let new_op_diff = operation_type
+            .call1((2, "ro", 0))
+            .unwrap()
+            .downcast::<PyCell<PNRDetectionWrapper>>()
+            .unwrap();
+        let def_wrapper_diff = new_op_diff.extract::<PNRDetectionWrapper>().unwrap();
+        let helper_ne: bool = def_wrapper_diff != def_wrapper;
+        assert!(helper_ne);
+        let helper_eq: bool = def_wrapper == def_wrapper.clone();
+        assert!(helper_eq);
+
+        assert_eq!(
+            format!("{:?}", def_wrapper_diff),
+            "PNRDetectionWrapper { internal: PNRDetection { mode: 2, readout: \"ro\", readout_index: 0 } }"
+        );
+
+        let comparison_copy = bool::extract(
+            operation
+                .call_method0(py, "readout")
+                .unwrap()
+                .as_ref(py)
+                .call_method1("__eq__", ("ro",))
+                .unwrap(),
+        )
+        .unwrap();
+        assert!(comparison_copy);
+
+        let comparison_copy = bool::extract(
+            operation
+                .call_method0(py, "readout_index")
+                .unwrap()
+                .as_ref(py)
+                .call_method1("__eq__", (0_u32,))
+                .unwrap(),
+        )
+        .unwrap();
+        assert!(comparison_copy);
+    })
+}
+
+/// Test is_parametrized() function for SingleModeGate Operations
+#[test_case(Operation::from(Squeezing::new(0, CalculatorFloat::from("theta"), CalculatorFloat::from(0.0))); "Squeezing_theta")]
+#[test_case(Operation::from(Squeezing::new(0, CalculatorFloat::from(0.0), CalculatorFloat::from("phase"))); "Squeezing_phase")]
+#[test_case(Operation::from(Squeezing::new(0, CalculatorFloat::from("theta"), CalculatorFloat::from("phase"))); "Squeezing_theta_phase")]
+#[test_case(Operation::from(PhaseShift::new(0, CalculatorFloat::from("theta"))); "PhaseShift")]
+#[test_case(Operation::from(BeamSplitter::new(0, 1, CalculatorFloat::from("theta"), CalculatorFloat::from(0.1))); "BeamSplitter_theta")]
+#[test_case(Operation::from(BeamSplitter::new(0, 1, CalculatorFloat::from(0.1), CalculatorFloat::from("phi"))); "BeamSplitter_phi")]
+#[test_case(Operation::from(BeamSplitter::new(0, 1, CalculatorFloat::from("theta"), CalculatorFloat::from("phi"))); "BeamSplitter_theta_phi")]
+fn test_pyo3_is_parametrized(input_operation: Operation) {
+    pyo3::prepare_freethreaded_python();
+    Python::with_gil(|py| {
+        let operation = convert_operation_to_pyobject(input_operation).unwrap();
+        assert!(bool::extract(
+            operation
+                .call_method0(py, "is_parametrized")
+                .unwrap()
+                .as_ref(py)
+        )
+        .unwrap());
+    })
+}
+
+/// Test is_parametrized = false for SingleModeGate Operations
+#[test_case(Operation::from(Squeezing::new(1, CalculatorFloat::from(1.3), 0.0.into())); "Squeezing")]
+#[test_case(Operation::from(PhaseShift::new(0, CalculatorFloat::from(1.3))); "PhaseShift")]
+#[test_case(Operation::from(BeamSplitter::new(0, 1, CalculatorFloat::from(0.1), CalculatorFloat::from(0.1))); "BeamSplitter")]
+#[test_case(Operation::from(PNRDetection::new(0, "ro".into(), 0)); "PNRDetection")]
+fn test_pyo3_is_not_parametrized(input_operation: Operation) {
+    pyo3::prepare_freethreaded_python();
+    Python::with_gil(|py| {
+        let operation = convert_operation_to_pyobject(input_operation).unwrap();
+        assert!(!bool::extract(
+            operation
+                .call_method0(py, "is_parametrized")
+                .unwrap()
+                .as_ref(py)
+        )
+        .unwrap());
+    })
+}
+
+/// Test mode() function for SingleMode Operations
+#[test_case(0, Operation::from(Squeezing::new(0, CalculatorFloat::from(0), 0.0.into())); "Squeezing")]
+#[test_case(0, Operation::from(PhaseShift::new(0, CalculatorFloat::from(0))); "PhaseShift")]
+#[test_case(0, Operation::from(PNRDetection::new(0, "ro".into(), 0)); "PNRDetection")]
+fn test_pyo3_mode(mode: usize, input_operation: Operation) {
+    pyo3::prepare_freethreaded_python();
+    Python::with_gil(|py| {
+        let operation = convert_operation_to_pyobject(input_operation).unwrap();
+        let mode_op: usize =
+            usize::extract(operation.call_method0(py, "mode").unwrap().as_ref(py)).unwrap();
+        assert_eq!(mode_op, mode);
+    })
+}
+
+/// Test mode_0() and mode_1 function for TwoMode Operations
+#[test_case(0, 1, Operation::from(BeamSplitter::new(0, 1, CalculatorFloat::from(0.1), CalculatorFloat::from(0.1))); "BeamSplitter")]
+fn test_pyo3_mode0_mode_1(mode_0: usize, mode_1: usize, input_operation: Operation) {
+    pyo3::prepare_freethreaded_python();
+    Python::with_gil(|py| {
+        let operation = convert_operation_to_pyobject(input_operation).unwrap();
+        let mode_op_0: usize =
+            usize::extract(operation.call_method0(py, "mode_0").unwrap().as_ref(py)).unwrap();
+        assert_eq!(mode_op_0, mode_0);
+        let mode_op_1: usize =
+            usize::extract(operation.call_method0(py, "mode_1").unwrap().as_ref(py)).unwrap();
+        assert_eq!(mode_op_1, mode_1);
+    })
+}
+
+/// Test Squeezing hqslang() function for SingleModeGate Operations
+#[test_case("Squeezing", Operation::from(Squeezing::new(0, CalculatorFloat::from(0), 0.0.into())); "Squeezing")]
+#[test_case("PhaseShift", Operation::from(PhaseShift::new(0, CalculatorFloat::from(0))); "PhaseShift")]
+#[test_case("BeamSplitter", Operation::from(BeamSplitter::new(0, 1, CalculatorFloat::from(0), CalculatorFloat::from(0))); "BeamSplitter")]
+#[test_case("PNRDetection", Operation::from(PNRDetection::new(0, "ro".into(), 0)); "PNRDetection")]
+fn test_pyo3_hqslang(name: &'static str, input_operation: Operation) {
+    pyo3::prepare_freethreaded_python();
+    Python::with_gil(|py| {
+        let operation = convert_operation_to_pyobject(input_operation).unwrap();
+        let name_op: String =
+            String::extract(operation.call_method0(py, "hqslang").unwrap().as_ref(py)).unwrap();
+        assert_eq!(name_op, name.to_string());
+    })
+}
+
+/// Test tags() function for SingleModeGate Operations
+#[test_case(
+    Operation::from(Squeezing::new(0, CalculatorFloat::from(0), 0.0.into())),
+    vec![
+        "Operation",
+        "ModeGateOperation",
+        "SingleModeGateOperation",
+        "Squeezing",
+        ];
+    "Squeezing")]
+#[test_case(
+    Operation::from(PhaseShift::new(0, CalculatorFloat::from(0))),
+    vec![
+        "Operation",
+        "ModeGateOperation",
+        "SingleModeGateOperation",
+        "PhaseShift",
+        ];
+    "PhaseShift")]
+#[test_case(
+    Operation::from(BeamSplitter::new(0, 1, CalculatorFloat::from(0), CalculatorFloat::from(0))),
+    vec![
+        "Operation",
+        "ModeGateOperation",
+        "TwoModeGateOperation",
+        "BeamSplitter",
+        ];
+    "BeamSplitter")]
+#[test_case(
+    Operation::from(PNRDetection::new(0, "ro".into(), 0)),
+    vec![
+        "Operation",
+        "Measurement",
+        "PNRDetection",
+        ];
+    "PNRDetection")]
+fn test_pyo3_tags(input_operation: Operation, tags: Vec<&str>) {
+    pyo3::prepare_freethreaded_python();
+    Python::with_gil(|py| {
+        let operation = convert_operation_to_pyobject(input_operation).unwrap();
+        let tags_op: Vec<String> =
+            Vec::<String>::extract(operation.call_method0(py, "tags").unwrap().as_ref(py)).unwrap();
+        assert_eq!(tags_op.len(), tags.len());
+        for i in 0..tags.len() {
+            assert_eq!(tags_op[i], tags[i]);
+        }
+    })
+}
+
+/// Test involved_modes() function for SingleModeGate Operations
+#[test_case(Operation::from(Squeezing::new(0, CalculatorFloat::from(1.3), 0.0.into())), HashSet::<usize>::from([0]); "Squeezing")]
+#[test_case(Operation::from(PhaseShift::new(0, CalculatorFloat::from(1.3))), HashSet::<usize>::from([0]); "PhaseShift")]
+#[test_case(Operation::from(BeamSplitter::new(0, 1, CalculatorFloat::from(0.1), CalculatorFloat::from(1.3))), HashSet::<usize>::from([0, 1]); "BeamSplitter")]
+#[test_case(Operation::from(PNRDetection::new(0, "ro".into(), 0)), HashSet::<usize>::from([0]); "PNRDetection")]
+fn test_pyo3_involved_modes(input_operation: Operation, modes: HashSet<usize>) {
+    pyo3::prepare_freethreaded_python();
+    Python::with_gil(|py| {
+        let operation = convert_operation_to_pyobject(input_operation).unwrap();
+        // test initial mode
+        let involved_modes: HashSet<usize> = HashSet::<usize>::extract(
+            operation
+                .call_method0(py, "involved_modes")
+                .unwrap()
+                .as_ref(py),
+        )
+        .unwrap();
+        assert_eq!(involved_modes, modes);
+    })
+}
+
+/// Test remap_qubits() function for SingleModeGate Operations
+#[test_case(Operation::from(Squeezing::new(0, CalculatorFloat::from(1.3), 0.0.into())); "Squeezing")]
+#[test_case(Operation::from(PhaseShift::new(0, CalculatorFloat::from(1.3))); "PhaseShift")]
+#[test_case(Operation::from(BeamSplitter::new(0, 1, CalculatorFloat::from(0.1), CalculatorFloat::from(1.3))); "BeamSplitter")]
+#[test_case(Operation::from(PNRDetection::new(0, "ro".into(), 0)); "PNRDetection")]
+fn test_pyo3_remapqubits(input_operation: Operation) {
+    pyo3::prepare_freethreaded_python();
+    Python::with_gil(|py| {
+        let operation = convert_operation_to_pyobject(input_operation).unwrap();
+        // test initial mode
+        let involved_qubits: HashSet<usize> = HashSet::<usize>::extract(
+            operation
+                .call_method0(py, "involved_qubits")
+                .unwrap()
+                .as_ref(py),
+        )
+        .unwrap();
+        assert_eq!(involved_qubits, HashSet::<usize>::new());
+        // remap modes
+        let result = operation
+            .call_method1(py, "remap_qubits", (HashMap::<usize, usize>::new(),))
+            .unwrap();
+        // test re-mapped qubit
+        let involved_qubits: HashSet<usize> = HashSet::<usize>::extract(
+            result
+                .call_method0(py, "involved_qubits")
+                .unwrap()
+                .as_ref(py),
+        )
+        .unwrap();
+        assert_eq!(involved_qubits, HashSet::<usize>::new());
+    })
+}
+
+/// Test remap_modes() function for SingleModeGate Operations
+#[test_case(Operation::from(Squeezing::new(0, CalculatorFloat::from(1.3), 0.0.into())); "Squeezing")]
+#[test_case(Operation::from(PhaseShift::new(0, CalculatorFloat::from(1.3))); "PhaseShift")]
+#[test_case(Operation::from(PNRDetection::new(0, "ro".into(), 0)); "PNRDetection")]
+fn test_pyo3_remapmodes_single(input_operation: Operation) {
+    pyo3::prepare_freethreaded_python();
+    Python::with_gil(|py| {
+        let operation = convert_operation_to_pyobject(input_operation).unwrap();
+        // test initial mode
+        let mode: usize =
+            usize::extract(operation.call_method0(py, "mode").unwrap().as_ref(py)).unwrap();
+        assert_eq!(mode.clone(), 0);
+        // remap modes
+        let mut mode_mapping: HashMap<usize, usize> = HashMap::new();
+        mode_mapping.insert(0, 1);
+        mode_mapping.insert(1, 0);
+        let result = operation
+            .call_method1(py, "remap_modes", (mode_mapping,))
+            .unwrap();
+        // test re-mapped mode
+        let mode_new: usize =
+            usize::extract(result.call_method0(py, "mode").unwrap().as_ref(py)).unwrap();
+        assert_eq!(mode_new.clone(), 1);
+        // test that initial and rempapped modes are different
+        assert_ne!(mode, mode_new);
+    })
+}
+
+/// Test remap_modes() function for TwoModeGate Operations
+#[test_case(Operation::from(BeamSplitter::new(0, 1, CalculatorFloat::from(0.1), CalculatorFloat::from(1.3))); "BeamSplitter")]
+fn test_pyo3_remapmodes_two(input_operation: Operation) {
+    pyo3::prepare_freethreaded_python();
+    Python::with_gil(|py| {
+        let operation = convert_operation_to_pyobject(input_operation).unwrap();
+        // test initial mode
+        let mode_0: usize =
+            usize::extract(operation.call_method0(py, "mode_0").unwrap().as_ref(py)).unwrap();
+        assert_eq!(mode_0.clone(), 0);
+        let mode_1: usize =
+            usize::extract(operation.call_method0(py, "mode_1").unwrap().as_ref(py)).unwrap();
+        assert_eq!(mode_1.clone(), 1);
+        // remap modes
+        let mut mode_mapping: HashMap<usize, usize> = HashMap::new();
+        mode_mapping.insert(0, 1);
+        mode_mapping.insert(1, 0);
+        let result = operation
+            .call_method1(py, "remap_modes", (mode_mapping,))
+            .unwrap();
+        // test re-mapped mode
+        let mode_new_0: usize =
+            usize::extract(result.call_method0(py, "mode_0").unwrap().as_ref(py)).unwrap();
+        assert_eq!(mode_new_0.clone(), 1);
+        let mode_new_1: usize =
+            usize::extract(result.call_method0(py, "mode_1").unwrap().as_ref(py)).unwrap();
+        assert_eq!(mode_new_1.clone(), 0);
+        // test that initial and rempapped modes are different
+        assert_ne!(mode_0, mode_new_0);
+        assert_ne!(mode_1, mode_new_1);
+    })
+}
+
+// test remap_modes() function returning an error.
+#[test_case(Operation::from(Squeezing::new(0, CalculatorFloat::from(1.3), 0.0.into())); "Squeezing")]
+#[test_case(Operation::from(PhaseShift::new(0, CalculatorFloat::from(1.3))); "PhaseShift")]
+#[test_case(Operation::from(BeamSplitter::new(0, 1, CalculatorFloat::from(0.1), CalculatorFloat::from(1.3))); "BeamSplitter")]
+#[test_case(Operation::from(PNRDetection::new(0, "ro".into(), 0)); "PNRDetection")]
+fn test_pyo3_remapmodes_error(input_operation: Operation) {
+    // preparation
+    pyo3::prepare_freethreaded_python();
+    Python::with_gil(|py| {
+        let operation = convert_operation_to_pyobject(input_operation).unwrap();
+        // remap modes
+        let mut mode_mapping: HashMap<usize, usize> = HashMap::new();
+        mode_mapping.insert(2, 0);
+        let result = operation.call_method1(py, "remap_modes", (mode_mapping,));
+        let result_ref = result.as_ref();
+        assert!(result_ref.is_err());
+    })
+}
+
+/// Test copy and deepcopy functions
+#[test_case(Operation::from(Squeezing::new(1, CalculatorFloat::from(1.3), 0.0.into())); "Squeezing")]
+#[test_case(Operation::from(PhaseShift::new(0, CalculatorFloat::from(1.3))); "PhaseShift")]
+#[test_case(Operation::from(BeamSplitter::new(0, 1, CalculatorFloat::from(0.1), CalculatorFloat::from(1.3))); "BeamSplitter")]
+#[test_case(Operation::from(PNRDetection::new(0, "ro".into(), 0)); "PNRDetection")]
+fn test_pyo3_copy_deepcopy(input_operation: Operation) {
+    pyo3::prepare_freethreaded_python();
+    Python::with_gil(|py| {
+        let operation = convert_operation_to_pyobject(input_operation).unwrap();
+        let copy_op = operation.call_method0(py, "__copy__").unwrap();
+        let deepcopy_op = operation.call_method1(py, "__deepcopy__", ("",)).unwrap();
+        let copy_deepcopy_param = operation;
+
+        let comparison_copy = bool::extract(
+            copy_op
+                .as_ref(py)
+                .call_method1("__eq__", (copy_deepcopy_param.clone(),))
+                .unwrap(),
+        )
+        .unwrap();
+        assert!(comparison_copy);
+        let comparison_deepcopy = bool::extract(
+            deepcopy_op
+                .as_ref(py)
+                .call_method1("__eq__", (copy_deepcopy_param,))
+                .unwrap(),
+        )
+        .unwrap();
+        assert!(comparison_deepcopy);
+    })
+}
+
+/// Test format and repr functions
+#[test_case(
+    "Squeezing { mode: 0, squeezing: Float(0.0), phase: Float(0.0) }",
+    Operation::from(Squeezing::new(0, CalculatorFloat::from(0), 0.0.into()));
+    "Squeezing")]
+#[test_case(
+    "PhaseShift { mode: 0, phase: Float(0.0) }",
+    Operation::from(PhaseShift::new(0, CalculatorFloat::from(0)));
+    "PhaseShift")]
+#[test_case(
+    "BeamSplitter { mode_0: 0, mode_1: 1, theta: Float(0.0), phi: Float(0.0) }",
+    Operation::from(BeamSplitter::new(0, 1, CalculatorFloat::from(0), CalculatorFloat::from(0)));
+    "BeamSplitter")]
+#[test_case(
+    "PNRDetection { mode: 0, readout: \"ro\", readout_index: 0 }",
+    Operation::from(PNRDetection::new(0, "ro".into(), 0));
+    "PNRDetection")]
+fn test_pyo3_format_repr(format_repr: &str, input_operation: Operation) {
+    pyo3::prepare_freethreaded_python();
+    Python::with_gil(|py| {
+        let operation = convert_operation_to_pyobject(input_operation).unwrap();
+        let to_format = operation.call_method1(py, "__format__", ("",)).unwrap();
+        let format_op: &str = <&str>::extract(to_format.as_ref(py)).unwrap();
+        assert_eq!(format_op, format_repr);
+        let to_repr = operation.call_method0(py, "__repr__").unwrap();
+        let repr_op: &str = <&str>::extract(to_repr.as_ref(py)).unwrap();
+        assert_eq!(repr_op, format_repr);
+    })
+}
+
+/// Test substitute_parameters() function for gates having multiple parameters
+#[test_case(Operation::from(Squeezing::new(1, CalculatorFloat::from("theta"), CalculatorFloat::from(1.0))); "Squeezing_theta")]
+#[test_case(Operation::from(Squeezing::new(1, CalculatorFloat::from(1.0), CalculatorFloat::from("phi"))); "Squeezing_phi")]
+#[test_case(Operation::from(Squeezing::new(1, CalculatorFloat::from("theta"), CalculatorFloat::from("phi"))); "Squeezing_theta_phi")]
+#[test_case(
+    Operation::from(
+        BeamSplitter::new(
+            0,
+            1,
+            CalculatorFloat::from("theta"),
+            CalculatorFloat::from(0.1),
+        )
+    ); "BeamSplitter_theta")]
+#[test_case(
+    Operation::from(
+        BeamSplitter::new(
+            0,
+            1,
+            CalculatorFloat::from(0.1),
+            CalculatorFloat::from("phi"),
+        )
+    ); "BeamSplitter_phi")]
+#[test_case(
+    Operation::from(
+        BeamSplitter::new(
+            0,
+            1,
+            CalculatorFloat::from("theta"),
+            CalculatorFloat::from("phi"),
+        )
+    ); "BeamSplitter_theta_phi")]
+fn test_pyo3_substitute_parameters(input_operation: Operation) {
+    pyo3::prepare_freethreaded_python();
+    Python::with_gil(|py| {
+        let operation = convert_operation_to_pyobject(input_operation.clone()).unwrap();
+        let mut substitution_dict_py: HashMap<&str, f64> = HashMap::new();
+        substitution_dict_py.insert("theta", 1.0);
+        substitution_dict_py.insert("phi", 0.0);
+        let substitute_op = operation
+            .call_method1(py, "substitute_parameters", (substitution_dict_py,))
+            .unwrap();
+
+        let mut substitution_dict: Calculator = Calculator::new();
+        substitution_dict.set_variable("theta", 1.0);
+        substitution_dict.set_variable("phi", 0.0);
+        let substitute_param = input_operation
+            .substitute_parameters(&substitution_dict)
+            .unwrap();
+        let test_operation = convert_operation_to_pyobject(substitute_param).unwrap();
+
+        let comparison = bool::extract(
+            substitute_op
+                .as_ref(py)
+                .call_method1("__eq__", (test_operation,))
+                .unwrap(),
+        )
+        .unwrap();
+        assert!(comparison);
+    })
+}
+
+/// Test substitute_parameters() function for one parameter
+#[test_case(Operation::from(PhaseShift::new(1, CalculatorFloat::from("theta"))); "PhaseShift")]
+fn test_pyo3_substitute_params_single(input_operation: Operation) {
+    pyo3::prepare_freethreaded_python();
+    Python::with_gil(|py| {
+        let operation = convert_operation_to_pyobject(input_operation.clone()).unwrap();
+        let mut substitution_dict_py: HashMap<&str, f64> = HashMap::new();
+        substitution_dict_py.insert("theta", 1.0);
+        let substitute_op = operation
+            .call_method1(py, "substitute_parameters", (substitution_dict_py,))
+            .unwrap();
+
+        let mut substitution_dict: Calculator = Calculator::new();
+        substitution_dict.set_variable("theta", 1.0);
+        let substitute_param = input_operation
+            .substitute_parameters(&substitution_dict)
+            .unwrap();
+        let test_operation = convert_operation_to_pyobject(substitute_param).unwrap();
+
+        let comparison = bool::extract(
+            substitute_op
+                .as_ref(py)
+                .call_method1("__eq__", (test_operation,))
+                .unwrap(),
+        )
+        .unwrap();
+        assert!(comparison);
+    })
+}
+
+/// Test substitute_parameters() causing an error `None`
+#[test_case(Operation::from(Squeezing::new(1, CalculatorFloat::from("test"), 0.0.into())); "Squeezing")]
+#[test_case(Operation::from(PhaseShift::new(1, CalculatorFloat::from("test"))); "PhaseShift")]
+#[test_case(Operation::from(BeamSplitter::new(0, 1, CalculatorFloat::from("test"), CalculatorFloat::from(0.1))); "BeamSplitter")]
+fn test_pyo3_substitute_params_error(input_operation: Operation) {
+    pyo3::prepare_freethreaded_python();
+    Python::with_gil(|py| {
+        let operation = convert_operation_to_pyobject(input_operation).unwrap();
+        let substitution_dict: HashMap<&str, f64> = HashMap::new();
+        let result = operation.call_method1(py, "substitute_parameters", (substitution_dict,));
+        let result_ref = result.as_ref();
+        assert!(result_ref.is_err());
+    })
+}
+
+/// Test substitute parameters function for SingleModeGate Operations where it has no effect
+#[test_case(Operation::from(Squeezing::new(1, 0.1.into(), 0.0.into())); "Squeezing")]
+#[test_case(Operation::from(PhaseShift::new(1, 0.1.into())); "PhaseShift")]
+#[test_case(Operation::from(BeamSplitter::new(0, 1, CalculatorFloat::from(0.1), CalculatorFloat::from(0.1))); "BeamSplitter")]
+#[test_case(Operation::from(PNRDetection::new(0, "ro".into(), 0)); "PNRDetection")]
+fn test_ineffective_substitute_parameters(input_operation: Operation) {
+    pyo3::prepare_freethreaded_python();
+    Python::with_gil(|py| {
+        let operation = convert_operation_to_pyobject(input_operation.clone()).unwrap();
+        let mut substitution_dict_py: HashMap<&str, f64> = HashMap::new();
+        substitution_dict_py.insert("theta", 0.0);
+        let substitute_op = operation
+            .call_method1(py, "substitute_parameters", (substitution_dict_py,))
+            .unwrap();
+
+        let comparison = bool::extract(
+            substitute_op
+                .as_ref(py)
+                .call_method1("__eq__", (operation,))
+                .unwrap(),
+        )
+        .unwrap();
+        assert!(comparison);
+    })
+}
+
+/// Test the __richcmp__ function
+#[test_case(
+    Operation::from(Squeezing::new(0, CalculatorFloat::from(0), 0.0.into())),
+    Operation::from(Squeezing::new(1, CalculatorFloat::from(0), 0.0.into())); "Squeezing")]
+#[test_case(
+    Operation::from(PhaseShift::new(0, CalculatorFloat::from(0))),
+    Operation::from(PhaseShift::new(1, CalculatorFloat::from(0))); "PhaseShift")]
+#[test_case(
+    Operation::from(BeamSplitter::new(0, 1, CalculatorFloat::from(0), CalculatorFloat::from(0))),
+    Operation::from(BeamSplitter::new(1, 2, CalculatorFloat::from(0), CalculatorFloat::from(0))); "BeamSplitter")]
+#[test_case(
+    Operation::from(PNRDetection::new(0, "ro".into(), 0)),
+    Operation::from(PNRDetection::new(1, "ro".into(), 0)); "PNRDetection")]
+fn test_pyo3_richcmp(definition_1: Operation, definition_2: Operation) {
+    pyo3::prepare_freethreaded_python();
+    Python::with_gil(|py| {
+        let operation_one = convert_operation_to_pyobject(definition_1).unwrap();
+        let operation_two = convert_operation_to_pyobject(definition_2).unwrap();
+
+        let comparison = bool::extract(
+            operation_one
+                .as_ref(py)
+                .call_method1("__eq__", (operation_two.clone(),))
+                .unwrap(),
+        )
+        .unwrap();
+        assert!(!comparison);
+
+        let comparison = bool::extract(
+            operation_one
+                .as_ref(py)
+                .call_method1("__ne__", (operation_two.clone(),))
+                .unwrap(),
+        )
+        .unwrap();
+        assert!(comparison);
+
+        let comparison = operation_one.call_method1(py, "__eq__", (vec!["fails"],));
+        assert!(comparison.is_err());
+
+        let comparison = operation_one.call_method1(py, "__ge__", (operation_two,));
+        assert!(comparison.is_err());
+    })
+}
diff --git a/qoqo/tests/integration/operations/mod.rs b/qoqo/tests/integration/operations/mod.rs
index daf0ce1c..eae0c305 100644
--- a/qoqo/tests/integration/operations/mod.rs
+++ b/qoqo/tests/integration/operations/mod.rs
@@ -34,6 +34,9 @@ pub use two_qubit_gate_operations::*;
 mod three_qubit_gate_operations;
 pub use three_qubit_gate_operations::*;
 
+mod bosonic_operations;
+pub use bosonic_operations::*;
+
 use pyo3::prelude::*;
 use qoqo_calculator::CalculatorFloat;
 use qoqo_calculator_pyo3::CalculatorFloatWrapper;
diff --git a/roqoqo/tests/integration/operations/bosonic_operations.rs b/roqoqo/tests/integration/operations/bosonic_operations.rs
index 5c238066..aa729044 100644
--- a/roqoqo/tests/integration/operations/bosonic_operations.rs
+++ b/roqoqo/tests/integration/operations/bosonic_operations.rs
@@ -22,9 +22,10 @@ use test_case::test_case;
 /// Test Squeezing inputs
 #[test]
 fn squeezing_inputs() {
-    let op = Squeezing::new(1, 0.1.into());
+    let op = Squeezing::new(1, 0.1.into(), 0.0.into());
     assert_eq!(op.mode(), &1_usize);
     assert_eq!(op.squeezing(), &CalculatorFloat::from(0.1));
+    assert_eq!(op.phase(), &CalculatorFloat::from(0.0));
 }
 
 /// Test Squeezing inputs
@@ -54,7 +55,7 @@ fn pnrdetection_inputs() {
     assert_eq!(op.readout_index(), &0_usize)
 }
 
-#[test_case(Operation::from(Squeezing::new(0, 0.5.into())))]
+#[test_case(Operation::from(Squeezing::new(0, 0.5.into(), 0.0.into())))]
 #[test_case(Operation::from(PhaseShift::new(0, 0.5.into())))]
 #[test_case(Operation::from(BeamSplitter::new(0, 1, 0.1.into(), 0.5.into())))]
 #[test_case(Operation::from(PNRDetection::new(0, "ro".into(), 0)))]
@@ -62,7 +63,7 @@ fn clone(op: Operation) {
     assert_eq!(op.clone(), op);
 }
 
-#[test_case(Operation::from(Squeezing::new(0, 0.5.into())), "Squeezing(Squeezing { mode: 0, squeezing: Float(0.5) })")]
+#[test_case(Operation::from(Squeezing::new(0, 0.5.into(), 0.0.into())), "Squeezing(Squeezing { mode: 0, squeezing: Float(0.5), phase: Float(0.0) })")]
 #[test_case(Operation::from(PhaseShift::new(0, 0.5.into())), "PhaseShift(PhaseShift { mode: 0, phase: Float(0.5) })")]
 #[test_case(Operation::from(BeamSplitter::new(0, 1, 0.1.into(), 0.5.into())), "BeamSplitter(BeamSplitter { mode_0: 0, mode_1: 1, theta: Float(0.1), phi: Float(0.5) })")]
 #[test_case(Operation::from(PNRDetection::new(0, "ro".into(), 0)), "PNRDetection(PNRDetection { mode: 0, readout: \"ro\", readout_index: 0 })")]
@@ -70,7 +71,7 @@ fn debug(op: Operation, string: &str) {
     assert_eq!(format!("{:?}", op), string);
 }
 
-#[test_case(Operation::from(Squeezing::new(0, 0.5.into())), Operation::from(Squeezing::new(0, 0.5.into())), Operation::from(Squeezing::new(1, 0.5.into())))]
+#[test_case(Operation::from(Squeezing::new(0, 0.5.into(), 0.0.into())), Operation::from(Squeezing::new(0, 0.5.into(), 0.0.into())), Operation::from(Squeezing::new(1, 0.5.into(), 0.0.into())))]
 #[test_case(Operation::from(PhaseShift::new(0, 0.5.into())), Operation::from(PhaseShift::new(0, 0.5.into())), Operation::from(PhaseShift::new(1, 0.5.into())))]
 #[test_case(Operation::from(BeamSplitter::new(0, 1, 0.1.into(), 0.5.into())), Operation::from(BeamSplitter::new(0, 1, 0.1.into(), 0.5.into())), Operation::from(BeamSplitter::new(1, 2, 0.1.into(), 0.5.into())))]
 #[test_case(Operation::from(PNRDetection::new(0, "ro".into(), 0)), Operation::from(PNRDetection::new(0, "ro".into(), 0)), Operation::from(PNRDetection::new(1, "ro".into(), 0)))]
@@ -81,10 +82,9 @@ fn partial_eq(op: Operation, op_0: Operation, op_1: Operation) {
     assert!(op != op_1);
 }
 
-#[test_case(ModeGateOperation::from(Squeezing::new(0, 0.1.into())), InvolvedQubits::None, InvolvedClassical::None, InvolvedModes::Set(HashSet::from([0_usize])))]
+#[test_case(ModeGateOperation::from(Squeezing::new(0, 0.1.into(), 0.0.into())), InvolvedQubits::None, InvolvedClassical::None, InvolvedModes::Set(HashSet::from([0_usize])))]
 #[test_case(ModeGateOperation::from(PhaseShift::new(0, 0.1.into())), InvolvedQubits::None, InvolvedClassical::None, InvolvedModes::Set(HashSet::from([0_usize])))]
 #[test_case(ModeGateOperation::from(BeamSplitter::new(0, 1, 0.5.into(), 0.1.into())), InvolvedQubits::None, InvolvedClassical::None, InvolvedModes::Set(HashSet::from([0_usize, 1_usize])))]
-// #[test_case(ModeGateOperation::from(PNRDetection::new(0, "ro".into(), 0)), InvolvedQubits::None, InvolvedClassical::Set(HashSet::from([0_usize])), InvolvedModes::Set(HashSet::from([0_usize])))]
 fn involved_qubits_classical_modes(
     op: ModeGateOperation,
     qubits: InvolvedQubits,
@@ -108,7 +108,7 @@ fn involved_qubits_classical_modes_measurement(
     assert_eq!(op.involved_modes(), modes);
 }
 
-#[test_case(ModeGateOperation::from(Squeezing::new(2, "test".into())), ModeGateOperation::from(Squeezing::new(0, 0.1.into())))]
+#[test_case(ModeGateOperation::from(Squeezing::new(2, "test".into(), "test1".into())), ModeGateOperation::from(Squeezing::new(0, 0.1.into(), 0.5.into())))]
 #[test_case(ModeGateOperation::from(PhaseShift::new(2, "test".into())), ModeGateOperation::from(PhaseShift::new(0, 0.1.into())))]
 #[test_case(ModeGateOperation::from(BeamSplitter::new(2, 0, "test".into(), "test1".into())), ModeGateOperation::from(BeamSplitter::new(0, 1, 0.1.into(), 0.5.into())))]
 fn substitute_subsitutemodes(op: ModeGateOperation, op_test: ModeGateOperation) {
@@ -156,7 +156,7 @@ fn substitute_subsitutemodes_measurement(op: SingleModeOperation, op_test: Singl
     assert_eq!(result, op_test);
 }
 
-#[test_case(Operation::from(Squeezing::new(0, 0.1.into())), Operation::from(Squeezing::new(0, "param".into())), "Squeezing")]
+#[test_case(Operation::from(Squeezing::new(0, 0.1.into(), 0.0.into())), Operation::from(Squeezing::new(0, "param".into(), 0.0.into())), "Squeezing")]
 #[test_case(Operation::from(PhaseShift::new(0, 0.1.into())), Operation::from(PhaseShift::new(0, "param".into())), "PhaseShift")]
 fn operate_one_mode(op: Operation, op_param: Operation, name: &str) {
     // (1) Test tags function
@@ -208,13 +208,13 @@ fn operate_measurement(op: Operation, name: &str) {
     assert!(!op.is_parametrized());
 }
 
-#[test_case(SingleModeOperation::from(Squeezing::new(0, 0.5.into())))]
+#[test_case(SingleModeOperation::from(Squeezing::new(0, 0.5.into(), 0.0.into())))]
 #[test_case(SingleModeOperation::from(PhaseShift::new(0, 0.5.into())))]
 #[test_case(SingleModeOperation::from(PNRDetection::new(0, "ro".into(), 0)))]
 fn single_mode_op(op: SingleModeOperation) {
     assert_eq!(op.mode(), &0_usize);
 }
-#[test_case(SingleModeGateOperation::from(Squeezing::new(0, 0.5.into())))]
+#[test_case(SingleModeGateOperation::from(Squeezing::new(0, 0.5.into(), 0.0.into())))]
 #[test_case(SingleModeGateOperation::from(PhaseShift::new(0, 0.5.into())))]
 fn single_mode_gate_op(op: SingleModeGateOperation) {
     assert_eq!(op.mode(), &0_usize);
@@ -234,19 +234,21 @@ fn two_mode_gate_op(op: TwoModeGateOperation) {
 #[cfg(feature = "serialize")]
 #[test]
 fn squeezing_serde() {
-    let op = Squeezing::new(0, 0.1.into());
+    let op = Squeezing::new(0, 0.1.into(), 0.0.into());
 
     assert_tokens(
         &op.clone().readable(),
         &[
             Token::Struct {
                 name: "Squeezing",
-                len: 2,
+                len: 3,
             },
             Token::Str("mode"),
             Token::U64(0),
             Token::Str("squeezing"),
             Token::F64(0.1),
+            Token::Str("phase"),
+            Token::F64(0.0),
             Token::StructEnd,
         ],
     );
@@ -256,7 +258,7 @@ fn squeezing_serde() {
         &[
             Token::Struct {
                 name: "Squeezing",
-                len: 2,
+                len: 3,
             },
             Token::Str("mode"),
             Token::U64(0),
@@ -266,6 +268,12 @@ fn squeezing_serde() {
                 variant: "Float",
             },
             Token::F64(0.1),
+            Token::Str("phase"),
+            Token::NewtypeVariant {
+                name: "CalculatorFloat",
+                variant: "Float",
+            },
+            Token::F64(0.0),
             Token::StructEnd,
         ],
     );
diff --git a/roqoqo/tests/integration/operations/supported_version.rs b/roqoqo/tests/integration/operations/supported_version.rs
index b9c77108..48a5a11e 100644
--- a/roqoqo/tests/integration/operations/supported_version.rs
+++ b/roqoqo/tests/integration/operations/supported_version.rs
@@ -110,7 +110,7 @@ fn test_version_1_0_0_multi_qubit_gate(operation: operations::MultiQubitGateOper
     assert_eq!(op.minimum_supported_roqoqo_version(), (1, 0, 0));
 }
 
-#[test_case(operations::SingleModeGateOperation::from(operations::Squeezing::new(0, 1.0.into())); "Squeezing")]
+#[test_case(operations::SingleModeGateOperation::from(operations::Squeezing::new(0, 1.0.into(), 0.0.into())); "Squeezing")]
 #[test_case(operations::SingleModeGateOperation::from(operations::PhaseShift::new(0, 1.0.into())); "PhaseShift")]
 fn test_version_1_6_0_single_mode_gate(operation: operations::SingleModeGateOperation) {
     assert_eq!(operation.minimum_supported_roqoqo_version(), (1, 6, 0));