Skip to content

Commit

Permalink
Deprecate device.decompose_operation in cirq_ionq. (#4925)
Browse files Browse the repository at this point in the history
Yet more of #4744 .
  • Loading branch information
MichaelBroughton authored Feb 2, 2022
1 parent d3a122b commit 610b0d4
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 59 deletions.
1 change: 1 addition & 0 deletions cirq-ionq/cirq_ionq/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

from cirq_ionq.ionq_devices import (
IonQAPIDevice,
decompose_to_device,
)

from cirq_ionq.ionq_exceptions import (
Expand Down
134 changes: 83 additions & 51 deletions cirq-ionq/cirq_ionq/ionq_devices.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,23 @@
from typing import AbstractSet, Sequence, Union

import cirq
from cirq import _compat


_VALID_GATES = cirq.Gateset(
cirq.H,
cirq.CNOT,
cirq.SWAP,
cirq.XPowGate,
cirq.YPowGate,
cirq.ZPowGate,
cirq.XXPowGate,
cirq.YYPowGate,
cirq.ZZPowGate,
cirq.MeasurementGate,
unroll_circuit_op=False,
accept_global_phase_op=False,
)


class IonQAPIDevice(cirq.Device):
Expand Down Expand Up @@ -49,20 +66,6 @@ def __init__(self, qubits: Union[Sequence[cirq.LineQubit], int], atol=1e-8):
else:
self.qubits = frozenset(qubits)
self.atol = atol
self.gateset = cirq.Gateset(
cirq.H,
cirq.CNOT,
cirq.SWAP,
cirq.XPowGate,
cirq.YPowGate,
cirq.ZPowGate,
cirq.XXPowGate,
cirq.YYPowGate,
cirq.ZZPowGate,
cirq.MeasurementGate,
unroll_circuit_op=False,
accept_global_phase_op=False,
)

def qubit_set(self) -> AbstractSet['cirq.Qid']:
return self.qubits
Expand All @@ -78,42 +81,71 @@ def validate_operation(self, operation: cirq.Operation):
raise ValueError(f'Operation with qubits not on the device. Qubits: {operation.qubits}')

def is_api_gate(self, operation: cirq.Operation) -> bool:
return operation in self.gateset
return operation in _VALID_GATES

@_compat.deprecated(
fix='Use cirq_ionq.decompose_to_device operation instead.',
deadline='v0.15',
)
def decompose_operation(self, operation: cirq.Operation) -> cirq.OP_TREE:
if self.is_api_gate(operation):
return operation
assert cirq.has_unitary(operation), (
f'Operation {operation} that is not available on the IonQ API nor does it have a '
'unitary matrix to use to decompose it to the API.'
)
num_qubits = len(operation.qubits)
if num_qubits == 1:
return self._decompose_single_qubit(operation)
if num_qubits == 2:
return self._decompose_two_qubit(operation)
raise ValueError(f'Operation {operation} not supported by IonQ API.')

def _decompose_single_qubit(self, operation: cirq.Operation) -> cirq.OP_TREE:
qubit = operation.qubits[0]
mat = cirq.unitary(operation)
for gate in cirq.single_qubit_matrix_to_gates(mat, self.atol):
yield gate(qubit)

def _decompose_two_qubit(self, operation: cirq.Operation) -> cirq.OP_TREE:
"""Decomposes a two qubit unitary operation into ZPOW, XPOW, and CNOT."""
mat = cirq.unitary(operation)
q0, q1 = operation.qubits
naive = cirq.two_qubit_matrix_to_operations(q0, q1, mat, allow_partial_czs=False)
temp = cirq.map_operations_and_unroll(
cirq.Circuit(naive),
lambda op, _: [cirq.H(op.qubits[1]), cirq.CNOT(*op.qubits), cirq.H(op.qubits[1])]
if type(op.gate) == cirq.CZPowGate
else op,
)
cirq.merge_single_qubit_gates_into_phased_x_z(temp)
# A final pass breaks up PhasedXPow into Rz, Rx.
yield cirq.map_operations_and_unroll(
temp,
lambda op, _: cirq.decompose_once(op) if type(op.gate) == cirq.PhasedXPowGate else op,
).all_operations()
return decompose_to_device(operation)


def decompose_to_device(operation: cirq.Operation, atol: float = 1e-8) -> cirq.OP_TREE:
"""Decompose operation to ionq native operations.
Merges single qubit operations and decomposes two qubit operations
into CZ gates.
Args:
operation: `cirq.Operation` to decompose.
atol: absolute error tolerance to use when declaring two unitary
operations equal.
Returns:
cirq.OP_TREE containing decomposed operations.
Raises:
ValueError: If supplied operation cannot be decomposed
for the ionq device.
"""
if operation in _VALID_GATES:
return operation
assert cirq.has_unitary(operation), (
f'Operation {operation} is not available on the IonQ API nor does it have a '
'unitary matrix to use to decompose it to the API.'
)
num_qubits = len(operation.qubits)
if num_qubits == 1:
return _decompose_single_qubit(operation, atol)
if num_qubits == 2:
return _decompose_two_qubit(operation)
raise ValueError(f'Operation {operation} not supported by IonQ API.')


def _decompose_single_qubit(operation: cirq.Operation, atol: float) -> cirq.OP_TREE:
qubit = operation.qubits[0]
mat = cirq.unitary(operation)
for gate in cirq.single_qubit_matrix_to_gates(mat, atol):
yield gate(qubit)


def _decompose_two_qubit(operation: cirq.Operation) -> cirq.OP_TREE:
"""Decomposes a two qubit unitary operation into ZPOW, XPOW, and CNOT."""
mat = cirq.unitary(operation)
q0, q1 = operation.qubits
naive = cirq.two_qubit_matrix_to_operations(q0, q1, mat, allow_partial_czs=False)
temp = cirq.map_operations_and_unroll(
cirq.Circuit(naive),
lambda op, _: [cirq.H(op.qubits[1]), cirq.CNOT(*op.qubits), cirq.H(op.qubits[1])]
if type(op.gate) == cirq.CZPowGate
else op,
)
cirq.merge_single_qubit_gates_into_phased_x_z(temp)
# A final pass breaks up PhasedXPow into Rz, Rx.
yield cirq.map_operations_and_unroll(
temp,
lambda op, _: cirq.decompose_once(op) if type(op.gate) == cirq.PhasedXPowGate else op,
).all_operations()
58 changes: 50 additions & 8 deletions cirq-ionq/cirq_ionq/ionq_devices_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,45 +122,87 @@ def test_validate_circuit_valid():


@pytest.mark.parametrize('gate', VALID_GATES)
def test_decompose_leaves_supported_alone(gate):
def test_decompose_leaves_supported_alone_deprecated(gate):
qubits = cirq.LineQubit.range(gate.num_qubits())
device = ionq.IonQAPIDevice(qubits=qubits)
operation = gate(*qubits)
assert device.decompose_operation(operation) == operation
with cirq.testing.assert_deprecated('decompose_to_device', deadline='v0.15'):
assert device.decompose_operation(operation) == operation


@pytest.mark.parametrize('gate', VALID_GATES)
def test_decompose_leaves_supported_alone(gate):
qubits = cirq.LineQubit.range(gate.num_qubits())
operation = gate(*qubits)
assert ionq.decompose_to_device(operation) == operation


VALID_DECOMPOSED_GATES = cirq.Gateset(cirq.XPowGate, cirq.ZPowGate, cirq.CNOT)


def test_decompose_single_qubit_matrix_gate():
def test_decompose_single_qubit_matrix_gate_deprecated():
q = cirq.LineQubit(0)
device = ionq.IonQAPIDevice(qubits=[q])
for _ in range(100):
gate = cirq.MatrixGate(cirq.testing.random_unitary(2))
circuit = cirq.Circuit(gate(q))
decomposed_circuit = cirq.Circuit(*device.decompose_operation(gate(q)))
with cirq.testing.assert_deprecated('decompose_to_device', deadline='v0.15'):
decomposed_circuit = cirq.Circuit(*device.decompose_operation(gate(q)))
cirq.testing.assert_circuits_with_terminal_measurements_are_equivalent(
circuit, decomposed_circuit, atol=1e-8
)
assert VALID_DECOMPOSED_GATES.validate(decomposed_circuit)


def test_decompose_two_qubit_matrix_gate():
def test_decompose_single_qubit_matrix_gate():
q = cirq.LineQubit(0)
for _ in range(100):
gate = cirq.MatrixGate(cirq.testing.random_unitary(2))
circuit = cirq.Circuit(gate(q))
decomposed_circuit = cirq.Circuit(*ionq.decompose_to_device(gate(q)))
cirq.testing.assert_circuits_with_terminal_measurements_are_equivalent(
circuit, decomposed_circuit, atol=1e-8
)
assert VALID_DECOMPOSED_GATES.validate(decomposed_circuit)


def test_decompose_two_qubit_matrix_gate_deprecated():
q0, q1 = cirq.LineQubit.range(2)
device = ionq.IonQAPIDevice(qubits=[q0, q1])
for _ in range(10):
gate = cirq.MatrixGate(cirq.testing.random_unitary(4))
circuit = cirq.Circuit(gate(q0, q1))
decomposed_circuit = cirq.Circuit(*device.decompose_operation(gate(q0, q1)))
with cirq.testing.assert_deprecated('decompose_to_device', deadline='v0.15'):
decomposed_circuit = cirq.Circuit(*device.decompose_operation(gate(q0, q1)))
cirq.testing.assert_circuits_with_terminal_measurements_are_equivalent(
circuit, decomposed_circuit, atol=1e-8
)
assert VALID_DECOMPOSED_GATES.validate(decomposed_circuit)


def test_decompose_unsupported_gate():
def test_decompose_two_qubit_matrix_gate():
q0, q1 = cirq.LineQubit.range(2)
for _ in range(10):
gate = cirq.MatrixGate(cirq.testing.random_unitary(4))
circuit = cirq.Circuit(gate(q0, q1))
decomposed_circuit = cirq.Circuit(*ionq.decompose_to_device(gate(q0, q1)))
cirq.testing.assert_circuits_with_terminal_measurements_are_equivalent(
circuit, decomposed_circuit, atol=1e-8
)
assert VALID_DECOMPOSED_GATES.validate(decomposed_circuit)


def test_decompose_unsupported_gate_deprecated():
q0, q1, q2 = cirq.LineQubit.range(3)
device = ionq.IonQAPIDevice(qubits=[q0, q1, q2])
op = cirq.CCZ(q0, q1, q2)
with pytest.raises(ValueError, match='not supported'):
_ = device.decompose_operation(op)
with cirq.testing.assert_deprecated('decompose_to_device', deadline='v0.15'):
_ = device.decompose_operation(op)


def test_decompose_unsupported_gate():
q0, q1, q2 = cirq.LineQubit.range(3)
op = cirq.CCZ(q0, q1, q2)
with pytest.raises(ValueError, match='not supported'):
_ = ionq.decompose_to_device(op)

0 comments on commit 610b0d4

Please sign in to comment.