Skip to content

Commit f9ed148

Browse files
authored
Transpile to native IonQ gates (#6572)
* add ionq native gate cirq_ionq.ZZGate * add ionq native gatesets AriaNativeGateset, ForteNativeGateset * support JSON serialization for the ZZGate and new gatesets
1 parent 81f66b9 commit f9ed148

14 files changed

+1050
-23
lines changed

cirq-ionq/cirq_ionq/__init__.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@
2323
decompose_all_to_all_connect_ccz_gate as decompose_all_to_all_connect_ccz_gate,
2424
)
2525

26+
from cirq_ionq.ionq_native_target_gateset import (
27+
AriaNativeGateset as AriaNativeGateset,
28+
ForteNativeGateset as ForteNativeGateset,
29+
)
30+
2631
from cirq_ionq.ionq_exceptions import (
2732
IonQException as IonQException,
2833
IonQNotFoundException as IonQNotFoundException,
@@ -39,7 +44,12 @@
3944

4045
from cirq_ionq.service import Service as Service
4146

42-
from cirq_ionq.ionq_native_gates import GPIGate as GPIGate, GPI2Gate as GPI2Gate, MSGate as MSGate
47+
from cirq_ionq.ionq_native_gates import (
48+
GPIGate as GPIGate,
49+
GPI2Gate as GPI2Gate,
50+
MSGate as MSGate,
51+
ZZGate as ZZGate,
52+
)
4353

4454
from cirq.protocols.json_serialization import _register_resolver
4555
from cirq_ionq.json_resolver_cache import _class_resolver_dictionary

cirq-ionq/cirq_ionq/ionq_native_gates.py

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,11 @@
1717

1818
import cmath
1919
import math
20+
import numpy as np
21+
2022
import cirq
2123
from cirq import protocols
2224
from cirq._doc import document
23-
import numpy as np
2425

2526

2627
@cirq.value.value_equality
@@ -270,3 +271,92 @@ def __pow__(self, power):
270271
See [IonQ best practices](https://ionq.com/docs/getting-started-with-native-gates){:external}.
271272
""",
272273
)
274+
275+
276+
@cirq.value.value_equality
277+
class ZZGate(cirq.Gate):
278+
r"""The ZZ gate is another two qubit gate native to trapped ions. The ZZ gate only
279+
requires a single parameter, θ, to set the phase of the entanglement.
280+
281+
The unitary matrix of this gate using the parameter $\theta$ is:
282+
283+
$$
284+
\begin{bmatrix}
285+
e{-i\pi\theta} & 0 & 0 & 0 \\
286+
0 & e{i\pi\theta} & 0 & 0 \\
287+
0 & 0 & e{i\pi\theta} & 0 \\
288+
0 & 0 & 0 & e{-i\pi\theta}
289+
\end{bmatrix}
290+
$$
291+
292+
See [IonQ best practices](https://ionq.com/docs/getting-started-with-native-gates){:external}.
293+
"""
294+
295+
def __init__(self, *, theta):
296+
self.theta = theta
297+
298+
def _unitary_(self) -> np.ndarray:
299+
theta = self.theta
300+
301+
return np.array(
302+
[
303+
[cmath.exp(-1j * theta * math.pi), 0, 0, 0],
304+
[0, cmath.exp(1j * theta * math.pi), 0, 0],
305+
[0, 0, cmath.exp(1j * theta * math.pi), 0],
306+
[0, 0, 0, cmath.exp(-1j * theta * math.pi)],
307+
]
308+
)
309+
310+
@property
311+
def phase(self) -> float:
312+
return self.theta
313+
314+
def __str__(self) -> str:
315+
return 'ZZ'
316+
317+
def _num_qubits_(self) -> int:
318+
return 2
319+
320+
def _circuit_diagram_info_(
321+
self, args: 'cirq.CircuitDiagramInfoArgs'
322+
) -> Union[str, 'protocols.CircuitDiagramInfo']:
323+
return protocols.CircuitDiagramInfo(wire_symbols=(f'ZZ({self.theta!r})', 'ZZ'))
324+
325+
def __repr__(self) -> str:
326+
return f'cirq_ionq.ZZGate(theta={self.theta!r})'
327+
328+
def _json_dict_(self) -> Dict[str, Any]:
329+
return cirq.obj_to_dict_helper(self, ['theta'])
330+
331+
def _value_equality_values_(self) -> Any:
332+
return self.theta
333+
334+
def __pow__(self, power):
335+
if power == 1:
336+
return self
337+
338+
if power == -1:
339+
return ZZGate(theta=-self.theta)
340+
341+
return NotImplemented
342+
343+
344+
ZZ = ZZGate(theta=0)
345+
document(
346+
ZZ,
347+
r"""An instance of the two qubit ZZ gate with no phase.
348+
349+
The unitary matrix of this gate for parameters $\theta$ is
350+
351+
$$
352+
\begin{bmatrix}
353+
1 & 0 & 0 & 0 \\
354+
0 & 1 & 0 & 0 \\
355+
0 & 0 & 1 & 0 \\
356+
0 & 0 & 0 & 1 \\
357+
\end{bmatrix}
358+
$$
359+
360+
See [IonQ best practices](https://ionq.com/docs/getting-started-with-native-gates){:external}.
361+
""",
362+
)

cirq-ionq/cirq_ionq/ionq_native_gates_test.py

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,23 +13,15 @@
1313
# limitations under the License.
1414
"""Tests for IonQ native gates"""
1515

16-
import math
1716
import cirq
1817
import numpy
1918
import pytest
2019

2120
import cirq_ionq as ionq
2221

2322

24-
PARAMS_FOR_ONE_ANGLE_GATE = [0, 0.1, 0.4, math.pi / 2, math.pi, 2 * math.pi]
25-
PARAMS_FOR_TWO_ANGLE_GATE = [
26-
(0, 1),
27-
(0.1, 1),
28-
(0.4, 1),
29-
(math.pi / 2, 0),
30-
(0, math.pi),
31-
(0.1, 2 * math.pi),
32-
]
23+
PARAMS_FOR_ONE_ANGLE_GATE = [0, 0.1, 0.4, 0.5, 1, 2]
24+
PARAMS_FOR_TWO_ANGLE_GATE = [(0, 1), (0.1, 1), (0.4, 1), (0.5, 0), (0, 1), (0.1, 2)]
3325
INVALID_GATE_POWER = [-2, -0.5, 0, 0.5, 2]
3426

3527

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

5346

5447
@pytest.mark.parametrize(
55-
"gate", [ionq.GPIGate(phi=0.1), ionq.GPI2Gate(phi=0.2), ionq.MSGate(phi0=0.1, phi1=0.2)]
48+
"gate",
49+
[
50+
ionq.GPIGate(phi=0.1),
51+
ionq.GPI2Gate(phi=0.2),
52+
ionq.MSGate(phi0=0.1, phi1=0.2),
53+
ionq.ZZGate(theta=0.4),
54+
],
5655
)
5756
def test_gate_json(gate):
5857
g_json = cirq.to_json(gate)
5958
assert cirq.read_json(json_text=g_json) == gate
6059

6160

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

7069

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

7978

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

9087

88+
@pytest.mark.parametrize("phase", [0, 0.1, 0.4, 0.5, 1, 2])
89+
def test_zz_unitary(phase):
90+
"""Tests that the ZZ gate is unitary."""
91+
gate = ionq.ZZGate(theta=phase)
92+
93+
mat = cirq.protocols.unitary(gate)
94+
numpy.testing.assert_array_almost_equal(mat.dot(mat.conj().T), numpy.identity(4))
95+
96+
9197
@pytest.mark.parametrize(
9298
"gate",
9399
[
94100
*[ionq.GPIGate(phi=angle) for angle in PARAMS_FOR_ONE_ANGLE_GATE],
95101
*[ionq.GPI2Gate(phi=angle) for angle in PARAMS_FOR_ONE_ANGLE_GATE],
96102
*[ionq.MSGate(phi0=angles[0], phi1=angles[1]) for angles in PARAMS_FOR_TWO_ANGLE_GATE],
103+
*[ionq.ZZGate(theta=angle) for angle in PARAMS_FOR_ONE_ANGLE_GATE],
97104
],
98105
)
99106
def test_gate_inverse(gate):
@@ -110,6 +117,7 @@ def test_gate_inverse(gate):
110117
[
111118
*[ionq.GPIGate(phi=angle) for angle in PARAMS_FOR_ONE_ANGLE_GATE],
112119
*[ionq.GPI2Gate(phi=angle) for angle in PARAMS_FOR_ONE_ANGLE_GATE],
120+
*[ionq.ZZGate(theta=angle) for angle in PARAMS_FOR_ONE_ANGLE_GATE],
113121
*[ionq.MSGate(phi0=angles[0], phi1=angles[1]) for angles in PARAMS_FOR_TWO_ANGLE_GATE],
114122
],
115123
)
@@ -127,6 +135,7 @@ def test_gate_power1(gate):
127135
*[(ionq.GPIGate(phi=0.1), power) for power in INVALID_GATE_POWER],
128136
*[(ionq.GPI2Gate(phi=0.1), power) for power in INVALID_GATE_POWER],
129137
*[(ionq.MSGate(phi0=0.1, phi1=0.2), power) for power in INVALID_GATE_POWER],
138+
*[(ionq.ZZGate(theta=0.1), power) for power in INVALID_GATE_POWER],
130139
],
131140
)
132141
def test_gate_power_not_implemented(gate, power):

0 commit comments

Comments
 (0)