Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix global phase for PauliI rotation and DumpRegister #1461

Merged
merged 4 commits into from
May 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ wasm-bindgen-futures = "0.4"
rand = "0.8"
serde_json = "1.0"
pyo3 = "0.20"
quantum-sparse-sim = { git = "https://github.com/qir-alliance/qir-runner", rev = "e7de80bf06dcaf69367576ec31f901c0496a9832", default-features = false }
quantum-sparse-sim = { git = "https://github.com/qir-alliance/qir-runner", rev = "5ceea4ff0d53cf7bb26d722dae91efe5586bd7ec", default-features = false }
async-trait = "0.1"
tokio = { version = "1.35", features = ["macros", "rt"] }

Expand Down
6 changes: 6 additions & 0 deletions compiler/qsc_codegen/src/qir_base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,12 @@ impl Backend for BaseProfSim {
name: &str,
arg: Value,
) -> Option<std::result::Result<Value, String>> {
// 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)),
Expand Down
63 changes: 63 additions & 0 deletions compiler/qsc_codegen/src/qir_base/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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}
"#]],
);
}
22 changes: 21 additions & 1 deletion compiler/qsc_eval/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,8 +264,28 @@ impl Backend for SparseSim {
self.sim.qubit_is_zero(q)
}

fn custom_intrinsic(&mut self, name: &str, _arg: Value) -> Option<Result<Value, String>> {
fn custom_intrinsic(&mut self, name: &str, arg: Value) -> Option<Result<Value, String>> {
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::<Vec<_>>();
let q = self.sim.allocate();
swernli marked this conversation as resolved.
Show resolved Hide resolved
// 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"
Expand Down
2 changes: 1 addition & 1 deletion compiler/qsc_eval/src/intrinsic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()),
Expand Down
39 changes: 39 additions & 0 deletions compiler/qsc_eval/src/intrinsic/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
14 changes: 8 additions & 6 deletions compiler/qsc_eval/src/intrinsic/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Vec<(BigUint, Complex64)>, ()> {
let state = state.into_iter().collect::<FxHashMap<_, _>>();
let mut dump_state = FxHashMap::default();
let mut other_state = FxHashMap::default();

Expand All @@ -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,
Expand Down Expand Up @@ -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<BigUint, Complex64>,
state: &[(BigUint, Complex64)],
dump_mask: &BigUint,
other_mask: &BigUint,
dump_state: &mut FxHashMap<BigUint, Complex64>,
other_state: &mut FxHashMap<BigUint, Complex64>,
) -> Result<f64, ()> {
// 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::<FxHashMap<_, _>>();
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;
Expand All @@ -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(());
};

Expand Down
3 changes: 2 additions & 1 deletion compiler/qsc_partial_eval/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -777,7 +777,8 @@ impl<'a> PartialEvaluator<'a> {
"DumpRegister"
| "AccountForEstimatesInternal"
| "BeginRepeatEstimatesInternal"
| "EndRepeatEstimatesInternal" => Ok(Value::unit()),
| "EndRepeatEstimatesInternal"
| "GlobalPhase" => Ok(Value::unit()),
// The following intrinsic functions and operations should never make it past conditional compilation and
// the capabilities check pass.
"CheckZero" | "DrawRandomInt" | "DrawRandomDouble" | "Length" => {
Expand Down
2 changes: 1 addition & 1 deletion compiler/qsc_partial_eval/src/management.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ impl Backend for QuantumIntrinsicsChecker {
) -> Option<std::result::Result<Value, String>> {
match name {
"BeginEstimateCaching" => Some(Ok(Value::Bool(true))),
"EndEstimateCaching" => Some(Ok(Value::unit())),
"EndEstimateCaching" | "GlobalPhase" => Some(Ok(Value::unit())),
_ => None,
}
}
Expand Down
23 changes: 23 additions & 0 deletions compiler/qsc_partial_eval/tests/intrinsics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1031,3 +1031,26 @@ fn call_to_length_in_inner_function_succeeds() {
Return"#]],
);
}

#[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"#]],
);
}
Loading
Loading