From 479eb665f25948187bea206f9a261f0df1e05d7e Mon Sep 17 00:00:00 2001 From: "Stefan J. Wernli" Date: Sat, 28 Dec 2024 21:51:17 -0800 Subject: [PATCH 1/2] Fix `Relabel` for odd size arrays When relabeling qubits arrays with odd lengths 5 or greater, the final case in the match statement triggered with a bug that caused the mappings tracked for swaps to be updated incorrectly, resulting in the wrong qubit labels being swapped. This fixes the bug, updates some variable names and comments for clarity, and adds new test cases to verify the expected behavior. It also fixes a minor test bug in a related test that was verifying the wrong state. Fixes #2077 --- compiler/qsc_eval/src/intrinsic.rs | 23 +++--- library/src/tests/canon.rs | 108 ++++++++++++++++++++++++++++- 2 files changed, 121 insertions(+), 10 deletions(-) diff --git a/compiler/qsc_eval/src/intrinsic.rs b/compiler/qsc_eval/src/intrinsic.rs index 711f34de19..4d2b2e5ebb 100644 --- a/compiler/qsc_eval/src/intrinsic.rs +++ b/compiler/qsc_eval/src/intrinsic.rs @@ -392,14 +392,14 @@ pub fn qubit_relabel( } (false, true) => { // The right qubit has been relabeled, so we need to swap the left qubit with the - // qubit that the right qubit was relabeled to. - let mapped = *map + // new label for the right qubit. + let label = *map .keys() .find(|k| map[*k] == r) .expect("mapped qubit should be present as both key and value"); - swap(l, mapped); + swap(l, label); map.insert(l, r); - map.insert(mapped, l); + map.insert(label, l); } (true, false) => { // The left qubit has been relabeled, so we swap the qubits as normal but @@ -410,14 +410,21 @@ pub fn qubit_relabel( map.insert(r, mapped); } (true, true) => { - // Both qubits have been relabeled, so we need to swap the mapped right qubit with + // Both qubits have been relabeled, so we need to swap new label for the right qubit with // the left qubit and remember the new mapping of both qubits. + // This is effectively a combination of the second and third cases above. + let label_r = *map + .keys() + .find(|k| map[*k] == r) + .expect("mapped qubit should be present as both key and value"); let mapped_l = *map.get(&l).expect("mapped qubit should be present"); let mapped_r = *map.get(&r).expect("mapped qubit should be present"); + + // This swap is only necessary if the labels don't already point to each other. if mapped_l != r && mapped_r != l { - swap(mapped_r, l); - map.insert(mapped_r, mapped_l); - map.insert(l, r); + swap(label_r, l); + map.insert(label_r, mapped_l); + map.insert(l, mapped_r); } } } diff --git a/library/src/tests/canon.rs b/library/src/tests/canon.rs index 18d06bacc4..7133ebf22e 100644 --- a/library/src/tests/canon.rs +++ b/library/src/tests/canon.rs @@ -154,6 +154,78 @@ fn check_relabel_rotational_permutation_alternate_expression() { ); } +#[test] +fn check_relabel_rotational_permutation_size_4() { + test_expression( + "{ + use qs = Qubit[4]; + // Prepare |01+0⟩ + X(qs[1]); + H(qs[2]); + Relabel(qs, qs[2...] + qs[0..1]); + // Expected state is |+001⟩, perform adjoint to get back to ground state. + H(qs[0]); + X(qs[Length(qs)-1]); + // Qubit release will fail if the state is not |000⟩ + }", + &Value::unit(), + ); +} + +#[test] +fn check_relabel_rotational_permutation_size_5() { + test_expression( + "{ + use qs = Qubit[5]; + // Prepare |01+00⟩ + X(qs[1]); + H(qs[2]); + Relabel(qs, qs[2...] + qs[0..1]); + // Expected state is |+0001⟩, perform adjoint to get back to ground state. + H(qs[0]); + X(qs[Length(qs)-1]); + // Qubit release will fail if the state is not |000⟩ + }", + &Value::unit(), + ); +} + +#[test] +fn check_relabel_rotational_permutation_size_6() { + test_expression( + "{ + use qs = Qubit[6]; + // Prepare |01+000⟩ + X(qs[1]); + H(qs[2]); + Relabel(qs, qs[2...] + qs[0..1]); + // Expected state is |+00001⟩, perform adjoint to get back to ground state. + H(qs[0]); + X(qs[Length(qs)-1]); + // Qubit release will fail if the state is not |000⟩ + }", + &Value::unit(), + ); +} + +#[test] +fn check_relabel_rotational_permutation_size_7() { + test_expression( + "{ + use qs = Qubit[7]; + // Prepare |01+0000⟩ + X(qs[1]); + H(qs[2]); + Relabel(qs, qs[2...] + qs[0..1]); + // Expected state is |+000001⟩, perform adjoint to get back to ground state. + H(qs[0]); + X(qs[Length(qs)-1]); + // Qubit release will fail if the state is not |000⟩ + }", + &Value::unit(), + ); +} + #[test] fn check_relabel_four_qubit_shuffle_permutation() { test_expression( @@ -162,12 +234,44 @@ fn check_relabel_four_qubit_shuffle_permutation() { // Prepare |01+i⟩ X(qs[1]); H(qs[2]); - Y(qs[3]); + H(qs[3]); + S(qs[3]); + H(qs[3]); Relabel([qs[0], qs[1], qs[2], qs[3]], [qs[1], qs[0], qs[3], qs[2]]); // Expected state is |10i+⟩, perform adjoint to get back to ground state. X(qs[0]); - Y(qs[2]); + H(qs[2]); + Adjoint S(qs[2]); + H(qs[2]); + H(qs[3]); + // Qubit release will fail if the state is not |0000⟩ + }", + &Value::unit(), + ); +} + +#[test] +fn check_relabel_five_qubit_shuffle_permutation() { + test_expression( + "{ + use qs = Qubit[5]; + // Prepare |01+i-⟩ + X(qs[1]); + H(qs[2]); + H(qs[3]); + S(qs[3]); + H(qs[3]); + H(qs[4]); + Z(qs[4]); + Relabel([qs[0], qs[1], qs[2], qs[3], qs[4]], [qs[1], qs[0], qs[3], qs[4], qs[2]]); + // Expected state is |10i-+⟩, perform adjoint to get back to ground state. + X(qs[0]); + H(qs[2]); + Adjoint S(qs[2]); + H(qs[2]); + Z(qs[3]); H(qs[3]); + H(qs[4]); // Qubit release will fail if the state is not |0000⟩ }", &Value::unit(), From 95bf67b328fdd2812e2287e002c357824680224c Mon Sep 17 00:00:00 2001 From: Dmitry Vasilevsky Date: Mon, 30 Dec 2024 02:16:52 -0800 Subject: [PATCH 2/2] Alternative version of relabeling --- compiler/qsc_eval/src/intrinsic.rs | 90 +++++++++++++----------------- 1 file changed, 38 insertions(+), 52 deletions(-) diff --git a/compiler/qsc_eval/src/intrinsic.rs b/compiler/qsc_eval/src/intrinsic.rs index 4d2b2e5ebb..0fc402796c 100644 --- a/compiler/qsc_eval/src/intrinsic.rs +++ b/compiler/qsc_eval/src/intrinsic.rs @@ -347,7 +347,7 @@ fn two_qubit_rotation( pub fn qubit_relabel( arg: Value, arg_span: PackageSpan, - mut swap: impl FnMut(usize, usize), + swap: impl FnMut(usize, usize), ) -> Result { let [left, right] = unwrap_tuple(arg); let left = left.unwrap_array(); @@ -377,58 +377,44 @@ pub fn qubit_relabel( return Err(Error::RelabelingMismatch(arg_span)); } - let mut map = FxHashMap::default(); - map.reserve(left.len()); - for (l, r) in left.into_iter().zip(right.into_iter()) { - if l == r { - continue; - } - match (map.contains_key(&l), map.contains_key(&r)) { - (false, false) => { - // Neither qubit has been relabeled yet. - swap(l, r); - map.insert(l, r); - map.insert(r, l); - } - (false, true) => { - // The right qubit has been relabeled, so we need to swap the left qubit with the - // new label for the right qubit. - let label = *map - .keys() - .find(|k| map[*k] == r) - .expect("mapped qubit should be present as both key and value"); - swap(l, label); - map.insert(l, r); - map.insert(label, l); - } - (true, false) => { - // The left qubit has been relabeled, so we swap the qubits as normal but - // remember the new mapping of the right qubit. - let mapped = *map.get(&l).expect("mapped qubit should be present"); - swap(l, r); - map.insert(l, r); - map.insert(r, mapped); - } - (true, true) => { - // Both qubits have been relabeled, so we need to swap new label for the right qubit with - // the left qubit and remember the new mapping of both qubits. - // This is effectively a combination of the second and third cases above. - let label_r = *map - .keys() - .find(|k| map[*k] == r) - .expect("mapped qubit should be present as both key and value"); - let mapped_l = *map.get(&l).expect("mapped qubit should be present"); - let mapped_r = *map.get(&r).expect("mapped qubit should be present"); + permutation_via_transpositions(&right, &left, swap); + Ok(Value::unit()) +} - // This swap is only necessary if the labels don't already point to each other. - if mapped_l != r && mapped_r != l { - swap(label_r, l); - map.insert(label_r, mapped_l); - map.insert(l, mapped_r); - } - } - } +/// Performs arbitrary permutation of object labels using label swap function. +/// Upon completions an object labeled with ``original_labels``[i] will be labeled ``permuted_labels``[i]. +/// Transposition function swaps labels on objects identified by their labels. +pub fn permutation_via_transpositions( + original_labels: &[usize], // Original placement of labels + permuted_labels: &[usize], // Desired placement of labels + mut swap: impl FnMut(usize, usize), // Swap these labels +) { + // Create a map of objects that may potentially need relabeling. + let mut object_with_label = FxHashMap::default(); + object_with_label.reserve(original_labels.len()); + for (i, &label) in original_labels.iter().enumerate() { + object_with_label.insert(label, i); } - Ok(Value::unit()) + // While we have more objects that need relabeling + while let Some((&label, &i)) = object_with_label.iter().next() { + // Currently label is on object i. What is the desired label for it? + let desired = permuted_labels[i]; + if desired == label { + // When desired label and current label is the same - + // remove from the map. We are done with this object. + object_with_label.remove(&label); + continue; + } + // Which object has the desired label now? It's object j. + let &j = object_with_label + .get(&desired) + .expect("Missing label that still needs relabeling."); + // Swap labels on objects with current and desired label. + swap(label, desired); + // We are done with the desired label, remove it for map. + object_with_label.remove(&desired); + // Now label is on object j and it may still need relabeling. + object_with_label.insert(label, j); + } }