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

Scoping for control keys #4736

Merged
merged 106 commits into from
Dec 20, 2021
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
106 commits
Select commit Hold shift + click to select a range
b65b009
Remove decomposition in measurement_key_protocol
daxfohl Aug 25, 2021
56ab6c1
Merge branch 'master' into nodecompose
daxfohl Aug 25, 2021
60214b6
deprecation
daxfohl Aug 25, 2021
3b6480c
Fix deprecation description
daxfohl Aug 25, 2021
3943cb1
Simplify, removing unused method.
daxfohl Aug 25, 2021
2589da3
mypy
daxfohl Aug 25, 2021
49d75a0
Add control key protocol
daxfohl Aug 25, 2021
801e5bb
repr spec
daxfohl Aug 25, 2021
9f92126
Prevent infinite recursion
daxfohl Aug 25, 2021
4dfd6ba
infinite recur
daxfohl Sep 3, 2021
b611e74
Prevent infinite recursion, fix decompose check
daxfohl Aug 25, 2021
dc2e399
Prevent infinite recursion in test
daxfohl Aug 25, 2021
db21447
Fix tests
daxfohl Sep 3, 2021
4f073dd
Fix tests
daxfohl Sep 3, 2021
f2366a2
fix caching
daxfohl Sep 3, 2021
2116ce5
Doubled box lines charsets
daxfohl Sep 4, 2021
2c4e09d
Doubled box lines charsets
daxfohl Sep 4, 2021
2cb5b0d
readd missing tuple names
daxfohl Sep 4, 2021
db74173
fix deprecated protocol
daxfohl Sep 4, 2021
896f417
fix unit tests
daxfohl Sep 4, 2021
e106375
fix unit tests
daxfohl Sep 5, 2021
22c2914
test
daxfohl Sep 8, 2021
ad446ae
Simplify CircuitOperation.mapped_circuit implementation
daxfohl Nov 1, 2021
d9bd703
Ensure subcircuit.mapped_circuit applies parent path if no repetitions
daxfohl Nov 2, 2021
f802453
format
daxfohl Nov 2, 2021
08b93b5
undo one change
daxfohl Nov 2, 2021
62024f6
Use prefix everywhere
daxfohl Nov 2, 2021
63903bb
coverage
daxfohl Nov 2, 2021
6c4f13f
format
daxfohl Nov 2, 2021
3170f03
introduce scoping params
daxfohl Nov 2, 2021
d4c133e
thread scoping params through subcircuits
daxfohl Nov 3, 2021
bcb569a
Put conflict check into MeasurementKey
daxfohl Nov 3, 2021
caffff6
Put conflict check into MeasurementKey
daxfohl Nov 3, 2021
97bf4dd
Put conflict check into MeasurementKey
daxfohl Nov 3, 2021
d9a9da4
Merge branch 'master' into doublelines2
daxfohl Nov 4, 2021
08f6667
Only add @ symbol if qubit map is there.
daxfohl Nov 4, 2021
4fd1208
Fix merge error
daxfohl Nov 4, 2021
236d719
mypy
daxfohl Nov 4, 2021
9c3f1f1
code review comments
daxfohl Nov 4, 2021
47beb37
Only add symbols for control keys we're drawing
daxfohl Nov 4, 2021
0659c48
mypy
daxfohl Nov 4, 2021
ab1e1b6
mypy
daxfohl Nov 4, 2021
956324d
Remove control_keys protocol implementation from circuit; it was left…
daxfohl Nov 4, 2021
e7c9510
Only draw cbits that are controls
daxfohl Nov 4, 2021
2f6e66a
Rename control_keys to cbits
daxfohl Nov 4, 2021
dd8fdb8
foprmat
daxfohl Nov 4, 2021
b2b5370
Test ControlOp
daxfohl Nov 4, 2021
db169b3
format
daxfohl Nov 4, 2021
f44cc10
More tests
daxfohl Nov 5, 2021
58b3bca
Coverage
daxfohl Nov 5, 2021
8fa7ea2
more tests
daxfohl Nov 5, 2021
a1eced2
more test
daxfohl Nov 5, 2021
06883d5
conditional operation
daxfohl Nov 5, 2021
05eae73
teleportation
daxfohl Nov 5, 2021
d09250f
teleportation
daxfohl Nov 5, 2021
5bdad13
QASM support
daxfohl Nov 6, 2021
37adbd0
test all simulators
daxfohl Nov 6, 2021
0dc073b
format
daxfohl Nov 6, 2021
e7b1d0c
Remove mps from examples
daxfohl Nov 6, 2021
55c61a7
Merge branch 'cbit' into scope2
daxfohl Nov 10, 2021
427208e
scope for conditionals
daxfohl Nov 10, 2021
516d54d
tests
daxfohl Nov 10, 2021
155cdff
remove extra extern_keys param
daxfohl Nov 10, 2021
8d76826
include lower control keys
daxfohl Nov 10, 2021
7e454fa
Merge branch 'cbit' into scope2
daxfohl Nov 10, 2021
0388108
format
daxfohl Nov 10, 2021
2778248
repr
daxfohl Nov 10, 2021
02ad5e8
Merge branch 'cbit' into scope2
daxfohl Nov 10, 2021
b566cdb
format
daxfohl Nov 10, 2021
c321061
add protocol and test for circuits
daxfohl Nov 10, 2021
9c08890
fix deep mapped circuit
daxfohl Nov 10, 2021
da8b2e6
Infinite recursion
daxfohl Nov 10, 2021
95c152b
get control keys from circuit
daxfohl Nov 10, 2021
6b37abf
Merge branch 'master' into cbit
daxfohl Nov 10, 2021
d8b2d89
bugs
daxfohl Nov 10, 2021
fba189c
cover
daxfohl Nov 11, 2021
6709627
Merge branch 'cbit' into scope3
daxfohl Nov 11, 2021
7c532b8
tests
daxfohl Nov 11, 2021
4c37c8f
rename
daxfohl Nov 11, 2021
1b410ff
rename pre merge
daxfohl Dec 8, 2021
eb5c4a2
Merge branch 'master' into scope5
daxfohl Dec 8, 2021
4d55ead
format/mypy
daxfohl Dec 8, 2021
25e8bdf
reorder imports, remove `conditions`
daxfohl Dec 8, 2021
d768ee4
fix diagrams
daxfohl Dec 8, 2021
6b767fa
lint
daxfohl Dec 8, 2021
fd3660e
code nits
daxfohl Dec 10, 2021
d374d64
Add decomposition tests
daxfohl Dec 10, 2021
1b97327
Add some tests.
daxfohl Dec 10, 2021
946f14a
with_rescoped_keys
daxfohl Dec 12, 2021
3ccc79c
varargs
daxfohl Dec 12, 2021
31c3d1a
pydoc, format
daxfohl Dec 12, 2021
52c0faa
format
daxfohl Dec 12, 2021
57922f7
format
daxfohl Dec 12, 2021
dad481f
format
daxfohl Dec 12, 2021
f8ee6d1
format
daxfohl Dec 12, 2021
03f72a8
format
daxfohl Dec 12, 2021
8c9794a
coverage
daxfohl Dec 12, 2021
e7eb47a
Merge branch 'master' into scope5
daxfohl Dec 12, 2021
834a5eb
interface
daxfohl Dec 12, 2021
e6f7d26
simplify
daxfohl Dec 12, 2021
600dc44
Merge branch 'scope5' of https://github.com/daxfohl/Cirq into scope5
daxfohl Dec 12, 2021
f520402
format
daxfohl Dec 12, 2021
b3df03b
fix path
daxfohl Dec 12, 2021
544e0d5
make sure to add path to existing keys
daxfohl Dec 13, 2021
f2f1a34
improve docstring on extern_keys.
daxfohl Dec 13, 2021
001e989
Merge branch 'master' into scope5
CirqBot Dec 20, 2021
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
1 change: 1 addition & 0 deletions cirq-core/cirq/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -597,6 +597,7 @@
with_key_path,
with_key_path_prefix,
with_measurement_key_mapping,
with_rescoped_keys,
)

from cirq.ion import (
Expand Down
22 changes: 12 additions & 10 deletions cirq-core/cirq/circuits/circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -908,17 +908,21 @@ def _with_key_path_(self, path: Tuple[str, ...]):
[protocols.with_key_path(moment, path) for moment in self.moments]
)

def _with_key_path_prefix_(
def _with_key_path_prefix_(self, prefix: Tuple[str, ...]):
return self._with_sliced_moments(
[protocols.with_key_path_prefix(moment, prefix) for moment in self.moments]
)

def _with_rescoped_keys_(
self,
path: Tuple[str, ...],
local_keys: FrozenSet[value.MeasurementKey],
extern_keys: FrozenSet[value.MeasurementKey],
bindable_keys: FrozenSet['cirq.MeasurementKey'],
):
moments = []
for moment in self.moments:
moment_keys = protocols.measurement_key_objs(moment)
moments.append(protocols.with_key_path_prefix(moment, path, local_keys, extern_keys))
local_keys = local_keys.union(moment_keys)
new_moment = protocols.with_rescoped_keys(moment, path, bindable_keys)
moments.append(new_moment)
bindable_keys |= protocols.measurement_key_objs(new_moment)
return self._with_sliced_moments(moments)

def _qid_shape_(self) -> Tuple[int, ...]:
Expand Down Expand Up @@ -1534,10 +1538,8 @@ def factorize(self: CIRCUIT_TYPE) -> Iterable[CIRCUIT_TYPE]:
)

def _control_keys_(self) -> FrozenSet[value.MeasurementKey]:
all_keys: FrozenSet[value.MeasurementKey] = frozenset()
for op in self.all_operations():
all_keys = all_keys.union(protocols.control_keys(op))
return all_keys.difference(protocols.measurement_key_objs(self))
controls = frozenset(k for op in self.all_operations() for k in protocols.control_keys(op))
return controls - protocols.measurement_key_objs(self)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To confirm: a circuit which contains control and measurement of key "A" excludes "A" from what it returns here because "A" is internal, correct? (i.e. it's measured and used entirely within the circuit)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the protocol returns all keys that must be defined externally before this element can be used.



def _overlap_collision_time(
Expand Down
55 changes: 28 additions & 27 deletions cirq-core/cirq/circuits/circuit_operation.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,12 @@ class CircuitOperation(ops.Operation):
initialized to strings for numbers in `range(repetitions)`.
parent_path: A tuple of identifiers for any parent CircuitOperations
containing this one.
extern_keys: The set of measurement keys defined at extern scope.
extern_keys: The set of measurement keys defined at extern scope. The
values here are used by decomposition and simulation routines to
cache which external measurement keys exist as possible binding
targets for unbound `ClassicallyControlledOperation` keys. This
field is not intended to be set or changed manually, and should be
empty in circuits that aren't in the middle of decomposition.
"""

_hash: Optional[int] = dataclasses.field(default=None, init=False)
Expand All @@ -99,7 +104,7 @@ class CircuitOperation(ops.Operation):
param_resolver: study.ParamResolver = study.ParamResolver()
repetition_ids: Optional[List[str]] = dataclasses.field(default=None)
parent_path: Tuple[str, ...] = dataclasses.field(default_factory=tuple)
extern_keys: FrozenSet[value.MeasurementKey] = dataclasses.field(default_factory=frozenset)
extern_keys: FrozenSet['cirq.MeasurementKey'] = dataclasses.field(default_factory=frozenset)

def __post_init__(self):
if not isinstance(self.circuit, circuits.FrozenCircuit):
Expand Down Expand Up @@ -188,9 +193,7 @@ def _measurement_key_objs_(self) -> AbstractSet[value.MeasurementKey]:
for repetition_id in self.repetition_ids
for key in circuit_keys
}
circuit_keys = {
protocols.with_key_path_prefix(key, self.parent_path) for key in circuit_keys
}
circuit_keys = {key.with_key_path_prefix(*self.parent_path) for key in circuit_keys}
object.__setattr__(
self,
'_cached_measurement_key_objs',
Expand All @@ -206,7 +209,7 @@ def _measurement_key_names_(self) -> AbstractSet[str]:

def _control_keys_(self) -> AbstractSet[value.MeasurementKey]:
if not protocols.control_keys(self.circuit):
return set()
return frozenset()
return protocols.control_keys(self.mapped_circuit())

def _parameter_names_(self) -> AbstractSet[str]:
Expand Down Expand Up @@ -244,10 +247,10 @@ def mapped_circuit(self, deep: bool = False) -> 'cirq.Circuit':
circuit = circuit * abs(self.repetitions)
else:
circuit = circuits.Circuit(
protocols.with_key_path_prefix(circuit, (rep,)) for rep in self.repetition_ids
protocols.with_rescoped_keys(circuit, (rep,)) for rep in self.repetition_ids
)
circuit = protocols.with_key_path_prefix(
circuit, self.parent_path, extern_keys=self.extern_keys
circuit = protocols.with_rescoped_keys(
circuit, self.parent_path, bindable_keys=self.extern_keys
)
if deep:
circuit = circuit.map_operations(
Expand Down Expand Up @@ -438,22 +441,23 @@ def __pow__(self, power: int) -> 'CircuitOperation':
def _with_key_path_(self, path: Tuple[str, ...]):
return dataclasses.replace(self, parent_path=path)

def _with_key_path_prefix_(
def _with_key_path_prefix_(self, prefix: Tuple[str, ...]):
return dataclasses.replace(self, parent_path=prefix + self.parent_path)

def _with_rescoped_keys_(
self,
path: Tuple[str, ...],
local_keys: FrozenSet[value.MeasurementKey],
extern_keys: FrozenSet[value.MeasurementKey],
bindable_keys: FrozenSet['cirq.MeasurementKey'],
):
new_keys = set(extern_keys)
for key in local_keys:
new_keys.add(protocols.with_key_path_prefix(key, path))
for key in self.extern_keys:
new_keys.add(protocols.with_key_path_prefix(key, path))
return dataclasses.replace(
self,
parent_path=path + self.parent_path,
extern_keys=frozenset(new_keys),
)
# The following line prevents binding to measurement keys in previous repeated subcircuits
# "just because their repetition ids matched". If we eventually decide to change that
# requirement and allow binding across subcircuits (possibly conditionally upon the key or
# the subcircuit having some 'allow_cross_circuit_binding' field set), this is the line to
# change or remove.
bindable_keys = frozenset(k for k in bindable_keys if len(k.path) <= len(path))
bindable_keys |= {k.with_key_path_prefix(*path) for k in self.extern_keys}
path += self.parent_path
return dataclasses.replace(self, parent_path=path, extern_keys=bindable_keys)

def with_key_path(self, path: Tuple[str, ...]):
return self._with_key_path_(path)
Expand Down Expand Up @@ -550,11 +554,8 @@ def with_measurement_key_mapping(self, key_map: Dict[str, str]) -> 'CircuitOpera
if k_new != k:
new_map[k] = k_new
new_op = self.replace(measurement_key_map=new_map)
if (
len(protocols.measurement_key_objs(new_op)) != len(protocols.measurement_key_objs(self))
or len(protocols.control_keys(new_op)) != len(protocols.control_keys(self))
or len(protocols.measurement_keys_touched(new_op))
!= len(protocols.measurement_keys_touched(self))
if len(protocols.measurement_keys_touched(new_op)) != len(
protocols.measurement_keys_touched(self)
):
raise ValueError(
f'Collision in measurement key map composition. Original map:\n'
Expand Down
13 changes: 0 additions & 13 deletions cirq-core/cirq/circuits/circuit_operation_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -844,19 +844,6 @@ def test_keys_conflict_no_repetitions():
_ = op2.mapped_circuit(deep=True)


def test_keys_conflict_with_repetitions():
q = cirq.LineQubit(0)
op1 = cirq.CircuitOperation(
cirq.FrozenCircuit(
cirq.measure(q, key='A'),
),
repetitions=2,
)
op2 = cirq.CircuitOperation(cirq.FrozenCircuit(op1, op1), repetitions=2)
with pytest.raises(ValueError, match='Conflicting measurement keys found: 0:0:A'):
_ = op2.mapped_circuit(deep=True)


def test_keys_conflict_locally():
q = cirq.LineQubit(0)
op1 = cirq.measure(q, key='A')
Expand Down
32 changes: 16 additions & 16 deletions cirq-core/cirq/ops/classically_controlled_operation.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,31 +169,31 @@ def not_zero(measurement):
def _with_measurement_key_mapping_(
self, key_map: Dict[str, str]
) -> 'ClassicallyControlledOperation':
return ClassicallyControlledOperation(
self._sub_operation,
[protocols.with_measurement_key_mapping(k, key_map) for k in self._control_keys],
sub_operation = protocols.with_measurement_key_mapping(self._sub_operation, key_map)
sub_operation = self._sub_operation if sub_operation is NotImplemented else sub_operation
return sub_operation.with_classical_controls(
*[protocols.with_measurement_key_mapping(k, key_map) for k in self._control_keys]
)

def _with_key_path_prefix_(
def _with_key_path_prefix_(self, path: Tuple[str, ...]) -> 'ClassicallyControlledOperation':
keys = [protocols.with_key_path_prefix(k, path) for k in self._control_keys]
return self._sub_operation.with_classical_controls(*keys)

def _with_rescoped_keys_(
self,
path: Tuple[str, ...],
local_keys: FrozenSet[value.MeasurementKey],
extern_keys: FrozenSet[value.MeasurementKey],
bindable_keys: FrozenSet['cirq.MeasurementKey'],
) -> 'ClassicallyControlledOperation':
def map_key(key: value.MeasurementKey) -> value.MeasurementKey:
if key in local_keys:
return protocols.with_key_path_prefix(key, path)
for i in range(len(path)):
back_path = path[0 : len(path) - i]
new_key = protocols.with_key_path_prefix(key, back_path)
if new_key in extern_keys:
for i in range(len(path) + 1):
back_path = path[: len(path) - i]
new_key = key.with_key_path_prefix(*back_path)
if new_key in bindable_keys:
return new_key
return key

return ClassicallyControlledOperation(
self._sub_operation,
[map_key(k) for k in self._control_keys],
)
sub_operation = protocols.with_rescoped_keys(self._sub_operation, path, bindable_keys)
return sub_operation.with_classical_controls(*[map_key(k) for k in self._control_keys])

def _control_keys_(self) -> FrozenSet[value.MeasurementKey]:
return frozenset(self._control_keys).union(protocols.control_keys(self._sub_operation))
Expand Down
111 changes: 86 additions & 25 deletions cirq-core/cirq/ops/classically_controlled_operation_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,7 @@ def test_scope_local():
""",
use_unicode_characters=True,
)
assert circuit == cirq.Circuit(cirq.decompose(outer_subcircuit))


def test_scope_extern():
Expand Down Expand Up @@ -471,6 +472,45 @@ def test_scope_extern():
""",
use_unicode_characters=True,
)
assert circuit == cirq.Circuit(cirq.decompose(outer_subcircuit))


def test_scope_extern_wrapping_with_non_repeating_subcircuits():
def wrap(*ops):
return cirq.CircuitOperation(cirq.FrozenCircuit(*ops))

def wrap_frozen(*ops):
return cirq.FrozenCircuit(wrap(*ops))

q = cirq.LineQubit(0)
inner = wrap_frozen(
wrap(cirq.measure(q, key='a')),
wrap(cirq.X(q).with_classical_controls('b')),
)
middle = wrap_frozen(
wrap(cirq.measure(q, key=cirq.MeasurementKey('b'))),
wrap(cirq.CircuitOperation(inner, repetitions=2)),
)
outer_subcircuit = cirq.CircuitOperation(middle, repetitions=2)
circuit = outer_subcircuit.mapped_circuit(deep=True)
internal_control_keys = [
str(condition) for op in circuit.all_operations() for condition in cirq.control_keys(op)
]
assert internal_control_keys == ['0:b', '0:b', '1:b', '1:b']
assert not cirq.control_keys(outer_subcircuit)
assert not cirq.control_keys(circuit)
cirq.testing.assert_has_diagram(
circuit,
"""
0: ─────M───M('0:0:a')───X───M('0:1:a')───X───M───M('1:0:a')───X───M('1:1:a')───X───
║ ║ ║ ║ ║ ║
0:b: ═══@════════════════^════════════════^═══╬════════════════╬════════════════╬═══
║ ║ ║
1:b: ═════════════════════════════════════════@════════════════^════════════════^═══
""",
use_unicode_characters=True,
)
assert circuit == cirq.Circuit(cirq.decompose(outer_subcircuit))


def test_scope_root():
Expand Down Expand Up @@ -513,6 +553,7 @@ def test_scope_root():
""",
use_unicode_characters=True,
)
assert circuit == cirq.Circuit(cirq.decompose(outer_subcircuit))


def test_scope_extern_mismatch():
Expand Down Expand Up @@ -555,31 +596,7 @@ def test_scope_extern_mismatch():
""",
use_unicode_characters=True,
)


def test_scope_conflict():
q = cirq.LineQubit(0)
inner = cirq.Circuit(
cirq.measure(q, key='a'),
cirq.X(q).with_classical_controls('a'),
)
middle = cirq.Circuit(
cirq.measure(q, key=cirq.MeasurementKey('a', ('0',))),
cirq.CircuitOperation(inner.freeze(), repetitions=2),
)
op = cirq.CircuitOperation(middle.freeze(), repetitions=2)
assert not cirq.control_keys(op)
cirq.testing.assert_has_diagram(
cirq.Circuit(op),
"""
[ [ 0: ───M───X─── ] ]
0: ───[ 0: ───M('0:a')───[ ║ ║ ]──────────── ]────────────
[ [ a: ═══@═══^═══ ](loops=2) ](loops=2)
""",
use_unicode_characters=True,
)
with pytest.raises(ValueError, match='Conflicting measurement keys found: 0:0:a'):
_ = op.mapped_circuit(deep=True)
assert circuit == cirq.Circuit(cirq.decompose(outer_subcircuit))


def test_repr():
Expand Down Expand Up @@ -608,3 +625,47 @@ def test_unmeasured_condition():
),
):
_ = cirq.Simulator().simulate(bad_circuit)


def test_layered_circuit_operations_with_controls_in_between():
q = cirq.LineQubit(0)
outer_subcircuit = cirq.CircuitOperation(
cirq.Circuit(
cirq.CircuitOperation(
cirq.FrozenCircuit(
cirq.X(q),
cirq.Y(q),
)
).with_classical_controls('m')
).freeze()
)
circuit = outer_subcircuit.mapped_circuit(deep=True)
cirq.testing.assert_has_diagram(
cirq.Circuit(outer_subcircuit),
"""
[ 0: ───[ 0: ───X───Y─── ].with_classical_controls(m)─── ]
0: ───[ ║ ]───
[ m: ═══╩═══════════════════════════════════════════════ ]
m: ═══╩════════════════════════════════════════════════════════════
""",
use_unicode_characters=True,
)
cirq.testing.assert_has_diagram(
circuit,
"""
0: ───[ 0: ───X───Y─── ].with_classical_controls(m)───
m: ═══╩═══════════════════════════════════════════════
""",
use_unicode_characters=True,
)
cirq.testing.assert_has_diagram(
cirq.Circuit(cirq.decompose(outer_subcircuit)),
"""
0: ───X───Y───
║ ║
m: ═══^═══^═══
""",
use_unicode_characters=True,
)
Loading