From 3951e1081205738c5238317f7382a7ab4bcc57d6 Mon Sep 17 00:00:00 2001 From: Dax Fohl Date: Fri, 15 Jul 2022 18:50:53 -0400 Subject: [PATCH 1/4] Support multi-qubit measurements in deferred measurement transformer --- .../transformers/measurement_transformers.py | 22 ++++++------- .../measurement_transformers_test.py | 32 +++++++++++++++---- 2 files changed, 36 insertions(+), 18 deletions(-) diff --git a/cirq-core/cirq/transformers/measurement_transformers.py b/cirq-core/cirq/transformers/measurement_transformers.py index 24583e5bf1d..a613e109933 100644 --- a/cirq-core/cirq/transformers/measurement_transformers.py +++ b/cirq-core/cirq/transformers/measurement_transformers.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import itertools from typing import Any, Dict, List, Optional, TYPE_CHECKING, Union from cirq import ops, protocols, value @@ -111,23 +112,22 @@ def defer(op: 'cirq.Operation', _) -> 'cirq.OP_TREE': elif protocols.is_measurement(op): return [defer(op, None) for op in protocols.decompose_once(op)] elif op.classical_controls: - controls = [] + new_op = op.without_classical_controls() for c in op.classical_controls: if isinstance(c, value.KeyCondition): if c.key not in measurement_qubits: raise ValueError(f'Deferred measurement for key={c.key} not found.') - qubits = measurement_qubits[c.key] - if len(qubits) != 1: - # TODO: Multi-qubit conditions require - # https://github.com/quantumlib/Cirq/issues/4512 - # Remember to update docstring above once this works. - raise ValueError('Only single qubit conditions are allowed.') - controls.extend(qubits) + qs = measurement_qubits[c.key] + if len(qs) != 1: + all_values = itertools.product(*[range(q.dimension) for q in qs]) + anything_but_all_zeros = tuple(itertools.islice(all_values, 1, None)) + control_values = ops.SumOfProducts(anything_but_all_zeros) + else: + control_values = range(1, qs[0].dimension) + new_op = new_op.controlled_by(*qs, control_values=control_values) else: raise ValueError('Only KeyConditions are allowed.') - return op.without_classical_controls().controlled_by( - *controls, control_values=[tuple(range(1, q.dimension)) for q in controls] - ) + return new_op return op circuit = transformer_primitives.map_operations_and_unroll( diff --git a/cirq-core/cirq/transformers/measurement_transformers_test.py b/cirq-core/cirq/transformers/measurement_transformers_test.py index cd1041f2860..1308614a82d 100644 --- a/cirq-core/cirq/transformers/measurement_transformers_test.py +++ b/cirq-core/cirq/transformers/measurement_transformers_test.py @@ -225,6 +225,31 @@ def test_multi_qubit_measurements(): ) +def test_multi_qubit_control(): + q0, q1, q2 = cirq.LineQubit.range(3) + circuit = cirq.Circuit( + cirq.measure(q0, q1, key='a'), + cirq.X(q2).with_classical_controls('a'), + cirq.measure(q2, key='b'), + ) + assert_equivalent_to_deferred(circuit) + deferred = cirq.defer_measurements(circuit) + q_ma0 = _MeasurementQid('a', q0) + q_ma1 = _MeasurementQid('a', q1) + cirq.testing.assert_same_circuits( + deferred, + cirq.Circuit( + cirq.CX(q0, q_ma0), + cirq.CX(q1, q_ma1), + cirq.X(q2).controlled_by( + q_ma0, q_ma1, control_values=cirq.SumOfProducts(((0, 1), (1, 0), (1, 1))) + ), + cirq.measure(q_ma0, q_ma1, key='a'), + cirq.measure(q2, key='b'), + ), + ) + + def test_diagram(): q0, q1, q2, q3 = cirq.LineQubit.range(4) circuit = cirq.Circuit( @@ -270,13 +295,6 @@ def test_repr(qid: _MeasurementQid): test_repr(_MeasurementQid('0:1:a', cirq.LineQid(9, 4))) -def test_multi_qubit_control(): - q0, q1 = cirq.LineQubit.range(2) - circuit = cirq.Circuit(cirq.measure(q0, q1, key='a'), cirq.X(q1).with_classical_controls('a')) - with pytest.raises(ValueError, match='Only single qubit conditions are allowed'): - _ = cirq.defer_measurements(circuit) - - def test_sympy_control(): q0, q1 = cirq.LineQubit.range(2) circuit = cirq.Circuit( From d56d1417932646d2cacbb3f6f924ef82c59e5baa Mon Sep 17 00:00:00 2001 From: Dax Fohl Date: Fri, 15 Jul 2022 19:46:52 -0700 Subject: [PATCH 2/4] mypy --- cirq-core/cirq/transformers/measurement_transformers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cirq-core/cirq/transformers/measurement_transformers.py b/cirq-core/cirq/transformers/measurement_transformers.py index a613e109933..7f68f22cc42 100644 --- a/cirq-core/cirq/transformers/measurement_transformers.py +++ b/cirq-core/cirq/transformers/measurement_transformers.py @@ -121,7 +121,7 @@ def defer(op: 'cirq.Operation', _) -> 'cirq.OP_TREE': if len(qs) != 1: all_values = itertools.product(*[range(q.dimension) for q in qs]) anything_but_all_zeros = tuple(itertools.islice(all_values, 1, None)) - control_values = ops.SumOfProducts(anything_but_all_zeros) + control_values: Any = ops.SumOfProducts(anything_but_all_zeros) else: control_values = range(1, qs[0].dimension) new_op = new_op.controlled_by(*qs, control_values=control_values) From 23042d3193dcd6a87da70b97de711359cc6bb47d Mon Sep 17 00:00:00 2001 From: Dax Fohl Date: Sat, 16 Jul 2022 07:59:41 -0700 Subject: [PATCH 3/4] invert if branch --- cirq-core/cirq/transformers/measurement_transformers.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cirq-core/cirq/transformers/measurement_transformers.py b/cirq-core/cirq/transformers/measurement_transformers.py index 7f68f22cc42..932dbd182e4 100644 --- a/cirq-core/cirq/transformers/measurement_transformers.py +++ b/cirq-core/cirq/transformers/measurement_transformers.py @@ -118,12 +118,12 @@ def defer(op: 'cirq.Operation', _) -> 'cirq.OP_TREE': if c.key not in measurement_qubits: raise ValueError(f'Deferred measurement for key={c.key} not found.') qs = measurement_qubits[c.key] - if len(qs) != 1: + if len(qs) == 1: + control_values: Any = range(1, qs[0].dimension) + else: all_values = itertools.product(*[range(q.dimension) for q in qs]) anything_but_all_zeros = tuple(itertools.islice(all_values, 1, None)) - control_values: Any = ops.SumOfProducts(anything_but_all_zeros) - else: - control_values = range(1, qs[0].dimension) + control_values = ops.SumOfProducts(anything_but_all_zeros) new_op = new_op.controlled_by(*qs, control_values=control_values) else: raise ValueError('Only KeyConditions are allowed.') From 5e63f1dc3b0606da8b93858c0b21a38d64792a68 Mon Sep 17 00:00:00 2001 From: Dax Fohl Date: Sun, 17 Jul 2022 06:38:14 -0700 Subject: [PATCH 4/4] docstring --- cirq-core/cirq/transformers/measurement_transformers.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cirq-core/cirq/transformers/measurement_transformers.py b/cirq-core/cirq/transformers/measurement_transformers.py index 932dbd182e4..2e101bef4a4 100644 --- a/cirq-core/cirq/transformers/measurement_transformers.py +++ b/cirq-core/cirq/transformers/measurement_transformers.py @@ -82,9 +82,7 @@ def defer_measurements( A circuit with equivalent logic, but all measurements at the end of the circuit. Raises: - ValueError: If sympy-based classical conditions are used, or if - conditions based on multi-qubit measurements exist. (The latter of - these is planned to be implemented soon). + ValueError: If sympy-based classical conditions are used. NotImplementedError: When attempting to defer a measurement with a confusion map. (https://github.com/quantumlib/Cirq/issues/5482) """