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

Deferred measurements transformer #4849

Merged
merged 56 commits into from
Feb 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
ad1d5b5
defer
daxfohl Jan 17, 2022
88c0951
ignore terminal
daxfohl Jan 17, 2022
c158130
Merge branch 'master' into defer
daxfohl Jan 17, 2022
7ed369a
comment
daxfohl Jan 17, 2022
7e81213
try with_scope
daxfohl Jan 18, 2022
202e05f
try things
daxfohl Jan 18, 2022
50c4e20
done
daxfohl Jan 19, 2022
d69684a
simulator tests
daxfohl Jan 19, 2022
4e6ec74
go back to flattening subcircuits
daxfohl Jan 19, 2022
a52f520
Add a dephase transformer
daxfohl Jan 19, 2022
81fda35
Revert to flattening subcircuits, split out dephase transformer
daxfohl Jan 19, 2022
08092b9
lint
daxfohl Jan 19, 2022
417d1b3
error on multi-qubit measurements
daxfohl Jan 19, 2022
02161be
Merge branch 'master' into defer
daxfohl Jan 19, 2022
11ca909
Add expected circuits to tests
daxfohl Jan 20, 2022
3216b87
lint
daxfohl Jan 20, 2022
2db017b
tests
daxfohl Jan 20, 2022
43b9847
rename
daxfohl Jan 20, 2022
475518a
Update mux to use dephase
daxfohl Jan 20, 2022
263c2fc
Merge branch 'master' into defer
daxfohl Jan 20, 2022
85ddd15
pydocs
daxfohl Jan 20, 2022
7639a5c
format
daxfohl Jan 20, 2022
15200fe
Merge branch 'master' into defer
daxfohl Jan 20, 2022
438df1a
format
daxfohl Jan 20, 2022
4b1ed60
Merge remote-tracking branch 'origin/defer' into defer
daxfohl Jan 20, 2022
3d33639
cover
daxfohl Jan 20, 2022
f1169c5
use test_equiv_repr
daxfohl Jan 20, 2022
ecd74db
code review
daxfohl Jan 23, 2022
66ec384
docstring
daxfohl Jan 23, 2022
1853929
decompose nonstandard measurements
daxfohl Jan 23, 2022
1683aef
docstrings
daxfohl Jan 23, 2022
75a5a79
test
daxfohl Jan 23, 2022
5f17fa6
Merge branch 'master' into defer
daxfohl Jan 23, 2022
5ce5349
Merge branch 'master' into defer
daxfohl Jan 25, 2022
ca57376
Add transformer annotation
daxfohl Jan 25, 2022
5e7bf8c
Merge branch 'master' into defer
daxfohl Jan 26, 2022
96911ec
Merge branch 'master' into defer
daxfohl Jan 26, 2022
faac6a1
Merge branch 'master' into defer
daxfohl Jan 28, 2022
6bf3e71
fix build after merge
daxfohl Jan 28, 2022
e536af4
lint
daxfohl Jan 28, 2022
745a23d
check nocompile
daxfohl Jan 29, 2022
b6ed9ea
remove unneeded type annotation
daxfohl Jan 29, 2022
7214db2
Merge branch 'master' into defer
daxfohl Feb 3, 2022
02c9430
fix merge
daxfohl Feb 3, 2022
f11da9f
nits
daxfohl Feb 3, 2022
1465a10
Merge branch 'master' into defer
daxfohl Feb 4, 2022
44cd536
hash VirtualTag
daxfohl Feb 4, 2022
b74528c
Merge branch 'master' into defer
daxfohl Feb 5, 2022
6da3560
fix merge error
daxfohl Feb 5, 2022
921dcb9
use map_ops(deep)
daxfohl Feb 5, 2022
453db88
improve ignored tags logic
daxfohl Feb 5, 2022
9942b74
improve virtual tag hash
daxfohl Feb 5, 2022
37c6a47
Merge branch 'master' into defer
daxfohl Feb 8, 2022
93ad7e7
Merge branch 'master' into defer
daxfohl Feb 8, 2022
40ac14d
use tags_to_ignore
daxfohl Feb 8, 2022
2d465b9
Merge branch 'master' into defer
CirqBot Feb 9, 2022
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
2 changes: 2 additions & 0 deletions cirq-core/cirq/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,8 @@
decompose_multi_controlled_x,
decompose_multi_controlled_rotation,
decompose_two_qubit_interaction_into_four_fsim_gates,
defer_measurements,
dephase_measurements,
drop_empty_moments,
drop_negligible_operations,
eject_phased_paulis,
Expand Down
2 changes: 1 addition & 1 deletion cirq-core/cirq/ops/kraus_channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def __init__(
self._key = key

@staticmethod
def from_channel(channel: 'KrausChannel', key: Union[str, 'cirq.MeasurementKey', None] = None):
def from_channel(channel: 'cirq.Gate', key: Union[str, 'cirq.MeasurementKey', None] = None):
daxfohl marked this conversation as resolved.
Show resolved Hide resolved
"""Creates a copy of a channel with the given measurement key."""
return KrausChannel(kraus_ops=list(protocols.kraus(channel)), key=key)

Expand Down
3 changes: 3 additions & 0 deletions cirq-core/cirq/ops/tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,6 @@ def __repr__(self) -> str:

def _json_dict_(self) -> Dict[str, str]:
return {}

def __hash__(self):
return hash(VirtualTag)
6 changes: 4 additions & 2 deletions cirq-core/cirq/sim/mux.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from cirq._doc import document
from cirq.sim import sparse_simulator, density_matrix_simulator
from cirq.sim.clifford import clifford_simulator
from cirq.transformers import measurement_transformers

if TYPE_CHECKING:
import cirq
Expand Down Expand Up @@ -281,9 +282,10 @@ def final_density_matrix(
dtype=dtype,
noise=noise,
seed=seed,
ignore_measurement_results=(ignore_measurement_results),
).simulate(
program=circuit_like,
program=measurement_transformers.dephase_measurements(circuit_like)
if ignore_measurement_results
else circuit_like,
initial_state=initial_state,
qubit_order=qubit_order,
param_resolver=param_resolver,
Expand Down
5 changes: 5 additions & 0 deletions cirq-core/cirq/transformers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@

from cirq.transformers.eject_z import eject_z

from cirq.transformers.measurement_transformers import (
defer_measurements,
dephase_measurements,
)

from cirq.transformers.synchronize_terminal_measurements import synchronize_terminal_measurements

from cirq.transformers.transformer_api import (
Expand Down
177 changes: 177 additions & 0 deletions cirq-core/cirq/transformers/measurement_transformers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
# Copyright 2022 The Cirq Developers
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import Any, Dict, List, Optional, TYPE_CHECKING, Union

from cirq import ops, protocols, value
from cirq.transformers import (
transformer_api,
transformer_primitives,
)
from cirq.transformers.synchronize_terminal_measurements import find_terminal_measurements

if TYPE_CHECKING:
import cirq


class _MeasurementQid(ops.Qid):
"""A qubit that substitutes in for a deferred measurement.

Exactly one qubit will be created per qubit in the measurement gate.
"""

def __init__(self, key: Union[str, 'cirq.MeasurementKey'], qid: 'cirq.Qid'):
"""Initializes the qubit.

Args:
key: The key of the measurement gate being deferred.
qid: One qubit that is being measured. Each deferred measurement
should create one new _MeasurementQid per qubit being measured
by that gate.
"""
self._key = value.MeasurementKey.parse_serialized(key) if isinstance(key, str) else key
self._qid = qid

@property
def dimension(self) -> int:
return self._qid.dimension

def _comparison_key(self) -> Any:
return (str(self._key), self._qid._comparison_key())

def __str__(self) -> str:
return f"M('{self._key}', q={self._qid})"

def __repr__(self) -> str:
return f'_MeasurementQid({self._key!r}, {self._qid!r})'


@transformer_api.transformer
def defer_measurements(
circuit: 'cirq.AbstractCircuit', *, context: Optional['cirq.TransformerContext'] = None
) -> 'cirq.Circuit':
"""Implements the Deferred Measurement Principle.

Uses the Deferred Measurement Principle to move all measurements to the
end of the circuit. All non-terminal measurements are changed to
conditional quantum gates onto ancilla qubits, and classically controlled
operations are transformed to quantum controls from those ancilla qubits.
Finally, measurements of all ancilla qubits are appended to the end of the
circuit.

Optimizing deferred measurements is an area of active research, and future
iterations may contain optimizations that reduce the number of ancilla
qubits, so one should not depend on the exact shape of the output from this
function. Only the logical equivalence is guaranteed to remain unchanged.
Moment and subcircuit structure is not preserved.

Args:
circuit: The circuit to transform. It will not be modified.
context: `cirq.TransformerContext` storing common configurable options
for transformers.
Returns:
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).
"""

circuit = transformer_primitives.unroll_circuit_op(circuit, deep=True, tags_to_check=None)
terminal_measurements = {op for _, op in find_terminal_measurements(circuit)}
measurement_qubits: Dict['cirq.MeasurementKey', List['_MeasurementQid']] = {}

def defer(op: 'cirq.Operation', _) -> 'cirq.OP_TREE':
if op in terminal_measurements:
return op
gate = op.gate
if isinstance(gate, ops.MeasurementGate):
key = value.MeasurementKey.parse_serialized(gate.key)
targets = [_MeasurementQid(key, q) for q in op.qubits]
measurement_qubits[key] = targets
cxs = [ops.CX(q, target) for q, target in zip(op.qubits, targets)]
xs = [ops.X(targets[i]) for i, b in enumerate(gate.full_invert_mask()) if b]
return cxs + xs
elif protocols.is_measurement(op):
return [defer(op, None) for op in protocols.decompose_once(op)]
elif op.classical_controls:
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)
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 op

circuit = transformer_primitives.map_operations_and_unroll(
circuit=circuit,
map_func=defer,
tags_to_ignore=context.tags_to_ignore if context else (),
raise_if_add_qubits=False,
).unfreeze()
for k, qubits in measurement_qubits.items():
circuit.append(ops.measure(*qubits, key=k))
return circuit


@transformer_api.transformer
def dephase_measurements(
circuit: 'cirq.AbstractCircuit', *, context: Optional['cirq.TransformerContext'] = None
) -> 'cirq.Circuit':
"""Changes all measurements to a dephase operation.

This transformer is useful when using a density matrix simulator, when
wishing to calculate the final density matrix of a circuit and not simulate
the measurements themselves.

Args:
circuit: The circuit to transform. It will not be modified.
context: `cirq.TransformerContext` storing common configurable options
for transformers.
Returns:
A copy of the circuit, with dephase operations in place of all
measurements.
Raises:
ValueError: If the circuit contains classical controls. In this case,
it is required to change these to quantum controls via
`cirq.defer_measurements` first. Since deferral adds ancilla qubits
to the circuit, this is not done automatically, to prevent
surprises.
"""

def dephase(op: 'cirq.Operation', _) -> 'cirq.OP_TREE':
gate = op.gate
if isinstance(gate, ops.MeasurementGate):
key = value.MeasurementKey.parse_serialized(gate.key)
return ops.KrausChannel.from_channel(ops.phase_damp(1), key=key).on_each(op.qubits)
daxfohl marked this conversation as resolved.
Show resolved Hide resolved
elif isinstance(op, ops.ClassicallyControlledOperation):
raise ValueError('Use cirq.defer_measurements first to remove classical controls.')
return op

ignored = () if context is None else context.tags_to_ignore
return transformer_primitives.map_operations(
circuit, dephase, deep=True, tags_to_ignore=ignored
).unfreeze()
Loading