Skip to content

Commit

Permalink
Deprecate cirq.ion module (quantumlib#5563)
Browse files Browse the repository at this point in the history
  • Loading branch information
tanujkhattar authored and rht committed May 1, 2023
1 parent a2ae98a commit 942cb9e
Show file tree
Hide file tree
Showing 6 changed files with 188 additions and 60 deletions.
26 changes: 24 additions & 2 deletions cirq-aqt/cirq_aqt/aqt_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,29 @@ def simulate_samples(self, repetitions: int) -> cirq.Result:
return result


def get_aqt_device(num_qubits: int) -> Tuple[cirq.IonDevice, List[cirq.LineQubit]]:
class AQTTargetGateset(cirq.ion.ion_device._IonTargetGateset):
pass


class AQTDevice(cirq.ion.ion_device._IonDeviceImpl):
"""Ion trap device with qubits having all-to-all connectivity and placed on a line."""

def __repr__(self) -> str:
return (
f'cirq_aqt.aqt_device.AQTDevice('
f'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}'
f')'
)

def _repr_pretty_(self, p: Any, cycle: bool):
"""iPython (Jupyter) pretty print."""
p.text("AQTDevice(...)" if cycle else self.__str__())


def get_aqt_device(num_qubits: int) -> Tuple[AQTDevice, List[cirq.LineQubit]]:
"""Returns an AQT ion device
Args:
Expand All @@ -229,7 +251,7 @@ def get_aqt_device(num_qubits: int) -> Tuple[cirq.IonDevice, List[cirq.LineQubit
"""
qubit_list = cirq.LineQubit.range(num_qubits)
us = 1000 * cirq.Duration(nanos=1)
ion_device = cirq.IonDevice(
ion_device = AQTDevice(
measurement_duration=100 * us,
twoq_gates_duration=200 * us,
oneq_gates_duration=10 * us,
Expand Down
21 changes: 21 additions & 0 deletions cirq-aqt/cirq_aqt/aqt_device_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# 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.

import cirq
import cirq_aqt


def test_repr():
device = cirq_aqt.aqt_device.get_aqt_device(5)
cirq.testing.assert_equivalent_repr(device, setup_code='import cirq\nimport cirq_aqt\n')
11 changes: 9 additions & 2 deletions cirq-core/cirq/ion/convert_to_ion_gates.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""

Expand All @@ -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.
Expand Down
61 changes: 47 additions & 14 deletions cirq-core/cirq/ion/convert_to_ion_gates_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
[
Expand All @@ -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)],
Expand All @@ -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
)
91 changes: 61 additions & 30 deletions cirq-core/cirq/ion/ion_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__(
Expand Down Expand Up @@ -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)
Expand All @@ -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):
Expand Down Expand Up @@ -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()

Expand All @@ -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,
Expand All @@ -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__())
38 changes: 26 additions & 12 deletions cirq-core/cirq/ion/ion_device_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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():
Expand Down Expand Up @@ -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

0 comments on commit 942cb9e

Please sign in to comment.