diff --git a/qiskit_ionq/__init__.py b/qiskit_ionq/__init__.py index 5b12899e..465fcbfa 100644 --- a/qiskit_ionq/__init__.py +++ b/qiskit_ionq/__init__.py @@ -35,7 +35,7 @@ TrappedIonOptimizerPluginCommuteGpi2ThroughMs, ) from .version import __version__ -from .ionq_gates import GPIGate, GPI2Gate, MSGate, ZZGate +from .ionq_gates import GPIGate, GPI2Gate, VirtualZGate, MSGate, ZZGate from .constants import ErrorMitigation from .ionq_equivalence_library import add_equivalences @@ -50,6 +50,7 @@ "GPIGate", "GPI2Gate", "MSGate", + "VirtualZGate", "ZZGate", "ErrorMitigation", "add_equivalences", diff --git a/qiskit_ionq/helpers.py b/qiskit_ionq/helpers.py index 03b330b0..1e29c897 100644 --- a/qiskit_ionq/helpers.py +++ b/qiskit_ionq/helpers.py @@ -127,6 +127,7 @@ ionq_native_basis_gates = [ "gpi", "gpi2", + "gz", # VirtualZ gate (RZ) "ms", # Pairwise MS gate "zz", # ZZ gate ] diff --git a/qiskit_ionq/ionq_equivalence_library.py b/qiskit_ionq/ionq_equivalence_library.py index 482164b8..1a56702e 100644 --- a/qiskit_ionq/ionq_equivalence_library.py +++ b/qiskit_ionq/ionq_equivalence_library.py @@ -38,7 +38,7 @@ RZZGate, UGate, ) -from .ionq_gates import GPIGate, GPI2Gate, MSGate, ZZGate +from .ionq_gates import GPIGate, GPI2Gate, VirtualZGate, MSGate, ZZGate # 1q gates @@ -81,6 +81,15 @@ def gpi2_gate_equivalence() -> None: SessionEquivalenceLibrary.add_equivalence(GPI2Gate(phi), circ) +def virtualz_gate_equivalence() -> None: + """VirtualZ(θ) -> RZ(θ) (for Aer/QIS simulation).""" + q = QuantumRegister(1, "q") + theta = Parameter("theta_param") + circ = QuantumCircuit(q) + circ.append(RZGate(theta), [0]) + SessionEquivalenceLibrary.add_equivalence(VirtualZGate(theta), circ) + + # 2q native gates -> standard rotations (helps simulation & pattern matching) @@ -126,6 +135,7 @@ def add_equivalences() -> None: u_gate_equivalence() gpi_gate_equivalence() gpi2_gate_equivalence() + virtualz_gate_equivalence() # 2q zz_gate_equivalence() # CX (both backends) diff --git a/qiskit_ionq/ionq_gates.py b/qiskit_ionq/ionq_gates.py index 1e5c3875..513b3e4d 100644 --- a/qiskit_ionq/ionq_gates.py +++ b/qiskit_ionq/ionq_gates.py @@ -102,6 +102,43 @@ def __array__(self, dtype=None, copy=None): return arr +class VirtualZGate(Gate): + r"""Single-qubit VirtualZ (GZ) gate. + **Circuit symbol:** + .. parsed-literal:: + ┌───────┐ + q_0: ┤ GZ(θ) ├ + └───────┘ + **Matrix Representation:** + + .. math:: + + GZ(\theta) = + \begin{pmatrix} + e^{-i\theta/2} & 0 \\ + 0 & e^{i\theta/2} + \end{pmatrix} + + Equivalent to RZ(θ). + """ + + def __init__(self, theta: ParameterValueType, label: Optional[str] = None): + """Create new VirtualZ gate.""" + super().__init__("gz", 1, [theta], label=label) + + def __array__(self, dtype=None, copy=None): + """Return a numpy array for the VirtualZ gate.""" + theta = float(self.params[0]) + arr = np.array( + [[np.exp(-1j * theta / 2), 0], [0, np.exp(1j * theta / 2)]] + ) + if dtype is not None: + arr = arr.astype(dtype, copy=False) + if copy is True: + return arr.copy() + return arr + + class MSGate(Gate): r"""Entangling 2-Qubit MS gate. **Circuit symbol:** diff --git a/test/helpers/test_qiskit_to_ionq.py b/test/helpers/test_qiskit_to_ionq.py index c672072b..3e916970 100644 --- a/test/helpers/test_qiskit_to_ionq.py +++ b/test/helpers/test_qiskit_to_ionq.py @@ -41,7 +41,7 @@ compress_to_metadata_string, get_user_agent, ) -from qiskit_ionq.ionq_gates import GPIGate, GPI2Gate, MSGate, ZZGate +from qiskit_ionq.ionq_gates import GPIGate, GPI2Gate, VirtualZGate, MSGate, ZZGate from qiskit_ionq.constants import ErrorMitigation @@ -320,6 +320,7 @@ def test_native_circuit_transpile(simulator_backend): circ.append(GPIGate(0.1), [0]) circ.append(GPI2Gate(0.2), [1]) circ.append(MSGate(0.2, 0.3, 0.25), [1, 2]) + circ.append(VirtualZGate(0.3), [0]) circ.append(ZZGate(0.4), [0, 2]) with pytest.raises(TranspilerError) as exc_info: @@ -338,6 +339,7 @@ def test_full_native_circuit(simulator_backend): qc.append(GPIGate(0.1), [0]) qc.append(GPI2Gate(0.2), [1]) qc.append(MSGate(0.2, 0.3, 0.25), [1, 2]) + qc.append(VirtualZGate(0.3), [0]) qc.append(ZZGate(0.4), [0, 2]) ionq_json = qiskit_to_ionq( qc, @@ -378,6 +380,7 @@ def test_full_native_circuit(simulator_backend): {"gate": "gpi", "target": 0, "phase": 0.1}, {"gate": "gpi2", "target": 1, "phase": 0.2}, {"gate": "ms", "targets": [1, 2], "phases": [0.2, 0.3], "angle": 0.25}, + {"gate": "gz", "targets": [0], "phase": 0.3}, {"gate": "zz", "angle": 0.4, "targets": [0, 2]}, ], }, diff --git a/test/ionq_gates/test_gates.py b/test/ionq_gates/test_gates.py index e5e3b274..5b757a0b 100644 --- a/test/ionq_gates/test_gates.py +++ b/test/ionq_gates/test_gates.py @@ -31,8 +31,8 @@ import pytest -from qiskit.circuit.library import XGate, YGate, RXGate, RYGate, HGate -from qiskit_ionq import GPIGate, GPI2Gate, MSGate, ZZGate +from qiskit.circuit.library import XGate, YGate, RXGate, RYGate, RZGate, HGate +from qiskit_ionq import GPIGate, GPI2Gate, VirtualZGate, MSGate, ZZGate @pytest.mark.parametrize("gate,phase", [(XGate(), 0), (YGate(), 0.25)]) @@ -97,6 +97,22 @@ def test_ms_inverse(params): np.testing.assert_array_almost_equal(mat.dot(mat.conj().T), np.identity(4)) +@pytest.mark.parametrize("theta", [0, 0.1, 0.4, np.pi / 2, np.pi, 2 * np.pi]) +def test_virtualz_equivalence(theta): + """Tests that VirtualZGate matches RZGate at various angles.""" + gz_gate = VirtualZGate(theta) + rz_gate = RZGate(theta) + np.testing.assert_array_almost_equal(gz_gate.to_matrix(), rz_gate.to_matrix()) + + +@pytest.mark.parametrize("theta", [0, 0.1, 0.4, np.pi / 2, np.pi, 2 * np.pi]) +def test_gz_inverse(theta): + """Tests that the VirtualZ gate is unitary.""" + gate = VirtualZGate(theta) + mat = np.array(gate) + np.testing.assert_array_almost_equal(mat.dot(mat.conj().T), np.identity(2)) + + @pytest.mark.parametrize( "angle", [0, 0.1, 0.4, np.pi / 2, np.pi, 2 * np.pi],