diff --git a/compiler/qsc_codegen/src/qir_base.rs b/compiler/qsc_codegen/src/qir_base.rs index 6173fb6570..5a57a5d843 100644 --- a/compiler/qsc_codegen/src/qir_base.rs +++ b/compiler/qsc_codegen/src/qir_base.rs @@ -462,6 +462,12 @@ impl Backend for BaseProfSim { name: &str, arg: Value, ) -> Option> { + // Global phase is a special case that is non-physical, so there is no need to generate + // a call here, just do a shortcut return. + if name == "GlobalPhase" { + return Some(Ok(Value::unit())); + } + match self.write_decl(name, &arg) { Ok(()) => {} Err(e) => return Some(Err(e)), diff --git a/compiler/qsc_codegen/src/qir_base/tests.rs b/compiler/qsc_codegen/src/qir_base/tests.rs index 9d3b2ef978..8cf1af5fec 100644 --- a/compiler/qsc_codegen/src/qir_base/tests.rs +++ b/compiler/qsc_codegen/src/qir_base/tests.rs @@ -1592,3 +1592,66 @@ fn custom_intrinsic_fail_on_non_unit_return() { "#]], ); } + +#[test] +fn pauli_i_rotation_for_global_phase_is_noop() { + check( + indoc! {" + namespace Test { + @EntryPoint() + operation Test() : Result { + use q = Qubit(); + R(PauliI, 1.0, q); + return MResetZ(q); + } + } + "}, + None, + &expect![[r#" + %Result = type opaque + %Qubit = type opaque + + define void @ENTRYPOINT__main() #0 { + call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*)) #1 + call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null) + ret void + } + + declare void @__quantum__qis__ccx__body(%Qubit*, %Qubit*, %Qubit*) + declare void @__quantum__qis__cx__body(%Qubit*, %Qubit*) + declare void @__quantum__qis__cy__body(%Qubit*, %Qubit*) + declare void @__quantum__qis__cz__body(%Qubit*, %Qubit*) + declare void @__quantum__qis__rx__body(double, %Qubit*) + declare void @__quantum__qis__rxx__body(double, %Qubit*, %Qubit*) + declare void @__quantum__qis__ry__body(double, %Qubit*) + declare void @__quantum__qis__ryy__body(double, %Qubit*, %Qubit*) + declare void @__quantum__qis__rz__body(double, %Qubit*) + declare void @__quantum__qis__rzz__body(double, %Qubit*, %Qubit*) + declare void @__quantum__qis__h__body(%Qubit*) + declare void @__quantum__qis__s__body(%Qubit*) + declare void @__quantum__qis__s__adj(%Qubit*) + declare void @__quantum__qis__t__body(%Qubit*) + declare void @__quantum__qis__t__adj(%Qubit*) + declare void @__quantum__qis__x__body(%Qubit*) + declare void @__quantum__qis__y__body(%Qubit*) + declare void @__quantum__qis__z__body(%Qubit*) + declare void @__quantum__qis__swap__body(%Qubit*, %Qubit*) + declare void @__quantum__qis__mz__body(%Qubit*, %Result* writeonly) #1 + declare void @__quantum__rt__result_record_output(%Result*, i8*) + declare void @__quantum__rt__array_record_output(i64, i8*) + declare void @__quantum__rt__tuple_record_output(i64, i8*) + + attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="base_profile" "required_num_qubits"="1" "required_num_results"="1" } + attributes #1 = { "irreversible" } + + ; module flags + + !llvm.module.flags = !{!0, !1, !2, !3} + + !0 = !{i32 1, !"qir_major_version", i32 1} + !1 = !{i32 7, !"qir_minor_version", i32 0} + !2 = !{i32 1, !"dynamic_qubit_management", i1 false} + !3 = !{i32 1, !"dynamic_result_management", i1 false} + "#]], + ); +} diff --git a/compiler/qsc_eval/src/backend.rs b/compiler/qsc_eval/src/backend.rs index bde1802e4f..0438357ac8 100644 --- a/compiler/qsc_eval/src/backend.rs +++ b/compiler/qsc_eval/src/backend.rs @@ -264,8 +264,28 @@ impl Backend for SparseSim { self.sim.qubit_is_zero(q) } - fn custom_intrinsic(&mut self, name: &str, _arg: Value) -> Option> { + fn custom_intrinsic(&mut self, name: &str, arg: Value) -> Option> { match name { + "GlobalPhase" => { + // Apply a global phase to the simulation by doing an Rz to a fresh qubit. + // The controls list may be empty, in which case the phase is applied unconditionally. + let [ctls_val, theta] = &*arg.unwrap_tuple() else { + panic!("tuple arity for GlobalPhase intrinsic should be 2"); + }; + let ctls = ctls_val + .clone() + .unwrap_array() + .iter() + .map(|q| q.clone().unwrap_qubit().0) + .collect::>(); + let q = self.sim.allocate(); + // The new qubit is by-definition in the |0⟩ state, so by reversing the sign of the + // angle we can apply the phase to the entire state without increasing its size in memory. + self.sim + .mcrz(&ctls, -2.0 * theta.clone().unwrap_double(), q); + self.sim.release(q); + Some(Ok(Value::unit())) + } "BeginEstimateCaching" => Some(Ok(Value::Bool(true))), "EndEstimateCaching" | "AccountForEstimatesInternal" diff --git a/compiler/qsc_eval/src/intrinsic.rs b/compiler/qsc_eval/src/intrinsic.rs index 6d63de31af..21c1f19407 100644 --- a/compiler/qsc_eval/src/intrinsic.rs +++ b/compiler/qsc_eval/src/intrinsic.rs @@ -53,7 +53,7 @@ pub(crate) fn call( return Err(Error::QubitUniqueness(arg_span)); } let (state, qubit_count) = sim.capture_quantum_state(); - let state = utils::split_state(&qubits, state, qubit_count) + let state = utils::split_state(&qubits, &state, qubit_count) .map_err(|()| Error::QubitsNotSeparable(arg_span))?; match out.state(state, qubits.len()) { Ok(()) => Ok(Value::unit()), diff --git a/compiler/qsc_eval/src/intrinsic/tests.rs b/compiler/qsc_eval/src/intrinsic/tests.rs index 7925858da0..9aa50bc015 100644 --- a/compiler/qsc_eval/src/intrinsic/tests.rs +++ b/compiler/qsc_eval/src/intrinsic/tests.rs @@ -465,6 +465,45 @@ fn dump_register_qubits_not_unique_fails() { ); } +#[test] +fn dump_register_target_in_minus_with_other_in_zero() { + check_intrinsic_output( + "", + indoc! {"{ + use qs = Qubit[2]; + X(qs[0]); + H(qs[0]); + Microsoft.Quantum.Diagnostics.DumpRegister([qs[0]]); + ResetAll(qs); + }"}, + &expect![[r#" + STATE: + |0⟩: 0.7071+0.0000𝑖 + |1⟩: −0.7071+0.0000𝑖 + "#]], + ); +} + +#[test] +fn dump_register_target_in_minus_with_other_in_one() { + check_intrinsic_output( + "", + indoc! {"{ + use qs = Qubit[2]; + X(qs[1]); + X(qs[0]); + H(qs[0]); + Microsoft.Quantum.Diagnostics.DumpRegister([qs[0]]); + ResetAll(qs); + }"}, + &expect![[r#" + STATE: + |0⟩: 0.7071+0.0000𝑖 + |1⟩: −0.7071+0.0000𝑖 + "#]], + ); +} + #[test] fn message() { check_intrinsic_output( diff --git a/compiler/qsc_eval/src/intrinsic/utils.rs b/compiler/qsc_eval/src/intrinsic/utils.rs index d71f0afc4f..1314a82083 100644 --- a/compiler/qsc_eval/src/intrinsic/utils.rs +++ b/compiler/qsc_eval/src/intrinsic/utils.rs @@ -12,10 +12,9 @@ use rustc_hash::FxHashMap; /// This function will return an error if the state is not separable using the provided qubit identifiers. pub fn split_state( qubits: &[usize], - state: Vec<(BigUint, Complex64)>, + state: &[(BigUint, Complex64)], qubit_count: usize, ) -> Result, ()> { - let state = state.into_iter().collect::>(); let mut dump_state = FxHashMap::default(); let mut other_state = FxHashMap::default(); @@ -25,7 +24,7 @@ pub fn split_state( // Try to split out the state for the given qubits from the whole state, detecting any entanglement // and returning an error if the qubits are not separable. let dump_norm = collect_split_state( - &state, + state, &dump_mask, &other_mask, &mut dump_state, @@ -70,13 +69,16 @@ fn compute_mask(qubit_count: usize, qubits: &[usize]) -> (BigUint, BigUint) { /// On success, the `dump_state` and `other_state` maps will be populated with the separated states, and the /// function returns the accumulated norm of the dump state. fn collect_split_state( - state: &FxHashMap, + state: &[(BigUint, Complex64)], dump_mask: &BigUint, other_mask: &BigUint, dump_state: &mut FxHashMap, other_state: &mut FxHashMap, ) -> Result { + // To ensure consistent ordering, we iterate over the vector directly (returned from the simulator in a deterministic order), + // and not the map used for arbitrary lookup. let mut state_iter = state.iter(); + let state_map = state.iter().cloned().collect::>(); let (base_label, base_val) = state_iter.next().expect("state should never be empty"); let dump_base_label = base_label & dump_mask; let other_base_label = base_label & other_mask; @@ -93,10 +95,10 @@ fn collect_split_state( // If either the state identified by the dump mask or the state identified by the other mask // is None, that means it has zero amplitude and we can conclude the state is not separable. - let Some(dump_val) = state.get(&(&dump_label | &other_base_label)) else { + let Some(dump_val) = state_map.get(&(&dump_label | &other_base_label)) else { return Err(()); }; - let Some(other_val) = state.get(&(&dump_base_label | &other_label)) else { + let Some(other_val) = state_map.get(&(&dump_base_label | &other_label)) else { return Err(()); }; diff --git a/compiler/qsc_partial_eval/src/lib.rs b/compiler/qsc_partial_eval/src/lib.rs index f1f9f3b15f..1b3bf6b972 100644 --- a/compiler/qsc_partial_eval/src/lib.rs +++ b/compiler/qsc_partial_eval/src/lib.rs @@ -548,7 +548,8 @@ impl<'a> PartialEvaluator<'a> { "DumpRegister" | "AccountForEstimatesInternal" | "BeginRepeatEstimatesInternal" - | "EndRepeatEstimatesInternal" => Value::unit(), + | "EndRepeatEstimatesInternal" + | "GlobalPhase" => Value::unit(), _ => self.eval_expr_call_to_intrinsic_qis(store_item_id, callable_decl, args_value), } } diff --git a/compiler/qsc_partial_eval/src/management.rs b/compiler/qsc_partial_eval/src/management.rs index 238663f59f..5325b905ab 100644 --- a/compiler/qsc_partial_eval/src/management.rs +++ b/compiler/qsc_partial_eval/src/management.rs @@ -105,7 +105,7 @@ impl Backend for QuantumIntrinsicsChecker { ) -> Option> { match name { "BeginEstimateCaching" => Some(Ok(Value::Bool(true))), - "EndEstimateCaching" => Some(Ok(Value::unit())), + "EndEstimateCaching" | "GlobalPhase" => Some(Ok(Value::unit())), _ => None, } } diff --git a/compiler/qsc_partial_eval/tests/intrinsics.rs b/compiler/qsc_partial_eval/tests/intrinsics.rs index f36bb9db18..bee1c9f36c 100644 --- a/compiler/qsc_partial_eval/tests/intrinsics.rs +++ b/compiler/qsc_partial_eval/tests/intrinsics.rs @@ -973,3 +973,26 @@ fn call_to_draw_random_double_panics() { "#, }); } + +#[test] +fn call_to_pauli_i_rotation_for_global_phase_is_noop() { + let program = get_rir_program(indoc! { + r#" + namespace Test { + @EntryPoint() + operation Main() : Unit { + use q = Qubit(); + R(PauliI, 1.0, q); + } + } + "#, + }); + assert_block_instructions( + &program, + BlockId(0), + &expect![[r#" + Block: + Call id(1), args( Integer(0), Pointer, ) + Return"#]], + ); +} diff --git a/library/src/tests/intrinsic.rs b/library/src/tests/intrinsic.rs index b1842565b5..01ad9cdb48 100644 --- a/library/src/tests/intrinsic.rs +++ b/library/src/tests/intrinsic.rs @@ -5,7 +5,7 @@ use expect_test::expect; use indoc::indoc; use qsc::{interpret::Value, target::Profile, SparseSim}; -use super::test_expression_with_lib_and_profile_and_sim; +use super::{test_expression, test_expression_with_lib_and_profile_and_sim}; // These tests verify multi-controlled decomposition logic for gate operations. Each test // manually allocates 2N qubits, performs the decomposed operation from the library on the first N, @@ -2569,3 +2569,259 @@ fn test_base_mcz_4_control() { assert!(sim.sim.qubit_is_zero(i), "qubit {i} is not zero"); } } + +#[test] +fn global_phase_correct_for_r1() { + let dump = test_expression( + indoc! {" + { + open Microsoft.Quantum.Math; + open Microsoft.Quantum.Diagnostics; + use q = Qubit(); + H(q); + R1(PI() / 2.0, q); + Adjoint S(q); + H(q); + DumpMachine(); + Reset(q); + } + "}, + &Value::unit(), + ); + + expect![[r#" + STATE: + |0⟩: 1.0000+0.0000𝑖 + "#]] + .assert_eq(&dump); +} + +#[test] +fn global_phase_correct_for_adjoint_r1() { + let dump = test_expression( + indoc! {" + { + open Microsoft.Quantum.Math; + open Microsoft.Quantum.Diagnostics; + use q = Qubit(); + H(q); + Adjoint R1(PI() / 2.0, q); + S(q); + H(q); + DumpMachine(); + Reset(q); + } + "}, + &Value::unit(), + ); + + expect![[r#" + STATE: + |0⟩: 1.0000+0.0000𝑖 + "#]] + .assert_eq(&dump); +} + +#[test] +fn global_phase_correct_for_singly_controlled_r1() { + let dump = test_expression( + indoc! {" + { + open Microsoft.Quantum.Math; + open Microsoft.Quantum.Diagnostics; + use ctls = Qubit[1]; + use q = Qubit(); + for c in ctls { + H(c); + } + H(q); + Controlled R1(ctls, (PI() / 2.0, q)); + Controlled Adjoint S(ctls, q); + H(q); + for c in ctls { + H(c); + } + DumpMachine(); + Reset(q); + ResetAll(ctls); + } + "}, + &Value::unit(), + ); + + expect![[r#" + STATE: + |00⟩: 1.0000+0.0000𝑖 + "#]] + .assert_eq(&dump); +} + +#[test] +fn global_phase_correct_for_singly_controlled_adjoint_r1() { + let dump = test_expression( + indoc! {" + { + open Microsoft.Quantum.Math; + open Microsoft.Quantum.Diagnostics; + use ctls = Qubit[1]; + use q = Qubit(); + for c in ctls { + H(c); + } + H(q); + Adjoint Controlled R1(ctls, (PI() / 2.0, q)); + Controlled S(ctls, q); + H(q); + for c in ctls { + H(c); + } + DumpMachine(); + Reset(q); + ResetAll(ctls); + } + "}, + &Value::unit(), + ); + + expect![[r#" + STATE: + |00⟩: 1.0000+0.0000𝑖 + "#]] + .assert_eq(&dump); +} + +#[test] +fn global_phase_correct_for_doubly_controlled_r1() { + let dump = test_expression( + indoc! {" + { + open Microsoft.Quantum.Math; + open Microsoft.Quantum.Diagnostics; + use ctls = Qubit[2]; + use q = Qubit(); + for c in ctls { + H(c); + } + H(q); + Controlled R1(ctls, (PI() / 2.0, q)); + Controlled Adjoint S(ctls, q); + H(q); + for c in ctls { + H(c); + } + DumpMachine(); + Reset(q); + ResetAll(ctls); + } + "}, + &Value::unit(), + ); + + expect![[r#" + STATE: + |000⟩: 1.0000+0.0000𝑖 + "#]] + .assert_eq(&dump); +} + +#[test] +fn global_phase_correct_for_doubly_controlled_adjoint_r1() { + let dump = test_expression( + indoc! {" + { + open Microsoft.Quantum.Math; + open Microsoft.Quantum.Diagnostics; + use ctls = Qubit[2]; + use q = Qubit(); + for c in ctls { + H(c); + } + H(q); + Adjoint Controlled R1(ctls, (PI() / 2.0, q)); + Controlled S(ctls, q); + H(q); + for c in ctls { + H(c); + } + DumpMachine(); + Reset(q); + ResetAll(ctls); + } + "}, + &Value::unit(), + ); + + expect![[r#" + STATE: + |000⟩: 1.0000+0.0000𝑖 + "#]] + .assert_eq(&dump); +} + +#[test] +fn global_phase_correct_for_triply_controlled_r1() { + let dump = test_expression( + indoc! {" + { + open Microsoft.Quantum.Math; + open Microsoft.Quantum.Diagnostics; + use ctls = Qubit[3]; + use q = Qubit(); + for c in ctls { + H(c); + } + H(q); + Controlled R1(ctls, (PI() / 2.0, q)); + Controlled Adjoint S(ctls, q); + H(q); + for c in ctls { + H(c); + } + DumpMachine(); + Reset(q); + ResetAll(ctls); + } + "}, + &Value::unit(), + ); + + expect![[r#" + STATE: + |0000⟩: 1.0000+0.0000𝑖 + "#]] + .assert_eq(&dump); +} + +#[test] +fn global_phase_correct_for_triply_controlled_adjoint_r1() { + let dump = test_expression( + indoc! {" + { + open Microsoft.Quantum.Math; + open Microsoft.Quantum.Diagnostics; + use ctls = Qubit[3]; + use q = Qubit(); + for c in ctls { + H(c); + } + H(q); + Adjoint Controlled R1(ctls, (PI() / 2.0, q)); + Controlled S(ctls, q); + H(q); + for c in ctls { + H(c); + } + DumpMachine(); + Reset(q); + ResetAll(ctls); + } + "}, + &Value::unit(), + ); + + expect![[r#" + STATE: + |0000⟩: 1.0000+0.0000𝑖 + "#]] + .assert_eq(&dump); +} diff --git a/library/src/tests/resources/qft_le.qs b/library/src/tests/resources/qft_le.qs index 6e8b4c2837..7a9f7b256f 100644 --- a/library/src/tests/resources/qft_le.qs +++ b/library/src/tests/resources/qft_le.qs @@ -45,7 +45,7 @@ namespace Test { Fact(Length(target) == 2, $"`Length(target!)` must be 2"); let (q1, q2) = ((target)[0], (target)[1]); H(q1); - Controlled R1Frac([q2], (2, 2, q1)); + Controlled R1Frac([q1], (1, 1, q2)); H(q2); } @@ -55,10 +55,10 @@ namespace Test { Fact(Length(target) == 3, $"`Length(target)` must be 3"); let (q1, q2, q3) = ((target)[0], (target)[1], (target)[2]); H(q1); - Controlled R1Frac([q2], (2, 2, q1)); - Controlled R1Frac([q3], (2, 3, q1)); + Controlled R1Frac([q1], (1, 1, q2)); + Controlled R1Frac([q1], (1, 2, q3)); H(q2); - Controlled R1Frac([q3], (2, 2, q2)); + Controlled R1Frac([q2], (1, 1, q3)); H(q3); } @@ -68,14 +68,14 @@ namespace Test { Fact(Length(target) == 4, $"`Length(target!)` must be 4"); let (q1, q2, q3, q4) = ((target)[0], (target)[1], (target)[2], (target)[3]); H(q1); - Controlled R1Frac([q2], (2, 2, q1)); - Controlled R1Frac([q3], (2, 3, q1)); - Controlled R1Frac([q4], (2, 4, q1)); + Controlled R1Frac([q1], (1, 1, q2)); + Controlled R1Frac([q1], (1, 2, q3)); + Controlled R1Frac([q1], (1, 3, q4)); H(q2); - Controlled R1Frac([q3], (2, 2, q2)); - Controlled R1Frac([q4], (2, 3, q2)); + Controlled R1Frac([q2], (1, 1, q3)); + Controlled R1Frac([q2], (1, 2, q4)); H(q3); - Controlled R1Frac([q4], (2, 2, q3)); + Controlled R1Frac([q3], (1, 1, q4)); H(q4); } diff --git a/library/src/tests/state_preparation.rs b/library/src/tests/state_preparation.rs index 63e6585f21..d31f7614a8 100644 --- a/library/src/tests/state_preparation.rs +++ b/library/src/tests/state_preparation.rs @@ -36,8 +36,8 @@ fn check_minus_state_preparation() { expect![[r#" STATE: - |0⟩: 0.0000−0.7071𝑖 - |1⟩: 0.0000+0.7071𝑖 + |0⟩: 0.7071+0.0000𝑖 + |1⟩: −0.7071+0.0000𝑖 "#]] .assert_eq(&out); } @@ -84,10 +84,10 @@ fn check_complex_preparation() { expect![[r#" STATE: - |00⟩: 0.1913−0.4619𝑖 - |01⟩: 0.4619−0.1913𝑖 - |10⟩: 0.4619+0.1913𝑖 - |11⟩: 0.1913+0.4619𝑖 + |00⟩: 0.5000+0.0000𝑖 + |01⟩: 0.3536+0.3536𝑖 + |10⟩: 0.0000+0.5000𝑖 + |11⟩: −0.3536+0.3536𝑖 "#]] .assert_eq(&out); } @@ -136,55 +136,55 @@ fn check_preparation_completion() { |1110⟩: 0.3067+0.0000𝑖 |1111⟩: 0.2277+0.0000𝑖 STATE: - |0⟩: 0.0000+0.7738𝑖 - |1⟩: 0.0000−0.6335𝑖 - STATE: - |00⟩: 0.1294−0.1294𝑖 - |01⟩: −0.2878+0.2878𝑖 - |10⟩: 0.4277−0.4277𝑖 - |11⟩: 0.4663−0.4663𝑖 - STATE: - |000⟩: 0.0378−0.0911𝑖 - |001⟩: −0.1374+0.3317𝑖 - |010⟩: 0.1782−0.4302𝑖 - |011⟩: −0.1789+0.4318𝑖 - |100⟩: 0.1607−0.3879𝑖 - |101⟩: 0.0453−0.1094𝑖 - |110⟩: −0.1768+0.4267𝑖 - |111⟩: 0.0573−0.1382𝑖 - STATE: - |0000⟩: −0.1039+0.2508𝑖 - |0001⟩: 0.0223−0.0539𝑖 - |0010⟩: 0.0445−0.1075𝑖 - |0011⟩: 0.1382−0.3336𝑖 - |0100⟩: −0.1176+0.2840𝑖 - |0101⟩: 0.0740−0.1787𝑖 - |0110⟩: −0.1049+0.2533𝑖 - |0111⟩: 0.1273−0.3072𝑖 - |1000⟩: 0.0498−0.1203𝑖 - |1001⟩: 0.0852−0.2056𝑖 - |1010⟩: 0.1205−0.2909𝑖 - |1011⟩: −0.0806+0.1947𝑖 - |1100⟩: 0.0813−0.1963𝑖 - |1101⟩: 0.0940−0.2268𝑖 - |1110⟩: −0.1174+0.2833𝑖 - |1111⟩: −0.0871+0.2104𝑖 - STATE: - |000⟩: 0.6847−0.2836𝑖 - |001⟩: 0.2238−0.0927𝑖 - |010⟩: 0.2902−0.1202𝑖 - |011⟩: −0.2913+0.1207𝑖 - |100⟩: 0.2617−0.1084𝑖 - |101⟩: 0.0738−0.0306𝑖 - |110⟩: 0.2879−0.1192𝑖 - |111⟩: 0.0932−0.0386𝑖 - STATE: - |000⟩: 0.7247−0.3002𝑖 - |001⟩: 0.2368−0.0981𝑖 - |010⟩: 0.3072−0.1272𝑖 - |011⟩: −0.3083+0.1277𝑖 - |100⟩: 0.2770−0.1147𝑖 - |101⟩: 0.0781−0.0324𝑖 + |0⟩: −0.7738+0.0000𝑖 + |1⟩: 0.6335+0.0000𝑖 + STATE: + |00⟩: 0.1830+0.0000𝑖 + |01⟩: −0.4070+0.0000𝑖 + |10⟩: 0.6049+0.0000𝑖 + |11⟩: 0.6595+0.0000𝑖 + STATE: + |000⟩: 0.0987+0.0000𝑖 + |001⟩: −0.3590+0.0000𝑖 + |010⟩: 0.4657+0.0000𝑖 + |011⟩: −0.4674+0.0000𝑖 + |100⟩: 0.4199+0.0000𝑖 + |101⟩: 0.1184+0.0000𝑖 + |110⟩: −0.4619+0.0000𝑖 + |111⟩: 0.1496+0.0000𝑖 + STATE: + |0000⟩: −0.2715+0.0000𝑖 + |0001⟩: 0.0584+0.0000𝑖 + |0010⟩: 0.1164+0.0000𝑖 + |0011⟩: 0.3611+0.0000𝑖 + |0100⟩: −0.3074+0.0000𝑖 + |0101⟩: 0.1934+0.0000𝑖 + |0110⟩: −0.2742+0.0000𝑖 + |0111⟩: 0.3325+0.0000𝑖 + |1000⟩: 0.1302+0.0000𝑖 + |1001⟩: 0.2225+0.0000𝑖 + |1010⟩: 0.3149+0.0000𝑖 + |1011⟩: −0.2107+0.0000𝑖 + |1100⟩: 0.2124+0.0000𝑖 + |1101⟩: 0.2455+0.0000𝑖 + |1110⟩: −0.3067+0.0000𝑖 + |1111⟩: −0.2277+0.0000𝑖 + STATE: + |000⟩: 0.7412+0.0000𝑖 + |001⟩: 0.2422+0.0000𝑖 + |010⟩: 0.3142+0.0000𝑖 + |011⟩: −0.3153+0.0000𝑖 + |100⟩: 0.2833+0.0000𝑖 + |101⟩: 0.0799+0.0000𝑖 + |110⟩: 0.3116+0.0000𝑖 + |111⟩: 0.1009+0.0000𝑖 + STATE: + |000⟩: 0.7844+0.0000𝑖 + |001⟩: 0.2563+0.0000𝑖 + |010⟩: 0.3325+0.0000𝑖 + |011⟩: −0.3337+0.0000𝑖 + |100⟩: 0.2998+0.0000𝑖 + |101⟩: 0.0846+0.0000𝑖 "#]] .assert_eq(&out); } diff --git a/library/std/internal.qs b/library/std/internal.qs index 55e3d06672..4ff90bf121 100644 --- a/library/std/internal.qs +++ b/library/std/internal.qs @@ -28,24 +28,43 @@ namespace Microsoft.Quantum.Intrinsic { } internal operation ApplyGlobalPhase(theta : Double) : Unit is Ctl + Adj { - body ... {} + body ... { + ControllableGlobalPhase(theta); + } + adjoint ... { + ControllableGlobalPhase(-theta); + } + } + + // Global phase is not relevant for physical systems, but controlled global phase is physical. We use + // the Rz gate to implement controlled global phase physically, and then correct for the extra global phase it + // introduces in simulation using additional calls to the simulation-only global phase intrinsic. + // We use a separate operation for this controlled case to avoid recursive calls to the same operation + // that can interfere with runtime capabilities analysis. + internal operation ControllableGlobalPhase(theta : Double) : Unit is Ctl { + body ... { + GlobalPhase([], theta); + } controlled (ctls, ...) { if Length(ctls) == 0 { - // Noop - } elif Length(ctls) == 1 { - Rz(theta, ctls[0]); + GlobalPhase([], theta); } else { - Controlled R1(ctls[1..(Length(ctls) - 1)], (theta, ctls[0])); + Controlled Rz(ctls[1...], (theta, ctls[0])); + GlobalPhase(ctls[1...], theta / 2.0); + // With a single control qubit, the call to Rz uses no controls and global phase is corrected + // by just the call above. + // Multi-controlled Rz gates use a decomposition that introduces an additional global + // phase, so we need to correct for that here. + if Length(ctls) > 1 { + GlobalPhase([], -theta / 4.0); + } } } } - internal operation CR1(theta : Double, control : Qubit, target : Qubit) : Unit is Adj { - Rz(theta / 2.0, target); - Rz(theta / 2.0, control); - CNOT(control, target); - Rz(-theta / 2.0, target); - CNOT(control, target); + // Global phase intrinsic, which only has affect in simulation and is a no-op otherwise. + internal operation GlobalPhase(ctls : Qubit[], theta : Double) : Unit { + body intrinsic; } internal operation CRz(control : Qubit, theta : Double, target : Qubit) : Unit is Adj { diff --git a/library/std/intrinsic.qs b/library/std/intrinsic.qs index 82d0adc500..09e825aeac 100644 --- a/library/std/intrinsic.qs +++ b/library/std/intrinsic.qs @@ -428,24 +428,8 @@ namespace Microsoft.Quantum.Intrinsic { /// R(PauliI, -theta, qubit); /// ``` operation R1(theta : Double, qubit : Qubit) : Unit is Adj + Ctl { - body ... { - Rz(theta, qubit); - } - controlled (ctls, ...) { - if Length(ctls) == 0 { - Rz(theta, qubit); - } elif Length(ctls) == 1 { - CR1(theta, ctls[0], qubit); - } else { - use aux = Qubit[Length(ctls) - 1]; - within { - CollectControls(ctls, aux, 0); - AdjustForSingleControl(ctls, aux); - } apply { - CR1(theta, aux[Length(ctls) - 2], qubit); - } - } - } + Rz(theta, qubit); + R(PauliI, -theta, qubit); } /// # Summary diff --git a/resource_estimator/src/counts.rs b/resource_estimator/src/counts.rs index d95eb29bdf..14db15c431 100644 --- a/resource_estimator/src/counts.rs +++ b/resource_estimator/src/counts.rs @@ -522,6 +522,7 @@ impl Backend for LogicalCounter { fn custom_intrinsic(&mut self, name: &str, arg: Value) -> Option> { match name { + "GlobalPhase" => Some(Ok(Value::unit())), "BeginEstimateCaching" => { let values = arg.unwrap_tuple(); let [cache_name, cache_variant] = array::from_fn(|i| values[i].clone()); diff --git a/resource_estimator/src/counts/tests.rs b/resource_estimator/src/counts/tests.rs index 08cf18b29e..4cf306aaf5 100644 --- a/resource_estimator/src/counts/tests.rs +++ b/resource_estimator/src/counts/tests.rs @@ -208,3 +208,31 @@ fn account_for_estimates_works() { "]], ); } + +#[test] +fn pauli_i_rotation_for_global_phase_is_noop() { + verify_logical_counts( + indoc! {" + namespace Test { + @EntryPoint() + operation Main() : Unit { + use q = Qubit(); + T(q); + R(PauliI, 1.0, q); + } + } + "}, + None, + &expect![[r#" + LogicalResourceCounts { + num_qubits: 1, + t_count: 1, + rotation_count: 0, + rotation_depth: 0, + ccz_count: 0, + ccix_count: 0, + measurement_count: 0, + } + "#]], + ); +}