Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions qiskit_ionq/ionq_gates.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from typing import Optional
import math
import numpy as np
from qiskit.circuit import QuantumCircuit
from qiskit.circuit.gate import Gate
from qiskit.circuit.parameterexpression import ParameterValueType

Expand All @@ -55,6 +56,13 @@ def __init__(self, phi: ParameterValueType, label: Optional[str] = None):
"""Create new GPI gate."""
super().__init__("gpi", 1, [phi], label=label)

def _define(self):
"""Define the GPI gate in terms of standard gates for QASM export."""
qc = QuantumCircuit(1, name=self.name)
phi = self.params[0]
qc.u(math.pi, 2 * math.pi * phi, math.pi - 2 * math.pi * phi, 0)
self.definition = qc

def __array__(self, dtype=None, copy=None):
"""Return a numpy array for the GPI gate."""
top = np.exp(-1j * 2 * math.pi * self.params[0])
Expand Down Expand Up @@ -90,6 +98,18 @@ def __init__(self, phi: ParameterValueType, label: Optional[str] = None):
"""Create new GPI2 gate."""
super().__init__("gpi2", 1, [phi], label=label)

def _define(self):
"""Define the GPI2 gate in terms of standard gates for QASM export."""
qc = QuantumCircuit(1, name=self.name)
phi = self.params[0]
qc.u(
math.pi / 2,
2 * math.pi * phi - math.pi / 2,
math.pi / 2 - 2 * math.pi * phi,
0,
)
self.definition = qc

def __array__(self, dtype=None, copy=None):
"""Return a numpy array for the GPI2 gate."""
top = -1j * np.exp(-1j * self.params[0] * 2 * math.pi)
Expand Down Expand Up @@ -139,6 +159,17 @@ def __init__(
label=label,
)

def _define(self):
"""Define the MS gate in terms of standard gates for QASM export."""
qc = QuantumCircuit(2, name=self.name)
phi0, phi1, theta = self.params
qc.rz(-2 * math.pi * phi0, 0)
qc.rz(-2 * math.pi * phi1, 1)
qc.rxx(2 * math.pi * theta, 0, 1)
qc.rz(2 * math.pi * phi0, 0)
qc.rz(2 * math.pi * phi1, 1)
self.definition = qc

def __array__(self, dtype=None, copy=None):
"""Return a numpy array for the MS gate."""
phi0, phi1, theta = self.params
Expand Down Expand Up @@ -183,6 +214,13 @@ def __init__(self, theta: ParameterValueType, label: Optional[str] = None):
"""Create new ZZ gate."""
super().__init__("zz", 2, [theta], label=label)

def _define(self):
"""Define the ZZ gate in terms of standard gates for QASM export."""
qc = QuantumCircuit(2, name=self.name)
theta = self.params[0]
qc.rzz(2 * math.pi * theta, 0, 1)
self.definition = qc

def __array__(self, dtype=None, copy=None) -> np.ndarray:
"""Return a numpy array for the ZZ gate."""
itheta2 = 1j * float(self.params[0]) * math.pi
Expand Down
1 change: 1 addition & 0 deletions requirements-test.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
qiskit>=2.0.0
openqasm3[parser]
pytest
requests-mock>=1.8.0
pytest-cov>=2.10.1
91 changes: 91 additions & 0 deletions test/ionq_gates/test_gates.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@

import pytest

from qiskit import QuantumCircuit
from qiskit.circuit.library import XGate, YGate, RXGate, RYGate, HGate
from qiskit.qasm3 import dumps as qasm3_dumps
from qiskit.quantum_info import Operator
from qiskit_ionq import GPIGate, GPI2Gate, MSGate, ZZGate


Expand Down Expand Up @@ -69,6 +72,15 @@ def test_gpi_inverse(phase):
np.testing.assert_array_almost_equal(mat.dot(mat.conj().T), np.identity(2))


@pytest.mark.parametrize("phase", [-0.5, -0.25, 0, 0.1, 0.25, 0.5, 0.75, 1.0])
def test_gpi_definition(phase):
"""Tests equivalence of the GPI gate matrix and its decomposition."""
gate = GPIGate(phase)
direct_mat = np.array(gate)
decomp_mat = Operator(gate.definition).data
np.testing.assert_array_almost_equal(direct_mat, decomp_mat)


@pytest.mark.parametrize("phase", [0, 0.1, 0.4, np.pi / 2, np.pi, 2 * np.pi])
def test_gpi2_inverse(phase):
"""Tests that the GPI2 gate is unitary."""
Expand All @@ -78,6 +90,15 @@ def test_gpi2_inverse(phase):
np.testing.assert_array_almost_equal(mat.dot(mat.conj().T), np.identity(2))


@pytest.mark.parametrize("phase", [-0.5, -0.25, 0, 0.1, 0.25, 0.5, 0.75, 1.0])
def test_gpi2_definition(phase):
"""Tests equivalence of the GPI2 gate matrix and its decomposition."""
gate = GPI2Gate(phase)
direct_mat = np.array(gate)
decomp_mat = Operator(gate.definition).data
np.testing.assert_array_almost_equal(direct_mat, decomp_mat)


@pytest.mark.parametrize(
"params",
[
Expand All @@ -97,6 +118,18 @@ def test_ms_inverse(params):
np.testing.assert_array_almost_equal(mat.dot(mat.conj().T), np.identity(4))


@pytest.mark.parametrize(
"params",
[(0, 0, 0.25), (0.1, 0.2, 0.25), (0.5, 0.5, 0.125)],
)
def test_ms_definition(params):
"""Tests equivalence of the MS gate matrix and its decomposition."""
gate = MSGate(params[0], params[1], params[2])
direct_mat = np.array(gate)
decomp_mat = Operator(gate.definition).data
np.testing.assert_array_almost_equal(direct_mat, decomp_mat)


@pytest.mark.parametrize(
"angle",
[0, 0.1, 0.4, np.pi / 2, np.pi, 2 * np.pi],
Expand All @@ -107,3 +140,61 @@ def test_zz_inverse(angle):

mat = np.array(gate)
np.testing.assert_array_almost_equal(mat.dot(mat.conj().T), np.identity(4))


@pytest.mark.parametrize("angle", [-0.5, -0.25, 0, 0.1, 0.25, 0.5, 0.75, 1.0])
def test_zz_definition(angle):
"""Tests equivalence of the ZZ gate matrix and its decomposition."""
gate = ZZGate(angle)
direct_mat = np.array(gate)
decomp_mat = Operator(gate.definition).data
np.testing.assert_array_almost_equal(direct_mat, decomp_mat)


def test_qasm3_export():
"""Tests that circuits with IonQ native gates can be exported and parsed as QASM3."""
import openqasm3.parser

circuit = QuantumCircuit(2)
circuit.append(GPIGate(0.25), [0])
circuit.append(GPI2Gate(0.5), [0])
circuit.append(MSGate(0.1, 0.2), [0, 1])
circuit.append(ZZGate(0.3), [0, 1])

# Should not raise QASM3ExporterError
qasm3_str = qasm3_dumps(circuit)

# Verify the output contains our gate definitions
assert "gate gpi" in qasm3_str
assert "gate gpi2" in qasm3_str
assert "gate ms" in qasm3_str
assert "gate zz" in qasm3_str

# Should not raise QASM3ParserError (issue #217).
openqasm3.parser.parse(qasm3_str)


def test_qasm_export_from_transpiled_circuit():
"""Tests QASM export and parse after transpiling to native gateset."""
import openqasm3.parser
import qiskit
import qiskit.qasm2
import qiskit.qasm3
from qiskit_ionq import IonQProvider

# Repro case from issue #217.
circuit = qiskit.QuantumCircuit(1)
circuit.u(0.1, 0.2, 0.3, 0)

provider = IonQProvider()
backend = provider.get_backend("simulator", gateset="native")

transpiled = qiskit.transpile(circuit, backend=backend, optimization_level=1)

# QASM2 export should work.
qasm2_str = qiskit.qasm2.dumps(transpiled)
assert "gate gpi" in qasm2_str or "gate gpi2" in qasm2_str
# QASM3 export and parse should work (issue #218).
qasm3_str = qiskit.qasm3.dumps(transpiled)
assert "gate gpi" in qasm3_str or "gate gpi2" in qasm3_str
openqasm3.parser.parse(qasm3_str) # Should not raise QASM3ParserError
Loading