diff --git a/cirq-core/cirq/circuits/circuit.py b/cirq-core/cirq/circuits/circuit.py index 5b3d64c9e55..bb298b823f8 100644 --- a/cirq-core/cirq/circuits/circuit.py +++ b/cirq-core/cirq/circuits/circuit.py @@ -20,10 +20,13 @@ """ import abc +import contextlib import enum import html import itertools import math +import re +import warnings from collections import defaultdict from typing import ( AbstractSet, @@ -50,7 +53,7 @@ import numpy as np import cirq._version -from cirq import devices, ops, protocols, qis +from cirq import _compat, devices, ops, protocols, qis from cirq.circuits._bucket_priority_queue import BucketPriorityQueue from cirq.circuits.circuit_operation import CircuitOperation from cirq.circuits.insert_strategy import InsertStrategy @@ -70,6 +73,17 @@ _DEVICE_DEP_MESSAGE = 'Attaching devices to circuits will no longer be supported.' +@contextlib.contextmanager +def _block_overlapping_deprecation(): + with warnings.catch_warnings(): + warnings.filterwarnings( + action='ignore', + category=DeprecationWarning, + message=f'(.|\n)*{re.escape(_DEVICE_DEP_MESSAGE)}(.|\n)*', + ) + yield + + class Alignment(enum.Enum): # Stop when left ends are lined up. LEFT = 1 @@ -139,7 +153,10 @@ def freeze(self) -> 'cirq.FrozenCircuit': if isinstance(self, FrozenCircuit): return self - return FrozenCircuit(self, strategy=InsertStrategy.EARLIEST, device=self.device) + + if self._device == cirq.UNCONSTRAINED_DEVICE: + return FrozenCircuit(self, strategy=InsertStrategy.EARLIEST) + return FrozenCircuit(self, strategy=InsertStrategy.EARLIEST, device=self._device) def unfreeze(self, copy: bool = True) -> 'cirq.Circuit': """Creates a Circuit from this circuit. @@ -149,7 +166,10 @@ def unfreeze(self, copy: bool = True) -> 'cirq.Circuit': """ if isinstance(self, Circuit): return Circuit.copy(self) if copy else self - return Circuit(self, strategy=InsertStrategy.EARLIEST, device=self.device) + + if self._device == cirq.UNCONSTRAINED_DEVICE: + return Circuit(self, strategy=InsertStrategy.EARLIEST) + return Circuit(self, strategy=InsertStrategy.EARLIEST, device=self._device) def __bool__(self): return bool(self.moments) @@ -157,7 +177,7 @@ def __bool__(self): def __eq__(self, other): if not isinstance(other, AbstractCircuit): return NotImplemented - return tuple(self.moments) == tuple(other.moments) and self.device == other.device + return tuple(self.moments) == tuple(other.moments) and self._device == other._device def _approx_eq_(self, other: Any, atol: Union[int, float]) -> bool: """See `cirq.protocols.SupportsApproximateEquality`.""" @@ -165,7 +185,7 @@ def _approx_eq_(self, other: Any, atol: Union[int, float]) -> bool: return NotImplemented return ( cirq.protocols.approx_eq(tuple(self.moments), tuple(other.moments), atol=atol) - and self.device == other.device + and self._device == other._device ) def __ne__(self, other) -> bool: @@ -241,7 +261,7 @@ def __repr__(self) -> str: args = [] if self.moments: args.append(_list_repr_with_indented_item_lines(self.moments)) - if self.device != devices.UNCONSTRAINED_DEVICE: + if self._device != devices.UNCONSTRAINED_DEVICE: args.append(f'device={self.device!r}') return f'cirq.{cls_name}({", ".join(args)})' @@ -1323,11 +1343,16 @@ def save_qasm( self._to_qasm_output(header, precision, qubit_order).save(file_path) def _json_dict_(self): - return protocols.obj_to_dict_helper(self, ['moments', 'device']) + ret = protocols.obj_to_dict_helper(self, ['moments', '_device']) + ret['device'] = ret['_device'] + del ret['_device'] + return ret @classmethod def _from_json_dict_(cls, moments, device, **kwargs): - return cls(moments, strategy=InsertStrategy.EARLIEST, device=device) + if device == cirq.UNCONSTRAINED_DEVICE: + return cls(moments, strategy=InsertStrategy.EARLIEST) + return cls(moments, device=device, strategy=InsertStrategy.EARLIEST) def zip( *circuits: 'cirq.AbstractCircuit', align: Union['cirq.Alignment', str] = Alignment.LEFT @@ -1677,6 +1702,12 @@ class Circuit(AbstractCircuit): independent 'factors' of the original Circuit. """ + @_compat.deprecated_parameter( + deadline='v0.15', + fix=_DEVICE_DEP_MESSAGE, + parameter_desc='device', + match=lambda args, kwargs: 'device' in kwargs, + ) def __init__( self, *contents: 'cirq.OP_TREE', @@ -1701,11 +1732,19 @@ def __init__( self._device = device self.append(contents, strategy=strategy) - @property - def device(self) -> 'cirq.Device': + @property # type: ignore + @_compat.deprecated( + deadline='v0.15', + fix=_DEVICE_DEP_MESSAGE, + ) + def device(self) -> devices.Device: return self._device - @device.setter + @device.setter # type: ignore + @_compat.deprecated( + deadline='v0.15', + fix=_DEVICE_DEP_MESSAGE, + ) def device(self, new_device: 'cirq.Device') -> None: new_device.validate_circuit(self) self._device = new_device @@ -1713,13 +1752,20 @@ def device(self, new_device: 'cirq.Device') -> None: def __copy__(self) -> 'cirq.Circuit': return self.copy() - def copy(self) -> 'cirq.Circuit': - copied_circuit = Circuit(device=self._device) + def copy(self) -> 'Circuit': + if self._device == cirq.UNCONSTRAINED_DEVICE: + copied_circuit = Circuit() + else: + copied_circuit = Circuit(device=self._device) copied_circuit._moments = self._moments[:] return copied_circuit - def _with_sliced_moments(self, moments: Iterable['cirq.Moment']) -> 'cirq.Circuit': - new_circuit = Circuit(device=self.device) + def _with_sliced_moments(self, moments: Iterable['cirq.Moment']) -> 'Circuit': + if self._device == cirq.UNCONSTRAINED_DEVICE: + new_circuit = Circuit() + else: + new_circuit = Circuit(device=self._device) + new_circuit._moments = list(moments) return new_circuit @@ -1759,8 +1805,8 @@ def __iadd__(self, other): def __add__(self, other): if isinstance(other, type(self)): if ( - devices.UNCONSTRAINED_DEVICE not in [self._device, other.device] - and self._device != other.device + devices.UNCONSTRAINED_DEVICE not in [self._device, other._device] + and self._device != other._device ): raise ValueError("Can't add circuits with incompatible devices.") elif not isinstance(other, (ops.Operation, Iterable)): @@ -1791,6 +1837,8 @@ def __imul__(self, repetitions: INT_TYPE): def __mul__(self, repetitions: INT_TYPE): if not isinstance(repetitions, (int, np.integer)): return NotImplemented + if self._device == cirq.UNCONSTRAINED_DEVICE: + return Circuit(self._moments * int(repetitions)) return Circuit(self._moments * int(repetitions), device=self._device) def __rmul__(self, repetitions: INT_TYPE): @@ -1816,10 +1864,17 @@ def __pow__(self, exponent: int) -> 'cirq.Circuit': if inv_moment is NotImplemented: return NotImplemented inv_moments.append(inv_moment) + + if self._device == cirq.UNCONSTRAINED_DEVICE: + return cirq.Circuit(inv_moments) return cirq.Circuit(inv_moments, device=self._device) __hash__ = None # type: ignore + @_compat.deprecated( + deadline='v0.15', + fix=_DEVICE_DEP_MESSAGE, + ) def with_device( self, new_device: 'cirq.Device', @@ -1835,15 +1890,16 @@ def with_device( Returns: The translated circuit. """ - return Circuit( - [ - ops.Moment( - operation.transform_qubits(qubit_mapping) for operation in moment.operations - ) - for moment in self._moments - ], - device=new_device, - ) + with _block_overlapping_deprecation(): + return Circuit( + [ + ops.Moment( + operation.transform_qubits(qubit_mapping) for operation in moment.operations + ) + for moment in self._moments + ], + device=new_device, + ) def tetris_concat( *circuits: 'cirq.AbstractCircuit', align: Union['cirq.Alignment', str] = Alignment.LEFT @@ -1859,6 +1915,12 @@ def zip( zip.__doc__ = AbstractCircuit.zip.__doc__ + @_compat.deprecated_parameter( + deadline='v0.15', + fix=_DEVICE_DEP_MESSAGE, + parameter_desc='new_device', + match=lambda args, kwargs: 'new_device' in kwargs, + ) def transform_qubits( self, qubit_map: Union[Dict['cirq.Qid', 'cirq.Qid'], Callable[['cirq.Qid'], 'cirq.Qid']], @@ -1867,10 +1929,6 @@ def transform_qubits( ) -> 'cirq.Circuit': """Returns the same circuit, but with different qubits. - Note that this method does essentially the same thing as - `cirq.Circuit.with_device`. It is included regardless because there are - also `transform_qubits` methods on `cirq.Operation` and `cirq.Moment`. - Args: qubit_map: A function or a dict mapping each current qubit into a desired new qubit. @@ -1891,9 +1949,17 @@ def transform_qubits( transform = lambda q: qubit_map.get(q, q) # type: ignore else: raise TypeError('qubit_map must be a function or dict mapping qubits to qubits.') - return self.with_device( - new_device=self.device if new_device is None else new_device, qubit_mapping=transform - ) + + op_list = [ + ops.Moment(operation.transform_qubits(transform) for operation in moment.operations) + for moment in self._moments + ] + + if new_device is None and self._device == devices.UNCONSTRAINED_DEVICE: + return Circuit(op_list) + + with _block_overlapping_deprecation(): + return Circuit(op_list, device=self._device if new_device is None else new_device) def _prev_moment_available(self, op: 'cirq.Operation', end_moment_index: int) -> Optional[int]: last_available = end_moment_index @@ -2309,8 +2375,10 @@ def _resolve_parameters_( resolved_operations = _resolve_operations(moment.operations, resolver, recursive) new_moment = ops.Moment(resolved_operations) resolved_moments.append(new_moment) - resolved_circuit = Circuit(resolved_moments, device=self.device) - return resolved_circuit + if self._device == devices.UNCONSTRAINED_DEVICE: + return Circuit(resolved_moments) + with _block_overlapping_deprecation(): + return Circuit(resolved_moments, device=self._device) @property def moments(self): diff --git a/cirq-core/cirq/circuits/circuit_dag.py b/cirq-core/cirq/circuits/circuit_dag.py index 4728fbe8132..e5c10904e09 100644 --- a/cirq-core/cirq/circuits/circuit_dag.py +++ b/cirq-core/cirq/circuits/circuit_dag.py @@ -17,7 +17,7 @@ import functools import networkx -from cirq import ops, devices +from cirq import _compat, ops, devices from cirq.circuits import circuit if TYPE_CHECKING: @@ -70,6 +70,12 @@ class CircuitDag(networkx.DiGraph): disjoint_qubits = staticmethod(_disjoint_qubits) + @_compat.deprecated_parameter( + deadline='v0.15', + fix=circuit._DEVICE_DEP_MESSAGE, + parameter_desc='device', + match=lambda args, kwargs: 'device' in kwargs or len(args) == 4, + ) def __init__( self, can_reorder: Callable[['cirq.Operation', 'cirq.Operation'], bool] = _disjoint_qubits, @@ -92,7 +98,15 @@ def __init__( """ super().__init__(incoming_graph_data) self.can_reorder = can_reorder - self.device = device + self._device = device + + @property # type: ignore + @_compat.deprecated( + deadline='v0.15', + fix=circuit._DEVICE_DEP_MESSAGE, + ) + def device(self) -> devices.Device: + return self._device @staticmethod def make_node(op: 'cirq.Operation') -> Unique: @@ -100,20 +114,33 @@ def make_node(op: 'cirq.Operation') -> Unique: @staticmethod def from_circuit( - circuit: 'cirq.Circuit', + circuit: circuit.Circuit, can_reorder: Callable[['cirq.Operation', 'cirq.Operation'], bool] = _disjoint_qubits, ) -> 'CircuitDag': + if circuit._device == devices.UNCONSTRAINED_DEVICE: + return CircuitDag.from_ops(circuit.all_operations(), can_reorder=can_reorder) return CircuitDag.from_ops( - circuit.all_operations(), can_reorder=can_reorder, device=circuit.device + circuit.all_operations(), can_reorder=can_reorder, device=circuit._device ) @staticmethod + @_compat.deprecated_parameter( + deadline='v0.15', + fix=circuit._DEVICE_DEP_MESSAGE, + parameter_desc='device', + match=lambda args, kwargs: 'device' in kwargs, + ) def from_ops( *operations: 'cirq.OP_TREE', can_reorder: Callable[['cirq.Operation', 'cirq.Operation'], bool] = _disjoint_qubits, device: 'cirq.Device' = devices.UNCONSTRAINED_DEVICE, ) -> 'CircuitDag': - dag = CircuitDag(can_reorder=can_reorder, device=device) + if device == devices.UNCONSTRAINED_DEVICE: + dag = CircuitDag(can_reorder=can_reorder) + else: + with circuit._block_overlapping_deprecation(): + dag = CircuitDag(can_reorder=can_reorder, device=device) + for op in ops.flatten_op_tree(operations): dag.append(cast(ops.Operation, op)) return dag @@ -184,9 +211,11 @@ def all_operations(self) -> Iterator['cirq.Operation']: def all_qubits(self): return frozenset(q for node in self.nodes for q in node.val.qubits) - def to_circuit(self) -> 'cirq.Circuit': + def to_circuit(self) -> circuit.Circuit: + if self._device == devices.UNCONSTRAINED_DEVICE: + return circuit.Circuit(self.all_operations(), strategy=circuit.InsertStrategy.EARLIEST) return circuit.Circuit( - self.all_operations(), strategy=circuit.InsertStrategy.EARLIEST, device=self.device + self.all_operations(), strategy=circuit.InsertStrategy.EARLIEST, device=self._device ) def findall_nodes_until_blocked( diff --git a/cirq-core/cirq/circuits/circuit_dag_test.py b/cirq-core/cirq/circuits/circuit_dag_test.py index 5422cc2adfc..38776e7953c 100644 --- a/cirq-core/cirq/circuits/circuit_dag_test.py +++ b/cirq-core/cirq/circuits/circuit_dag_test.py @@ -21,6 +21,11 @@ import cirq +class TestDevice(cirq.Device): + def __init__(self): + pass + + def test_wrapper_eq(): q0, q1 = cirq.LineQubit.range(2) eq = cirq.testing.EqualsTester() @@ -66,6 +71,21 @@ def test_init(): assert list(dag.edges()) == [] +def test_init_device_deprecated(): + with cirq.testing.assert_deprecated( + cirq.circuits.circuit._DEVICE_DEP_MESSAGE, deadline='v0.15' + ): + _ = cirq.CircuitDag(device=cirq.UNCONSTRAINED_DEVICE) + + +def test_device_deprecated(): + dag = cirq.CircuitDag() + with cirq.testing.assert_deprecated( + cirq.circuits.circuit._DEVICE_DEP_MESSAGE, deadline='v0.15' + ): + _ = dag.device + + def test_append(): q0 = cirq.LineQubit(0) dag = cirq.CircuitDag() @@ -99,6 +119,14 @@ def test_from_ops(): assert [(n1.val, n2.val) for n1, n2 in dag.edges()] == [(cirq.X(q0), cirq.Y(q0))] +def test_from_ops_device_deprecated(): + with cirq.testing.assert_deprecated( + cirq.circuits.circuit._DEVICE_DEP_MESSAGE, deadline='v0.15' + ): + q0 = cirq.LineQubit(0) + _ = cirq.CircuitDag.from_ops(cirq.X(q0), cirq.Y(q0), device=TestDevice()) + + def test_from_circuit(): q0 = cirq.LineQubit(0) circuit = cirq.Circuit(cirq.X(q0), cirq.Y(q0)) @@ -109,15 +137,14 @@ def test_from_circuit(): assert sorted(circuit.all_qubits()) == sorted(dag.all_qubits()) -def test_from_circuit_with_device(): - q0 = cirq.GridQubit(5, 5) - circuit = cirq.Circuit(cirq.X(q0), cirq.Y(q0), device=cirq.UNCONSTRAINED_DEVICE) - dag = cirq.CircuitDag.from_circuit(circuit) - assert networkx.dag.is_directed_acyclic_graph(dag) - assert dag.device == circuit.device - assert len(dag.nodes()) == 2 - assert [(n1.val, n2.val) for n1, n2 in dag.edges()] == [(cirq.X(q0), cirq.Y(q0))] - assert sorted(circuit.all_qubits()) == sorted(dag.all_qubits()) +def test_from_circuit_deprecated(): + q0 = cirq.LineQubit(0) + circuit = cirq.Circuit(cirq.X(q0), cirq.Y(q0)) + circuit._device = TestDevice() + with cirq.testing.assert_deprecated( + cirq.circuits.circuit._DEVICE_DEP_MESSAGE, deadline='v0.15' + ): + _ = cirq.CircuitDag.from_circuit(circuit) def test_to_empty_circuit(): @@ -141,6 +168,18 @@ def test_to_circuit(): ) +def test_to_circuit_device_deprecated(): + q0 = cirq.LineQubit(0) + circuit = cirq.Circuit(cirq.X(q0), cirq.Y(q0)) + dag = cirq.CircuitDag.from_circuit(circuit) + dag._device = TestDevice() + + with cirq.testing.assert_deprecated( + cirq.circuits.circuit._DEVICE_DEP_MESSAGE, deadline='v0.15' + ): + _ = dag.to_circuit() + + def test_equality(): q0, q1 = cirq.LineQubit.range(2) circuit1 = cirq.Circuit( @@ -213,13 +252,11 @@ def test_larger_circuit(): cirq.CZ(q0, q1), cirq.T(q3), strategy=cirq.InsertStrategy.EARLIEST, - device=cirq.UNCONSTRAINED_DEVICE, ) dag = cirq.CircuitDag.from_circuit(circuit) assert networkx.dag.is_directed_acyclic_graph(dag) - assert circuit.device == dag.to_circuit().device # Operation order within a moment is non-deterministic # but text diagrams still look the same. desired = """ diff --git a/cirq-core/cirq/circuits/circuit_test.py b/cirq-core/cirq/circuits/circuit_test.py index 81ff00e6292..6739cc05836 100644 --- a/cirq-core/cirq/circuits/circuit_test.py +++ b/cirq-core/cirq/circuits/circuit_test.py @@ -16,6 +16,7 @@ from collections import defaultdict from random import randint, random, sample, randrange from typing import Iterator, Optional, Tuple, TYPE_CHECKING +from unittest import mock import numpy as np import pytest @@ -91,7 +92,7 @@ def test_alignment(): assert repr(cirq.Alignment.RIGHT) == 'cirq.Alignment.RIGHT' -def test_insert_moment_types(): +def test_insert_moment_types_deprecated(): x = cirq.NamedQubit('x') with pytest.raises(ValueError): @@ -100,7 +101,10 @@ def test_insert_moment_types(): with pytest.raises(ValueError): moment_and_op_type_validating_device.validate_moment(cirq.X(x)) - circuit = cirq.Circuit(device=moment_and_op_type_validating_device) + with cirq.testing.assert_deprecated( + cirq.circuits.circuit._DEVICE_DEP_MESSAGE, deadline='v0.15' + ): + circuit = cirq.Circuit(device=moment_and_op_type_validating_device) moment_or_operation_tree = [cirq.X(x), cirq.Moment([cirq.Y(x)])] circuit.insert(0, moment_or_operation_tree) @@ -139,12 +143,10 @@ def test_equality(circuit_cls): # Default is empty. Iterables get listed. eq.add_equality_group( circuit_cls(), - circuit_cls(device=cirq.UNCONSTRAINED_DEVICE), circuit_cls([]), circuit_cls(()), ) eq.add_equality_group(circuit_cls([cirq.Moment()]), circuit_cls((cirq.Moment(),))) - eq.add_equality_group(circuit_cls(device=FOXY)) # Equality depends on structure and contents. eq.add_equality_group(circuit_cls([cirq.Moment([cirq.X(a)])])) @@ -174,10 +176,6 @@ def test_equality(circuit_cls): @pytest.mark.parametrize('circuit_cls', [cirq.Circuit, cirq.FrozenCircuit]) def test_approx_eq(circuit_cls): - class TestDevice(cirq.Device): - def validate_operation(self, operation: cirq.Operation) -> None: - pass - a = cirq.NamedQubit('a') b = cirq.NamedQubit('b') @@ -205,9 +203,21 @@ def validate_operation(self, operation: cirq.Operation) -> None: atol=1e-6, ) + +@pytest.mark.parametrize('circuit_cls', [cirq.Circuit, cirq.FrozenCircuit]) +def test_approx_eq_device_deprecated(circuit_cls): + class TestDevice(cirq.Device): + def validate_operation(self, operation: cirq.Operation) -> None: + pass + + a = cirq.NamedQubit('a') + with cirq.testing.assert_deprecated( + cirq.circuits.circuit._DEVICE_DEP_MESSAGE, deadline='v0.15' + ): + other_device = circuit_cls([cirq.Moment([cirq.X(a)])], device=TestDevice()) assert not cirq.approx_eq( circuit_cls([cirq.Moment([cirq.X(a)])]), - circuit_cls([cirq.Moment([cirq.X(a)])], device=TestDevice()), + other_device, ) @@ -412,6 +422,10 @@ def test_radd_op_tree(circuit_cls): [cirq.Moment([cirq.X(a)]), cirq.Moment([cirq.Y(b)])] ) + +@mock.patch.dict(os.environ, clear='CIRQ_TESTING') +@pytest.mark.parametrize('circuit_cls', [cirq.Circuit, cirq.FrozenCircuit]) +def test_radd_op_tree_device_deprecated(circuit_cls): # Preserves device. c = circuit_cls(device=FOXY) c2 = [] + c @@ -467,6 +481,10 @@ def test_repr(circuit_cls): ])""" ) + +@mock.patch.dict(os.environ, clear='CIRQ_TESTING') +@pytest.mark.parametrize('circuit_cls', [cirq.Circuit, cirq.FrozenCircuit]) +def test_repr_device_deprecated(circuit_cls): c = circuit_cls(device=FOXY) cirq.testing.assert_equivalent_repr(c) assert repr(c) == f'cirq.{circuit_cls.__name__}(device={repr(FOXY)})' @@ -692,7 +710,8 @@ def test_concatenate(): _ = c + 'a' -def test_concatenate_with_device(): +@mock.patch.dict(os.environ, clear='CIRQ_TESTING') +def test_concatenate_with_device_deprecated(): fox = cirq.Circuit(device=FOXY) cone = cirq.Circuit(device=BCONE) unr = cirq.Circuit() @@ -712,8 +731,9 @@ def test_concatenate_with_device(): assert len(cone) == 0 +@mock.patch.dict(os.environ, clear='CIRQ_TESTING') @pytest.mark.parametrize('circuit_cls', [cirq.Circuit, cirq.FrozenCircuit]) -def test_with_device(circuit_cls): +def test_with_device_logic_deprecated(circuit_cls): c = circuit_cls(cirq.X(cirq.LineQubit(0))) c2 = c.with_device(FOXY, lambda e: cirq.GridQubit(e.x, 0)) @@ -739,7 +759,17 @@ def test_with_device(circuit_cls): _ = c.with_device(BCONE) -def test_set_device(): +@pytest.mark.parametrize('circuit_cls', [cirq.Circuit, cirq.FrozenCircuit]) +def test_with_device_deprecated(circuit_cls): + c = circuit_cls() + with cirq.testing.assert_deprecated( + cirq.circuits.circuit._DEVICE_DEP_MESSAGE, deadline='v0.15' + ): + _ = c.with_device(FOXY) + + +@mock.patch.dict(os.environ, clear='CIRQ_TESTING') +def test_set_device_deprecated(): c = cirq.Circuit(cirq.X(cirq.LineQubit(0))) assert c.device is cirq.UNCONSTRAINED_DEVICE @@ -971,7 +1001,8 @@ def test_insert_moment(): assert c.operation_at(qubit, actual_index) == operation[0] -def test_insert_validates_all_operations_before_inserting(): +@mock.patch.dict(os.environ, clear='CIRQ_TESTING') +def test_insert_validates_all_operations_before_inserting_deprecated(): a, b = cirq.GridQubit(0, 0), cirq.GridQubit(1, 1) c = cirq.Circuit(device=FOXY) operations = [cirq.Z(a), cirq.CZ(a, b)] @@ -3690,7 +3721,8 @@ def test_insert_operations_errors(): circuit._insert_operations(operations, insertion_indices) -def test_validates_while_editing(): +@mock.patch.dict(os.environ, clear='CIRQ_TESTING') +def test_validates_while_editing_deprecated(): c = cirq.Circuit(device=FOXY) with pytest.raises(ValueError, match='Unsupported qubit type'): @@ -3708,7 +3740,8 @@ def test_validates_while_editing(): c.insert(0, cirq.CZ(cirq.GridQubit(0, 0), cirq.GridQubit(1, 0))) -def test_respects_additional_adjacency_constraints(): +@mock.patch.dict(os.environ, clear='CIRQ_TESTING') +def test_respects_additional_adjacency_constraints_deprecated(): c = cirq.Circuit(device=FOXY) c.append(cirq.CZ(cirq.GridQubit(0, 0), cirq.GridQubit(0, 1))) c.append( @@ -3726,7 +3759,8 @@ def test_respects_additional_adjacency_constraints(): ) -def test_commutes_past_adjacency_constraints(): +@mock.patch.dict(os.environ, clear='CIRQ_TESTING') +def test_commutes_past_adjacency_constraints_deprecated(): c = cirq.Circuit( [ cirq.Moment(), @@ -3751,7 +3785,8 @@ def test_commutes_past_adjacency_constraints(): ) -def test_decomposes_while_appending(): +@mock.patch.dict(os.environ, clear='CIRQ_TESTING') +def test_decomposes_while_appending_deprecated(): c = cirq.Circuit(device=FOXY) c.append(cirq.TOFFOLI(cirq.GridQubit(0, 0), cirq.GridQubit(0, 1), cirq.GridQubit(1, 0))) cirq.testing.assert_allclose_up_to_global_phase( @@ -4177,7 +4212,6 @@ def test_pow_valid_only_for_minus_1(circuit_cls): forward = circuit_cls((cirq.X ** 0.5)(a), (cirq.Y ** -0.2)(b), cirq.CZ(a, b)) backward = circuit_cls((cirq.CZ ** (-1.0))(a, b), (cirq.X ** (-0.5))(a), (cirq.Y ** (0.2))(b)) - cirq.testing.assert_same_circuits(cirq.pow(forward, -1), backward) with pytest.raises(TypeError, match='__pow__'): cirq.pow(forward, 1) @@ -4187,12 +4221,26 @@ def test_pow_valid_only_for_minus_1(circuit_cls): cirq.pow(forward, -2.5) +@mock.patch.dict(os.environ, clear='CIRQ_TESTING') @pytest.mark.parametrize('circuit_cls', [cirq.Circuit, cirq.FrozenCircuit]) -def test_device_propagates(circuit_cls): +def test_device_propagates_deprecated(circuit_cls): c = circuit_cls(device=moment_and_op_type_validating_device) assert c[:].device is moment_and_op_type_validating_device +def test_device_get_set_deprecated(): + c = cirq.Circuit() + with cirq.testing.assert_deprecated( + cirq.circuits.circuit._DEVICE_DEP_MESSAGE, deadline='v0.15' + ): + c.device = FOXY + + with cirq.testing.assert_deprecated( + cirq.circuits.circuit._DEVICE_DEP_MESSAGE, deadline='v0.15' + ): + assert c.device is FOXY + + @pytest.mark.parametrize('circuit_cls', [cirq.Circuit, cirq.FrozenCircuit]) def test_moment_groups(circuit_cls): qubits = [cirq.GridQubit(x, y) for x in range(8) for y in range(8)] @@ -4252,6 +4300,14 @@ def test_json_dict(circuit_cls): } +def test_from_json_device_deprecated(): + q0, q1 = cirq.GridQubit.rect(1, 2) + with cirq.testing.assert_deprecated( + cirq.circuits.circuit._DEVICE_DEP_MESSAGE, deadline='v0.15' + ): + _ = cirq.Circuit._from_json_dict_([cirq.CZ(q0, q1)], FOXY) + + def test_with_noise(): class Noise(cirq.NoiseModel): def noisy_operation(self, operation): @@ -4346,6 +4402,21 @@ def test_init_contents(circuit_cls): circuit_cls() +@pytest.mark.parametrize('circuit_cls', [cirq.Circuit, cirq.FrozenCircuit]) +def test_init_deprecated(circuit_cls): + a, b = cirq.GridQubit.rect(1, 2) + + # Moments are not subject to insertion rules. + with cirq.testing.assert_deprecated( + cirq.circuits.circuit._DEVICE_DEP_MESSAGE, deadline='v0.15' + ): + _ = circuit_cls( + cirq.Moment([cirq.X(a)]), + cirq.Moment([cirq.X(b)]), + device=FOXY, + ) + + def test_transform_qubits(): a, b, c = cirq.LineQubit.range(3) original = cirq.Circuit( @@ -4369,10 +4440,21 @@ def test_transform_qubits(): with pytest.raises(TypeError, match='must be a function or dict'): _ = original.transform_qubits('bad arg') - # Device - original = cirq.Circuit(device=FOXY) - assert original.transform_qubits(lambda q: q).device is FOXY - assert original.transform_qubits(lambda q: q, new_device=BCONE).device is BCONE + +def test_transform_qubits_deprecated_device(): + with cirq.testing.assert_deprecated( + cirq.circuits.circuit._DEVICE_DEP_MESSAGE, deadline='v0.15' + ): + original = cirq.Circuit(device=FOXY) + + with cirq.testing.assert_deprecated( + cirq.circuits.circuit._DEVICE_DEP_MESSAGE, deadline='v0.15' + ): + assert original.transform_qubits(lambda q: q).device is FOXY + + with cirq.testing.assert_deprecated('new_device', deadline='v0.15', count=2): + # count one for new_device and count one for accessing .device. + assert original.transform_qubits(lambda q: q, new_device=BCONE).device is BCONE @pytest.mark.parametrize('circuit_cls', [cirq.Circuit, cirq.FrozenCircuit]) diff --git a/cirq-core/cirq/circuits/frozen_circuit.py b/cirq-core/cirq/circuits/frozen_circuit.py index f7633907cd2..be1d6da52d3 100644 --- a/cirq-core/cirq/circuits/frozen_circuit.py +++ b/cirq-core/cirq/circuits/frozen_circuit.py @@ -24,18 +24,37 @@ Tuple, Union, ) +import contextlib +import warnings +import re -import numpy as np - -from cirq import devices, ops, protocols from cirq.circuits import AbstractCircuit, Alignment, Circuit from cirq.circuits.insert_strategy import InsertStrategy from cirq.type_workarounds import NotImplementedType +import numpy as np + +from cirq import _compat, devices, ops, protocols + + if TYPE_CHECKING: import cirq +_DEVICE_DEP_MESSAGE = 'Attaching devices to circuits will no longer be supported.' + + +@contextlib.contextmanager +def _block_overlapping_deprecation(): + with warnings.catch_warnings(): + warnings.filterwarnings( + action='ignore', + category=DeprecationWarning, + message=f'(.|\n)*{re.escape(_DEVICE_DEP_MESSAGE)}(.|\n)*', + ) + yield + + class FrozenCircuit(AbstractCircuit, protocols.SerializableByKey): """An immutable version of the Circuit data structure. @@ -44,6 +63,12 @@ class FrozenCircuit(AbstractCircuit, protocols.SerializableByKey): the `freeze` and `unfreeze` methods from AbstractCircuit. """ + @_compat.deprecated_parameter( + deadline='v0.15', + fix=_DEVICE_DEP_MESSAGE, + parameter_desc='device', + match=lambda args, kwargs: 'device' in kwargs, + ) def __init__( self, *contents: 'cirq.OP_TREE', @@ -63,9 +88,14 @@ def __init__( together. device: Hardware that the circuit should be able to run on. """ - base = Circuit(contents, strategy=strategy, device=device) + if device == devices.UNCONSTRAINED_DEVICE: + base = Circuit(contents, strategy=strategy) + else: + with _block_overlapping_deprecation(): + base = Circuit(contents, strategy=strategy, device=device) + self._moments = tuple(base.moments) - self._device = base.device + self._device = base._device # These variables are memoized when first requested. self._num_qubits: Optional[int] = None @@ -82,12 +112,16 @@ def __init__( def moments(self) -> Sequence['cirq.Moment']: return self._moments - @property - def device(self) -> 'cirq.Device': + @property # type: ignore + @_compat.deprecated( + deadline='v0.15', + fix=_DEVICE_DEP_MESSAGE, + ) + def device(self) -> devices.Device: return self._device def __hash__(self): - return hash((self.moments, self.device)) + return hash((self.moments, self._device)) # Memoized methods for commonly-retrieved properties. @@ -174,17 +208,25 @@ def __pow__(self, other) -> 'cirq.FrozenCircuit': except: return NotImplemented - def _with_sliced_moments(self, moments: Iterable['cirq.Moment']) -> 'cirq.FrozenCircuit': - new_circuit = FrozenCircuit(device=self.device) + def _with_sliced_moments(self, moments: Iterable['cirq.Moment']) -> 'FrozenCircuit': + if self._device == devices.UNCONSTRAINED_DEVICE: + new_circuit = FrozenCircuit() + else: + new_circuit = FrozenCircuit(device=self._device) new_circuit._moments = tuple(moments) return new_circuit + @_compat.deprecated( + deadline='v0.15', + fix=_DEVICE_DEP_MESSAGE, + ) def with_device( self, new_device: 'cirq.Device', qubit_mapping: Callable[['cirq.Qid'], 'cirq.Qid'] = lambda e: e, - ) -> 'cirq.FrozenCircuit': - return self.unfreeze().with_device(new_device, qubit_mapping).freeze() + ) -> 'FrozenCircuit': + with _block_overlapping_deprecation(): + return self.unfreeze().with_device(new_device, qubit_mapping).freeze() def _resolve_parameters_( self, resolver: 'cirq.ParamResolver', recursive: bool diff --git a/cirq-core/cirq/circuits/frozen_circuit_test.py b/cirq-core/cirq/circuits/frozen_circuit_test.py index 1fcd099ab71..745094713ed 100644 --- a/cirq-core/cirq/circuits/frozen_circuit_test.py +++ b/cirq-core/cirq/circuits/frozen_circuit_test.py @@ -47,6 +47,33 @@ def test_freeze_and_unfreeze(): assert fcc is not f +def test_init_device_deprecated(): + q = cirq.LineQubit(0) + with cirq.testing.assert_deprecated( + cirq.circuits.circuit._DEVICE_DEP_MESSAGE, deadline='v0.15' + ): + _ = cirq.FrozenCircuit(cirq.X(q), device=cirq.UNCONSTRAINED_DEVICE) + + +def test_device_deprecated(): + q = cirq.LineQubit(0) + a = cirq.FrozenCircuit(cirq.X(q)) + with cirq.testing.assert_deprecated( + cirq.circuits.circuit._DEVICE_DEP_MESSAGE, deadline='v0.15' + ): + _ = a.device + + +def test_with_device_deprecated(): + q = cirq.LineQubit(0) + a = cirq.FrozenCircuit(cirq.X(q)) + with cirq.testing.assert_deprecated( + cirq.circuits.circuit._DEVICE_DEP_MESSAGE, deadline='v0.15' + ): + # one for frozencircuit.with_device and one for circuit.with_device. + _ = a.with_device(cirq.UNCONSTRAINED_DEVICE) + + def test_immutable(): q = cirq.LineQubit(0) c = cirq.FrozenCircuit(cirq.X(q), cirq.H(q)) diff --git a/cirq-core/cirq/testing/devices_test.py b/cirq-core/cirq/testing/devices_test.py index 49d57d080cb..57f276f929d 100644 --- a/cirq-core/cirq/testing/devices_test.py +++ b/cirq-core/cirq/testing/devices_test.py @@ -11,8 +11,9 @@ # 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. +import os +from unittest import mock import pytest - import cirq from cirq.testing.devices import ValidatingTestDevice @@ -62,7 +63,8 @@ def test_validating_locality(): ) -def test_autodecompose(): +@mock.patch.dict(os.environ, clear='CIRQ_TESTING') +def test_autodecompose_deprecated(): dev = ValidatingTestDevice( allowed_qubit_types=(cirq.LineQubit,), allowed_gates=( diff --git a/cirq-core/cirq/testing/sample_circuits.py b/cirq-core/cirq/testing/sample_circuits.py index 0dcd4f83a74..f235858a7cc 100644 --- a/cirq-core/cirq/testing/sample_circuits.py +++ b/cirq-core/cirq/testing/sample_circuits.py @@ -13,19 +13,25 @@ # limitations under the License. from typing import TYPE_CHECKING -from cirq import ops, circuits, devices +from cirq import _compat, ops, circuits, devices if TYPE_CHECKING: import cirq +@_compat.deprecated_parameter( + deadline='v0.15', + fix='The returned circuit will no longer include a device object.', + parameter_desc='device', + match=lambda args, kwargs: 'device' in kwargs or len(args) == 4, +) def nonoptimal_toffoli_circuit( q0: 'cirq.Qid', q1: 'cirq.Qid', q2: 'cirq.Qid', device: devices.Device = devices.UNCONSTRAINED_DEVICE, ) -> circuits.Circuit: - return circuits.Circuit( + ret = circuits.Circuit( ops.Y(q2) ** 0.5, ops.X(q2), ops.CNOT(q1, q2), @@ -55,5 +61,6 @@ def nonoptimal_toffoli_circuit( ops.CNOT(q0, q1), ops.Y(q2) ** 0.5, ops.X(q2), - device=device, ) + ret._device = device + return ret diff --git a/cirq-core/cirq/testing/sample_circuits_test.py b/cirq-core/cirq/testing/sample_circuits_test.py index b7f75ddcbf6..052b4bbaaa7 100644 --- a/cirq-core/cirq/testing/sample_circuits_test.py +++ b/cirq-core/cirq/testing/sample_circuits_test.py @@ -22,3 +22,15 @@ def test_nonoptimal_toffoli_circuit(): cirq.unitary(cirq.TOFFOLI(q0, q1, q2)), atol=1e-7, ) + + +def test_nonoptimal_toffoli_circuit_device_deprecated(): + q0, q1, q2 = cirq.LineQubit.range(3) + with cirq.testing.assert_deprecated('no longer include a device', deadline='v0.15'): + cirq.testing.assert_allclose_up_to_global_phase( + cirq.testing.nonoptimal_toffoli_circuit( + q0, q1, q2, cirq.UNCONSTRAINED_DEVICE + ).unitary(), + cirq.unitary(cirq.TOFFOLI(q0, q1, q2)), + atol=1e-7, + )