Skip to content

Commit

Permalink
Transpile to native IonQ gates (#6572)
Browse files Browse the repository at this point in the history
* add ionq native gate cirq_ionq.ZZGate
* add ionq native gatesets AriaNativeGateset, ForteNativeGateset
* support JSON serialization for the ZZGate and new gatesets
  • Loading branch information
radumarg authored Oct 28, 2024
1 parent 81f66b9 commit f9ed148
Show file tree
Hide file tree
Showing 14 changed files with 1,050 additions and 23 deletions.
12 changes: 11 additions & 1 deletion cirq-ionq/cirq_ionq/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@
decompose_all_to_all_connect_ccz_gate as decompose_all_to_all_connect_ccz_gate,
)

from cirq_ionq.ionq_native_target_gateset import (
AriaNativeGateset as AriaNativeGateset,
ForteNativeGateset as ForteNativeGateset,
)

from cirq_ionq.ionq_exceptions import (
IonQException as IonQException,
IonQNotFoundException as IonQNotFoundException,
Expand All @@ -39,7 +44,12 @@

from cirq_ionq.service import Service as Service

from cirq_ionq.ionq_native_gates import GPIGate as GPIGate, GPI2Gate as GPI2Gate, MSGate as MSGate
from cirq_ionq.ionq_native_gates import (
GPIGate as GPIGate,
GPI2Gate as GPI2Gate,
MSGate as MSGate,
ZZGate as ZZGate,
)

from cirq.protocols.json_serialization import _register_resolver
from cirq_ionq.json_resolver_cache import _class_resolver_dictionary
Expand Down
92 changes: 91 additions & 1 deletion cirq-ionq/cirq_ionq/ionq_native_gates.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@

import cmath
import math
import numpy as np

import cirq
from cirq import protocols
from cirq._doc import document
import numpy as np


@cirq.value.value_equality
Expand Down Expand Up @@ -270,3 +271,92 @@ def __pow__(self, power):
See [IonQ best practices](https://ionq.com/docs/getting-started-with-native-gates){:external}.
""",
)


@cirq.value.value_equality
class ZZGate(cirq.Gate):
r"""The ZZ gate is another two qubit gate native to trapped ions. The ZZ gate only
requires a single parameter, θ, to set the phase of the entanglement.
The unitary matrix of this gate using the parameter $\theta$ is:
$$
\begin{bmatrix}
e{-i\pi\theta} & 0 & 0 & 0 \\
0 & e{i\pi\theta} & 0 & 0 \\
0 & 0 & e{i\pi\theta} & 0 \\
0 & 0 & 0 & e{-i\pi\theta}
\end{bmatrix}
$$
See [IonQ best practices](https://ionq.com/docs/getting-started-with-native-gates){:external}.
"""

def __init__(self, *, theta):
self.theta = theta

def _unitary_(self) -> np.ndarray:
theta = self.theta

return np.array(
[
[cmath.exp(-1j * theta * math.pi), 0, 0, 0],
[0, cmath.exp(1j * theta * math.pi), 0, 0],
[0, 0, cmath.exp(1j * theta * math.pi), 0],
[0, 0, 0, cmath.exp(-1j * theta * math.pi)],
]
)

@property
def phase(self) -> float:
return self.theta

def __str__(self) -> str:
return 'ZZ'

def _num_qubits_(self) -> int:
return 2

def _circuit_diagram_info_(
self, args: 'cirq.CircuitDiagramInfoArgs'
) -> Union[str, 'protocols.CircuitDiagramInfo']:
return protocols.CircuitDiagramInfo(wire_symbols=(f'ZZ({self.theta!r})', 'ZZ'))

def __repr__(self) -> str:
return f'cirq_ionq.ZZGate(theta={self.theta!r})'

def _json_dict_(self) -> Dict[str, Any]:
return cirq.obj_to_dict_helper(self, ['theta'])

def _value_equality_values_(self) -> Any:
return self.theta

def __pow__(self, power):
if power == 1:
return self

if power == -1:
return ZZGate(theta=-self.theta)

return NotImplemented


ZZ = ZZGate(theta=0)
document(
ZZ,
r"""An instance of the two qubit ZZ gate with no phase.
The unitary matrix of this gate for parameters $\theta$ is
$$
\begin{bmatrix}
1 & 0 & 0 & 0 \\
0 & 1 & 0 & 0 \\
0 & 0 & 1 & 0 \\
0 & 0 & 0 & 1 \\
\end{bmatrix}
$$
See [IonQ best practices](https://ionq.com/docs/getting-started-with-native-gates){:external}.
""",
)
41 changes: 25 additions & 16 deletions cirq-ionq/cirq_ionq/ionq_native_gates_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,15 @@
# limitations under the License.
"""Tests for IonQ native gates"""

import math
import cirq
import numpy
import pytest

import cirq_ionq as ionq


PARAMS_FOR_ONE_ANGLE_GATE = [0, 0.1, 0.4, math.pi / 2, math.pi, 2 * math.pi]
PARAMS_FOR_TWO_ANGLE_GATE = [
(0, 1),
(0.1, 1),
(0.4, 1),
(math.pi / 2, 0),
(0, math.pi),
(0.1, 2 * math.pi),
]
PARAMS_FOR_ONE_ANGLE_GATE = [0, 0.1, 0.4, 0.5, 1, 2]
PARAMS_FOR_TWO_ANGLE_GATE = [(0, 1), (0.1, 1), (0.4, 1), (0.5, 0), (0, 1), (0.1, 2)]
INVALID_GATE_POWER = [-2, -0.5, 0, 0.5, 2]


Expand All @@ -39,6 +31,7 @@
(ionq.GPIGate(phi=0.1), 1, "0: ───GPI(0.1)───"),
(ionq.GPI2Gate(phi=0.2), 1, "0: ───GPI2(0.2)───"),
(ionq.MSGate(phi0=0.1, phi1=0.2), 2, "0: ───MS(0.1)───\n\n1: ───MS(0.2)───"),
(ionq.ZZGate(theta=0.3), 2, "0: ───ZZ(0.3)───\n\n1: ───ZZ────────"),
],
)
def test_gate_methods(gate, nqubits, diagram):
Expand All @@ -52,14 +45,20 @@ def test_gate_methods(gate, nqubits, diagram):


@pytest.mark.parametrize(
"gate", [ionq.GPIGate(phi=0.1), ionq.GPI2Gate(phi=0.2), ionq.MSGate(phi0=0.1, phi1=0.2)]
"gate",
[
ionq.GPIGate(phi=0.1),
ionq.GPI2Gate(phi=0.2),
ionq.MSGate(phi0=0.1, phi1=0.2),
ionq.ZZGate(theta=0.4),
],
)
def test_gate_json(gate):
g_json = cirq.to_json(gate)
assert cirq.read_json(json_text=g_json) == gate


@pytest.mark.parametrize("phase", [0, 0.1, 0.4, math.pi / 2, math.pi, 2 * math.pi])
@pytest.mark.parametrize("phase", [0, 0.1, 0.4, 0.5, 1, 2])
def test_gpi_unitary(phase):
"""Tests that the GPI gate is unitary."""
gate = ionq.GPIGate(phi=phase)
Expand All @@ -68,7 +67,7 @@ def test_gpi_unitary(phase):
numpy.testing.assert_array_almost_equal(mat.dot(mat.conj().T), numpy.identity(2))


@pytest.mark.parametrize("phase", [0, 0.1, 0.4, math.pi / 2, math.pi, 2 * math.pi])
@pytest.mark.parametrize("phase", [0, 0.1, 0.4, 0.5, 1, 2])
def test_gpi2_unitary(phase):
"""Tests that the GPI2 gate is unitary."""
gate = ionq.GPI2Gate(phi=phase)
Expand All @@ -77,9 +76,7 @@ def test_gpi2_unitary(phase):
numpy.testing.assert_array_almost_equal(mat.dot(mat.conj().T), numpy.identity(2))


@pytest.mark.parametrize(
"phases", [(0, 1), (0.1, 1), (0.4, 1), (math.pi / 2, 0), (0, math.pi), (0.1, 2 * math.pi)]
)
@pytest.mark.parametrize("phases", [(0, 1), (0.1, 1), (0.4, 1), (0.5, 0), (0, 1), (0.1, 2)])
def test_ms_unitary(phases):
"""Tests that the MS gate is unitary."""
gate = ionq.MSGate(phi0=phases[0], phi1=phases[1])
Expand All @@ -88,12 +85,22 @@ def test_ms_unitary(phases):
numpy.testing.assert_array_almost_equal(mat.dot(mat.conj().T), numpy.identity(4))


@pytest.mark.parametrize("phase", [0, 0.1, 0.4, 0.5, 1, 2])
def test_zz_unitary(phase):
"""Tests that the ZZ gate is unitary."""
gate = ionq.ZZGate(theta=phase)

mat = cirq.protocols.unitary(gate)
numpy.testing.assert_array_almost_equal(mat.dot(mat.conj().T), numpy.identity(4))


@pytest.mark.parametrize(
"gate",
[
*[ionq.GPIGate(phi=angle) for angle in PARAMS_FOR_ONE_ANGLE_GATE],
*[ionq.GPI2Gate(phi=angle) for angle in PARAMS_FOR_ONE_ANGLE_GATE],
*[ionq.MSGate(phi0=angles[0], phi1=angles[1]) for angles in PARAMS_FOR_TWO_ANGLE_GATE],
*[ionq.ZZGate(theta=angle) for angle in PARAMS_FOR_ONE_ANGLE_GATE],
],
)
def test_gate_inverse(gate):
Expand All @@ -110,6 +117,7 @@ def test_gate_inverse(gate):
[
*[ionq.GPIGate(phi=angle) for angle in PARAMS_FOR_ONE_ANGLE_GATE],
*[ionq.GPI2Gate(phi=angle) for angle in PARAMS_FOR_ONE_ANGLE_GATE],
*[ionq.ZZGate(theta=angle) for angle in PARAMS_FOR_ONE_ANGLE_GATE],
*[ionq.MSGate(phi0=angles[0], phi1=angles[1]) for angles in PARAMS_FOR_TWO_ANGLE_GATE],
],
)
Expand All @@ -127,6 +135,7 @@ def test_gate_power1(gate):
*[(ionq.GPIGate(phi=0.1), power) for power in INVALID_GATE_POWER],
*[(ionq.GPI2Gate(phi=0.1), power) for power in INVALID_GATE_POWER],
*[(ionq.MSGate(phi0=0.1, phi1=0.2), power) for power in INVALID_GATE_POWER],
*[(ionq.ZZGate(theta=0.1), power) for power in INVALID_GATE_POWER],
],
)
def test_gate_power_not_implemented(gate, power):
Expand Down
Loading

0 comments on commit f9ed148

Please sign in to comment.