diff --git a/compiler/qsc_circuit/src/builder.rs b/compiler/qsc_circuit/src/builder.rs index 7d1c318854..bbc69efab1 100644 --- a/compiler/qsc_circuit/src/builder.rs +++ b/compiler/qsc_circuit/src/builder.rs @@ -129,6 +129,11 @@ impl Backend for Builder { self.push_gate(gate("S", [q])); } + fn sx(&mut self, q: usize) { + let q = self.map(q); + self.push_gate(gate("SX", [q])); + } + fn swap(&mut self, q0: usize, q1: usize) { let q0 = self.map(q0); let q1 = self.map(q1); diff --git a/compiler/qsc_circuit/src/circuit_to_qsharp.rs b/compiler/qsc_circuit/src/circuit_to_qsharp.rs index 5d717acc0d..b8c52eadc7 100644 --- a/compiler/qsc_circuit/src/circuit_to_qsharp.rs +++ b/compiler/qsc_circuit/src/circuit_to_qsharp.rs @@ -203,35 +203,8 @@ fn generate_unitary_call( qubits: &FxHashMap, indent: &str, ) -> String { - // "SX" will generate three operations: H, X and H - if unitary.gate == "SX" { - let h_str = operation_call( - &Unitary { - gate: "H".to_string(), - args: vec![], - children: vec![], - targets: unitary.targets.clone(), - controls: unitary.controls.clone(), - is_adjoint: false, - }, - qubits, - ); - let s_str = operation_call( - &Unitary { - gate: "S".to_string(), - args: vec![], - children: vec![], - targets: unitary.targets.clone(), - controls: unitary.controls.clone(), - is_adjoint: unitary.is_adjoint, - }, - qubits, - ); - format!("{indent}{h_str};\n{indent}{s_str};\n{indent}{h_str};\n") - } else { - let operation_str = operation_call(unitary, qubits); - format!("{indent}{operation_str};\n") - } + let operation_str = operation_call(unitary, qubits); + format!("{indent}{operation_str};\n") } fn generate_ket_call(ket: &Ket, qubits: &FxHashMap, indent: &str) -> String { diff --git a/compiler/qsc_circuit/src/circuit_to_qsharp/tests.rs b/compiler/qsc_circuit/src/circuit_to_qsharp/tests.rs index 2adb6e7fd7..9d53ac1334 100644 --- a/compiler/qsc_circuit/src/circuit_to_qsharp/tests.rs +++ b/compiler/qsc_circuit/src/circuit_to_qsharp/tests.rs @@ -611,9 +611,7 @@ fn circuit_with_sqrt_x_gate() { } H(qs[0]); Z(qs[0]); - H(qs[1]); - S(qs[1]); - H(qs[1]); + SX(qs[1]); Z(qs[1]); } @@ -660,9 +658,7 @@ fn circuit_with_ctrl_adj_sqrt_x_gate() { } H(qs[0]); Z(qs[0]); - Controlled H([qs[1]], qs[0]); - Controlled Adjoint S([qs[1]], qs[0]); - Controlled H([qs[1]], qs[0]); + Controlled Adjoint SX([qs[1]], qs[0]); Z(qs[1]); } diff --git a/compiler/qsc_eval/src/backend.rs b/compiler/qsc_eval/src/backend.rs index 62cd9021fe..b328e7fbb7 100644 --- a/compiler/qsc_eval/src/backend.rs +++ b/compiler/qsc_eval/src/backend.rs @@ -65,6 +65,9 @@ pub trait Backend { fn s(&mut self, _q: usize) { unimplemented!("s gate"); } + fn sx(&mut self, _q: usize) { + unimplemented!("sx gate"); + } fn swap(&mut self, _q0: usize, _q1: usize) { unimplemented!("swap gate"); } @@ -295,6 +298,13 @@ impl Backend for SparseSim { self.apply_noise(q); } + fn sx(&mut self, q: usize) { + self.sim.h(q); + self.sim.s(q); + self.sim.h(q); + self.apply_noise(q); + } + fn swap(&mut self, q0: usize, q1: usize) { self.sim.swap_qubit_ids(q0, q1); self.apply_noise(q0); @@ -593,6 +603,11 @@ where self.main.s(q); } + fn sx(&mut self, q: usize) { + self.chained.sx(q); + self.main.sx(q); + } + fn swap(&mut self, q0: usize, q1: usize) { self.chained.swap(q0, q1); self.main.swap(q0, q1); diff --git a/compiler/qsc_eval/src/intrinsic.rs b/compiler/qsc_eval/src/intrinsic.rs index df910c5085..856ca01ac3 100644 --- a/compiler/qsc_eval/src/intrinsic.rs +++ b/compiler/qsc_eval/src/intrinsic.rs @@ -183,6 +183,7 @@ pub(crate) fn call( "__quantum__qis__h__body" => one_qubit_gate(|q| sim.h(q), arg, arg_span), "__quantum__qis__s__body" => one_qubit_gate(|q| sim.s(q), arg, arg_span), "__quantum__qis__s__adj" => one_qubit_gate(|q| sim.sadj(q), arg, arg_span), + "__quantum__qis__sx__body" => one_qubit_gate(|q| sim.sx(q), arg, arg_span), "__quantum__qis__t__body" => one_qubit_gate(|q| sim.t(q), arg, arg_span), "__quantum__qis__t__adj" => one_qubit_gate(|q| sim.tadj(q), arg, arg_span), "__quantum__qis__x__body" => one_qubit_gate(|q| sim.x(q), arg, arg_span), diff --git a/compiler/qsc_eval/src/intrinsic/tests.rs b/compiler/qsc_eval/src/intrinsic/tests.rs index 428c9a411c..c8695f21f7 100644 --- a/compiler/qsc_eval/src/intrinsic/tests.rs +++ b/compiler/qsc_eval/src/intrinsic/tests.rs @@ -93,6 +93,12 @@ impl Backend for CustomSim { self.sim.s(q); } + fn sx(&mut self, q: usize) { + self.sim.h(q); + self.sim.s(q); + self.sim.h(q); + } + fn swap(&mut self, q0: usize, q1: usize) { self.sim.swap(q0, q1); } @@ -1228,6 +1234,37 @@ fn sadj_qubit_already_released_fails() { ); } +#[test] +fn sx() { + check_intrinsic_result( + "", + indoc! {r#"{ + use q1 = Qubit(); + QIR.Intrinsic.__quantum__qis__sx__body(q1); + if Microsoft.Quantum.Diagnostics.CheckZero(q1) { + fail "Qubit should be in one state."; + } + QIR.Intrinsic.__quantum__qis__sx__body(q1); + QIR.Intrinsic.__quantum__qis__sx__body(q1); + QIR.Intrinsic.__quantum__qis__sx__body(q1); + Microsoft.Quantum.Diagnostics.CheckZero(q1) + }"#}, + &expect!["true"], + ); +} + +#[test] +fn sx_qubit_already_released_fails() { + check_intrinsic_result( + "", + indoc! {"{ + let q = { use q = Qubit(); q }; + QIR.Intrinsic.__quantum__qis__sx__body(q) + }"}, + &expect!["qubit used after release"], + ); +} + #[test] fn t() { check_intrinsic_result( diff --git a/compiler/qsc_partial_eval/src/tests/intrinsics.rs b/compiler/qsc_partial_eval/src/tests/intrinsics.rs index 4a2379682f..2884720f65 100644 --- a/compiler/qsc_partial_eval/src/tests/intrinsics.rs +++ b/compiler/qsc_partial_eval/src/tests/intrinsics.rs @@ -191,6 +191,26 @@ fn call_to_intrinsic_adjoint_s_adds_callable_and_generates_instruction() { ); } +#[test] +fn call_to_intrinsic_sx_adds_callable_and_generates_instruction() { + check_call_to_single_qubit_instrinsic_adds_callable_and_generates_instruction( + "__quantum__qis__sx__body", + &expect![[r#" + Callable: + name: __quantum__qis__sx__body + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + &expect![[r#" + Block: + Call id(1), args( Qubit(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], + ); +} + #[test] fn call_to_intrinsic_t_adds_callable_and_generates_instruction() { check_call_to_single_qubit_instrinsic_adds_callable_and_generates_instruction( diff --git a/compiler/qsc_qasm/src/stdlib/QasmStd/src/QasmStd/Intrinsic.qs b/compiler/qsc_qasm/src/stdlib/QasmStd/src/QasmStd/Intrinsic.qs index 54a3977419..a97d1fe95a 100644 --- a/compiler/qsc_qasm/src/stdlib/QasmStd/src/QasmStd/Intrinsic.qs +++ b/compiler/qsc_qasm/src/stdlib/QasmStd/src/QasmStd/Intrinsic.qs @@ -113,8 +113,7 @@ operation tdg(qubit : Qubit) : Unit is Adj + Ctl { } operation sx(qubit : Qubit) : Unit is Adj + Ctl { - Rx(Std.Math.PI() / 2., qubit); - Adjoint R(PauliI, Std.Math.PI() / 2., qubit); + SX(qubit); } operation rx(theta : __Angle__, qubit : Qubit) : Unit is Adj + Ctl { diff --git a/compiler/qsc_rca/src/tests/intrinsics.rs b/compiler/qsc_rca/src/tests/intrinsics.rs index 54976da680..df590c9eab 100644 --- a/compiler/qsc_rca/src/tests/intrinsics.rs +++ b/compiler/qsc_rca/src/tests/intrinsics.rs @@ -910,6 +910,31 @@ fn check_rca_for_quantum_qis_s_adj() { ); } +#[test] +fn check_rca_for_quantum_qis_sx_body() { + let compilation_context = CompilationContext::default(); + check_callable_compute_properties( + &compilation_context.fir_store, + compilation_context.get_compute_properties(), + "__quantum__qis__sx__body", + &expect![ + r#" + Callable: CallableComputeProperties: + body: ApplicationsGeneratorSet: + inherent: Quantum: QuantumProperties: + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Element(Static) + dynamic_param_applications: + [0]: [Parameter Type Element] Quantum: QuantumProperties: + runtime_features: RuntimeFeatureFlags(UseOfDynamicQubit) + value_kind: Element(Static) + adj: + ctl: + ctl-adj: "# + ], + ); +} + #[test] fn check_rca_for_quantum_qis_t_body() { let compilation_context = CompilationContext::default(); diff --git a/library/src/tests/intrinsic.rs b/library/src/tests/intrinsic.rs index b8abc2f93e..e590ebd281 100644 --- a/library/src/tests/intrinsic.rs +++ b/library/src/tests/intrinsic.rs @@ -1256,6 +1256,674 @@ fn test_base_mcsadj_4_control() { } } +#[test] +fn test_mcsx_1_control() { + let mut sim = SparseSim::default(); + let dump = test_expression_with_lib_and_profile_and_sim( + indoc! {"{ + let qs = QIR.Runtime.AllocateQubitArray(2); + let aux = QIR.Runtime.AllocateQubitArray(2); + for i in 0..1 { + H(aux[i]); + CNOT(aux[i], qs[i]); + } + Controlled SX(qs[0..0], qs[1]); + Std.Diagnostics.DumpMachine(); + }"}, + "", + Profile::Unrestricted, + &mut sim, + &Value::unit(), + ); + expect![[r#" + STATE: + |0000⟩: 0.5000+0.0000𝑖 + |0101⟩: 0.5000+0.0000𝑖 + |1010⟩: 0.2500+0.2500𝑖 + |1011⟩: 0.2500−0.2500𝑖 + |1110⟩: 0.2500−0.2500𝑖 + |1111⟩: 0.2500+0.2500𝑖 + "#]] + .assert_eq(&dump); + + sim.sim.mch(&[0], 1); + sim.sim.mcsadj(&[0], 1); + sim.sim.mch(&[0], 1); + for i in 0..2 { + sim.sim.mcx(&[i + 2], i); + sim.sim.h(i + 2); + assert!(sim.sim.qubit_is_zero(i + 2), "qubit {} is not zero", i + 2); + assert!(sim.sim.qubit_is_zero(i), "qubit {i} is not zero"); + } +} + +#[test] +fn test_mcsx_2_control() { + let mut sim = SparseSim::default(); + let dump = test_expression_with_lib_and_profile_and_sim( + indoc! {"{ + let qs = QIR.Runtime.AllocateQubitArray(3); + let aux = QIR.Runtime.AllocateQubitArray(3); + for i in 0..2 { + H(aux[i]); + CNOT(aux[i], qs[i]); + } + Controlled SX(qs[0..1], qs[2]); + Std.Diagnostics.DumpMachine(); + }"}, + "", + Profile::Unrestricted, + &mut sim, + &Value::unit(), + ); + expect![[r#" + STATE: + |000000⟩: 0.3536+0.0000𝑖 + |001001⟩: 0.3536+0.0000𝑖 + |010010⟩: 0.3536+0.0000𝑖 + |011011⟩: 0.3536+0.0000𝑖 + |100100⟩: 0.3536+0.0000𝑖 + |101101⟩: 0.3536+0.0000𝑖 + |110110⟩: 0.1768+0.1768𝑖 + |110111⟩: 0.1768−0.1768𝑖 + |111110⟩: 0.1768−0.1768𝑖 + |111111⟩: 0.1768+0.1768𝑖 + "#]] + .assert_eq(&dump); + + sim.sim.mch(&[0, 1], 2); + sim.sim.mcsadj(&[0, 1], 2); + sim.sim.mch(&[0, 1], 2); + for i in 0..3 { + sim.sim.mcx(&[i + 3], i); + sim.sim.h(i + 3); + assert!(sim.sim.qubit_is_zero(i + 3), "qubit {} is not zero", i + 3); + assert!(sim.sim.qubit_is_zero(i), "qubit {i} is not zero"); + } +} + +#[test] +fn test_unrestricted_mcsx_3_control() { + let mut sim = SparseSim::default(); + let dump = test_expression_with_lib_and_profile_and_sim( + indoc! {"{ + let qs = QIR.Runtime.AllocateQubitArray(4); + let aux = QIR.Runtime.AllocateQubitArray(4); + for i in 0..3 { + H(aux[i]); + CNOT(aux[i], qs[i]); + } + Controlled SX(qs[0..2], qs[3]); + Std.Diagnostics.DumpMachine(); + }"}, + "", + Profile::Unrestricted, + &mut sim, + &Value::unit(), + ); + expect![[r#" + STATE: + |00000000⟩: 0.2500+0.0000𝑖 + |00010001⟩: 0.2500+0.0000𝑖 + |00100010⟩: 0.2500+0.0000𝑖 + |00110011⟩: 0.2500+0.0000𝑖 + |01000100⟩: 0.2500+0.0000𝑖 + |01010101⟩: 0.2500+0.0000𝑖 + |01100110⟩: 0.2500+0.0000𝑖 + |01110111⟩: 0.2500+0.0000𝑖 + |10001000⟩: 0.2500+0.0000𝑖 + |10011001⟩: 0.2500+0.0000𝑖 + |10101010⟩: 0.2500+0.0000𝑖 + |10111011⟩: 0.2500+0.0000𝑖 + |11001100⟩: 0.2500+0.0000𝑖 + |11011101⟩: 0.2500+0.0000𝑖 + |11101110⟩: 0.1250+0.1250𝑖 + |11101111⟩: 0.1250−0.1250𝑖 + |11111110⟩: 0.1250−0.1250𝑖 + |11111111⟩: 0.1250+0.1250𝑖 + "#]] + .assert_eq(&dump); + + sim.sim.mch(&[0, 1, 2], 3); + sim.sim.mcsadj(&[0, 1, 2], 3); + sim.sim.mch(&[0, 1, 2], 3); + for i in 0..4 { + sim.sim.mcx(&[i + 4], i); + sim.sim.h(i + 4); + assert!(sim.sim.qubit_is_zero(i + 4), "qubit {} is not zero", i + 4); + assert!(sim.sim.qubit_is_zero(i), "qubit {i} is not zero"); + } +} + +#[test] +fn test_base_mcsx_3_control() { + let mut sim = SparseSim::default(); + let dump = test_expression_with_lib_and_profile_and_sim( + indoc! {"{ + let qs = QIR.Runtime.AllocateQubitArray(4); + let aux = QIR.Runtime.AllocateQubitArray(4); + for i in 0..3 { + H(aux[i]); + CNOT(aux[i], qs[i]); + } + Controlled SX(qs[0..2], qs[3]); + Std.Diagnostics.DumpMachine(); + let result : Result[] = []; + result + }"}, + "", + Profile::Base, + &mut sim, + &Value::Array(Vec::new().into()), + ); + expect![[r#" + STATE: + |00000000⟩: 0.2500+0.0000𝑖 + |00010001⟩: 0.2500+0.0000𝑖 + |00100010⟩: 0.2500+0.0000𝑖 + |00110011⟩: 0.2500+0.0000𝑖 + |01000100⟩: 0.2500+0.0000𝑖 + |01010101⟩: 0.2500+0.0000𝑖 + |01100110⟩: 0.2500+0.0000𝑖 + |01110111⟩: 0.2500+0.0000𝑖 + |10001000⟩: 0.2500+0.0000𝑖 + |10011001⟩: 0.2500+0.0000𝑖 + |10101010⟩: 0.2500+0.0000𝑖 + |10111011⟩: 0.2500+0.0000𝑖 + |11001100⟩: 0.2500+0.0000𝑖 + |11011101⟩: 0.2500+0.0000𝑖 + |11101110⟩: 0.1250+0.1250𝑖 + |11101111⟩: 0.1250−0.1250𝑖 + |11111110⟩: 0.1250−0.1250𝑖 + |11111111⟩: 0.1250+0.1250𝑖 + "#]] + .assert_eq(&dump); + + sim.sim.mch(&[0, 1, 2], 3); + sim.sim.mcsadj(&[0, 1, 2], 3); + sim.sim.mch(&[0, 1, 2], 3); + for i in 0..4 { + sim.sim.mcx(&[i + 4], i); + sim.sim.h(i + 4); + assert!(sim.sim.qubit_is_zero(i + 4), "qubit {} is not zero", i + 4); + assert!(sim.sim.qubit_is_zero(i), "qubit {i} is not zero"); + } +} + +#[test] +fn test_unrestricted_mcsx_4_control() { + let mut sim = SparseSim::default(); + let dump = test_expression_with_lib_and_profile_and_sim( + indoc! {"{ + let qs = QIR.Runtime.AllocateQubitArray(5); + let aux = QIR.Runtime.AllocateQubitArray(5); + for i in 0..4 { + H(aux[i]); + CNOT(aux[i], qs[i]); + } + Controlled SX(qs[0..3], qs[4]); + Std.Diagnostics.DumpMachine(); + }"}, + "", + Profile::Unrestricted, + &mut sim, + &Value::unit(), + ); + expect![[r#" + STATE: + |0000000000⟩: 0.1768+0.0000𝑖 + |0000100001⟩: 0.1768+0.0000𝑖 + |0001000010⟩: 0.1768+0.0000𝑖 + |0001100011⟩: 0.1768+0.0000𝑖 + |0010000100⟩: 0.1768+0.0000𝑖 + |0010100101⟩: 0.1768+0.0000𝑖 + |0011000110⟩: 0.1768+0.0000𝑖 + |0011100111⟩: 0.1768+0.0000𝑖 + |0100001000⟩: 0.1768+0.0000𝑖 + |0100101001⟩: 0.1768+0.0000𝑖 + |0101001010⟩: 0.1768+0.0000𝑖 + |0101101011⟩: 0.1768+0.0000𝑖 + |0110001100⟩: 0.1768+0.0000𝑖 + |0110101101⟩: 0.1768+0.0000𝑖 + |0111001110⟩: 0.1768+0.0000𝑖 + |0111101111⟩: 0.1768+0.0000𝑖 + |1000010000⟩: 0.1768+0.0000𝑖 + |1000110001⟩: 0.1768+0.0000𝑖 + |1001010010⟩: 0.1768+0.0000𝑖 + |1001110011⟩: 0.1768+0.0000𝑖 + |1010010100⟩: 0.1768+0.0000𝑖 + |1010110101⟩: 0.1768+0.0000𝑖 + |1011010110⟩: 0.1768+0.0000𝑖 + |1011110111⟩: 0.1768+0.0000𝑖 + |1100011000⟩: 0.1768+0.0000𝑖 + |1100111001⟩: 0.1768+0.0000𝑖 + |1101011010⟩: 0.1768+0.0000𝑖 + |1101111011⟩: 0.1768+0.0000𝑖 + |1110011100⟩: 0.1768+0.0000𝑖 + |1110111101⟩: 0.1768+0.0000𝑖 + |1111011110⟩: 0.0884+0.0884𝑖 + |1111011111⟩: 0.0884−0.0884𝑖 + |1111111110⟩: 0.0884−0.0884𝑖 + |1111111111⟩: 0.0884+0.0884𝑖 + "#]] + .assert_eq(&dump); + + sim.sim.mch(&[0, 1, 2, 3], 4); + sim.sim.mcsadj(&[0, 1, 2, 3], 4); + sim.sim.mch(&[0, 1, 2, 3], 4); + for i in 0..5 { + sim.sim.mcx(&[i + 5], i); + sim.sim.h(i + 5); + assert!(sim.sim.qubit_is_zero(i + 5), "qubit {} is not zero", i + 5); + assert!(sim.sim.qubit_is_zero(i), "qubit {i} is not zero"); + } +} + +#[test] +fn test_base_mcsx_4_control() { + let mut sim = SparseSim::default(); + let dump = test_expression_with_lib_and_profile_and_sim( + indoc! {"{ + let qs = QIR.Runtime.AllocateQubitArray(5); + let aux = QIR.Runtime.AllocateQubitArray(5); + for i in 0..4 { + H(aux[i]); + CNOT(aux[i], qs[i]); + } + Controlled SX(qs[0..3], qs[4]); + Std.Diagnostics.DumpMachine(); + let result : Result[] = []; + result + }"}, + "", + Profile::Base, + &mut sim, + &Value::Array(Vec::new().into()), + ); + expect![[r#" + STATE: + |0000000000⟩: 0.1768+0.0000𝑖 + |0000100001⟩: 0.1768+0.0000𝑖 + |0001000010⟩: 0.1768+0.0000𝑖 + |0001100011⟩: 0.1768+0.0000𝑖 + |0010000100⟩: 0.1768+0.0000𝑖 + |0010100101⟩: 0.1768+0.0000𝑖 + |0011000110⟩: 0.1768+0.0000𝑖 + |0011100111⟩: 0.1768+0.0000𝑖 + |0100001000⟩: 0.1768+0.0000𝑖 + |0100101001⟩: 0.1768+0.0000𝑖 + |0101001010⟩: 0.1768+0.0000𝑖 + |0101101011⟩: 0.1768+0.0000𝑖 + |0110001100⟩: 0.1768+0.0000𝑖 + |0110101101⟩: 0.1768+0.0000𝑖 + |0111001110⟩: 0.1768+0.0000𝑖 + |0111101111⟩: 0.1768+0.0000𝑖 + |1000010000⟩: 0.1768+0.0000𝑖 + |1000110001⟩: 0.1768+0.0000𝑖 + |1001010010⟩: 0.1768+0.0000𝑖 + |1001110011⟩: 0.1768+0.0000𝑖 + |1010010100⟩: 0.1768+0.0000𝑖 + |1010110101⟩: 0.1768+0.0000𝑖 + |1011010110⟩: 0.1768+0.0000𝑖 + |1011110111⟩: 0.1768+0.0000𝑖 + |1100011000⟩: 0.1768+0.0000𝑖 + |1100111001⟩: 0.1768+0.0000𝑖 + |1101011010⟩: 0.1768+0.0000𝑖 + |1101111011⟩: 0.1768+0.0000𝑖 + |1110011100⟩: 0.1768+0.0000𝑖 + |1110111101⟩: 0.1768+0.0000𝑖 + |1111011110⟩: 0.0884+0.0884𝑖 + |1111011111⟩: 0.0884−0.0884𝑖 + |1111111110⟩: 0.0884−0.0884𝑖 + |1111111111⟩: 0.0884+0.0884𝑖 + "#]] + .assert_eq(&dump); + + sim.sim.mch(&[0, 1, 2, 3], 4); + sim.sim.mcsadj(&[0, 1, 2, 3], 4); + sim.sim.mch(&[0, 1, 2, 3], 4); + for i in 0..5 { + sim.sim.mcx(&[i + 5], i); + sim.sim.h(i + 5); + assert!(sim.sim.qubit_is_zero(i + 5), "qubit {} is not zero", i + 5); + assert!(sim.sim.qubit_is_zero(i), "qubit {i} is not zero"); + } +} + +#[test] +fn test_mcsxadj_1_control() { + let mut sim = SparseSim::default(); + let dump = test_expression_with_lib_and_profile_and_sim( + indoc! {"{ + let qs = QIR.Runtime.AllocateQubitArray(2); + let aux = QIR.Runtime.AllocateQubitArray(2); + for i in 0..1 { + H(aux[i]); + CNOT(aux[i], qs[i]); + } + Controlled Adjoint SX(qs[0..0], qs[1]); + Std.Diagnostics.DumpMachine(); + }"}, + "", + Profile::Unrestricted, + &mut sim, + &Value::unit(), + ); + expect![[r#" + STATE: + |0000⟩: 0.5000+0.0000𝑖 + |0101⟩: 0.5000+0.0000𝑖 + |1010⟩: 0.2500−0.2500𝑖 + |1011⟩: 0.2500+0.2500𝑖 + |1110⟩: 0.2500+0.2500𝑖 + |1111⟩: 0.2500−0.2500𝑖 + "#]] + .assert_eq(&dump); + + sim.sim.mch(&[0], 1); + sim.sim.mcs(&[0], 1); + sim.sim.mch(&[0], 1); + for i in 0..2 { + sim.sim.mcx(&[i + 2], i); + sim.sim.h(i + 2); + assert!(sim.sim.qubit_is_zero(i + 2), "qubit {} is not zero", i + 2); + assert!(sim.sim.qubit_is_zero(i), "qubit {i} is not zero"); + } +} + +#[test] +fn test_mcsxadj_2_control() { + let mut sim = SparseSim::default(); + let dump = test_expression_with_lib_and_profile_and_sim( + indoc! {"{ + let qs = QIR.Runtime.AllocateQubitArray(3); + let aux = QIR.Runtime.AllocateQubitArray(3); + for i in 0..2 { + H(aux[i]); + CNOT(aux[i], qs[i]); + } + Controlled Adjoint SX(qs[0..1], qs[2]); + Std.Diagnostics.DumpMachine(); + }"}, + "", + Profile::Unrestricted, + &mut sim, + &Value::unit(), + ); + expect![[r#" + STATE: + |000000⟩: 0.3536+0.0000𝑖 + |001001⟩: 0.3536+0.0000𝑖 + |010010⟩: 0.3536+0.0000𝑖 + |011011⟩: 0.3536+0.0000𝑖 + |100100⟩: 0.3536+0.0000𝑖 + |101101⟩: 0.3536+0.0000𝑖 + |110110⟩: 0.1768−0.1768𝑖 + |110111⟩: 0.1768+0.1768𝑖 + |111110⟩: 0.1768+0.1768𝑖 + |111111⟩: 0.1768−0.1768𝑖 + "#]] + .assert_eq(&dump); + + sim.sim.mch(&[0, 1], 2); + sim.sim.mcs(&[0, 1], 2); + sim.sim.mch(&[0, 1], 2); + for i in 0..3 { + sim.sim.mcx(&[i + 3], i); + sim.sim.h(i + 3); + assert!(sim.sim.qubit_is_zero(i + 3), "qubit {} is not zero", i + 3); + assert!(sim.sim.qubit_is_zero(i), "qubit {i} is not zero"); + } +} + +#[test] +fn test_unrestricted_mcsxadj_3_control() { + let mut sim = SparseSim::default(); + let dump = test_expression_with_lib_and_profile_and_sim( + indoc! {"{ + let qs = QIR.Runtime.AllocateQubitArray(4); + let aux = QIR.Runtime.AllocateQubitArray(4); + for i in 0..3 { + H(aux[i]); + CNOT(aux[i], qs[i]); + } + Controlled Adjoint SX(qs[0..2], qs[3]); + Std.Diagnostics.DumpMachine(); + }"}, + "", + Profile::Unrestricted, + &mut sim, + &Value::unit(), + ); + expect![[r#" + STATE: + |00000000⟩: 0.2500+0.0000𝑖 + |00010001⟩: 0.2500+0.0000𝑖 + |00100010⟩: 0.2500+0.0000𝑖 + |00110011⟩: 0.2500+0.0000𝑖 + |01000100⟩: 0.2500+0.0000𝑖 + |01010101⟩: 0.2500+0.0000𝑖 + |01100110⟩: 0.2500+0.0000𝑖 + |01110111⟩: 0.2500+0.0000𝑖 + |10001000⟩: 0.2500+0.0000𝑖 + |10011001⟩: 0.2500+0.0000𝑖 + |10101010⟩: 0.2500+0.0000𝑖 + |10111011⟩: 0.2500+0.0000𝑖 + |11001100⟩: 0.2500+0.0000𝑖 + |11011101⟩: 0.2500+0.0000𝑖 + |11101110⟩: 0.1250−0.1250𝑖 + |11101111⟩: 0.1250+0.1250𝑖 + |11111110⟩: 0.1250+0.1250𝑖 + |11111111⟩: 0.1250−0.1250𝑖 + "#]] + .assert_eq(&dump); + + sim.sim.mch(&[0, 1, 2], 3); + sim.sim.mcs(&[0, 1, 2], 3); + sim.sim.mch(&[0, 1, 2], 3); + for i in 0..4 { + sim.sim.mcx(&[i + 4], i); + sim.sim.h(i + 4); + assert!(sim.sim.qubit_is_zero(i + 4), "qubit {} is not zero", i + 4); + assert!(sim.sim.qubit_is_zero(i), "qubit {i} is not zero"); + } +} + +#[test] +fn test_base_mcsxadj_3_control() { + let mut sim = SparseSim::default(); + let dump = test_expression_with_lib_and_profile_and_sim( + indoc! {"{ + let qs = QIR.Runtime.AllocateQubitArray(4); + let aux = QIR.Runtime.AllocateQubitArray(4); + for i in 0..3 { + H(aux[i]); + CNOT(aux[i], qs[i]); + } + Controlled Adjoint SX(qs[0..2], qs[3]); + Std.Diagnostics.DumpMachine(); + let result : Result[] = []; + result + }"}, + "", + Profile::Base, + &mut sim, + &Value::Array(Vec::new().into()), + ); + expect![[r#" + STATE: + |00000000⟩: 0.2500+0.0000𝑖 + |00010001⟩: 0.2500+0.0000𝑖 + |00100010⟩: 0.2500+0.0000𝑖 + |00110011⟩: 0.2500+0.0000𝑖 + |01000100⟩: 0.2500+0.0000𝑖 + |01010101⟩: 0.2500+0.0000𝑖 + |01100110⟩: 0.2500+0.0000𝑖 + |01110111⟩: 0.2500+0.0000𝑖 + |10001000⟩: 0.2500+0.0000𝑖 + |10011001⟩: 0.2500+0.0000𝑖 + |10101010⟩: 0.2500+0.0000𝑖 + |10111011⟩: 0.2500+0.0000𝑖 + |11001100⟩: 0.2500+0.0000𝑖 + |11011101⟩: 0.2500+0.0000𝑖 + |11101110⟩: 0.1250−0.1250𝑖 + |11101111⟩: 0.1250+0.1250𝑖 + |11111110⟩: 0.1250+0.1250𝑖 + |11111111⟩: 0.1250−0.1250𝑖 + "#]] + .assert_eq(&dump); + + sim.sim.mch(&[0, 1, 2], 3); + sim.sim.mcs(&[0, 1, 2], 3); + sim.sim.mch(&[0, 1, 2], 3); + for i in 0..4 { + sim.sim.mcx(&[i + 4], i); + sim.sim.h(i + 4); + assert!(sim.sim.qubit_is_zero(i + 4), "qubit {} is not zero", i + 4); + assert!(sim.sim.qubit_is_zero(i), "qubit {i} is not zero"); + } +} + +#[test] +fn test_unrestricted_mcsxadj_4_control() { + let mut sim = SparseSim::default(); + let dump = test_expression_with_lib_and_profile_and_sim( + indoc! {"{ + let qs = QIR.Runtime.AllocateQubitArray(5); + let aux = QIR.Runtime.AllocateQubitArray(5); + for i in 0..4 { + H(aux[i]); + CNOT(aux[i], qs[i]); + } + Controlled Adjoint SX(qs[0..3], qs[4]); + Std.Diagnostics.DumpMachine(); + }"}, + "", + Profile::Unrestricted, + &mut sim, + &Value::unit(), + ); + expect![[r#" + STATE: + |0000000000⟩: 0.1768+0.0000𝑖 + |0000100001⟩: 0.1768+0.0000𝑖 + |0001000010⟩: 0.1768+0.0000𝑖 + |0001100011⟩: 0.1768+0.0000𝑖 + |0010000100⟩: 0.1768+0.0000𝑖 + |0010100101⟩: 0.1768+0.0000𝑖 + |0011000110⟩: 0.1768+0.0000𝑖 + |0011100111⟩: 0.1768+0.0000𝑖 + |0100001000⟩: 0.1768+0.0000𝑖 + |0100101001⟩: 0.1768+0.0000𝑖 + |0101001010⟩: 0.1768+0.0000𝑖 + |0101101011⟩: 0.1768+0.0000𝑖 + |0110001100⟩: 0.1768+0.0000𝑖 + |0110101101⟩: 0.1768+0.0000𝑖 + |0111001110⟩: 0.1768+0.0000𝑖 + |0111101111⟩: 0.1768+0.0000𝑖 + |1000010000⟩: 0.1768+0.0000𝑖 + |1000110001⟩: 0.1768+0.0000𝑖 + |1001010010⟩: 0.1768+0.0000𝑖 + |1001110011⟩: 0.1768+0.0000𝑖 + |1010010100⟩: 0.1768+0.0000𝑖 + |1010110101⟩: 0.1768+0.0000𝑖 + |1011010110⟩: 0.1768+0.0000𝑖 + |1011110111⟩: 0.1768+0.0000𝑖 + |1100011000⟩: 0.1768+0.0000𝑖 + |1100111001⟩: 0.1768+0.0000𝑖 + |1101011010⟩: 0.1768+0.0000𝑖 + |1101111011⟩: 0.1768+0.0000𝑖 + |1110011100⟩: 0.1768+0.0000𝑖 + |1110111101⟩: 0.1768+0.0000𝑖 + |1111011110⟩: 0.0884−0.0884𝑖 + |1111011111⟩: 0.0884+0.0884𝑖 + |1111111110⟩: 0.0884+0.0884𝑖 + |1111111111⟩: 0.0884−0.0884𝑖 + "#]] + .assert_eq(&dump); + + sim.sim.mch(&[0, 1, 2, 3], 4); + sim.sim.mcs(&[0, 1, 2, 3], 4); + sim.sim.mch(&[0, 1, 2, 3], 4); + for i in 0..5 { + sim.sim.mcx(&[i + 5], i); + sim.sim.h(i + 5); + assert!(sim.sim.qubit_is_zero(i + 5), "qubit {} is not zero", i + 5); + assert!(sim.sim.qubit_is_zero(i), "qubit {i} is not zero"); + } +} + +#[test] +fn test_base_mcsxadj_4_control() { + let mut sim = SparseSim::default(); + let dump = test_expression_with_lib_and_profile_and_sim( + indoc! {"{ + let qs = QIR.Runtime.AllocateQubitArray(5); + let aux = QIR.Runtime.AllocateQubitArray(5); + for i in 0..4 { + H(aux[i]); + CNOT(aux[i], qs[i]); + } + Controlled Adjoint SX(qs[0..3], qs[4]); + Std.Diagnostics.DumpMachine(); + let result : Result[] = []; + result + }"}, + "", + Profile::Base, + &mut sim, + &Value::Array(Vec::new().into()), + ); + expect![[r#" + STATE: + |0000000000⟩: 0.1768+0.0000𝑖 + |0000100001⟩: 0.1768+0.0000𝑖 + |0001000010⟩: 0.1768+0.0000𝑖 + |0001100011⟩: 0.1768+0.0000𝑖 + |0010000100⟩: 0.1768+0.0000𝑖 + |0010100101⟩: 0.1768+0.0000𝑖 + |0011000110⟩: 0.1768+0.0000𝑖 + |0011100111⟩: 0.1768+0.0000𝑖 + |0100001000⟩: 0.1768+0.0000𝑖 + |0100101001⟩: 0.1768+0.0000𝑖 + |0101001010⟩: 0.1768+0.0000𝑖 + |0101101011⟩: 0.1768+0.0000𝑖 + |0110001100⟩: 0.1768+0.0000𝑖 + |0110101101⟩: 0.1768+0.0000𝑖 + |0111001110⟩: 0.1768+0.0000𝑖 + |0111101111⟩: 0.1768+0.0000𝑖 + |1000010000⟩: 0.1768+0.0000𝑖 + |1000110001⟩: 0.1768+0.0000𝑖 + |1001010010⟩: 0.1768+0.0000𝑖 + |1001110011⟩: 0.1768+0.0000𝑖 + |1010010100⟩: 0.1768+0.0000𝑖 + |1010110101⟩: 0.1768+0.0000𝑖 + |1011010110⟩: 0.1768+0.0000𝑖 + |1011110111⟩: 0.1768+0.0000𝑖 + |1100011000⟩: 0.1768+0.0000𝑖 + |1100111001⟩: 0.1768+0.0000𝑖 + |1101011010⟩: 0.1768+0.0000𝑖 + |1101111011⟩: 0.1768+0.0000𝑖 + |1110011100⟩: 0.1768+0.0000𝑖 + |1110111101⟩: 0.1768+0.0000𝑖 + |1111011110⟩: 0.0884−0.0884𝑖 + |1111011111⟩: 0.0884+0.0884𝑖 + |1111111110⟩: 0.0884+0.0884𝑖 + |1111111111⟩: 0.0884−0.0884𝑖 + "#]] + .assert_eq(&dump); + + sim.sim.mch(&[0, 1, 2, 3], 4); + sim.sim.mcs(&[0, 1, 2, 3], 4); + sim.sim.mch(&[0, 1, 2, 3], 4); + for i in 0..5 { + sim.sim.mcx(&[i + 5], i); + sim.sim.h(i + 5); + assert!(sim.sim.qubit_is_zero(i + 5), "qubit {} is not zero", i + 5); + assert!(sim.sim.qubit_is_zero(i), "qubit {i} is not zero"); + } +} + #[test] fn test_mct_1_control() { let mut sim = SparseSim::default(); diff --git a/library/std/src/QIR/Intrinsic.qs b/library/std/src/QIR/Intrinsic.qs index d2f0c74209..6c703e853f 100644 --- a/library/std/src/QIR/Intrinsic.qs +++ b/library/std/src/QIR/Intrinsic.qs @@ -61,6 +61,10 @@ operation __quantum__qis__s__adj(target : Qubit) : Unit { body intrinsic; } +operation __quantum__qis__sx__body(target : Qubit) : Unit { + body intrinsic; +} + operation __quantum__qis__t__body(target : Qubit) : Unit { body intrinsic; } @@ -115,6 +119,7 @@ export __quantum__qis__h__body, __quantum__qis__s__body, __quantum__qis__s__adj, + __quantum__qis__sx__body, __quantum__qis__t__body, __quantum__qis__t__adj, __quantum__qis__x__body, diff --git a/library/std/src/Std/Intrinsic.qs b/library/std/src/Std/Intrinsic.qs index 44a5dd56b7..bac1cfd268 100644 --- a/library/std/src/Std/Intrinsic.qs +++ b/library/std/src/Std/Intrinsic.qs @@ -827,6 +827,93 @@ operation S(qubit : Qubit) : Unit is Adj + Ctl { } } +/// # Summary +/// Applies the square root of X gate to a single qubit. +/// +/// # Input +/// ## qubit +/// Qubit to which the gate should be applied. +/// +/// # Remarks +/// $$ +/// \begin{align} +/// SX \mathrel{:=} +/// \begin{bmatrix} +/// \frac{1}{2} + \frac{i}{2} & \frac{1}{2} - \frac{i}{2} \\\\ +/// \frac{1}{2} - \frac{i}{2} & \frac{1}{2} + \frac{i}{2} +/// \end{bmatrix}. +/// \end{align} +/// $$ +operation SX(qubit : Qubit) : Unit is Adj + Ctl { + body ... { + __quantum__qis__sx__body(qubit); + } + adjoint ... { + __quantum__qis__x__body(qubit); + __quantum__qis__sx__body(qubit); + } + controlled (ctls, ...) { + if Length(ctls) == 0 { + __quantum__qis__x__body(qubit); + __quantum__qis__sx__body(qubit); + } elif Length(ctls) == 1 { + within { + H(qubit); + } apply { + CS(ctls[0], qubit); + } + } elif Length(ctls) == 2 { + within { + H(qubit); + } apply { + Controlled CS([ctls[0]], (ctls[1], qubit)); + } + } else { + use aux = Qubit[Length(ctls) - 2]; + within { + CollectControls(ctls, aux, 1 - (Length(ctls) % 2)); + H(qubit); + } apply { + if Length(ctls) % 2 != 0 { + Controlled CS([ctls[Length(ctls) - 1]], (aux[Length(ctls) - 3], qubit)); + } else { + Controlled CS([aux[Length(ctls) - 3]], (aux[Length(ctls) - 4], qubit)); + } + } + } + } + controlled adjoint (ctls, ...) { + if Length(ctls) == 0 { + __quantum__qis__x__body(qubit); + __quantum__qis__sx__body(qubit); + } elif Length(ctls) == 1 { + within { + H(qubit); + } apply { + Adjoint CS(ctls[0], qubit); + } + } elif Length(ctls) == 2 { + within { + H(qubit); + } apply { + Controlled Adjoint CS([ctls[0]], (ctls[1], qubit)); + } + } else { + use aux = Qubit[Length(ctls) - 2]; + within { + CollectControls(ctls, aux, 1 - (Length(ctls) % 2)); + H(qubit); + } apply { + if Length(ctls) % 2 != 0 { + Controlled Adjoint CS([ctls[Length(ctls) - 1]], (aux[Length(ctls) - 3], qubit)); + } else { + Controlled Adjoint CS([aux[Length(ctls) - 3]], (aux[Length(ctls) - 4], qubit)); + } + } + } + } +} + /// # Summary /// Applies the SWAP gate to a pair of qubits. /// @@ -1127,4 +1214,4 @@ function Message(msg : String) : Unit { body intrinsic; } -export AND, CCNOT, CNOT, Exp, H, I, M, Measure, R, R1, R1Frac, Reset, ResetAll, RFrac, Rx, Rxx, Ry, Ryy, Rz, Rzz, S, SWAP, T, X, Y, Z, ApplyUnitary, Message; +export AND, CCNOT, CNOT, Exp, H, I, M, Measure, R, R1, R1Frac, Reset, ResetAll, RFrac, Rx, Rxx, Ry, Ryy, Rz, Rzz, S, SWAP, SX, T, X, Y, Z, ApplyUnitary, Message; diff --git a/pip/qsharp/interop/qiskit/backends/qirtarget.py b/pip/qsharp/interop/qiskit/backends/qirtarget.py index 51db0b0e33..545c699817 100644 --- a/pip/qsharp/interop/qiskit/backends/qirtarget.py +++ b/pip/qsharp/interop/qiskit/backends/qirtarget.py @@ -36,6 +36,7 @@ HGate, SGate, SdgGate, + SXGate, SwapGate, TGate, TdgGate, @@ -111,6 +112,8 @@ def __init__( self.add_instruction(SGate, name="s") self.add_instruction(SdgGate, name="sdg") + self.add_instruction(SXGate, name="sx") + self.add_instruction(SwapGate, name="swap") self.add_instruction(TGate, name="t") diff --git a/pip/tests-integration/interop_qiskit/test_gateset_qasm.py b/pip/tests-integration/interop_qiskit/test_gateset_qasm.py index 1fbe79006a..458a5e29c5 100644 --- a/pip/tests-integration/interop_qiskit/test_gateset_qasm.py +++ b/pip/tests-integration/interop_qiskit/test_gateset_qasm.py @@ -106,17 +106,12 @@ def test_gate_sdg_transpiles() -> None: @pytest.mark.skipif(not QISKIT_AVAILABLE, reason=SKIP_REASON) -def test_gate_sx_transpiles_to_rx_pi_over_2() -> None: +def test_gate_sx_transpiles_to_sx() -> None: run_transpile_test( - lambda circuit: circuit.sx(1), "rx(pi/2) q[1];", disable_constants=False + lambda circuit: circuit.sx(1), "sx q[1];", disable_constants=False ) -@pytest.mark.skipif(not QISKIT_AVAILABLE, reason=SKIP_REASON) -def test_gate_sx_transpiles_to_rx_pi_over_2_approx() -> None: - run_transpile_test(lambda circuit: circuit.sx(1), "rx(1.5707963267948966) q[1];") - - @pytest.mark.skipif(not QISKIT_AVAILABLE, reason=SKIP_REASON) def test_gate_swap_transpiles() -> None: run_transpile_test(lambda circuit: circuit.swap(1, 0), "swap q[1], q[0];") diff --git a/pip/tests-integration/test_base_qir.py b/pip/tests-integration/test_base_qir.py index 13d4649a02..0c75677021 100644 --- a/pip/tests-integration/test_base_qir.py +++ b/pip/tests-integration/test_base_qir.py @@ -70,6 +70,7 @@ def test_compile_qir_all_gates() -> None: H(q1);\ S(q1);\ Adjoint S(q1);\ + SX(q1); \ T(q1);\ Adjoint T(q1);\ X(q1);\ @@ -84,11 +85,11 @@ def test_compile_qir_all_gates() -> None: qir = operation._repr_qir_() assert isinstance(qir, bytes) module = Module.from_ir(Context(), qir.decode(), "module") - assert len(module.functions) == 23 + assert len(module.functions) == 24 assert module.functions[0].name == "ENTRYPOINT__main" func = module.functions[0] assert len(func.basic_blocks) == 1 - assert len(func.basic_blocks[0].instructions) == 26 + assert len(func.basic_blocks[0].instructions) == 27 def check_call(i: int, name: str, num_args: int) -> None: call = func.basic_blocks[0].instructions[i] @@ -109,18 +110,19 @@ def check_call(i: int, name: str, num_args: int) -> None: check_call(10, "__quantum__qis__h__body", 1) check_call(11, "__quantum__qis__s__body", 1) check_call(12, "__quantum__qis__s__adj", 1) - check_call(13, "__quantum__qis__t__body", 1) - check_call(14, "__quantum__qis__t__adj", 1) - check_call(15, "__quantum__qis__x__body", 1) - check_call(16, "__quantum__qis__y__body", 1) - check_call(17, "__quantum__qis__z__body", 1) - check_call(18, "__quantum__qis__swap__body", 2) - check_call(19, "__quantum__qis__cx__body", 2) - check_call(20, "__quantum__qis__m__body", 2) + check_call(13, "__quantum__qis__sx__body", 1) + check_call(14, "__quantum__qis__t__body", 1) + check_call(15, "__quantum__qis__t__adj", 1) + check_call(16, "__quantum__qis__x__body", 1) + check_call(17, "__quantum__qis__y__body", 1) + check_call(18, "__quantum__qis__z__body", 1) + check_call(19, "__quantum__qis__swap__body", 2) + check_call(20, "__quantum__qis__cx__body", 2) check_call(21, "__quantum__qis__m__body", 2) - check_call(22, "__quantum__rt__tuple_record_output", 2) - check_call(23, "__quantum__rt__result_record_output", 2) + check_call(22, "__quantum__qis__m__body", 2) + check_call(23, "__quantum__rt__tuple_record_output", 2) check_call(24, "__quantum__rt__result_record_output", 2) + check_call(25, "__quantum__rt__result_record_output", 2) assert required_num_qubits(module.functions[0]) == 5 assert required_num_results(module.functions[0]) == 2 diff --git a/resource_estimator/src/counts.rs b/resource_estimator/src/counts.rs index d04cd2021f..0b2737adb1 100644 --- a/resource_estimator/src/counts.rs +++ b/resource_estimator/src/counts.rs @@ -471,6 +471,8 @@ impl Backend for LogicalCounter { fn s(&mut self, _q: usize) {} + fn sx(&mut self, _q: usize) {} + fn swap(&mut self, q0: usize, q1: usize) { self.schedule_two_qubit_clifford(q0, q1); }