Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add VirtualZGate to native gateset #165

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
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
2 changes: 1 addition & 1 deletion qiskit_ionq/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,5 @@

from .ionq_provider import IonQProvider
from .version import __version__
from .ionq_gates import GPIGate, GPI2Gate, MSGate, ZZGate
from .ionq_gates import GPIGate, GPI2Gate, MSGate, VirtualZGate, ZZGate
from .constants import ErrorMitigation
1 change: 1 addition & 0 deletions qiskit_ionq/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@
"gpi", # TODO All single qubit gates can transpile into GPI/GPI2
"gpi2",
"ms", # Pairwise MS gate
"gz", # VirtualZ gate (RZ)
"zz", # ZZ gate
]

Expand Down
67 changes: 49 additions & 18 deletions qiskit_ionq/ionq_gates.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@
"""Native gateset for IonQ hardware."""

from typing import Optional
import cmath
import math
import numpy
from qiskit.circuit.gate import Gate
from qiskit.circuit.parameterexpression import ParameterValueType
Expand Down Expand Up @@ -58,8 +56,8 @@ def __init__(self, phi: ParameterValueType, label: Optional[str] = None):

def __array__(self, dtype=None):
"""Return a numpy.array for the GPI gate."""
top = cmath.exp(-self.params[0] * 2 * math.pi * 1j)
bot = cmath.exp(self.params[0] * 2 * math.pi * 1j)
top = numpy.exp(-self.params[0] * 2 * numpy.pi * 1j)
bot = numpy.exp(self.params[0] * 2 * numpy.pi * 1j)
return numpy.array([[0, top], [bot, 0]], dtype=dtype)


Expand Down Expand Up @@ -87,9 +85,42 @@ def __init__(self, phi: ParameterValueType, label: Optional[str] = None):

def __array__(self, dtype=None):
"""Return a numpy.array for the GPI2 gate."""
top = -1j * cmath.exp(self.params[0] * 2 * math.pi * -1j)
bot = -1j * cmath.exp(self.params[0] * 2 * math.pi * 1j)
return numpy.array([[1, top], [bot, 1]], dtype=dtype) / math.sqrt(2)
top = -1j * numpy.exp(self.params[0] * 2 * numpy.pi * -1j)
bot = -1j * numpy.exp(self.params[0] * 2 * numpy.pi * 1j)
return numpy.array([[1, top], [bot, 1]], dtype=dtype) / numpy.sqrt(2)


class VirtualZGate(Gate):
r"""Single-qubit rotation about the Z axis.
**Circuit symbol:**
.. parsed-literal::
┌───────┐
q_0: ┤ GZ(ϴ) ├
└───────┘
**Matrix Representation:**
.. math::

GZ(\theta) = \exp\left(-i\frac{\theta}{2}Z\right) =
\begin{pmatrix}
e^{-i\frac{\theta}{2}} & 0 \\
0 & e^{i\frac{\theta}{2}}
\end{pmatrix}
"""

def __init__(
self,
theta: ParameterValueType,
label: Optional[str] = None,
):
"""Create new GZ gate."""
super().__init__("gz", 1, [theta], label=label)

def __array__(self, dtype=None):
"""Return a numpy.array for the GZ gate."""
theta = self.params[0]
top = numpy.exp(-1j * theta / 2)
bot = numpy.exp(1j * theta / 2)
return numpy.array([[top, 0], [0, bot]], dtype=dtype)


class MSGate(Gate):
Expand Down Expand Up @@ -134,15 +165,15 @@ def __array__(self, dtype=None):
phi0 = self.params[0]
phi1 = self.params[1]
theta = self.params[2]
diag = numpy.cos(math.pi * theta)
sin = numpy.sin(math.pi * theta)
diag = numpy.cos(numpy.pi * theta)
sin = numpy.sin(numpy.pi * theta)

return numpy.array(
[
[diag, 0, 0, sin * -1j * cmath.exp(-1j * 2 * math.pi * (phi0 + phi1))],
[0, diag, sin * -1j * cmath.exp(-1j * 2 * math.pi * (phi0 - phi1)), 0],
[0, sin * -1j * cmath.exp(1j * 2 * math.pi * (phi0 - phi1)), diag, 0],
[sin * -1j * cmath.exp(1j * 2 * math.pi * (phi0 + phi1)), 0, 0, diag],
[diag, 0, 0, sin * -1j * numpy.exp(-1j * 2 * numpy.pi * (phi0 + phi1))],
[0, diag, sin * -1j * numpy.exp(-1j * 2 * numpy.pi * (phi0 - phi1)), 0],
[0, sin * -1j * numpy.exp(1j * 2 * numpy.pi * (phi0 - phi1)), diag, 0],
[sin * -1j * numpy.exp(1j * 2 * numpy.pi * (phi0 + phi1)), 0, 0, diag],
],
dtype=dtype,
)
Expand Down Expand Up @@ -174,13 +205,13 @@ def __init__(self, theta: ParameterValueType, label: Optional[str] = None):

def __array__(self, dtype=None):
"""Return a numpy.array for the ZZ gate."""
itheta2 = 1j * float(self.params[0]) * math.pi
itheta2 = 1j * float(self.params[0]) * numpy.pi
return numpy.array(
[
[cmath.exp(-itheta2), 0, 0, 0],
[0, cmath.exp(itheta2), 0, 0],
[0, 0, cmath.exp(itheta2), 0],
[0, 0, 0, cmath.exp(-itheta2)],
[numpy.exp(-itheta2), 0, 0, 0],
[0, numpy.exp(itheta2), 0, 0],
[0, 0, numpy.exp(itheta2), 0],
[0, 0, 0, numpy.exp(-itheta2)],
],
dtype=dtype,
)
5 changes: 4 additions & 1 deletion test/helpers/test_qiskit_to_ionq.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@

from qiskit_ionq.exceptions import IonQGateError
from qiskit_ionq.helpers import qiskit_to_ionq, decompress_metadata_string_to_dict
from qiskit_ionq.ionq_gates import GPIGate, GPI2Gate, MSGate, ZZGate
from qiskit_ionq.ionq_gates import GPIGate, GPI2Gate, MSGate, VirtualZGate, ZZGate
from qiskit_ionq.constants import ErrorMitigation


Expand Down Expand Up @@ -273,6 +273,7 @@ def test_native_circuit_transpile(simulator_backend):
circ = QuantumCircuit(3, name="blame_test")
circ.append(GPIGate(0.1), [0])
circ.append(GPI2Gate(0.2), [1])
circ.append(VirtualZGate(0.3), [1])
circ.append(MSGate(0.2, 0.3, 0.25), [1, 2])
circ.append(ZZGate(0.4), [0, 2])

Expand All @@ -291,6 +292,7 @@ def test_full_native_circuit(simulator_backend):
qc = QuantumCircuit(3, name="blame_test")
qc.append(GPIGate(0.1), [0])
qc.append(GPI2Gate(0.2), [1])
qc.append(VirtualZGate(0.3), [1])
qc.append(MSGate(0.2, 0.3, 0.25), [1, 2])
qc.append(ZZGate(0.4), [0, 2])
ionq_json = qiskit_to_ionq(
Expand Down Expand Up @@ -325,6 +327,7 @@ def test_full_native_circuit(simulator_backend):
"circuit": [
{"gate": "gpi", "target": 0, "phase": 0.1},
{"gate": "gpi2", "target": 1, "phase": 0.2},
{"gate": "gz", "targets": [1], "phase": 0.3},
{"gate": "ms", "targets": [1, 2], "phases": [0.2, 0.3], "angle": 0.25},
{"gate": "zz", "angle": 0.4, "targets": [0, 2]},
],
Expand Down
30 changes: 19 additions & 11 deletions test/ionq_gates/test_gates.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,15 @@
# See the License for the specific language governing permissions and
# limitations under the License.

"""Tests for the IonQ's GPIGate, GPI2Gate, MSGate, ZZGate."""
"""Tests for the IonQ's GPIGate, GPI2Gate, MSGate, VirtualZGate, ZZGate."""
# pylint: disable=redefined-outer-name

import math
import numpy

import pytest

from qiskit.circuit.library import XGate, YGate, RXGate, RYGate
from qiskit_ionq import GPIGate, GPI2Gate, MSGate, ZZGate
from qiskit.circuit.library import XGate, YGate, RXGate, RYGate, RZGate
from qiskit_ionq import GPIGate, GPI2Gate, MSGate, VirtualZGate, ZZGate


@pytest.mark.parametrize("gate,phase", [(XGate(), 0), (YGate(), 0.25)])
Expand All @@ -44,23 +43,32 @@ def test_gpi_equivalences(gate, phase):


@pytest.mark.parametrize(
"gate,phase", [(RXGate(math.pi / 2), 1), (RYGate(math.pi / 2), 0.25)]
"gate,phase", [(RXGate(numpy.pi / 2), 1), (RYGate(numpy.pi / 2), 0.25)]
)
def test_gpi2_equivalences(gate, phase):
"""Tests equivalence of the GPI2 gate at specific phases."""
gpi = GPI2Gate(phase)
numpy.testing.assert_array_almost_equal(numpy.array(gate), numpy.array(gpi))


@pytest.mark.parametrize("phase", [0, 0.1, 0.4, math.pi / 2, math.pi, 2 * math.pi])
@pytest.mark.parametrize("gate,angle", [(RZGate, 0), (RZGate, numpy.pi)])
def test_virtualz_equivalence(gate, angle):
"""Tests equivalence of the VirtualZGate gate."""
virtualz = VirtualZGate(angle)
numpy.testing.assert_array_almost_equal(
gate(angle).to_matrix(), virtualz.to_matrix()
)


@pytest.mark.parametrize("phase", [0, 0.1, 0.4, numpy.pi / 2, numpy.pi, 2 * numpy.pi])
def test_gpi_inverse(phase):
"""Tests that the GPI gate is unitary."""
gate = GPIGate(phase)
mat = numpy.array(gate)
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, numpy.pi / 2, numpy.pi, 2 * numpy.pi])
def test_gpi2_inverse(phase):
"""Tests that the GPI2 gate is unitary."""
gate = GPI2Gate(phase)
Expand All @@ -75,9 +83,9 @@ def test_gpi2_inverse(phase):
(0, 1, 0.25),
(0.1, 1, 0.25),
(0.4, 1, 0.25),
(math.pi / 2, 0, 0.25),
(0, math.pi, 0.25),
(0.1, 2 * math.pi, 0.25),
(numpy.pi / 2, 0, 0.25),
(0, numpy.pi, 0.25),
(0.1, 2 * numpy.pi, 0.25),
],
)
def test_ms_inverse(params):
Expand All @@ -90,7 +98,7 @@ def test_ms_inverse(params):

@pytest.mark.parametrize(
"angle",
[0, 0.1, 0.4, math.pi / 2, math.pi, 2 * math.pi],
[0, 0.1, 0.4, numpy.pi / 2, numpy.pi, 2 * numpy.pi],
)
def test_zz_inverse(angle):
"""Tests that the ZZ gate is unitary."""
Expand Down
Loading