diff --git a/.compatibility_tests/compatibility_test_1_9/tests/integration/compatibility_1_9.rs b/.compatibility_tests/compatibility_test_1_9/tests/integration/compatibility_1_9.rs index b5963f84..0aad9def 100644 --- a/.compatibility_tests/compatibility_test_1_9/tests/integration/compatibility_1_9.rs +++ b/.compatibility_tests/compatibility_test_1_9/tests/integration/compatibility_1_9.rs @@ -116,6 +116,9 @@ use test_roqoqo_1_9; // #[test_case(test_roqoqo_1_9::operations::QuantumRabi::new(0, 1, 0.1.into()).into(); "QuantumRabi")] // #[test_case(test_roqoqo_1_9::operations::LongitudinalCoupling::new(0, 1, 0.1.into()).into(); "LongitudinalCoupling")] // #[test_case(test_roqoqo_1_9::operations::JaynesCummings::new(0, 1, 0.1.into()).into(); "JaynesCummings")] +// #[test_case(test_roqoqo_1_9::operations::SingleExcitationLoad::new(0, 1).into(); "SingleExcitationLoad")] +// #[test_case(test_roqoqo_1_9::operations::SingleExcitationStore::new(0, 1).into(); "SingleExcitationStore")] +// #[test_case(test_roqoqo_1_9::operations::CZQubitResonator::new(0, 1).into(); "CZQubitResonator")] fn test_bincode_compatibility_1_9(operation: test_roqoqo_1_9::operations::Operation) { let mut test_circuit = test_roqoqo_1_9::Circuit::new(); test_circuit += operation; diff --git a/qoqo/src/operations/spin_boson_operations.rs b/qoqo/src/operations/spin_boson_operations.rs index 7dfa089d..5e904de0 100644 --- a/qoqo/src/operations/spin_boson_operations.rs +++ b/qoqo/src/operations/spin_boson_operations.rs @@ -21,7 +21,7 @@ use roqoqo::operations::*; use roqoqo::ROQOQO_VERSION; use std::collections::HashMap; -/// The quantum Rabi interaction exp(-i * θ * X * (b^{dagger} + b)) +/// The quantum Rabi interaction exp(-i * θ * X * (b^† + b)) /// /// Args: /// qubit (int): The qubit the gate is applied to. @@ -43,7 +43,7 @@ pub struct QuantumRabi { theta: CalculatorFloat, } -/// Longitudinal coupling gate exp(-i * θ * Z * (b^{dagger} + b)) +/// Longitudinal coupling gate exp(-i * θ * Z * (b^† + b)) /// /// Args: /// qubit (int): The qubit the gate is applied to. @@ -65,7 +65,7 @@ pub struct LongitudinalCoupling { theta: CalculatorFloat, } -/// The Jaynes-Cummings gate exp(-i * θ * (σ^- * b^{dagger} + σ^+ * b)) +/// The Jaynes-Cummings gate exp(-i * θ * (σ^- * b^† + σ^+ * b)) /// /// Args: /// qubit (int): The qubit the gate is applied to. @@ -86,3 +86,71 @@ pub struct JaynesCummings { mode: usize, theta: CalculatorFloat, } + +/// Loads a single excitation from a bosonic mode into a qubit as follows +/// (c1 |0⟩_B + c2 |1⟩_B) ⨂ |0⟩_Q -> |0⟩_B ⨂ (c1 |0⟩_Q + c2 |1⟩_Q) +/// +/// Note: if the initial qubit state is |1⟩_Q the operation is only defined if c2 = 0 +/// +/// Args: +/// qubit (int): The qubit the gate is applied to. +/// mode (int): The mode the gate is applied to. +#[wrap( + Operate, + Substitute, + OperateSingleMode, + SubstituteModes, + InvolveModes, + OperateSingleQubit, + InvolveQubits, + JsonSchema +)] +pub struct SingleExcitationLoad { + qubit: usize, + mode: usize, +} + +/// Stores a single excitation from the involved qubit into the involved bosonic mode as follows +/// |0⟩_B ⨂ (a |0⟩_Q + b |1⟩_Q) -> (a|0⟩_B + b |1⟩_B ) ⨂ |0⟩_Q +/// +/// Note: not defined if the bosonic mode is in a state |n⟩ with n != 0 +/// +/// Args: +/// qubit (int): The qubit the gate is applied to. +/// mode (int): The mode the gate is applied to. +#[wrap( + Operate, + Substitute, + OperateSingleMode, + SubstituteModes, + InvolveModes, + OperateSingleQubit, + InvolveQubits, + JsonSchema +)] +pub struct SingleExcitationStore { + qubit: usize, + mode: usize, +} + +/// Controlled-Z operation between a qubit and a bosonic mode, where the two-dimensional subspace of +/// the bosonic mode spanned by the occupation number states |0⟩_B and |1⟩_B is considered +/// as the second qubit involved in the CZ operation. +/// +/// Args: +/// qubit (int): The qubit the gate is applied to. +/// mode (int): The mode the gate is applied to. +#[wrap( + Operate, + Substitute, + OperateSingleMode, + SubstituteModes, + InvolveModes, + OperateSingleQubit, + InvolveQubits, + JsonSchema +)] +pub struct CZQubitResonator { + qubit: usize, + mode: usize, +} diff --git a/qoqo/tests/integration/operations/spin_boson_operations.rs b/qoqo/tests/integration/operations/spin_boson_operations.rs index ef656ed2..85b40a13 100644 --- a/qoqo/tests/integration/operations/spin_boson_operations.rs +++ b/qoqo/tests/integration/operations/spin_boson_operations.rs @@ -13,7 +13,10 @@ use pyo3::prelude::*; use pyo3::Python; use qoqo::operations::convert_operation_to_pyobject; -use qoqo::operations::{JaynesCummingsWrapper, LongitudinalCouplingWrapper, QuantumRabiWrapper}; +use qoqo::operations::{ + CZQubitResonatorWrapper, JaynesCummingsWrapper, LongitudinalCouplingWrapper, + QuantumRabiWrapper, SingleExcitationLoadWrapper, SingleExcitationStoreWrapper, +}; use qoqo_calculator::{Calculator, CalculatorFloat}; use qoqo_calculator_pyo3::CalculatorFloatWrapper; use roqoqo::operations::Operation; @@ -219,6 +222,148 @@ fn test_new_jaynes_cummings(input_operation: Operation, arguments: (u32, u32, f6 }) } +/// Test new() function for SingleExcitationLoad +#[test_case(Operation::from(SingleExcitationLoad::new(1, 0)), (1, 0), "__eq__"; "SingleExcitationLoad_eq")] +#[test_case(Operation::from(SingleExcitationLoad::new(1, 0)), (0, 1), "__ne__"; "SingleExcitationLoad_ne")] +fn test_new_single_excitation_load( + input_operation: Operation, + arguments: (u32, 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::(); + let operation_py = operation_type + .call1(arguments) + .unwrap() + .downcast::>() + .unwrap(); + + let comparison = bool::extract( + operation + .as_ref(py) + .call_method1(method, (operation_py,)) + .unwrap(), + ) + .unwrap(); + assert!(comparison); + + let def_wrapper = operation_py + .extract::() + .unwrap(); + let new_op_diff = operation_type + .call1((2, 3)) + .unwrap() + .downcast::>() + .unwrap(); + let def_wrapper_diff = new_op_diff + .extract::() + .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), + "SingleExcitationLoadWrapper { internal: SingleExcitationLoad { qubit: 2, mode: 3 } }" + ); + }) +} + +/// Test new() function for SingleExcitationStore +#[test_case(Operation::from(SingleExcitationStore::new(1, 0)), (1, 0), "__eq__"; "SingleExcitationStore_eq")] +#[test_case(Operation::from(SingleExcitationStore::new(1, 0)), (0, 1), "__ne__"; "SingleExcitationStore_ne")] +fn test_new_single_excitation_store( + input_operation: Operation, + arguments: (u32, 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::(); + let operation_py = operation_type + .call1(arguments) + .unwrap() + .downcast::>() + .unwrap(); + + let comparison = bool::extract( + operation + .as_ref(py) + .call_method1(method, (operation_py,)) + .unwrap(), + ) + .unwrap(); + assert!(comparison); + + let def_wrapper = operation_py + .extract::() + .unwrap(); + let new_op_diff = operation_type + .call1((2, 3)) + .unwrap() + .downcast::>() + .unwrap(); + let def_wrapper_diff = new_op_diff + .extract::() + .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), + "SingleExcitationStoreWrapper { internal: SingleExcitationStore { qubit: 2, mode: 3 } }" + ); + }) +} + +/// Test new() function for CZQubitResonator +#[test_case(Operation::from(CZQubitResonator::new(1, 0)), (1, 0), "__eq__"; "CZQubitResonator_eq")] +#[test_case(Operation::from(CZQubitResonator::new(1, 0)), (0, 1), "__ne__"; "CZQubitResonator_ne")] +fn test_new_cz_qubit_resonator(input_operation: Operation, arguments: (u32, 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::(); + let operation_py = operation_type + .call1(arguments) + .unwrap() + .downcast::>() + .unwrap(); + + let comparison = bool::extract( + operation + .as_ref(py) + .call_method1(method, (operation_py,)) + .unwrap(), + ) + .unwrap(); + assert!(comparison); + + let def_wrapper = operation_py.extract::().unwrap(); + let new_op_diff = operation_type + .call1((2, 3)) + .unwrap() + .downcast::>() + .unwrap(); + let def_wrapper_diff = new_op_diff.extract::().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), + "CZQubitResonatorWrapper { internal: CZQubitResonator { qubit: 2, mode: 3 } }" + ); + }) +} + /// Test is_parametrized() function for SingleModeGate Operations #[test_case(Operation::from(QuantumRabi::new(1, 0, CalculatorFloat::from("theta"))); "QuantumRabi")] #[test_case(Operation::from(LongitudinalCoupling::new(1, 0, CalculatorFloat::from("theta"))); "LongitudinalCoupling")] @@ -259,6 +404,9 @@ fn test_pyo3_is_not_parametrized(input_operation: Operation) { #[test_case(0, Operation::from(QuantumRabi::new(1, 0, 1.0.into())); "QuantumRabi")] #[test_case(0, Operation::from(LongitudinalCoupling::new(1, 0, 1.0.into())); "LongitudinalCoupling")] #[test_case(0, Operation::from(JaynesCummings::new(1, 0, 1.0.into())); "JaynesCummings")] +#[test_case(0, Operation::from(SingleExcitationLoad::new(1, 0)); "SingleExcitationLoad")] +#[test_case(0, Operation::from(SingleExcitationStore::new(1, 0)); "SingleExcitationStore")] +#[test_case(0, Operation::from(CZQubitResonator::new(1, 0)); "CZQubitResonator")] fn test_pyo3_mode(mode: usize, input_operation: Operation) { pyo3::prepare_freethreaded_python(); Python::with_gil(|py| { @@ -272,6 +420,9 @@ fn test_pyo3_mode(mode: usize, input_operation: Operation) { #[test_case(1, Operation::from(QuantumRabi::new(1, 0, 1.0.into())); "QuantumRabi")] #[test_case(1, Operation::from(LongitudinalCoupling::new(1, 0, 1.0.into())); "LongitudinalCoupling")] #[test_case(1, Operation::from(JaynesCummings::new(1, 0, 1.0.into())); "JaynesCummings")] +#[test_case(1, Operation::from(SingleExcitationLoad::new(1, 0)); "SingleExcitationLoad")] +#[test_case(1, Operation::from(SingleExcitationStore::new(1, 0)); "SingleExcitationStore")] +#[test_case(1, Operation::from(CZQubitResonator::new(1, 0)); "CZQubitResonator")] fn test_pyo3_qubit(qubit: usize, input_operation: Operation) { pyo3::prepare_freethreaded_python(); Python::with_gil(|py| { @@ -286,6 +437,9 @@ fn test_pyo3_qubit(qubit: usize, input_operation: Operation) { #[test_case("QuantumRabi", Operation::from(QuantumRabi::new(1, 0, 1.0.into())); "QuantumRabi")] #[test_case("LongitudinalCoupling", Operation::from(LongitudinalCoupling::new(1, 0, 1.0.into())); "LongitudinalCoupling")] #[test_case("JaynesCummings", Operation::from(JaynesCummings::new(1, 0, 1.0.into())); "JaynesCummings")] +#[test_case("SingleExcitationLoad", Operation::from(SingleExcitationLoad::new(1, 0)); "SingleExcitationLoad")] +#[test_case("SingleExcitationStore", Operation::from(SingleExcitationStore::new(1, 0)); "SingleExcitationStore")] +#[test_case("CZQubitResonator", Operation::from(CZQubitResonator::new(1, 0)); "CZQubitResonator")] fn test_pyo3_hqslang(name: &'static str, input_operation: Operation) { pyo3::prepare_freethreaded_python(); Python::with_gil(|py| { @@ -305,7 +459,6 @@ fn test_pyo3_hqslang(name: &'static str, input_operation: Operation) { "ModeGateOperation", "SingleModeGateOperation", "SingleQubitGateOperation", - "SingleQubitGate", "QuantumRabi", ]; "QuantumRabi")] @@ -317,7 +470,6 @@ fn test_pyo3_hqslang(name: &'static str, input_operation: Operation) { "ModeGateOperation", "SingleModeGateOperation", "SingleQubitGateOperation", - "SingleQubitGate", "LongitudinalCoupling", ]; "LongitudinalCoupling")] @@ -329,10 +481,42 @@ fn test_pyo3_hqslang(name: &'static str, input_operation: Operation) { "ModeGateOperation", "SingleModeGateOperation", "SingleQubitGateOperation", - "SingleQubitGate", "JaynesCummings", ]; "JaynesCummings")] +#[test_case( + Operation::from(SingleExcitationLoad::new(1, 0)), + vec![ + "Operation", + "GateOperation", + "ModeGateOperation", + "SingleModeGateOperation", + "SingleQubitGateOperation", + "SingleExcitationLoad", + ]; + "SingleExcitationLoad")] +#[test_case( + Operation::from(SingleExcitationStore::new(1, 0)), + vec![ + "Operation", + "GateOperation", + "ModeGateOperation", + "SingleModeGateOperation", + "SingleQubitGateOperation", + "SingleExcitationStore", + ]; + "SingleExcitationStore")] +#[test_case( + Operation::from(CZQubitResonator::new(1, 0)), + vec![ + "Operation", + "GateOperation", + "ModeGateOperation", + "SingleModeGateOperation", + "SingleQubitGateOperation", + "CZQubitResonator", + ]; + "CZQubitResonator")] fn test_pyo3_tags(input_operation: Operation, tags: Vec<&str>) { pyo3::prepare_freethreaded_python(); Python::with_gil(|py| { @@ -350,6 +534,9 @@ fn test_pyo3_tags(input_operation: Operation, tags: Vec<&str>) { #[test_case(Operation::from(QuantumRabi::new(1, 0, 1.0.into())), HashSet::::from([0]); "QuantumRabi")] #[test_case(Operation::from(LongitudinalCoupling::new(1, 0, 1.0.into())), HashSet::::from([0]); "LongitudinalCoupling")] #[test_case(Operation::from(JaynesCummings::new(1, 0, 1.0.into())), HashSet::::from([0]); "JaynesCummings")] +#[test_case(Operation::from(SingleExcitationLoad::new(1, 0)), HashSet::::from([0]); "SingleExcitationLoad")] +#[test_case(Operation::from(SingleExcitationStore::new(1, 0)), HashSet::::from([0]); "SingleExcitationStore")] +#[test_case(Operation::from(CZQubitResonator::new(1, 0)), HashSet::::from([0]); "CZQubitResonator")] fn test_pyo3_involved_modes(input_operation: Operation, modes: HashSet) { pyo3::prepare_freethreaded_python(); Python::with_gil(|py| { @@ -370,6 +557,9 @@ fn test_pyo3_involved_modes(input_operation: Operation, modes: HashSet) { #[test_case(Operation::from(QuantumRabi::new(1, 0, 1.0.into())); "QuantumRabi")] #[test_case(Operation::from(LongitudinalCoupling::new(1, 0, 1.0.into())); "LongitudinalCoupling")] #[test_case(Operation::from(JaynesCummings::new(1, 0, 1.0.into())); "JaynesCummings")] +#[test_case(Operation::from(SingleExcitationLoad::new(1, 0)); "SingleExcitationLoad")] +#[test_case(Operation::from(SingleExcitationStore::new(1, 0)); "SingleExcitationStore")] +#[test_case(Operation::from(CZQubitResonator::new(1, 0)); "CZQubitResonator")] fn test_pyo3_remapqubits(input_operation: Operation) { pyo3::prepare_freethreaded_python(); Python::with_gil(|py| { @@ -398,6 +588,9 @@ fn test_pyo3_remapqubits(input_operation: Operation) { #[test_case(Operation::from(QuantumRabi::new(1, 0, 1.0.into())); "QuantumRabi")] #[test_case(Operation::from(LongitudinalCoupling::new(1, 0, 1.0.into())); "LongitudinalCoupling")] #[test_case(Operation::from(JaynesCummings::new(1, 0, 1.0.into())); "JaynesCummings")] +#[test_case(Operation::from(SingleExcitationLoad::new(1, 0)); "SingleExcitationLoad")] +#[test_case(Operation::from(SingleExcitationStore::new(1, 0)); "SingleExcitationStore")] +#[test_case(Operation::from(CZQubitResonator::new(1, 0)); "CZQubitResonator")] fn test_pyo3_remapmodes_single(input_operation: Operation) { pyo3::prepare_freethreaded_python(); Python::with_gil(|py| { @@ -426,6 +619,9 @@ fn test_pyo3_remapmodes_single(input_operation: Operation) { #[test_case(Operation::from(QuantumRabi::new(1, 0, 1.0.into())); "QuantumRabi")] #[test_case(Operation::from(LongitudinalCoupling::new(1, 0, 1.0.into())); "LongitudinalCoupling")] #[test_case(Operation::from(JaynesCummings::new(1, 0, 1.0.into())); "JaynesCummings")] +#[test_case(Operation::from(SingleExcitationLoad::new(1, 0)); "SingleExcitationLoad")] +#[test_case(Operation::from(SingleExcitationStore::new(1, 0)); "SingleExcitationStore")] +#[test_case(Operation::from(CZQubitResonator::new(1, 0)); "CZQubitResonator")] fn test_pyo3_remapmodes_error(input_operation: Operation) { // preparation pyo3::prepare_freethreaded_python(); @@ -444,6 +640,9 @@ fn test_pyo3_remapmodes_error(input_operation: Operation) { #[test_case(Operation::from(QuantumRabi::new(1, 0, 1.0.into())); "QuantumRabi")] #[test_case(Operation::from(LongitudinalCoupling::new(1, 0, 1.0.into())); "LongitudinalCoupling")] #[test_case(Operation::from(JaynesCummings::new(1, 0, 1.0.into())); "JaynesCummings")] +#[test_case(Operation::from(SingleExcitationLoad::new(1, 0)); "SingleExcitationLoad")] +#[test_case(Operation::from(SingleExcitationStore::new(1, 0)); "SingleExcitationStore")] +#[test_case(Operation::from(CZQubitResonator::new(1, 0)); "CZQubitResonator")] fn test_pyo3_remapqubits_error(input_operation: Operation) { // preparation pyo3::prepare_freethreaded_python(); @@ -462,6 +661,9 @@ fn test_pyo3_remapqubits_error(input_operation: Operation) { #[test_case(Operation::from(QuantumRabi::new(1, 0, 1.0.into())); "QuantumRabi")] #[test_case(Operation::from(LongitudinalCoupling::new(1, 0, 1.0.into())); "LongitudinalCoupling")] #[test_case(Operation::from(JaynesCummings::new(1, 0, 1.0.into())); "JaynesCummings")] +#[test_case(Operation::from(SingleExcitationLoad::new(1, 0)); "SingleExcitationLoad")] +#[test_case(Operation::from(SingleExcitationStore::new(1, 0)); "SingleExcitationStore")] +#[test_case(Operation::from(CZQubitResonator::new(1, 0)); "CZQubitResonator")] fn test_pyo3_copy_deepcopy(input_operation: Operation) { pyo3::prepare_freethreaded_python(); Python::with_gil(|py| { @@ -502,6 +704,18 @@ fn test_pyo3_copy_deepcopy(input_operation: Operation) { "JaynesCummings { qubit: 1, mode: 0, theta: Float(1.0) }", Operation::from(JaynesCummings::new(1, 0, 1.0.into())); "JaynesCummings")] +#[test_case( + "SingleExcitationLoad { qubit: 1, mode: 0 }", + Operation::from(SingleExcitationLoad::new(1, 0)); + "SingleExcitationLoad")] +#[test_case( + "SingleExcitationStore { qubit: 1, mode: 0 }", + Operation::from(SingleExcitationStore::new(1, 0)); + "SingleExcitationStore")] +#[test_case( + "CZQubitResonator { qubit: 1, mode: 0 }", + Operation::from(CZQubitResonator::new(1, 0)); + "CZQubitResonator")] fn test_pyo3_format_repr(format_repr: &str, input_operation: Operation) { pyo3::prepare_freethreaded_python(); Python::with_gil(|py| { @@ -600,6 +814,18 @@ fn test_ineffective_substitute_parameters(input_operation: Operation) { Operation::from(JaynesCummings::new(1, 0, CalculatorFloat::from(1.0))), Operation::from(JaynesCummings::new(0, 1, CalculatorFloat::from(1.0))); "JaynesCummings")] +#[test_case( + Operation::from(SingleExcitationLoad::new(1, 0)), + Operation::from(SingleExcitationLoad::new(0, 1)); + "SingleExcitationLoad")] +#[test_case( + Operation::from(SingleExcitationStore::new(1, 0)), + Operation::from(SingleExcitationStore::new(0, 1)); + "SingleExcitationStore")] +#[test_case( + Operation::from(CZQubitResonator::new(1, 0)), + Operation::from(CZQubitResonator::new(0, 1)); + "CZQubitResonator")] fn test_pyo3_richcmp(definition_1: Operation, definition_2: Operation) { pyo3::prepare_freethreaded_python(); Python::with_gil(|py| { @@ -637,6 +863,9 @@ fn test_pyo3_richcmp(definition_1: Operation, definition_2: Operation) { #[test_case(Operation::from(QuantumRabi::new(1, 0, CalculatorFloat::from(1.0))); "QuantumRabi")] #[test_case(Operation::from(LongitudinalCoupling::new(1, 0, CalculatorFloat::from(1.0))); "LongitudinalCoupling")] #[test_case(Operation::from(JaynesCummings::new(1, 0, CalculatorFloat::from(1.0))); "JaynesCummings")] +#[test_case(Operation::from(SingleExcitationLoad::new(1, 0)); "SingleExcitationLoad")] +#[test_case(Operation::from(SingleExcitationStore::new(1, 0)); "SingleExcitationStore")] +#[test_case(Operation::from(CZQubitResonator::new(1, 0)); "CZQubitResonator")] fn test_pyo3_json_schema(operation: Operation) { let rust_schema = match operation { Operation::QuantumRabi(_) => { diff --git a/roqoqo/src/operations/spin_boson_operations.rs b/roqoqo/src/operations/spin_boson_operations.rs index 52f3c244..318acdca 100644 --- a/roqoqo/src/operations/spin_boson_operations.rs +++ b/roqoqo/src/operations/spin_boson_operations.rs @@ -20,7 +20,7 @@ use crate::operations::{ use crate::RoqoqoError; use qoqo_calculator::CalculatorFloat; -/// The quantum Rabi interaction exp(-i * θ * X * (b^{\dagger} + b)) +/// The quantum Rabi interaction exp(-i * θ * X * (b^† + b)) #[derive( Debug, Clone, @@ -47,13 +47,12 @@ pub struct QuantumRabi { } #[allow(non_upper_case_globals)] -const TAGS_QuantumRabi: &[&str; 7] = &[ +const TAGS_QuantumRabi: &[&str; 6] = &[ "Operation", "GateOperation", "ModeGateOperation", "SingleModeGateOperation", "SingleQubitGateOperation", - "SingleQubitGate", "QuantumRabi", ]; @@ -65,7 +64,7 @@ impl SupportedVersion for QuantumRabi { } } -/// Longitudinal coupling gate exp(-i * θ * Z * (b^{\dagger} + b)) +/// Longitudinal coupling gate exp(-i * θ * Z * (b^† + b)) #[derive( Debug, Clone, @@ -92,13 +91,12 @@ pub struct LongitudinalCoupling { } #[allow(non_upper_case_globals)] -const TAGS_LongitudinalCoupling: &[&str; 7] = &[ +const TAGS_LongitudinalCoupling: &[&str; 6] = &[ "Operation", "GateOperation", "ModeGateOperation", "SingleModeGateOperation", "SingleQubitGateOperation", - "SingleQubitGate", "LongitudinalCoupling", ]; @@ -110,7 +108,7 @@ impl SupportedVersion for LongitudinalCoupling { } } -/// The Jaynes-Cummings gate exp(-i * θ * (σ^- * b^{\dagger} + σ^+ * b)) +/// The Jaynes-Cummings gate exp(-i * θ * (σ^- * b^† + σ^+ * b)) #[derive( Debug, Clone, @@ -137,13 +135,12 @@ pub struct JaynesCummings { } #[allow(non_upper_case_globals)] -const TAGS_JaynesCummings: &[&str; 7] = &[ +const TAGS_JaynesCummings: &[&str; 6] = &[ "Operation", "GateOperation", "ModeGateOperation", "SingleModeGateOperation", "SingleQubitGateOperation", - "SingleQubitGate", "JaynesCummings", ]; @@ -154,3 +151,137 @@ impl SupportedVersion for JaynesCummings { (1, 10, 0) } } + +/// Stores a single excitation from the involved qubit into the involved bosonic mode as follows +/// |0⟩_B ⨂ (a |0⟩_Q + b |1⟩_Q) -> (a|0⟩_B + b |1⟩_B ) ⨂ |0⟩_Q +/// +/// Note: not defined if the bosonic mode is in a state |n⟩ with n != 0 +#[derive( + Debug, + Clone, + PartialEq, + OperateModeGate, + OperateSingleModeGate, + roqoqo_derive::Operate, + roqoqo_derive::OperateSingleQubit, + roqoqo_derive::InvolveQubits, + roqoqo_derive::Substitute, + roqoqo_derive::OperateSingleMode, + roqoqo_derive::InvolveModes, + roqoqo_derive::SubstituteModes, +)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "json_schema", derive(schemars::JsonSchema))] +pub struct SingleExcitationStore { + /// The qubit involved. + qubit: usize, + /// The bosonic mode involved. + mode: usize, +} + +#[allow(non_upper_case_globals)] +const TAGS_SingleExcitationStore: &[&str; 6] = &[ + "Operation", + "GateOperation", + "ModeGateOperation", + "SingleModeGateOperation", + "SingleQubitGateOperation", + "SingleExcitationStore", +]; + +impl ImplementedIn1point10 for SingleExcitationStore {} + +impl SupportedVersion for SingleExcitationStore { + fn minimum_supported_roqoqo_version(&self) -> (u32, u32, u32) { + (1, 10, 0) + } +} + +/// Loads a single excitation from a bosonic mode into a qubit as follows +/// (c1 |0⟩_B + c2 |1⟩_B) ⨂ |0⟩_Q -> |0⟩_B ⨂ (c1 |0⟩_Q + c2 |1⟩_Q) +/// +/// Note: if the initial qubit state is |1⟩_Q the operation is only defined if c2 = 0 +#[derive( + Debug, + Clone, + PartialEq, + OperateModeGate, + OperateSingleModeGate, + roqoqo_derive::Operate, + roqoqo_derive::OperateSingleQubit, + roqoqo_derive::InvolveQubits, + roqoqo_derive::Substitute, + roqoqo_derive::OperateSingleMode, + roqoqo_derive::InvolveModes, + roqoqo_derive::SubstituteModes, +)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "json_schema", derive(schemars::JsonSchema))] +pub struct SingleExcitationLoad { + /// The qubit involved. + qubit: usize, + /// The bosonic mode involved. + mode: usize, +} + +#[allow(non_upper_case_globals)] +const TAGS_SingleExcitationLoad: &[&str; 6] = &[ + "Operation", + "GateOperation", + "ModeGateOperation", + "SingleModeGateOperation", + "SingleQubitGateOperation", + "SingleExcitationLoad", +]; + +impl ImplementedIn1point10 for SingleExcitationLoad {} + +impl SupportedVersion for SingleExcitationLoad { + fn minimum_supported_roqoqo_version(&self) -> (u32, u32, u32) { + (1, 10, 0) + } +} + +/// Controlled-Z operation between a qubit and a bosonic mode, where the two-dimensional subspace of +/// the bosonic mode spanned by the occupation number states |0⟩_B and |1⟩_B is considered +/// as the second qubit involved in the CZ operation. +#[derive( + Debug, + Clone, + PartialEq, + OperateModeGate, + OperateSingleModeGate, + roqoqo_derive::Operate, + roqoqo_derive::OperateSingleQubit, + roqoqo_derive::InvolveQubits, + roqoqo_derive::Substitute, + roqoqo_derive::OperateSingleMode, + roqoqo_derive::InvolveModes, + roqoqo_derive::SubstituteModes, +)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "json_schema", derive(schemars::JsonSchema))] +pub struct CZQubitResonator { + /// The qubit involved. + qubit: usize, + /// The bosonic mode involved. + mode: usize, +} + +#[allow(non_upper_case_globals)] +const TAGS_CZQubitResonator: &[&str; 6] = &[ + "Operation", + "GateOperation", + "ModeGateOperation", + "SingleModeGateOperation", + "SingleQubitGateOperation", + "CZQubitResonator", +]; + +impl ImplementedIn1point10 for CZQubitResonator {} + +impl SupportedVersion for CZQubitResonator { + fn minimum_supported_roqoqo_version(&self) -> (u32, u32, u32) { + (1, 10, 0) + } +} diff --git a/roqoqo/tests/integration/operations/spin_boson_operations.rs b/roqoqo/tests/integration/operations/spin_boson_operations.rs index 26682376..8b96d3bd 100644 --- a/roqoqo/tests/integration/operations/spin_boson_operations.rs +++ b/roqoqo/tests/integration/operations/spin_boson_operations.rs @@ -40,11 +40,26 @@ fn inputs() { assert_eq!(op.qubit(), &4_usize); assert_eq!(op.mode(), &0_usize); assert_eq!(op.theta(), &CalculatorFloat::from(1.5)); + + let op = SingleExcitationStore::new(1, 0); + assert_eq!(op.qubit(), &1_usize); + assert_eq!(op.mode(), &0_usize); + + let op = SingleExcitationLoad::new(1, 0); + assert_eq!(op.qubit(), &1_usize); + assert_eq!(op.mode(), &0_usize); + + let op = CZQubitResonator::new(1, 0); + assert_eq!(op.qubit(), &1_usize); + assert_eq!(op.mode(), &0_usize); } #[test_case(Operation::from(QuantumRabi::new(4, 0, 1.5.into())))] #[test_case(Operation::from(LongitudinalCoupling::new(4, 0, 1.5.into())))] #[test_case(Operation::from(JaynesCummings::new(4, 0, 1.5.into())))] +#[test_case(Operation::from(SingleExcitationLoad::new(4, 0)))] +#[test_case(Operation::from(SingleExcitationStore::new(4, 0)))] +#[test_case(Operation::from(CZQubitResonator::new(4, 0)))] fn clone(op: Operation) { assert_eq!(op.clone(), op); } @@ -61,6 +76,18 @@ fn clone(op: Operation) { Operation::from(JaynesCummings::new(4, 0, 1.5.into())), "JaynesCummings(JaynesCummings { qubit: 4, mode: 0, theta: Float(1.5) })" )] +#[test_case( + Operation::from(SingleExcitationLoad::new(4, 0)), + "SingleExcitationLoad(SingleExcitationLoad { qubit: 4, mode: 0 })" +)] +#[test_case( + Operation::from(SingleExcitationStore::new(4, 0)), + "SingleExcitationStore(SingleExcitationStore { qubit: 4, mode: 0 })" +)] +#[test_case( + Operation::from(CZQubitResonator::new(4, 0)), + "CZQubitResonator(CZQubitResonator { qubit: 4, mode: 0 })" +)] fn debug(op: Operation, string: &str) { assert_eq!(format!("{:?}", op), string); } @@ -80,6 +107,21 @@ fn debug(op: Operation, string: &str) { Operation::from(JaynesCummings::new(4, 0, 1.5.into())), Operation::from(JaynesCummings::new(2, 1, 1.0.into())) )] +#[test_case( + Operation::from(SingleExcitationLoad::new(4, 0)), + Operation::from(SingleExcitationLoad::new(4, 0)), + Operation::from(SingleExcitationLoad::new(2, 1)) +)] +#[test_case( + Operation::from(SingleExcitationStore::new(4, 0)), + Operation::from(SingleExcitationStore::new(4, 0)), + Operation::from(SingleExcitationStore::new(2, 1)) +)] +#[test_case( + Operation::from(CZQubitResonator::new(4, 0)), + Operation::from(CZQubitResonator::new(4, 0)), + Operation::from(CZQubitResonator::new(2, 1)) +)] fn partial_eq(op: Operation, op_0: Operation, op_1: Operation) { assert!(op_0 == op); assert!(op == op_0); @@ -105,6 +147,24 @@ fn partial_eq(op: Operation, op_0: Operation, op_1: Operation) { InvolvedClassical::None, InvolvedModes::Set(HashSet::from([0_usize])) )] +#[test_case( + SingleModeOperation::from(SingleExcitationLoad::new(4, 0)), + InvolvedQubits::Set(HashSet::from([4_usize])), + InvolvedClassical::None, + InvolvedModes::Set(HashSet::from([0_usize])) +)] +#[test_case( + SingleModeOperation::from(SingleExcitationStore::new(4, 0)), + InvolvedQubits::Set(HashSet::from([4_usize])), + InvolvedClassical::None, + InvolvedModes::Set(HashSet::from([0_usize])) +)] +#[test_case( + SingleModeOperation::from(CZQubitResonator::new(4, 0)), + InvolvedQubits::Set(HashSet::from([4_usize])), + InvolvedClassical::None, + InvolvedModes::Set(HashSet::from([0_usize])) +)] fn involved_qubits_classical_modes( op: SingleModeOperation, qubits: InvolvedQubits, @@ -128,6 +188,18 @@ fn involved_qubits_classical_modes( SingleModeOperation::from(JaynesCummings::new(1, 0, "test".into())), SingleModeOperation::from(JaynesCummings::new(2, 3, 1.5.into())) )] +#[test_case( + SingleModeOperation::from(SingleExcitationLoad::new(1, 0)), + SingleModeOperation::from(SingleExcitationLoad::new(2, 3)) +)] +#[test_case( + SingleModeOperation::from(SingleExcitationStore::new(1, 0)), + SingleModeOperation::from(SingleExcitationStore::new(2, 3)) +)] +#[test_case( + SingleModeOperation::from(CZQubitResonator::new(1, 0)), + SingleModeOperation::from(CZQubitResonator::new(2, 3)) +)] fn substitute_subsitutemodes(op: SingleModeOperation, op_test: SingleModeOperation) { let mut mapping_test: HashMap = HashMap::new(); mapping_test.insert(0, 3); @@ -151,50 +223,80 @@ fn substitute_subsitutemodes(op: SingleModeOperation, op_test: SingleModeOperati #[test_case( Operation::from(QuantumRabi::new(2, 3, 1.5.into())), - Operation::from(QuantumRabi::new(1, 0, "test".into())), "QuantumRabi" )] #[test_case( Operation::from(LongitudinalCoupling::new(2, 3, 1.5.into())), - Operation::from(LongitudinalCoupling::new(1, 0, "test".into())), "LongitudinalCoupling" )] #[test_case( Operation::from(JaynesCummings::new(2, 3, 1.5.into())), - Operation::from(JaynesCummings::new(1, 0, "test".into())), "JaynesCummings" )] -fn operate_one_mode_one_spin(op: Operation, op_param: Operation, name: &str) { +#[test_case( + Operation::from(SingleExcitationLoad::new(1, 0)), + "SingleExcitationLoad" +)] +#[test_case( + Operation::from(SingleExcitationStore::new(1, 0)), + "SingleExcitationStore" +)] +#[test_case(Operation::from(CZQubitResonator::new(1, 0)), "CZQubitResonator")] +fn operate_tags_hqslang(op: Operation, name: &str) { // (1) Test tags function - let tags: &[&str; 7] = &[ + let tags: &[&str; 6] = &[ "Operation", "GateOperation", "ModeGateOperation", "SingleModeGateOperation", "SingleQubitGateOperation", - "SingleQubitGate", name, ]; assert_eq!(op.tags(), tags); // (2) Test hqslang function assert_eq!(op.hqslang(), String::from(name)); +} - // (3) Test is_parametrized function - assert!(!op.is_parametrized()); +#[test_case( + Operation::from(QuantumRabi::new(1, 0, "test".into())) +)] +#[test_case( + Operation::from(LongitudinalCoupling::new(1, 0, "test".into())) +)] +#[test_case( + Operation::from(JaynesCummings::new(1, 0, "test".into())) +)] +fn is_parametrized(op_param: Operation) { assert!(op_param.is_parametrized()); } +#[test_case(Operation::from(QuantumRabi::new(1, 0, 1.0.into())))] +#[test_case(Operation::from(LongitudinalCoupling::new(1, 0, 1.0.into())))] +#[test_case(Operation::from(JaynesCummings::new(1, 0, 1.0.into())))] +#[test_case(Operation::from(SingleExcitationLoad::new(1, 0)))] +#[test_case(Operation::from(SingleExcitationStore::new(1, 0)))] +#[test_case(Operation::from(CZQubitResonator::new(1, 0)))] +fn is_parametrized_false(op: Operation) { + assert!(!op.is_parametrized()); +} + #[test_case(SingleModeOperation::from(QuantumRabi::new(0, 0, 1.0.into())))] #[test_case(SingleModeOperation::from(LongitudinalCoupling::new(0, 0, 1.0.into())))] #[test_case(SingleModeOperation::from(JaynesCummings::new(0, 0, 1.0.into())))] +#[test_case(SingleModeOperation::from(SingleExcitationLoad::new(1, 0)))] +#[test_case(SingleModeOperation::from(SingleExcitationStore::new(1, 0)))] +#[test_case(SingleModeOperation::from(CZQubitResonator::new(1, 0)))] fn single_mode_op(op: SingleModeOperation) { assert_eq!(op.mode(), &0_usize); } -#[test_case(SingleQubitOperation::from(QuantumRabi::new(0, 0, 1.0.into())))] -#[test_case(SingleQubitOperation::from(LongitudinalCoupling::new(0, 0, 1.0.into())))] -#[test_case(SingleQubitOperation::from(JaynesCummings::new(0, 0, 1.0.into())))] +#[test_case(SingleQubitOperation::from(QuantumRabi::new(0, 1, 1.0.into())))] +#[test_case(SingleQubitOperation::from(LongitudinalCoupling::new(0, 1, 1.0.into())))] +#[test_case(SingleQubitOperation::from(JaynesCummings::new(0, 1, 1.0.into())))] +#[test_case(SingleQubitOperation::from(SingleExcitationLoad::new(0, 1)))] +#[test_case(SingleQubitOperation::from(SingleExcitationStore::new(0, 1)))] +#[test_case(SingleQubitOperation::from(CZQubitResonator::new(0, 1)))] fn single_qubit_op(op: SingleQubitOperation) { assert_eq!(op.qubit(), &0_usize); } @@ -203,7 +305,6 @@ fn single_qubit_op(op: SingleQubitOperation) { #[test] fn quantumrabi_serde() { let op = QuantumRabi::new(0, 0, 1.0.into()); - assert_tokens( &op.clone().readable(), &[ @@ -220,7 +321,6 @@ fn quantumrabi_serde() { Token::StructEnd, ], ); - assert_tokens( &op.compact(), &[ @@ -242,12 +342,10 @@ fn quantumrabi_serde() { ], ); } - #[cfg(feature = "serialize")] #[test] fn longitudinal_coupling_serde() { let op = LongitudinalCoupling::new(0, 0, 1.0.into()); - assert_tokens( &op.clone().readable(), &[ @@ -264,7 +362,6 @@ fn longitudinal_coupling_serde() { Token::StructEnd, ], ); - assert_tokens( &op.compact(), &[ @@ -286,12 +383,10 @@ fn longitudinal_coupling_serde() { ], ); } - #[cfg(feature = "serialize")] #[test] fn jaynes_cummings_serde() { let op = JaynesCummings::new(0, 0, 1.0.into()); - assert_tokens( &op.clone().readable(), &[ @@ -308,7 +403,6 @@ fn jaynes_cummings_serde() { Token::StructEnd, ], ); - assert_tokens( &op.compact(), &[ @@ -331,6 +425,114 @@ fn jaynes_cummings_serde() { ); } +#[cfg(feature = "serialize")] +#[test] +fn single_excitation_load_serde() { + let op = SingleExcitationLoad::new(0, 0); + + assert_tokens( + &op.clone().readable(), + &[ + Token::Struct { + name: "SingleExcitationLoad", + len: 2, + }, + Token::Str("qubit"), + Token::U64(0), + Token::Str("mode"), + Token::U64(0), + Token::StructEnd, + ], + ); + + assert_tokens( + &op.compact(), + &[ + Token::Struct { + name: "SingleExcitationLoad", + len: 2, + }, + Token::Str("qubit"), + Token::U64(0), + Token::Str("mode"), + Token::U64(0), + Token::StructEnd, + ], + ); +} + +#[cfg(feature = "serialize")] +#[test] +fn single_excitation_store_serde() { + let op = SingleExcitationStore::new(0, 0); + + assert_tokens( + &op.clone().readable(), + &[ + Token::Struct { + name: "SingleExcitationStore", + len: 2, + }, + Token::Str("qubit"), + Token::U64(0), + Token::Str("mode"), + Token::U64(0), + Token::StructEnd, + ], + ); + + assert_tokens( + &op.compact(), + &[ + Token::Struct { + name: "SingleExcitationStore", + len: 2, + }, + Token::Str("qubit"), + Token::U64(0), + Token::Str("mode"), + Token::U64(0), + Token::StructEnd, + ], + ); +} + +#[cfg(feature = "serialize")] +#[test] +fn cz_qubit_resonator_serde() { + let op = CZQubitResonator::new(0, 0); + + assert_tokens( + &op.clone().readable(), + &[ + Token::Struct { + name: "CZQubitResonator", + len: 2, + }, + Token::Str("qubit"), + Token::U64(0), + Token::Str("mode"), + Token::U64(0), + Token::StructEnd, + ], + ); + + assert_tokens( + &op.compact(), + &[ + Token::Struct { + name: "CZQubitResonator", + len: 2, + }, + Token::Str("qubit"), + Token::U64(0), + Token::Str("mode"), + Token::U64(0), + Token::StructEnd, + ], + ); +} + #[cfg(feature = "json_schema")] #[test] fn quantumrabi_json_schema() { @@ -393,3 +595,66 @@ fn jaynes_cummings_json_schema() { let validation_result = compiled_schema.validate(&test_value); assert!(validation_result.is_ok()); } + +#[cfg(feature = "json_schema")] +#[test] +fn single_excitation_load_json_schema() { + let def = SingleExcitationLoad::new(0, 0); + // Serialize + let test_json = serde_json::to_string(&def).unwrap(); + let test_value: serde_json::Value = serde_json::from_str(&test_json).unwrap(); + + // Create JSONSchema + let test_schema = schema_for!(SingleExcitationLoad); + let schema = serde_json::to_string(&test_schema).unwrap(); + let schema_value: serde_json::Value = serde_json::from_str(&schema).unwrap(); + let compiled_schema = JSONSchema::options() + .with_draft(Draft::Draft7) + .compile(&schema_value) + .unwrap(); + + let validation_result = compiled_schema.validate(&test_value); + assert!(validation_result.is_ok()); +} + +#[cfg(feature = "json_schema")] +#[test] +fn single_excitation_store_json_schema() { + let def = SingleExcitationStore::new(0, 0); + // Serialize + let test_json = serde_json::to_string(&def).unwrap(); + let test_value: serde_json::Value = serde_json::from_str(&test_json).unwrap(); + + // Create JSONSchema + let test_schema = schema_for!(SingleExcitationStore); + let schema = serde_json::to_string(&test_schema).unwrap(); + let schema_value: serde_json::Value = serde_json::from_str(&schema).unwrap(); + let compiled_schema = JSONSchema::options() + .with_draft(Draft::Draft7) + .compile(&schema_value) + .unwrap(); + + let validation_result = compiled_schema.validate(&test_value); + assert!(validation_result.is_ok()); +} + +#[cfg(feature = "json_schema")] +#[test] +fn cz_qubit_resonator_json_schema() { + let def = CZQubitResonator::new(0, 0); + // Serialize + let test_json = serde_json::to_string(&def).unwrap(); + let test_value: serde_json::Value = serde_json::from_str(&test_json).unwrap(); + + // Create JSONSchema + let test_schema = schema_for!(CZQubitResonator); + let schema = serde_json::to_string(&test_schema).unwrap(); + let schema_value: serde_json::Value = serde_json::from_str(&schema).unwrap(); + let compiled_schema = JSONSchema::options() + .with_draft(Draft::Draft7) + .compile(&schema_value) + .unwrap(); + + let validation_result = compiled_schema.validate(&test_value); + assert!(validation_result.is_ok()); +} diff --git a/roqoqo/tests/integration/operations/supported_version.rs b/roqoqo/tests/integration/operations/supported_version.rs index 9bd37c80..02abcdfb 100644 --- a/roqoqo/tests/integration/operations/supported_version.rs +++ b/roqoqo/tests/integration/operations/supported_version.rs @@ -141,6 +141,9 @@ fn test_version_1_8_0_single_mode_gate(operation: operations::SingleModeGateOper #[test_case(operations::SingleModeGateOperation::from(operations::QuantumRabi::new(1, 0, 1.0.into()));"QuantumRabi")] #[test_case(operations::SingleModeGateOperation::from(operations::LongitudinalCoupling::new(1, 0, 1.0.into()));"LongitudinalCoupling")] #[test_case(operations::SingleModeGateOperation::from(operations::JaynesCummings::new(1, 0, 1.0.into()));"JaynesCummings")] +#[test_case(operations::SingleModeGateOperation::from(operations::SingleExcitationLoad::new(1, 0));"SingleExcitationLoad")] +#[test_case(operations::SingleModeGateOperation::from(operations::SingleExcitationStore::new(1, 0));"SingleExcitationStore")] +#[test_case(operations::SingleModeGateOperation::from(operations::CZQubitResonator::new(1, 0));"CZQubitResonator")] fn test_version_1_10_0_single_mode_gate(operation: operations::SingleModeGateOperation) { assert_eq!(operation.minimum_supported_roqoqo_version(), (1, 10, 0)); let op = operations::Operation::try_from(operation).unwrap();