From 289ba88de1506d8be1374e4f84d5bc30200c22ea Mon Sep 17 00:00:00 2001 From: Tanuj Khattar Date: Tue, 21 Jun 2022 15:43:27 -0700 Subject: [PATCH] Deprecate cirq.ion module (#5563) Fixes https://github.com/quantumlib/Cirq/issues/2793 Fixes https://github.com/quantumlib/Cirq/issues/5128 --- cirq/ion/convert_to_ion_gates.py | 11 +++- cirq/ion/convert_to_ion_gates_test.py | 61 +++++++++++++----- cirq/ion/ion_device.py | 91 ++++++++++++++++++--------- cirq/ion/ion_device_test.py | 38 +++++++---- 4 files changed, 143 insertions(+), 58 deletions(-) diff --git a/cirq/ion/convert_to_ion_gates.py b/cirq/ion/convert_to_ion_gates.py index c6e94994007..d9c75ed74f4 100644 --- a/cirq/ion/convert_to_ion_gates.py +++ b/cirq/ion/convert_to_ion_gates.py @@ -14,10 +14,17 @@ import numpy as np -from cirq import ops, protocols, circuits, transformers +from cirq import _compat, ops, protocols, circuits, transformers from cirq.ion import ms, two_qubit_matrix_to_ion_operations, ion_device +@_compat.deprecated_class( + deadline='v0.16', + fix='Use cirq.optimize_for_target_gateset(' + 'circuit, ' + 'gateset=cirq_aqt.aqt_device.AQTTargetGateset()' + ').', +) class ConvertToIonGates: """Attempts to convert non-native gates into IonGates.""" @@ -30,7 +37,7 @@ def __init__(self, ignore_failures: bool = False) -> None: """ super().__init__() self.ignore_failures = ignore_failures - self.gateset = ion_device.get_ion_gateset() + self.gateset = ion_device._IonTargetGateset() def convert_one(self, op: ops.Operation) -> ops.OP_TREE: """Convert a single (one- or two-qubit) operation into ion trap native gates. diff --git a/cirq/ion/convert_to_ion_gates_test.py b/cirq/ion/convert_to_ion_gates_test.py index 6fcb8f74dfd..fb04a7f4e66 100644 --- a/cirq/ion/convert_to_ion_gates_test.py +++ b/cirq/ion/convert_to_ion_gates_test.py @@ -38,21 +38,38 @@ def test_convert_to_ion_gates(): q1 = cirq.GridQubit(0, 1) op = cirq.CNOT(q0, q1) circuit = cirq.Circuit() + ion_gateset = cirq.ion.ion_device._IonTargetGateset() + with cirq.testing.assert_deprecated( + "cirq_aqt.aqt_device.AQTTargetGateset", deadline='v0.16', count=None + ): + convert_to_ion_gates = cirq.ion.ConvertToIonGates() with pytest.raises(TypeError): - cirq.ion.ConvertToIonGates().convert_one(circuit) - - with pytest.raises(TypeError): - cirq.ion.ConvertToIonGates().convert_one(NoUnitary().on(q0)) + convert_to_ion_gates.convert_one(circuit) no_unitary_op = NoUnitary().on(q0) - assert cirq.ion.ConvertToIonGates(ignore_failures=True).convert_one(no_unitary_op) == [ - no_unitary_op - ] - - rx = cirq.ion.ConvertToIonGates().convert_one(OtherX().on(q0)) - rop = cirq.ion.ConvertToIonGates().convert_one(op) + with pytest.raises(TypeError): + convert_to_ion_gates.convert_one(no_unitary_op) + assert ion_gateset._decompose_single_qubit_operation(no_unitary_op, 0) is NotImplemented + + with cirq.testing.assert_deprecated( + "cirq_aqt.aqt_device.AQTTargetGateset", deadline='v0.16', count=None + ): + assert cirq.ion.ConvertToIonGates(ignore_failures=True).convert_one(no_unitary_op) == [ + no_unitary_op + ] + rx = convert_to_ion_gates.convert_one(OtherX().on(q0)) + rx_via_gateset = ion_gateset._decompose_single_qubit_operation(OtherX().on(q0), 0) assert cirq.approx_eq(rx, [cirq.PhasedXPowGate(phase_exponent=1.0).on(cirq.GridQubit(0, 0))]) + assert cirq.approx_eq( + rx_via_gateset, [cirq.PhasedXPowGate(phase_exponent=1.0).on(cirq.GridQubit(0, 0))] + ) + + rop = convert_to_ion_gates.convert_one(op) + rop_via_gateset = ion_gateset._decompose_two_qubit_operation(op, 0) + cirq.testing.assert_circuits_with_terminal_measurements_are_equivalent( + cirq.Circuit(rop), cirq.Circuit(rop_via_gateset), atol=1e-6 + ) assert cirq.approx_eq( rop, [ @@ -64,7 +81,7 @@ def test_convert_to_ion_gates(): ], ) - rcnot = cirq.ion.ConvertToIonGates().convert_one(OtherCNOT().on(q0, q1)) + rcnot = convert_to_ion_gates.convert_one(OtherCNOT().on(q0, q1)) assert cirq.approx_eq( [op for op in rcnot if len(op.qubits) > 1], [cirq.ms(-0.5 * np.pi / 2).on(q0, q1)], @@ -79,20 +96,36 @@ def test_convert_to_ion_circuit(): q0 = cirq.LineQubit(0) q1 = cirq.LineQubit(1) us = cirq.Duration(nanos=1000) - ion_device = cirq.IonDevice(us, us, us, [q0, q1]) + with cirq.testing.assert_deprecated("cirq_aqt.aqt_device.AQTDevice", deadline='v0.16', count=2): + ion_device = cirq.IonDevice(us, us, us, [q0, q1]) + with cirq.testing.assert_deprecated( + "cirq_aqt.aqt_device.AQTTargetGateset", deadline='v0.16', count=None + ): + convert_to_ion_gates = cirq.ion.ConvertToIonGates() clifford_circuit_1 = cirq.Circuit() clifford_circuit_1.append([cirq.X(q0), cirq.H(q1), cirq.ms(np.pi / 4).on(q0, q1)]) - ion_circuit_1 = cirq.ion.ConvertToIonGates().convert_circuit(clifford_circuit_1) + ion_circuit_1 = convert_to_ion_gates.convert_circuit(clifford_circuit_1) + ion_circuit_1_using_device = ion_device.decompose_circuit(clifford_circuit_1) ion_device.validate_circuit(ion_circuit_1) + ion_device.validate_circuit(ion_circuit_1_using_device) cirq.testing.assert_circuits_with_terminal_measurements_are_equivalent( clifford_circuit_1, ion_circuit_1, atol=1e-6 ) + cirq.testing.assert_circuits_with_terminal_measurements_are_equivalent( + clifford_circuit_1, ion_circuit_1_using_device, atol=1e-6 + ) + clifford_circuit_2 = cirq.Circuit() clifford_circuit_2.append([cirq.X(q0), cirq.CNOT(q1, q0), cirq.ms(np.pi / 4).on(q0, q1)]) - ion_circuit_2 = cirq.ion.ConvertToIonGates().convert_circuit(clifford_circuit_2) + ion_circuit_2 = convert_to_ion_gates.convert_circuit(clifford_circuit_2) + ion_circuit_2_using_device = ion_device.decompose_circuit(clifford_circuit_2) ion_device.validate_circuit(ion_circuit_2) + ion_device.validate_circuit(ion_circuit_2_using_device) cirq.testing.assert_circuits_with_terminal_measurements_are_equivalent( clifford_circuit_2, ion_circuit_2, atol=1e-6 ) + cirq.testing.assert_circuits_with_terminal_measurements_are_equivalent( + clifford_circuit_2, ion_circuit_2_using_device, atol=1e-6 + ) diff --git a/cirq/ion/ion_device.py b/cirq/ion/ion_device.py index 1df62caec05..f7e24707204 100644 --- a/cirq/ion/ion_device.py +++ b/cirq/ion/ion_device.py @@ -12,32 +12,55 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Any, Iterable, Optional, Set, TYPE_CHECKING +from typing import Any, Iterable, List, Optional, Set, TYPE_CHECKING +import numpy as np import networkx as nx -from cirq import circuits, value, devices, ops, protocols -from cirq.ion import convert_to_ion_gates +from cirq import _compat, circuits, value, devices, ops, protocols, transformers +from cirq.protocols.decompose_protocol import DecomposeResult if TYPE_CHECKING: import cirq -def get_ion_gateset() -> ops.Gateset: - return ops.Gateset( - ops.XXPowGate, - ops.MeasurementGate, - ops.XPowGate, - ops.YPowGate, - ops.ZPowGate, - ops.PhasedXPowGate, - unroll_circuit_op=False, - ) +class _IonTargetGateset(transformers.TwoQubitCompilationTargetGateset): + def __init__(self): + super().__init__( + ops.XXPowGate, + ops.MeasurementGate, + ops.XPowGate, + ops.YPowGate, + ops.ZPowGate, + ops.PhasedXPowGate, + unroll_circuit_op=False, + ) + + def _decompose_single_qubit_operation(self, op: 'cirq.Operation', _: int) -> DecomposeResult: + if isinstance(op.gate, ops.HPowGate) and op.gate.exponent == 1: + return [ops.rx(np.pi).on(op.qubits[0]), ops.ry(-1 * np.pi / 2).on(op.qubits[0])] + if protocols.has_unitary(op): + gates = transformers.single_qubit_matrix_to_phased_x_z(protocols.unitary(op)) + return [g.on(op.qubits[0]) for g in gates] + return NotImplemented + + def _decompose_two_qubit_operation(self, op: 'cirq.Operation', _) -> DecomposeResult: + if protocols.has_unitary(op): + return transformers.two_qubit_matrix_to_ion_operations( + op.qubits[0], op.qubits[1], protocols.unitary(op) + ) + return NotImplemented + + @property + def postprocess_transformers(self) -> List['cirq.TRANSFORMER']: + """List of transformers which should be run after decomposing individual operations.""" + return [transformers.drop_negligible_operations, transformers.drop_empty_moments] @value.value_equality -class IonDevice(devices.Device): - """A device with qubits placed on a line. +class _IonDeviceImpl(devices.Device): + """Shared implementation of `cirq.IonDevice` (deprecated) and `cirq_aqt.AQTDevice`. - Qubits have all-to-all connectivity. + This class will be removed once `cirq.IonDevice` is deprecated and removed. The implementation + will be moved to `cirq_aqt.AQTDevice`. """ def __init__( @@ -68,7 +91,7 @@ def __init__( f"{set(type(qubit) for qubit in qubits)}" ) self.qubits = frozenset(qubits) - self.gateset = get_ion_gateset() + self.gateset = _IonTargetGateset() graph = nx.Graph() graph.add_edges_from([(a, b) for a in qubits for b in qubits if a != b], directed=False) @@ -79,7 +102,7 @@ def metadata(self) -> devices.DeviceMetadata: return self._metadata def decompose_circuit(self, circuit: circuits.Circuit) -> circuits.Circuit: - return convert_to_ion_gates.ConvertToIonGates().convert_circuit(circuit) + return transformers.optimize_for_target_gateset(circuit, gateset=self.gateset) def duration_of(self, operation): if isinstance(operation.gate, ops.XXPowGate): @@ -122,14 +145,6 @@ def neighbors_of(self, qubit: devices.LineQubit) -> Iterable[devices.LineQubit]: possibles = [devices.LineQubit(qubit.x + 1), devices.LineQubit(qubit.x - 1)] return [e for e in possibles if e in self.qubits] - def __repr__(self) -> str: - return ( - f'IonDevice(measurement_duration={self._measurement_duration!r}, ' - f'twoq_gates_duration={self._twoq_gates_duration!r}, ' - f'oneq_gates_duration={self._oneq_gates_duration!r} ' - f'qubits={sorted(self.qubits)!r})' - ) - def __str__(self) -> str: diagram = circuits.TextDiagramDrawer() @@ -140,10 +155,6 @@ def __str__(self) -> str: return diagram.render(horizontal_spacing=3, vertical_spacing=2, use_unicode_characters=True) - def _repr_pretty_(self, p: Any, cycle: bool): - """iPython (Jupyter) pretty print.""" - p.text("IonDevice(...)" if cycle else self.__str__()) - def _value_equality_values_(self) -> Any: return ( self._measurement_duration, @@ -162,3 +173,23 @@ def _verify_unique_measurement_keys(operations: Iterable[ops.Operation]): if key in seen: raise ValueError(f'Measurement key {key} repeated') seen.add(key) + + +@_compat.deprecated_class(deadline='v0.16', fix='Use cirq_aqt.aqt_device.AQTDevice.') +class IonDevice(_IonDeviceImpl): + """A device with qubits placed on a line. + + Qubits have all-to-all connectivity. + """ + + def __repr__(self) -> str: + return ( + f'IonDevice(measurement_duration={self._measurement_duration!r}, ' + f'twoq_gates_duration={self._twoq_gates_duration!r}, ' + f'oneq_gates_duration={self._oneq_gates_duration!r} ' + f'qubits={sorted(self.qubits)!r})' + ) + + def _repr_pretty_(self, p: Any, cycle: bool): + """iPython (Jupyter) pretty print.""" + p.text("IonDevice(...)" if cycle else self.__str__()) diff --git a/cirq/ion/ion_device_test.py b/cirq/ion/ion_device_test.py index 6172c56c6d3..a870cb3fa29 100644 --- a/cirq/ion/ion_device_test.py +++ b/cirq/ion/ion_device_test.py @@ -19,16 +19,20 @@ import cirq import cirq.ion as ci import cirq.testing +import sympy.utilities.matchpy_connector def ion_device(chain_length: int, use_timedelta=False) -> ci.IonDevice: ms = 1000 * cirq.Duration(nanos=1) if not use_timedelta else timedelta(microseconds=1) - return ci.IonDevice( # type: ignore - measurement_duration=100 * ms, # type: ignore - twoq_gates_duration=200 * ms, # type: ignore - oneq_gates_duration=10 * ms, # type: ignore - qubits=cirq.LineQubit.range(chain_length), - ) + with cirq.testing.assert_deprecated( + "Use cirq_aqt.aqt_device.AQTDevice", deadline='v0.16', count=None + ): + return ci.IonDevice( # type: ignore + measurement_duration=100 * ms, # type: ignore + twoq_gates_duration=200 * ms, # type: ignore + oneq_gates_duration=10 * ms, # type: ignore + qubits=cirq.LineQubit.range(chain_length), + ) class NotImplementedOperation(cirq.Operation): @@ -56,12 +60,13 @@ def test_init(): _ = d.duration_of(cirq.I(q0)) with pytest.raises(TypeError, match="NamedQubit"): - _ = cirq.IonDevice( - measurement_duration=ms, - twoq_gates_duration=ms, - oneq_gates_duration=ms, - qubits=[cirq.LineQubit(0), cirq.NamedQubit("a")], - ) + with cirq.testing.assert_deprecated("Use cirq_aqt.aqt_device.AQTDevice", deadline='v0.16'): + _ = cirq.IonDevice( + measurement_duration=ms, + twoq_gates_duration=ms, + oneq_gates_duration=ms, + qubits=[cirq.LineQubit(0), cirq.NamedQubit("a")], + ) def test_metadata(): @@ -178,3 +183,12 @@ def test_at(): assert d.at(-1) is None assert d.at(0) == cirq.LineQubit(0) assert d.at(2) == cirq.LineQubit(2) + + +def test_decompose_parameterized_gates(): + theta = sympy.Symbol("theta") + q = cirq.LineQubit.range(2) + circuit = cirq.Circuit(cirq.H(q[0]) ** theta, cirq.XX(*q) ** theta) + d = ion_device(3) + assert d.gateset.validate(d.decompose_circuit(circuit)) + assert d.gateset._decompose_two_qubit_operation(cirq.CZ(*q) ** theta, 0) is NotImplemented