diff --git a/pyproject.toml b/pyproject.toml index 739e976..254b4e7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,6 +32,7 @@ classifiers = [ "Operating System :: Unix", "Operating System :: MacOS", ] + dependencies = ["numpy", "openqasm3[parser]>=1.0.0,<2.0.0"] [project.urls] @@ -43,6 +44,7 @@ cli = ["typer>=0.12.1", "rich>=10.11.0", "typing-extensions"] test = ["pytest", "pytest-cov"] lint = ["black", "isort", "pylint", "mypy", "qbraid-cli>=0.8.5"] docs = ["sphinx>=7.3.7,<8.2.0", "sphinx-autodoc-typehints>=1.24,<3.1", "sphinx-rtd-theme>=2.0.0,<4.0.0", "docutils<0.22", "sphinx-copybutton"] +visualization = ["matplotlib"] [tool.setuptools_scm] write_to = "src/pyqasm/_version.py" diff --git a/src/pyqasm/maps/gates.py b/src/pyqasm/maps/gates.py index d8f5f4b..524cb2a 100644 --- a/src/pyqasm/maps/gates.py +++ b/src/pyqasm/maps/gates.py @@ -1,1169 +1,1184 @@ -# Copyright (C) 2024 qBraid -# -# This file is part of pyqasm -# -# Pyqasm is free software released under the GNU General Public License v3 -# or later. You can redistribute and/or modify it under the terms of the GPL v3. -# See the LICENSE file in the project root or . -# -# THERE IS NO WARRANTY for pyqasm, as per Section 15 of the GPL v3. - -# pylint: disable=too-many-lines - -""" -Module mapping supported QASM gates to lower level gate operations. - -""" - - -from typing import Callable, Union - -import numpy as np -from openqasm3.ast import FloatLiteral, Identifier, IndexedIdentifier, QuantumGate, QuantumPhase - -from pyqasm.elements import InversionOp -from pyqasm.exceptions import ValidationError -from pyqasm.linalg import kak_decomposition_angles -from pyqasm.maps.expressions import CONSTANTS_MAP - - -def u3_gate( - theta: Union[int, float], - phi: Union[int, float], - lam: Union[int, float], - qubit_id, -) -> list[QuantumGate]: - """ - Implements the U3 gate using the following decomposition: - https://docs.quantum.ibm.com/api/qiskit/qiskit.circuit.library.UGate - https://docs.quantum.ibm.com/api/qiskit/qiskit.circuit.library.PhaseGate - - Args: - name (str): The name of the gate. - theta (Union[int, float]): The theta parameter. - phi (Union[int, float]): The phi parameter. - lam (Union[int, float]): The lambda parameter. - qubit_id (IndexedIdentifier): The qubit on which to apply the gate. - - Returns: - list: A list of QuantumGate objects representing the decomposition of the U3 gate. - """ - result: list[QuantumGate] = [] - result.extend(one_qubit_rotation_op("rz", lam, qubit_id)) - result.extend(one_qubit_rotation_op("rx", CONSTANTS_MAP["pi"] / 2, qubit_id)) - result.extend(one_qubit_rotation_op("rz", theta + CONSTANTS_MAP["pi"], qubit_id)) - result.extend(one_qubit_rotation_op("rx", CONSTANTS_MAP["pi"] / 2, qubit_id)) - result.extend(one_qubit_rotation_op("rz", phi + CONSTANTS_MAP["pi"], qubit_id)) - return result - # global phase - e^(i*(phi+lambda)/2) is missing in the above implementation - - -def u3_inv_gate( - theta: Union[int, float], - phi: Union[int, float], - lam: Union[int, float], - qubits, -) -> list[QuantumGate]: - """ - Implements the inverse of the U3 gate using the decomposition present in - the u3_gate function. - """ - result: list[QuantumGate] = [] - result.extend(one_qubit_rotation_op("rz", -1.0 * (phi + CONSTANTS_MAP["pi"]), qubits)) - result.extend(one_qubit_rotation_op("rx", -1.0 * (CONSTANTS_MAP["pi"] / 2), qubits)) - result.extend(one_qubit_rotation_op("rz", -1.0 * (theta + CONSTANTS_MAP["pi"]), qubits)) - result.extend(one_qubit_rotation_op("rx", -1.0 * (CONSTANTS_MAP["pi"] / 2), qubits)) - result.extend(one_qubit_rotation_op("rz", -1.0 * lam, qubits)) - return result - - -def u2_gate(phi, lam, qubits) -> list[QuantumGate]: - """ - Implements the U2 gate using the following decomposition: - https://docs.quantum.ibm.com/api/qiskit/qiskit.circuit.library.U2Gate - """ - return u3_gate(CONSTANTS_MAP["pi"] / 2, phi, lam, qubits) - - -def u2_inv_gate(phi, lam, qubits) -> list[QuantumGate]: - """ - Implements the inverse of the U2 gate using the decomposition present in - the u2_gate function. - """ - return u3_inv_gate(CONSTANTS_MAP["pi"] / 2, phi, lam, qubits) - - -def global_phase_gate(theta: float, qubit_list: list[IndexedIdentifier]) -> list[QuantumPhase]: - """ - Builds a global phase gate with the given theta and qubit list. - - Args: - theta (float): The phase angle. - qubit_list (list[IndexedIdentifier]): The list of qubits on which to apply the phase. - - Returns: - list[QuantumPhase]: A QuantumPhase object representing the global phase gate. - """ - return [ - QuantumPhase( - argument=FloatLiteral(value=theta), qubits=qubit_list, modifiers=[] # type: ignore - ) - ] - - -def sxdg_gate_op(qubit_id) -> list[QuantumGate]: - """ - Implements the conjugate transpose of the Sqrt(X) gate as a decomposition of other gates. - """ - return one_qubit_rotation_op("rx", -CONSTANTS_MAP["pi"] / 2, qubit_id) - - -def cy_gate(qubit0: IndexedIdentifier, qubit1: IndexedIdentifier) -> list[QuantumGate]: - """ - Implements the CY gate as a decomposition of other gates. - """ - result: list[QuantumGate] = [] - result.extend(one_qubit_gate_op("sdg", qubit1)) - result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) - result.extend(one_qubit_gate_op("s", qubit1)) - return result - - -def ch_gate(qubit0: IndexedIdentifier, qubit1: IndexedIdentifier) -> list[QuantumGate]: - """ - Implements the CH gate as a decomposition of other gates. - - Used the following qiskit decomposition - - - In [10]: q = QuantumCircuit(2) - - In [11]: q.ch(0, 1) - Out[11]: - - In [12]: q.decompose().draw() - Out[12]: - - q_0: ─────────────────■───────────────────── - ┌───┐┌───┐┌───┐┌─┴─┐┌─────┐┌───┐┌─────┐ - q_1: ┤ S ├┤ H ├┤ T ├┤ X ├┤ Tdg ├┤ H ├┤ Sdg ├ - └───┘└───┘└───┘└───┘└─────┘└───┘└─────┘ - """ - result: list[QuantumGate] = [] - result.extend(one_qubit_gate_op("s", qubit1)) - result.extend(one_qubit_gate_op("h", qubit1)) - result.extend(one_qubit_gate_op("t", qubit1)) - result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) - result.extend(one_qubit_gate_op("tdg", qubit1)) - result.extend(one_qubit_gate_op("h", qubit1)) - result.extend(one_qubit_gate_op("sdg", qubit1)) - - return result - - -def xy_gate( - theta: Union[int, float], qubit0: IndexedIdentifier, qubit1: IndexedIdentifier -) -> list[QuantumGate]: - """Implements the XXPlusYY gate matrix as defined by braket. - - Reference : - https://amazon-braket-sdk-python.readthedocs.io/en/latest/_apidoc/braket.circuits.gate.html#braket.circuits.gate.Gate.XY - - """ - return xx_plus_yy_gate(theta, CONSTANTS_MAP["pi"], qubit0, qubit1) - - -def xx_plus_yy_gate( - theta: Union[int, float], - phi: Union[int, float], - qubit0: IndexedIdentifier, - qubit1: IndexedIdentifier, -) -> list[QuantumGate]: - """ - Implements the XXPlusYY gate as a decomposition of other gates. - - Uses the following qiskit decomposition: - - In [7]: qc.draw() - Out[7]: - ┌─────────────────────┐ - q_0: ┤0 ├ - │ (XX+YY)(theta,phi) │ - q_1: ┤1 ├ - └─────────────────────┘ - - In [8]: qc.decompose().draw() - Out[8]: - ┌─────────┐ ┌───┐ ┌───┐┌──────────────┐┌───┐ ┌─────┐ ┌──────────┐ - q_0: ┤ Rz(phi) ├─┤ S ├────────────┤ X ├┤ Ry(-theta/2) ├┤ X ├──┤ Sdg ├───┤ Rz(-phi) ├─────────── - ├─────────┴┐├───┴┐┌─────────┐└─┬─┘├──────────────┤└─┬─┘┌─┴─────┴──┐└─┬──────┬─┘┌─────────┐ - q_1: ┤ Rz(-π/2) ├┤ √X ├┤ Rz(π/2) ├──■──┤ Ry(-theta/2) ├──■──┤ Rz(-π/2) ├──┤ √Xdg ├──┤ Rz(π/2) ├ - └──────────┘└────┘└─────────┘ └──────────────┘ └──────────┘ └──────┘ └─────────┘ - """ - result: list[QuantumGate] = [] - - result.extend(one_qubit_rotation_op("rz", phi, qubit0)) - result.extend(one_qubit_rotation_op("rz", -1 * (CONSTANTS_MAP["pi"] / 2), qubit1)) - result.extend(one_qubit_gate_op("s", qubit0)) - result.extend(one_qubit_gate_op("sx", qubit1)) - result.extend(one_qubit_rotation_op("rz", (CONSTANTS_MAP["pi"] / 2), qubit0)) - result.extend(two_qubit_gate_op("cx", qubit1, qubit0)) - result.extend(one_qubit_rotation_op("ry", -1 * theta / 2, qubit0)) - result.extend(one_qubit_rotation_op("ry", -1 * theta / 2, qubit1)) - result.extend(two_qubit_gate_op("cx", qubit1, qubit0)) - result.extend(one_qubit_rotation_op("rz", (-1 * CONSTANTS_MAP["pi"] / 2), qubit0)) - result.extend(one_qubit_gate_op("sxdg", qubit1)) - result.extend(one_qubit_gate_op("sdg", qubit0)) - result.extend(one_qubit_rotation_op("rz", (CONSTANTS_MAP["pi"] / 2), qubit1)) - result.extend(one_qubit_rotation_op("rz", -1 * phi, qubit0)) - - return result - - -def ryy_gate( - theta: Union[int, float], qubit0: IndexedIdentifier, qubit1: IndexedIdentifier -) -> list[QuantumGate]: - """ - Implements the YY gate as a decomposition of other gates. - - Uses the following qiskit decomposition: - - In [9]: qc.draw() - Out[9]: - ┌─────────────┐ - q_0: ┤0 ├ - │ Ryy(theta) │ - q_1: ┤1 ├ - └─────────────┘ - - In [10]: qc.decompose().draw() - Out[10]: - ┌─────────┐ ┌──────────┐ - q_0: ┤ Rx(π/2) ├──■─────────────────■──┤ Rx(-π/2) ├ - ├─────────┤┌─┴─┐┌───────────┐┌─┴─┐├──────────┤ - q_1: ┤ Rx(π/2) ├┤ X ├┤ Rz(theta) ├┤ X ├┤ Rx(-π/2) ├ - └─────────┘└───┘└───────────┘└───┘└──────────┘ - - """ - result: list[QuantumGate] = [] - result.extend(one_qubit_rotation_op("rx", CONSTANTS_MAP["pi"] / 2, qubit0)) - result.extend(one_qubit_rotation_op("rx", CONSTANTS_MAP["pi"] / 2, qubit1)) - result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) - result.extend(one_qubit_rotation_op("rz", theta, qubit1)) - result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) - result.extend(one_qubit_rotation_op("rx", -CONSTANTS_MAP["pi"] / 2, qubit0)) - result.extend(one_qubit_rotation_op("rx", -CONSTANTS_MAP["pi"] / 2, qubit1)) - return result - - -def zz_gate( - theta: Union[int, float], qubit0: IndexedIdentifier, qubit1: IndexedIdentifier -) -> list[QuantumGate]: - """ - Implements the ZZ gate as a decomposition of other gates. - """ - result: list[QuantumGate] = [] - result.extend(two_qubit_gate_op("cz", qubit0, qubit1)) - result.extend(one_qubit_gate_op("h", qubit1)) - result.extend(one_qubit_rotation_op("rz", theta, qubit1)) - result.extend(one_qubit_gate_op("h", qubit1)) - result.extend(two_qubit_gate_op("cz", qubit0, qubit1)) - return result - - -def phaseshift_gate(theta: Union[int, float], qubit: IndexedIdentifier) -> list[QuantumGate]: - """ - Implements the phase shift gate as a decomposition of other gates. - """ - result: list[QuantumGate] = [] - result.extend(one_qubit_gate_op("h", qubit)) - result.extend(one_qubit_rotation_op("rx", theta, qubit)) - result.extend(one_qubit_gate_op("h", qubit)) - return result - - -def cswap_gate( - qubit0: IndexedIdentifier, qubit1: IndexedIdentifier, qubit2: IndexedIdentifier -) -> list[QuantumGate]: - """ - Implements the CSWAP gate as a decomposition of other gates. - """ - result: list[QuantumGate] = [] - result.extend(two_qubit_gate_op("cx", qubit2, qubit1)) - result.extend(one_qubit_gate_op("h", qubit2)) - result.extend(two_qubit_gate_op("cx", qubit1, qubit2)) - result.extend(one_qubit_gate_op("tdg", qubit2)) - result.extend(two_qubit_gate_op("cx", qubit0, qubit2)) - result.extend(one_qubit_gate_op("t", qubit2)) - result.extend(two_qubit_gate_op("cx", qubit1, qubit2)) - result.extend(one_qubit_gate_op("t", qubit1)) - result.extend(one_qubit_gate_op("tdg", qubit2)) - result.extend(two_qubit_gate_op("cx", qubit0, qubit2)) - result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) - result.extend(one_qubit_gate_op("t", qubit2)) - result.extend(one_qubit_gate_op("t", qubit0)) - result.extend(one_qubit_gate_op("tdg", qubit1)) - result.extend(one_qubit_gate_op("h", qubit2)) - result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) - result.extend(two_qubit_gate_op("cx", qubit2, qubit1)) - return result - - -def pswap_gate( - theta: Union[int, float], qubit0: IndexedIdentifier, qubit1: IndexedIdentifier -) -> list[QuantumGate]: - """ - Implements the PSWAP gate as a decomposition of other gates. - """ - result: list[QuantumGate] = [] - result.extend(two_qubit_gate_op("swap", qubit0, qubit1)) - result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) - result.extend(u3_gate(0, 0, theta, qubit1)) - result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) - return result - - -def iswap_gate(qubit0: IndexedIdentifier, qubit1: IndexedIdentifier) -> list[QuantumGate]: - """Implements the iSwap gate as a decomposition of other gates. - - Reference: https://docs.quantum.ibm.com/api/qiskit/qiskit.circuit.library.iSwapGate - """ - - result: list[QuantumGate] = [] - - result.extend(one_qubit_gate_op("s", qubit0)) - result.extend(one_qubit_gate_op("s", qubit1)) - result.extend(one_qubit_gate_op("h", qubit0)) - result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) - result.extend(two_qubit_gate_op("cx", qubit1, qubit0)) - result.extend(one_qubit_gate_op("h", qubit1)) - - return result - - -def crx_gate( - theta: Union[int, float], qubit0: IndexedIdentifier, qubit1: IndexedIdentifier -) -> list[QuantumGate]: - """ - Implements the CRX gate as a decomposition of other gates. - - Used the following qiskit decomposition: - - In [26]: q.draw() - Out[26]: - - q_0: ──────■────── - ┌─────┴─────┐ - q_1: ┤ Rx(theta) ├ - └───────────┘ - - In [27]: q.decompose().decompose().decompose().draw() - Out[27]: - - q_0: ────────────────■───────────────────────■─────────────────────── - ┌────────────┐┌─┴─┐┌─────────────────┐┌─┴─┐┌───────────────────┐ - q_1: ┤ U(0,0,π/2) ├┤ X ├┤ U(-theta/2,0,0) ├┤ X ├┤ U(theta/2,-π/2,0) ├ - └────────────┘└───┘└─────────────────┘└───┘└───────────────────┘ - """ - result: list[QuantumGate] = [] - result.extend(u3_gate(0, 0, CONSTANTS_MAP["pi"] / 2, qubit1)) - result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) - result.extend(u3_gate(-1 * theta / 2, 0, 0, qubit1)) - result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) - result.extend(u3_gate(theta / 2, -1 * CONSTANTS_MAP["pi"] / 2, 0, qubit1)) - return result - - -def cry_gate( - theta: Union[int, float], qubit0: IndexedIdentifier, qubit1: IndexedIdentifier -) -> list[QuantumGate]: - """ - Implements the CRY gate as a decomposition of other gates. - - Used the following qiskit decomposition - - - In [4]: q.draw() - Out[4]: - - q_0: ──────■────── - ┌─────┴─────┐ - q_1: ┤ Ry(theta) ├ - └───────────┘ - - In [5]: q.decompose().decompose().decompose().draw() - Out[5]: - - q_0: ─────────────────────■────────────────────────■── - ┌─────────────────┐┌─┴─┐┌──────────────────┐┌─┴─┐ - q_1: ┤ U3(theta/2,0,0) ├┤ X ├┤ U3(-theta/2,0,0) ├┤ X ├ - └─────────────────┘└───┘└──────────────────┘└───┘ - """ - result: list[QuantumGate] = [] - result.extend(u3_gate(theta / 2, 0, 0, qubit1)) - result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) - result.extend(u3_gate(-1 * theta / 2, 0, 0, qubit1)) - result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) - return result - - -def crz_gate( - theta: Union[int, float], qubit0: IndexedIdentifier, qubit1: IndexedIdentifier -) -> list[QuantumGate]: - """ - Implements the CRZ gate as a decomposition of other gates. - - Used the following qiskit decomposition - - - In [4]: q.draw() - Out[4]: - - q_0: ──────■────── - ┌─────┴─────┐ - q_1: ┤ Rz(theta) ├ - └───────────┘ - - In [5]: q.decompose().decompose().decompose().draw() - Out[5]: - global phase: 0 - - q_0: ─────────────────────■────────────────────────■── - ┌─────────────────┐┌─┴─┐┌──────────────────┐┌─┴─┐ - q_1: ┤ U3(0,0,theta/2) ├┤ X ├┤ U3(0,0,-theta/2) ├┤ X ├ - └─────────────────┘└───┘└──────────────────┘└───┘ - """ - result: list[QuantumGate] = [] - result.extend(u3_gate(0, 0, theta / 2, qubit1)) - result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) - result.extend(u3_gate(0, 0, -1 * theta / 2, qubit1)) - result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) - return result - - -def cu_gate( # pylint: disable=too-many-arguments - theta: Union[int, float], - phi: Union[int, float], - lam: Union[int, float], - gamma: Union[int, float], - qubit0: IndexedIdentifier, - qubit1: IndexedIdentifier, -) -> list[QuantumGate]: - """ - Implements the CU gate as a decomposition of other gates. - - Uses the following qiskit decomposition - - - In [7]: qc.draw() - Out[7]: - - q_0: ────────────■───────────── - ┌───────────┴────────────┐ - q_1: ┤ U(theta,phi,lam,gamma) ├ - └────────────────────────┘ - - In [8]: qc.decompose().decompose().decompose().draw() - Out[8]: - ┌──────────────┐ ┌──────────────────────┐ » - q_0: ────┤ U(0,0,gamma) ├────┤ U(0,0,lam/2 + phi/2) ├──■──────────────────────────────────» - ┌───┴──────────────┴───┐└──────────────────────┘┌─┴─┐┌──────────────────────────────┐» - q_1: ┤ U(0,0,lam/2 - phi/2) ├────────────────────────┤ X ├┤ U(-theta/2,0,-lam/2 - phi/2) ├» - └──────────────────────┘ └───┘└──────────────────────────────┘» - « - «q_0: ──■────────────────────── - « ┌─┴─┐┌──────────────────┐ - «q_1: ┤ X ├┤ U(theta/2,phi,0) ├ - « └───┘└──────────────────┘ - """ - result: list[QuantumGate] = [] - result.extend(u3_gate(0, 0, gamma, qubit0)) - result.extend(u3_gate(0, 0, lam / 2 + phi / 2, qubit0)) - result.extend(u3_gate(0, 0, lam / 2 - phi / 2, qubit1)) - result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) - result.extend(u3_gate(-theta / 2, 0, -lam / 2 - phi / 2, qubit1)) - result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) - result.extend(u3_gate(theta / 2, phi, 0, qubit1)) - - return result - - -def cu3_gate( # pylint: disable=too-many-arguments - theta: Union[int, float], - phi: Union[int, float], - lam: Union[int, float], - qubit0: IndexedIdentifier, - qubit1: IndexedIdentifier, -) -> list[QuantumGate]: - """ - Implements the CU3 gate as a decomposition of other gates. - - Uses the following qiskit decomposition - - - In [7]: qc.draw() - Out[7]: - - q_0: ──────────■────────── - ┌─────────┴─────────┐ - q_1: ┤ U3(theta,phi,lam) ├ - └───────────────────┘ - - In [8]: qc.decompose().decompose().decompose().draw() - Out[8]: - ┌──────────────────────┐ - q_0: ┤ U(0,0,lam/2 + phi/2) ├──■────────────────────────────────────■────────────────────── - ├──────────────────────┤┌─┴─┐┌──────────────────────────────┐┌─┴─┐┌──────────────────┐ - q_1: ┤ U(0,0,lam/2 - phi/2) ├┤ X ├┤ U(-theta/2,0,-lam/2 - phi/2) ├┤ X ├┤ U(theta/2,phi,0) ├ - └──────────────────────┘└───┘└──────────────────────────────┘└───┘└──────────────────┘ - """ - result: list[QuantumGate] = [] - result.extend(u3_gate(0, 0, lam / 2 + phi / 2, qubit0)) - result.extend(u3_gate(0, 0, lam / 2 - phi / 2, qubit1)) - result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) - result.extend(u3_gate(-theta / 2, 0, -lam / 2 - phi / 2, qubit1)) - result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) - result.extend(u3_gate(theta / 2, phi, 0, qubit1)) - - return result - - -def cu1_gate( - theta: Union[int, float], qubit0: IndexedIdentifier, qubit1: IndexedIdentifier -) -> list[QuantumGate]: - """ - Implements the CU1 gate as a decomposition of other gates. - - Uses the following qiskit decomposition - - - In [11]: qc.draw() - Out[11]: - - q_0: ─■────────── - │U1(theta) - q_1: ─■────────── - - - In [12]: qc.decompose().decompose().decompose().draw() - Out[12]: - ┌────────────────┐ - q_0: ┤ U(0,0,theta/2) ├──■───────────────────────■──────────────────── - └────────────────┘┌─┴─┐┌─────────────────┐┌─┴─┐┌────────────────┐ - q_1: ──────────────────┤ X ├┤ U(0,0,-theta/2) ├┤ X ├┤ U(0,0,theta/2) ├ - └───┘└─────────────────┘└───┘└────────────────┘ - """ - result: list[QuantumGate] = [] - result.extend(u3_gate(0, 0, theta / 2, qubit0)) - result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) - result.extend(u3_gate(0, 0, -theta / 2, qubit1)) - result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) - result.extend(u3_gate(0, 0, theta / 2, qubit1)) - - return result - - -def csx_gate(qubit0: IndexedIdentifier, qubit1: IndexedIdentifier) -> list[QuantumGate]: - """Implement the CSX gate as a decomposition of other gates. - - Used the following qiskit decomposition - - - In [19]: q = QuantumCircuit(2) - - In [20]: q.csx(0,1) - Out[20]: - - In [21]: q.draw() - Out[21]: - - q_0: ──■─── - ┌─┴──┐ - q_1: ┤ Sx ├ - └────┘ - - In [22]: q.decompose().decompose().draw() - Out[22]: - ┌─────────┐ - q_0: ┤ U1(π/4) ├──■────────────────■──────────────────────── - ├─────────┤┌─┴─┐┌──────────┐┌─┴─┐┌─────────┐┌─────────┐ - q_1: ┤ U2(0,π) ├┤ X ├┤ U1(-π/4) ├┤ X ├┤ U1(π/4) ├┤ U2(0,π) ├ - └─────────┘└───┘└──────────┘└───┘└─────────┘└─────────┘ - """ - result: list[QuantumGate] = [] - result.extend(phaseshift_gate(CONSTANTS_MAP["pi"] / 4, qubit0)) - result.extend(u2_gate(0, CONSTANTS_MAP["pi"], qubit1)) - result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) - result.extend(phaseshift_gate(-CONSTANTS_MAP["pi"] / 4, qubit1)) - result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) - result.extend(phaseshift_gate(CONSTANTS_MAP["pi"] / 4, qubit1)) - result.extend(u2_gate(0, CONSTANTS_MAP["pi"], qubit1)) - - return result - - -def rxx_gate( - theta: Union[int, float], qubit0: IndexedIdentifier, qubit1: IndexedIdentifier -) -> list[Union[QuantumGate, QuantumPhase]]: - """ - Implements the RXX gate as a decomposition of other gates. - """ - - result: list[Union[QuantumGate, QuantumPhase]] = [] - result.extend(global_phase_gate(-theta / 2, [qubit0, qubit1])) - result.extend(one_qubit_gate_op("h", qubit0)) - result.extend(one_qubit_gate_op("h", qubit1)) - result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) - result.extend(one_qubit_rotation_op("rz", theta, qubit1)) - result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) - result.extend(one_qubit_gate_op("h", qubit1)) - result.extend(one_qubit_gate_op("h", qubit0)) - - return result - - -def rccx_gate( - qubit0: IndexedIdentifier, qubit1: IndexedIdentifier, qubit2: IndexedIdentifier -) -> list[QuantumGate]: - result: list[QuantumGate] = [] - result.extend(u2_gate(0, CONSTANTS_MAP["pi"], qubit2)) - result.extend(phaseshift_gate(CONSTANTS_MAP["pi"] / 4, qubit2)) - result.extend(two_qubit_gate_op("cx", qubit1, qubit2)) - result.extend(phaseshift_gate(-CONSTANTS_MAP["pi"] / 4, qubit2)) - result.extend(two_qubit_gate_op("cx", qubit0, qubit2)) - result.extend(phaseshift_gate(CONSTANTS_MAP["pi"] / 4, qubit2)) - result.extend(two_qubit_gate_op("cx", qubit1, qubit2)) - result.extend(phaseshift_gate(-CONSTANTS_MAP["pi"] / 4, qubit2)) - result.extend(u2_gate(0, CONSTANTS_MAP["pi"], qubit2)) - - return result - - -def rzz_gate( - theta: Union[int, float], qubit0: IndexedIdentifier, qubit1: IndexedIdentifier -) -> list[Union[QuantumGate, QuantumPhase]]: - """ - Implements the RZZ gate as a decomposition of other gates. - - Used the following qiskit decomposition - - - In [32]: q.draw() - Out[32]: - - q_0: ─■────────── - │ZZ(theta) - q_1: ─■────────── - - - In [33]: q.decompose().decompose().decompose().draw() - Out[33]: - global phase: -theta/2 - - q_0: ──■─────────────────────■── - ┌─┴─┐┌───────────────┐┌─┴─┐ - q_1: ┤ X ├┤ U3(0,0,theta) ├┤ X ├ - └───┘└───────────────┘└───┘ - """ - result: list[Union[QuantumGate, QuantumPhase]] = [] - - result.extend(global_phase_gate(-theta / 2, [qubit0, qubit1])) - result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) - result.extend(u3_gate(0, 0, theta, qubit1)) - result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) - - return result - - -def cphaseshift_gate( - theta: Union[int, float], qubit0: IndexedIdentifier, qubit1: IndexedIdentifier -) -> list[QuantumGate]: - """ - Implements the controlled phase shift gate as a decomposition of other gates. - - Uses the following qiskit decomposition - - - In [11]: qc.draw() - Out[11]: - - q_0: ─■───────── - │P(theta) - q_1: ─■───────── - - - In [12]: qc.decompose().decompose().decompose().draw() - Out[12]: - ┌────────────────┐ - q_0: ┤ U(0,0,theta/2) ├──■───────────────────────■──────────────────── - └────────────────┘┌─┴─┐┌─────────────────┐┌─┴─┐┌────────────────┐ - q_1: ──────────────────┤ X ├┤ U(0,0,-theta/2) ├┤ X ├┤ U(0,0,theta/2) ├ - └───┘└─────────────────┘└───┘└────────────────┘ - """ - result: list[QuantumGate] = [] - result.extend(u3_gate(0, 0, theta / 2, qubit0)) - result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) - result.extend(u3_gate(0, 0, -theta / 2, qubit1)) - result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) - result.extend(u3_gate(0, 0, theta / 2, qubit1)) - - return result - - -def cphaseshift00_gate( - theta: Union[int, float], qubit0: IndexedIdentifier, qubit1: IndexedIdentifier -) -> list[QuantumGate]: - """ - Implements the controlled phase shift 00 gate as a decomposition of other gates. - """ - result: list[QuantumGate] = [] - result.extend(one_qubit_gate_op("x", qubit0)) - result.extend(one_qubit_gate_op("x", qubit1)) - result.extend(u3_gate(0, 0, theta / 2, qubit0)) - result.extend(u3_gate(0, 0, theta / 2, qubit1)) - result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) - result.extend(u3_gate(0, 0, -theta / 2, qubit1)) - result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) - result.extend(one_qubit_gate_op("x", qubit0)) - result.extend(one_qubit_gate_op("x", qubit1)) - return result - - -def cphaseshift01_gate( - theta: Union[int, float], qubit0: IndexedIdentifier, qubit1: IndexedIdentifier -) -> list[QuantumGate]: - """ - Implements the controlled phase shift 01 gate as a decomposition of other gates. - """ - result: list[QuantumGate] = [] - result.extend(one_qubit_gate_op("x", qubit0)) - result.extend(u3_gate(0, 0, theta / 2, qubit1)) - result.extend(u3_gate(0, 0, theta / 2, qubit0)) - result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) - result.extend(u3_gate(0, 0, -theta / 2, qubit1)) - result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) - result.extend(one_qubit_gate_op("x", qubit0)) - return result - - -def cphaseshift10_gate( - theta: Union[int, float], qubit0: IndexedIdentifier, qubit1: IndexedIdentifier -) -> list[QuantumGate]: - """ - Implements the controlled phase shift 10 gate as a decomposition of other gates. - """ - result: list[QuantumGate] = [] - result.extend(u3_gate(0, 0, theta / 2, qubit0)) - result.extend(one_qubit_gate_op("x", qubit1)) - result.extend(u3_gate(0, 0, theta / 2, qubit1)) - result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) - result.extend(u3_gate(0, 0, -theta / 2, qubit1)) - result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) - result.extend(one_qubit_gate_op("x", qubit1)) - return result - - -def gpi_gate(phi, qubit_id) -> list[QuantumGate]: - """ - Implements the gpi gate as a decomposition of other gates. - """ - theta_0 = CONSTANTS_MAP["pi"] - phi_0 = phi - lambda_0 = -phi_0 + CONSTANTS_MAP["pi"] - return u3_gate(theta_0, phi_0, lambda_0, qubit_id) - - -def gpi2_gate(phi, qubit_id) -> list[QuantumGate]: - """ - Implements the gpi2 gate as a decomposition of other gates. - """ - # Reference: - # https://amazon-braket-sdk-python.readthedocs.io/en/latest/_apidoc/braket.circuits.circuit.html#braket.circuits.circuit.Circuit.gpi2 - # https://docs.quantum.ibm.com/api/qiskit/qiskit.circuit.library.U3Gate#u3gate - theta_0 = CONSTANTS_MAP["pi"] / 2 - phi_0 = phi - CONSTANTS_MAP["pi"] / 2 - lambda_0 = CONSTANTS_MAP["pi"] / 2 - phi - return u3_gate(theta_0, phi_0, lambda_0, qubit_id) - - -# pylint: disable-next=too-many-arguments -def ms_gate(phi0, phi1, theta, qubit0, qubit1) -> list[QuantumGate]: - """ - Implements the Molmer Sorenson gate as a decomposition of other gates. - """ - mat = np.array( - [ - [ - np.cos(np.pi * theta), - 0, - 0, - -1j * np.exp(-1j * 2 * np.pi * (phi0 + phi1)) * np.sin(np.pi * theta), - ], - [ - 0, - np.cos(np.pi * theta), - -1j * np.exp(-1j * 2 * np.pi * (phi0 - phi1)) * np.sin(np.pi * theta), - 0, - ], - [ - 0, - -1j * np.exp(1j * 2 * np.pi * (phi0 - phi1)) * np.sin(np.pi * theta), - np.cos(np.pi * theta), - 0, - ], - [ - -1j * np.exp(1j * 2 * np.pi * (phi0 + phi1)) * np.sin(np.pi * theta), - 0, - 0, - np.cos(np.pi * theta), - ], - ] - ) - angles = kak_decomposition_angles(mat) - qubits = [qubit0, qubit1] - - result: list[QuantumGate] = [] - result.extend(u3_gate(angles[0][0], angles[0][1], angles[0][2], qubits[0])) - result.extend(u3_gate(angles[1][0], angles[1][1], angles[1][2], qubits[1])) - result.extend(one_qubit_gate_op("sx", qubits[0])) - result.extend(two_qubit_gate_op("cx", qubits[0], qubits[1])) - result.extend( - one_qubit_rotation_op("rx", ((1 / 2) - 2 * theta) * CONSTANTS_MAP["pi"], qubits[0]) - ) - result.extend(one_qubit_rotation_op("rx", CONSTANTS_MAP["pi"] / 2, qubits[1])) - result.extend(two_qubit_gate_op("cx", qubits[1], qubits[0])) - result.extend(sxdg_gate_op(qubits[1])) - result.extend(one_qubit_gate_op("s", qubits[1])) - result.extend(two_qubit_gate_op("cx", qubits[0], qubits[1])) - result.extend(u3_gate(angles[2][0], angles[2][1], angles[2][2], qubits[0])) - result.extend(u3_gate(angles[3][0], angles[3][1], angles[3][2], qubits[1])) - return result - - -def ccx_gate_op( - qubit0: IndexedIdentifier, qubit1: IndexedIdentifier, qubit2: IndexedIdentifier -) -> list[QuantumGate]: - return [ - QuantumGate( - modifiers=[], - name=Identifier(name="ccx"), - arguments=[], - qubits=[qubit0, qubit1, qubit2], - ) - ] - - -def ecr_gate(qubit0: IndexedIdentifier, qubit1: IndexedIdentifier) -> list[QuantumGate]: - """ - Implements the ECR gate as a decomposition of other gates. - """ - result: list[QuantumGate] = [] - result.extend(one_qubit_gate_op("s", qubit0)) - result.extend(one_qubit_rotation_op("rx", CONSTANTS_MAP["pi"] / 2, qubit1)) - result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) - result.extend(one_qubit_gate_op("x", qubit0)) - return result - - -def c3sx_gate( - qubit0: IndexedIdentifier, - qubit1: IndexedIdentifier, - qubit2: IndexedIdentifier, - qubit3: IndexedIdentifier, -) -> list[QuantumGate]: - """ - Implements the c3sx gate as a decomposition of other gates. - - Uses the following qiskit decomposition - - - In [15]: qc.draw() - Out[15]: - - q_0: ──■─── - │ - q_1: ──■─── - │ - q_2: ──■─── - ┌─┴──┐ - q_3: ┤ Sx ├ - └────┘ - - In [16]: qc.decompose().draw() - Out[16]: - - q_0: ──────■──────────■────────────────────■────────────────────────────────────────■──────── - │ ┌─┴─┐ ┌─┴─┐ │ - q_1: ──────┼────────┤ X ├──────■─────────┤ X ├──────■──────────■────────────────────┼──────── - │ └───┘ │ └───┘ │ ┌─┴─┐ ┌─┴─┐ - q_2: ──────┼───────────────────┼────────────────────┼────────┤ X ├──────■─────────┤ X ├────── - ┌───┐ │U1(π/8) ┌───┐┌───┐ │U1(-π/8) ┌───┐┌───┐ │U1(π/8) ├───┤┌───┐ │U1(-π/8) ├───┤┌───┐ - q_3: ┤ H ├─■────────┤ H ├┤ H ├─■─────────┤ H ├┤ H ├─■────────┤ H ├┤ H ├─■─────────┤ H ├┤ H ├─ - └───┘ └───┘└───┘ └───┘└───┘ └───┘└───┘ └───┘└───┘ - « - «q_0:─────────────────────────────────■────────────────────── - « │ - «q_1:────────────■────────────────────┼────────────────────── - « ┌─┴─┐ ┌─┴─┐ - «q_2:─■────────┤ X ├──────■─────────┤ X ├──────■───────────── - « │U1(π/8) ├───┤┌───┐ │U1(-π/8) ├───┤┌───┐ │U1(π/8) ┌───┐ - «q_3:─■────────┤ H ├┤ H ├─■─────────┤ H ├┤ H ├─■────────┤ H ├ - « └───┘└───┘ └───┘└───┘ └───┘ - """ - - result: list[QuantumGate] = [] - result.extend(one_qubit_gate_op("h", qubit3)) - result.extend(cu1_gate(CONSTANTS_MAP["pi"] / 8, qubit0, qubit3)) - result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) - # h(q[3]) * h (q[3]) = Identity - result.extend(cu1_gate(-CONSTANTS_MAP["pi"] / 8, qubit1, qubit3)) - result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) - # h(q[3]) * h (q[3]) = Identity - result.extend(cu1_gate(CONSTANTS_MAP["pi"] / 8, qubit1, qubit3)) - result.extend(two_qubit_gate_op("cx", qubit1, qubit2)) - # h(q[3]) * h (q[3]) = Identity - result.extend(cu1_gate(-CONSTANTS_MAP["pi"] / 8, qubit2, qubit3)) - result.extend(two_qubit_gate_op("cx", qubit0, qubit2)) - # h(q[3]) * h (q[3]) = Identity - result.extend(cu1_gate(CONSTANTS_MAP["pi"] / 8, qubit2, qubit3)) - result.extend(two_qubit_gate_op("cx", qubit1, qubit2)) - # h(q[3]) * h (q[3]) = Identity - result.extend(cu1_gate(-CONSTANTS_MAP["pi"] / 8, qubit2, qubit3)) - result.extend(two_qubit_gate_op("cx", qubit0, qubit2)) - # h(q[3]) * h (q[3]) = Identity - result.extend(cu1_gate(CONSTANTS_MAP["pi"] / 8, qubit2, qubit3)) - result.extend(one_qubit_gate_op("h", qubit3)) - - return result - - -def c4x_gate( - qubit0: IndexedIdentifier, - qubit1: IndexedIdentifier, - qubit2: IndexedIdentifier, - qubit3: IndexedIdentifier, -) -> list[QuantumGate]: - """ - Implements the c4x gate - """ - return [ - QuantumGate( - modifiers=[], - name=Identifier(name="c4x"), - arguments=[], - qubits=[qubit0, qubit1, qubit2, qubit3], - ) - ] - - -def prx_gate(theta, phi, qubit_id) -> list[QuantumGate]: - """ - Implements the PRX gate as a decomposition of other gates. - """ - # Reference: - # https://amazon-braket-sdk-python.readthedocs.io/en/latest/_apidoc/braket.circuits.circuit.html#braket.circuits.circuit.Circuit.prx - # https://docs.quantum.ibm.com/api/qiskit/qiskit.circuit.library.U3Gate#u3gate - theta_0 = theta - phi_0 = phi - CONSTANTS_MAP["pi"] / 2 - lambda_0 = CONSTANTS_MAP["pi"] / 2 - phi - return u3_gate(theta_0, phi_0, lambda_0, qubit_id) - - -def one_qubit_gate_op(gate_name: str, qubit_id: IndexedIdentifier) -> list[QuantumGate]: - return [ - QuantumGate( - modifiers=[], - name=Identifier(name=gate_name), - arguments=[], - qubits=[qubit_id], - ) - ] - - -def one_qubit_rotation_op( - gate_name: str, rotation: float, qubit_id: IndexedIdentifier -) -> list[QuantumGate]: - return [ - QuantumGate( - modifiers=[], - name=Identifier(name=gate_name), - arguments=[FloatLiteral(value=rotation)], - qubits=[qubit_id], - ) - ] - - -def two_qubit_gate_op( - gate_name: str, qubit_id1: IndexedIdentifier, qubit_id2: IndexedIdentifier -) -> list[QuantumGate]: - return [ - QuantumGate( - modifiers=[], - name=Identifier(name=gate_name.lower()), - arguments=[], - qubits=[qubit_id1, qubit_id2], - ) - ] - - -ONE_QUBIT_OP_MAP = { - "id": lambda qubit_id: one_qubit_gate_op("id", qubit_id), - "h": lambda qubit_id: one_qubit_gate_op("h", qubit_id), - "x": lambda qubit_id: one_qubit_gate_op("x", qubit_id), - "not": lambda qubit_id: one_qubit_gate_op("x", qubit_id), - "y": lambda qubit_id: one_qubit_gate_op("y", qubit_id), - "z": lambda qubit_id: one_qubit_gate_op("z", qubit_id), - "s": lambda qubit_id: one_qubit_gate_op("s", qubit_id), - "t": lambda qubit_id: one_qubit_gate_op("t", qubit_id), - "sdg": lambda qubit_id: one_qubit_gate_op("sdg", qubit_id), - "si": lambda qubit_id: one_qubit_gate_op("sdg", qubit_id), - "tdg": lambda qubit_id: one_qubit_gate_op("tdg", qubit_id), - "ti": lambda qubit_id: one_qubit_gate_op("tdg", qubit_id), - "v": lambda qubit_id: one_qubit_gate_op("sx", qubit_id), - "sx": lambda qubit_id: one_qubit_gate_op("sx", qubit_id), - "vi": sxdg_gate_op, - "sxdg": sxdg_gate_op, -} - - -ONE_QUBIT_ROTATION_MAP = { - "rx": lambda rotation, qubit_id: one_qubit_rotation_op("rx", rotation, qubit_id), - "ry": lambda rotation, qubit_id: one_qubit_rotation_op("ry", rotation, qubit_id), - "rz": lambda rotation, qubit_id: one_qubit_rotation_op("rz", rotation, qubit_id), - "u1": phaseshift_gate, - "U1": phaseshift_gate, - "u": u3_gate, - "U": u3_gate, - "u3": u3_gate, - "U3": u3_gate, - "U2": u2_gate, - "u2": u2_gate, - "prx": prx_gate, - "phaseshift": phaseshift_gate, - "p": phaseshift_gate, - "gpi": gpi_gate, - "gpi2": gpi2_gate, -} - -TWO_QUBIT_OP_MAP = { - "cx": lambda qubit_id1, qubit_id2: two_qubit_gate_op("cx", qubit_id1, qubit_id2), - "CX": lambda qubit_id1, qubit_id2: two_qubit_gate_op("cx", qubit_id1, qubit_id2), - "cnot": lambda qubit_id1, qubit_id2: two_qubit_gate_op("cx", qubit_id1, qubit_id2), - "cz": lambda qubit_id1, qubit_id2: two_qubit_gate_op("cz", qubit_id1, qubit_id2), - "swap": lambda qubit_id1, qubit_id2: two_qubit_gate_op("swap", qubit_id1, qubit_id2), - "cv": csx_gate, - "cy": cy_gate, - "ch": ch_gate, - "xx": rxx_gate, - "rxx": rxx_gate, - "yy": ryy_gate, - "ryy": ryy_gate, - "zz": rzz_gate, - "rzz": rzz_gate, - "xy": xy_gate, - "xx_plus_yy": xx_plus_yy_gate, - "pswap": pswap_gate, - "iswap": iswap_gate, - "cp": cphaseshift_gate, - "crx": crx_gate, - "cry": cry_gate, - "crz": crz_gate, - "cu": cu_gate, - "cu3": cu3_gate, - "csx": csx_gate, - "cphaseshift": cphaseshift_gate, - "cu1": cu1_gate, - "cp00": cphaseshift00_gate, - "cphaseshift00": cphaseshift00_gate, - "cp01": cphaseshift01_gate, - "cphaseshift01": cphaseshift01_gate, - "cp10": cphaseshift10_gate, - "cphaseshift10": cphaseshift10_gate, - "ecr": ecr_gate, - "ms": ms_gate, -} - -THREE_QUBIT_OP_MAP = { - "ccx": ccx_gate_op, - "toffoli": ccx_gate_op, - "ccnot": ccx_gate_op, - "cswap": cswap_gate, - "rccx": rccx_gate, -} - -FOUR_QUBIT_OP_MAP = {"c3sx": c3sx_gate, "c3sqrtx": c3sx_gate} - -FIVE_QUBIT_OP_MAP = { - "c4x": c4x_gate, -} - - -def map_qasm_op_to_callable(op_name: str) -> tuple[Callable, int]: - """ - Map a QASM operation to a callable. - - Args: - op_name (str): The QASM operation name. - - Returns: - tuple: A tuple containing the callable and the number of qubits the operation acts on. - - Raises: - ValidationError: If the QASM operation is unsupported or undeclared. - """ - op_maps: list[tuple[dict, int]] = [ - (ONE_QUBIT_OP_MAP, 1), - (ONE_QUBIT_ROTATION_MAP, 1), - (TWO_QUBIT_OP_MAP, 2), - (THREE_QUBIT_OP_MAP, 3), - (FOUR_QUBIT_OP_MAP, 4), - (FIVE_QUBIT_OP_MAP, 5), - ] - - for op_map, qubit_count in op_maps: - try: - return op_map[op_name], qubit_count - except KeyError: - continue - - raise ValidationError(f"Unsupported / undeclared QASM operation: {op_name}") - - -SELF_INVERTING_ONE_QUBIT_OP_SET = {"id", "h", "x", "y", "z"} -ST_GATE_INV_MAP = { - "s": "sdg", - "t": "tdg", - "sdg": "s", - "tdg": "t", -} -ROTATION_INVERSION_ONE_QUBIT_OP_MAP = {"rx", "ry", "rz"} -U_INV_ROTATION_MAP = { - "U": u3_inv_gate, - "u3": u3_inv_gate, - "U3": u3_inv_gate, - "U2": u2_inv_gate, - "u2": u2_inv_gate, -} - - -def map_qasm_inv_op_to_callable(op_name: str): - """ - Map a QASM operation to a callable. - - Args: - op_name (str): The QASM operation name. - - Returns: - tuple: A tuple containing the callable, the number of qubits the operation acts on, - and what is to be done with the basic gate which we are trying to invert. - """ - if op_name in SELF_INVERTING_ONE_QUBIT_OP_SET: - return ONE_QUBIT_OP_MAP[op_name], 1, InversionOp.NO_OP - if op_name in ST_GATE_INV_MAP: - inv_gate_name = ST_GATE_INV_MAP[op_name] - return ONE_QUBIT_OP_MAP[inv_gate_name], 1, InversionOp.NO_OP - if op_name in TWO_QUBIT_OP_MAP: - return TWO_QUBIT_OP_MAP[op_name], 2, InversionOp.NO_OP - if op_name in THREE_QUBIT_OP_MAP: - return THREE_QUBIT_OP_MAP[op_name], 3, InversionOp.NO_OP - if op_name in U_INV_ROTATION_MAP: - # Special handling for U gate as it is composed of multiple - # basic gates and we need to invert each of them - return U_INV_ROTATION_MAP[op_name], 1, InversionOp.NO_OP - if op_name in ROTATION_INVERSION_ONE_QUBIT_OP_MAP: - return ( - ONE_QUBIT_ROTATION_MAP[op_name], - 1, - InversionOp.INVERT_ROTATION, - ) - raise ValidationError(f"Unsupported / undeclared QASM operation: {op_name}") +# Copyright (C) 2024 qBraid +# +# This file is part of pyqasm +# +# Pyqasm is free software released under the GNU General Public License v3 +# or later. You can redistribute and/or modify it under the terms of the GPL v3. +# See the LICENSE file in the project root or . +# +# THERE IS NO WARRANTY for pyqasm, as per Section 15 of the GPL v3. + +# pylint: disable=too-many-lines + +""" +Module mapping supported QASM gates to lower level gate operations. + +""" + + +from typing import Callable, Union + +import numpy as np +from openqasm3.ast import FloatLiteral, Identifier, IndexedIdentifier, QuantumGate, QuantumPhase + +from pyqasm.elements import InversionOp +from pyqasm.exceptions import ValidationError +from pyqasm.linalg import kak_decomposition_angles +from pyqasm.maps.expressions import CONSTANTS_MAP + + +def u3_gate( + theta: Union[int, float], + phi: Union[int, float], + lam: Union[int, float], + qubit_id, +) -> list[QuantumGate]: + """ + Implements the U3 gate using the following decomposition: + https://docs.quantum.ibm.com/api/qiskit/qiskit.circuit.library.UGate + https://docs.quantum.ibm.com/api/qiskit/qiskit.circuit.library.PhaseGate + + Args: + name (str): The name of the gate. + theta (Union[int, float]): The theta parameter. + phi (Union[int, float]): The phi parameter. + lam (Union[int, float]): The lambda parameter. + qubit_id (IndexedIdentifier): The qubit on which to apply the gate. + + Returns: + list: A list of QuantumGate objects representing the decomposition of the U3 gate. + """ + result: list[QuantumGate] = [] + result.extend(one_qubit_rotation_op("rz", lam, qubit_id)) + result.extend(one_qubit_rotation_op("rx", CONSTANTS_MAP["pi"] / 2, qubit_id)) + result.extend(one_qubit_rotation_op("rz", theta + CONSTANTS_MAP["pi"], qubit_id)) + result.extend(one_qubit_rotation_op("rx", CONSTANTS_MAP["pi"] / 2, qubit_id)) + result.extend(one_qubit_rotation_op("rz", phi + CONSTANTS_MAP["pi"], qubit_id)) + return result + # global phase - e^(i*(phi+lambda)/2) is missing in the above implementation + + +def u3_inv_gate( + theta: Union[int, float], + phi: Union[int, float], + lam: Union[int, float], + qubits, +) -> list[QuantumGate]: + """ + Implements the inverse of the U3 gate using the decomposition present in + the u3_gate function. + """ + result: list[QuantumGate] = [] + result.extend(one_qubit_rotation_op("rz", -1.0 * (phi + CONSTANTS_MAP["pi"]), qubits)) + result.extend(one_qubit_rotation_op("rx", -1.0 * (CONSTANTS_MAP["pi"] / 2), qubits)) + result.extend(one_qubit_rotation_op("rz", -1.0 * (theta + CONSTANTS_MAP["pi"]), qubits)) + result.extend(one_qubit_rotation_op("rx", -1.0 * (CONSTANTS_MAP["pi"] / 2), qubits)) + result.extend(one_qubit_rotation_op("rz", -1.0 * lam, qubits)) + return result + + +def u2_gate(phi, lam, qubits) -> list[QuantumGate]: + """ + Implements the U2 gate using the following decomposition: + https://docs.quantum.ibm.com/api/qiskit/qiskit.circuit.library.U2Gate + """ + return u3_gate(CONSTANTS_MAP["pi"] / 2, phi, lam, qubits) + + +def u2_inv_gate(phi, lam, qubits) -> list[QuantumGate]: + """ + Implements the inverse of the U2 gate using the decomposition present in + the u2_gate function. + """ + return u3_inv_gate(CONSTANTS_MAP["pi"] / 2, phi, lam, qubits) + + +def global_phase_gate(theta: float, qubit_list: list[IndexedIdentifier]) -> list[QuantumPhase]: + """ + Builds a global phase gate with the given theta and qubit list. + + Args: + theta (float): The phase angle. + qubit_list (list[IndexedIdentifier]): The list of qubits on which to apply the phase. + + Returns: + list[QuantumPhase]: A QuantumPhase object representing the global phase gate. + """ + return [ + QuantumPhase( + argument=FloatLiteral(value=theta), qubits=qubit_list, modifiers=[] # type: ignore + ) + ] + + +def sxdg_gate_op(qubit_id) -> list[QuantumGate]: + """ + Implements the conjugate transpose of the Sqrt(X) gate as a decomposition of other gates. + """ + return one_qubit_rotation_op("rx", -CONSTANTS_MAP["pi"] / 2, qubit_id) + + +def cy_gate(qubit0: IndexedIdentifier, qubit1: IndexedIdentifier) -> list[QuantumGate]: + """ + Implements the CY gate as a decomposition of other gates. + """ + result: list[QuantumGate] = [] + result.extend(one_qubit_gate_op("sdg", qubit1)) + result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) + result.extend(one_qubit_gate_op("s", qubit1)) + return result + + +def ch_gate(qubit0: IndexedIdentifier, qubit1: IndexedIdentifier) -> list[QuantumGate]: + """ + Implements the CH gate as a decomposition of other gates. + + Used the following qiskit decomposition - + + In [10]: q = QuantumCircuit(2) + + In [11]: q.ch(0, 1) + Out[11]: + + In [12]: q.decompose().draw() + Out[12]: + + q_0: ─────────────────■───────────────────── + ┌───┐┌───┐┌───┐┌─┴─┐┌─────┐┌───┐┌─────┐ + q_1: ┤ S ├┤ H ├┤ T ├┤ X ├┤ Tdg ├┤ H ├┤ Sdg ├ + └───┘└───┘└───┘└───┘└─────┘└───┘└─────┘ + """ + result: list[QuantumGate] = [] + result.extend(one_qubit_gate_op("s", qubit1)) + result.extend(one_qubit_gate_op("h", qubit1)) + result.extend(one_qubit_gate_op("t", qubit1)) + result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) + result.extend(one_qubit_gate_op("tdg", qubit1)) + result.extend(one_qubit_gate_op("h", qubit1)) + result.extend(one_qubit_gate_op("sdg", qubit1)) + + return result + + +def xy_gate( + theta: Union[int, float], qubit0: IndexedIdentifier, qubit1: IndexedIdentifier +) -> list[QuantumGate]: + """Implements the XXPlusYY gate matrix as defined by braket. + + Reference : + https://amazon-braket-sdk-python.readthedocs.io/en/latest/_apidoc/braket.circuits.gate.html#braket.circuits.gate.Gate.XY + + """ + return xx_plus_yy_gate(theta, CONSTANTS_MAP["pi"], qubit0, qubit1) + + +def xx_plus_yy_gate( + theta: Union[int, float], + phi: Union[int, float], + qubit0: IndexedIdentifier, + qubit1: IndexedIdentifier, +) -> list[QuantumGate]: + """ + Implements the XXPlusYY gate as a decomposition of other gates. + + Uses the following qiskit decomposition: + + In [7]: qc.draw() + Out[7]: + ┌─────────────────────┐ + q_0: ┤0 ├ + │ (XX+YY)(theta,phi) │ + q_1: ┤1 ├ + └─────────────────────┘ + + In [8]: qc.decompose().draw() + Out[8]: + ┌─────────┐ ┌───┐ ┌───┐┌──────────────┐┌───┐ ┌─────┐ ┌──────────┐ + q_0: ┤ Rz(phi) ├─┤ S ├────────────┤ X ├┤ Ry(-theta/2) ├┤ X ├──┤ Sdg ├───┤ Rz(-phi) ├─────────── + ├─────────┴┐├───┴┐┌─────────┐└─┬─┘├──────────────┤└─┬─┘┌─┴─────┴──┐└─┬──────┬─┘┌─────────┐ + q_1: ┤ Rz(-π/2) ├┤ √X ├┤ Rz(π/2) ├──■──┤ Ry(-theta/2) ├──■──┤ Rz(-π/2) ├──┤ √Xdg ├──┤ Rz(π/2) ├ + └──────────┘└────┘└─────────┘ └──────────────┘ └──────────┘ └──────┘ └─────────┘ + """ + result: list[QuantumGate] = [] + + result.extend(one_qubit_rotation_op("rz", phi, qubit0)) + result.extend(one_qubit_rotation_op("rz", -1 * (CONSTANTS_MAP["pi"] / 2), qubit1)) + result.extend(one_qubit_gate_op("s", qubit0)) + result.extend(one_qubit_gate_op("sx", qubit1)) + result.extend(one_qubit_rotation_op("rz", (CONSTANTS_MAP["pi"] / 2), qubit0)) + result.extend(two_qubit_gate_op("cx", qubit1, qubit0)) + result.extend(one_qubit_rotation_op("ry", -1 * theta / 2, qubit0)) + result.extend(one_qubit_rotation_op("ry", -1 * theta / 2, qubit1)) + result.extend(two_qubit_gate_op("cx", qubit1, qubit0)) + result.extend(one_qubit_rotation_op("rz", (-1 * CONSTANTS_MAP["pi"] / 2), qubit0)) + result.extend(one_qubit_gate_op("sxdg", qubit1)) + result.extend(one_qubit_gate_op("sdg", qubit0)) + result.extend(one_qubit_rotation_op("rz", (CONSTANTS_MAP["pi"] / 2), qubit1)) + result.extend(one_qubit_rotation_op("rz", -1 * phi, qubit0)) + + return result + + +def ryy_gate( + theta: Union[int, float], qubit0: IndexedIdentifier, qubit1: IndexedIdentifier +) -> list[QuantumGate]: + """ + Implements the YY gate as a decomposition of other gates. + + Uses the following qiskit decomposition: + + In [9]: qc.draw() + Out[9]: + ┌─────────────┐ + q_0: ┤0 ├ + │ Ryy(theta) │ + q_1: ┤1 ├ + └─────────────┘ + + In [10]: qc.decompose().draw() + Out[10]: + ┌─────────┐ ┌──────────┐ + q_0: ┤ Rx(π/2) ├──■─────────────────■──┤ Rx(-π/2) ├ + ├─────────┤┌─┴─┐┌───────────┐┌─┴─┐├──────────┤ + q_1: ┤ Rx(π/2) ├┤ X ├┤ Rz(theta) ├┤ X ├┤ Rx(-π/2) ├ + └─────────┘└───┘└───────────┘└───┘└──────────┘ + + """ + result: list[QuantumGate] = [] + result.extend(one_qubit_rotation_op("rx", CONSTANTS_MAP["pi"] / 2, qubit0)) + result.extend(one_qubit_rotation_op("rx", CONSTANTS_MAP["pi"] / 2, qubit1)) + result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) + result.extend(one_qubit_rotation_op("rz", theta, qubit1)) + result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) + result.extend(one_qubit_rotation_op("rx", -CONSTANTS_MAP["pi"] / 2, qubit0)) + result.extend(one_qubit_rotation_op("rx", -CONSTANTS_MAP["pi"] / 2, qubit1)) + return result + + +def zz_gate( + theta: Union[int, float], qubit0: IndexedIdentifier, qubit1: IndexedIdentifier +) -> list[QuantumGate]: + """ + Implements the ZZ gate as a decomposition of other gates. + """ + result: list[QuantumGate] = [] + result.extend(two_qubit_gate_op("cz", qubit0, qubit1)) + result.extend(one_qubit_gate_op("h", qubit1)) + result.extend(one_qubit_rotation_op("rz", theta, qubit1)) + result.extend(one_qubit_gate_op("h", qubit1)) + result.extend(two_qubit_gate_op("cz", qubit0, qubit1)) + return result + + +def phaseshift_gate(theta: Union[int, float], qubit: IndexedIdentifier) -> list[QuantumGate]: + """ + Implements the phase shift gate as a decomposition of other gates. + """ + result: list[QuantumGate] = [] + result.extend(one_qubit_gate_op("h", qubit)) + result.extend(one_qubit_rotation_op("rx", theta, qubit)) + result.extend(one_qubit_gate_op("h", qubit)) + return result + + +def cswap_gate( + qubit0: IndexedIdentifier, qubit1: IndexedIdentifier, qubit2: IndexedIdentifier +) -> list[QuantumGate]: + """ + Implements the CSWAP gate as a decomposition of other gates. + """ + result: list[QuantumGate] = [] + result.extend(two_qubit_gate_op("cx", qubit2, qubit1)) + result.extend(one_qubit_gate_op("h", qubit2)) + result.extend(two_qubit_gate_op("cx", qubit1, qubit2)) + result.extend(one_qubit_gate_op("tdg", qubit2)) + result.extend(two_qubit_gate_op("cx", qubit0, qubit2)) + result.extend(one_qubit_gate_op("t", qubit2)) + result.extend(two_qubit_gate_op("cx", qubit1, qubit2)) + result.extend(one_qubit_gate_op("t", qubit1)) + result.extend(one_qubit_gate_op("tdg", qubit2)) + result.extend(two_qubit_gate_op("cx", qubit0, qubit2)) + result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) + result.extend(one_qubit_gate_op("t", qubit2)) + result.extend(one_qubit_gate_op("t", qubit0)) + result.extend(one_qubit_gate_op("tdg", qubit1)) + result.extend(one_qubit_gate_op("h", qubit2)) + result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) + result.extend(two_qubit_gate_op("cx", qubit2, qubit1)) + return result + + +def pswap_gate( + theta: Union[int, float], qubit0: IndexedIdentifier, qubit1: IndexedIdentifier +) -> list[QuantumGate]: + """ + Implements the PSWAP gate as a decomposition of other gates. + """ + result: list[QuantumGate] = [] + result.extend(two_qubit_gate_op("swap", qubit0, qubit1)) + result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) + result.extend(u3_gate(0, 0, theta, qubit1)) + result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) + return result + + +def iswap_gate(qubit0: IndexedIdentifier, qubit1: IndexedIdentifier) -> list[QuantumGate]: + """Implements the iSwap gate as a decomposition of other gates. + + Reference: https://docs.quantum.ibm.com/api/qiskit/qiskit.circuit.library.iSwapGate + """ + + result: list[QuantumGate] = [] + + result.extend(one_qubit_gate_op("s", qubit0)) + result.extend(one_qubit_gate_op("s", qubit1)) + result.extend(one_qubit_gate_op("h", qubit0)) + result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) + result.extend(two_qubit_gate_op("cx", qubit1, qubit0)) + result.extend(one_qubit_gate_op("h", qubit1)) + + return result + + +def crx_gate( + theta: Union[int, float], qubit0: IndexedIdentifier, qubit1: IndexedIdentifier +) -> list[QuantumGate]: + """ + Implements the CRX gate as a decomposition of other gates. + + Used the following qiskit decomposition: + + In [26]: q.draw() + Out[26]: + + q_0: ──────■────── + ┌─────┴─────┐ + q_1: ┤ Rx(theta) ├ + └───────────┘ + + In [27]: q.decompose().decompose().decompose().draw() + Out[27]: + + q_0: ────────────────■───────────────────────■─────────────────────── + ┌────────────┐┌─┴─┐┌─────────────────┐┌─┴─┐┌───────────────────┐ + q_1: ┤ U(0,0,π/2) ├┤ X ├┤ U(-theta/2,0,0) ├┤ X ├┤ U(theta/2,-π/2,0) ├ + └────────────┘└───┘└─────────────────┘└───┘└───────────────────┘ + """ + result: list[QuantumGate] = [] + result.extend(u3_gate(0, 0, CONSTANTS_MAP["pi"] / 2, qubit1)) + result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) + result.extend(u3_gate(-1 * theta / 2, 0, 0, qubit1)) + result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) + result.extend(u3_gate(theta / 2, -1 * CONSTANTS_MAP["pi"] / 2, 0, qubit1)) + return result + + +def cry_gate( + theta: Union[int, float], qubit0: IndexedIdentifier, qubit1: IndexedIdentifier +) -> list[QuantumGate]: + """ + Implements the CRY gate as a decomposition of other gates. + + Used the following qiskit decomposition - + + In [4]: q.draw() + Out[4]: + + q_0: ──────■────── + ┌─────┴─────┐ + q_1: ┤ Ry(theta) ├ + └───────────┘ + + In [5]: q.decompose().decompose().decompose().draw() + Out[5]: + + q_0: ─────────────────────■────────────────────────■── + ┌─────────────────┐┌─┴─┐┌──────────────────┐┌─┴─┐ + q_1: ┤ U3(theta/2,0,0) ├┤ X ├┤ U3(-theta/2,0,0) ├┤ X ├ + └─────────────────┘└───┘└──────────────────┘└───┘ + """ + result: list[QuantumGate] = [] + result.extend(u3_gate(theta / 2, 0, 0, qubit1)) + result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) + result.extend(u3_gate(-1 * theta / 2, 0, 0, qubit1)) + result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) + return result + + +def crz_gate( + theta: Union[int, float], qubit0: IndexedIdentifier, qubit1: IndexedIdentifier +) -> list[QuantumGate]: + """ + Implements the CRZ gate as a decomposition of other gates. + + Used the following qiskit decomposition - + + In [4]: q.draw() + Out[4]: + + q_0: ──────■────── + ┌─────┴─────┐ + q_1: ┤ Rz(theta) ├ + └───────────┘ + + In [5]: q.decompose().decompose().decompose().draw() + Out[5]: + global phase: 0 + + q_0: ─────────────────────■────────────────────────■── + ┌─────────────────┐┌─┴─┐┌──────────────────┐┌─┴─┐ + q_1: ┤ U3(0,0,theta/2) ├┤ X ├┤ U3(0,0,-theta/2) ├┤ X ├ + └─────────────────┘└───┘└──────────────────┘└───┘ + """ + result: list[QuantumGate] = [] + result.extend(u3_gate(0, 0, theta / 2, qubit1)) + result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) + result.extend(u3_gate(0, 0, -1 * theta / 2, qubit1)) + result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) + return result + + +def cu_gate( # pylint: disable=too-many-arguments + theta: Union[int, float], + phi: Union[int, float], + lam: Union[int, float], + gamma: Union[int, float], + qubit0: IndexedIdentifier, + qubit1: IndexedIdentifier, +) -> list[QuantumGate]: + """ + Implements the CU gate as a decomposition of other gates. + + Uses the following qiskit decomposition - + + In [7]: qc.draw() + Out[7]: + + q_0: ────────────■───────────── + ┌───────────┴────────────┐ + q_1: ┤ U(theta,phi,lam,gamma) ├ + └────────────────────────┘ + + In [8]: qc.decompose().decompose().decompose().draw() + Out[8]: + ┌──────────────┐ ┌──────────────────────┐ » + q_0: ────┤ U(0,0,gamma) ├────┤ U(0,0,lam/2 + phi/2) ├──■──────────────────────────────────» + ┌───┴──────────────┴───┐└──────────────────────┘┌─┴─┐┌──────────────────────────────┐» + q_1: ┤ U(0,0,lam/2 - phi/2) ├────────────────────────┤ X ├┤ U(-theta/2,0,-lam/2 - phi/2) ├» + └──────────────────────┘ └───┘└──────────────────────────────┘» + « + «q_0: ──■────────────────────── + « ┌─┴─┐┌──────────────────┐ + «q_1: ┤ X ├┤ U(theta/2,phi,0) ├ + « └───┘└──────────────────┘ + """ + result: list[QuantumGate] = [] + result.extend(u3_gate(0, 0, gamma, qubit0)) + result.extend(u3_gate(0, 0, lam / 2 + phi / 2, qubit0)) + result.extend(u3_gate(0, 0, lam / 2 - phi / 2, qubit1)) + result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) + result.extend(u3_gate(-theta / 2, 0, -lam / 2 - phi / 2, qubit1)) + result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) + result.extend(u3_gate(theta / 2, phi, 0, qubit1)) + + return result + + +def cu3_gate( # pylint: disable=too-many-arguments + theta: Union[int, float], + phi: Union[int, float], + lam: Union[int, float], + qubit0: IndexedIdentifier, + qubit1: IndexedIdentifier, +) -> list[QuantumGate]: + """ + Implements the CU3 gate as a decomposition of other gates. + + Uses the following qiskit decomposition - + + In [7]: qc.draw() + Out[7]: + + q_0: ──────────■────────── + ┌─────────┴─────────┐ + q_1: ┤ U3(theta,phi,lam) ├ + └───────────────────┘ + + In [8]: qc.decompose().decompose().decompose().draw() + Out[8]: + ┌──────────────────────┐ + q_0: ┤ U(0,0,lam/2 + phi/2) ├──■────────────────────────────────────■────────────────────── + ├──────────────────────┤┌─┴─┐┌──────────────────────────────┐┌─┴─┐┌──────────────────┐ + q_1: ┤ U(0,0,lam/2 - phi/2) ├┤ X ├┤ U(-theta/2,0,-lam/2 - phi/2) ├┤ X ├┤ U(theta/2,phi,0) ├ + └──────────────────────┘└───┘└──────────────────────────────┘└───┘└──────────────────┘ + """ + result: list[QuantumGate] = [] + result.extend(u3_gate(0, 0, lam / 2 + phi / 2, qubit0)) + result.extend(u3_gate(0, 0, lam / 2 - phi / 2, qubit1)) + result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) + result.extend(u3_gate(-theta / 2, 0, -lam / 2 - phi / 2, qubit1)) + result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) + result.extend(u3_gate(theta / 2, phi, 0, qubit1)) + + return result + + +def cu1_gate( + theta: Union[int, float], qubit0: IndexedIdentifier, qubit1: IndexedIdentifier +) -> list[QuantumGate]: + """ + Implements the CU1 gate as a decomposition of other gates. + + Uses the following qiskit decomposition - + + In [11]: qc.draw() + Out[11]: + + q_0: ─■────────── + │U1(theta) + q_1: ─■────────── + + + In [12]: qc.decompose().decompose().decompose().draw() + Out[12]: + ┌────────────────┐ + q_0: ┤ U(0,0,theta/2) ├──■───────────────────────■──────────────────── + └────────────────┘┌─┴─┐┌─────────────────┐┌─┴─┐┌────────────────┐ + q_1: ──────────────────┤ X ├┤ U(0,0,-theta/2) ├┤ X ├┤ U(0,0,theta/2) ├ + └───┘└─────────────────┘└───┘└────────────────┘ + """ + result: list[QuantumGate] = [] + result.extend(u3_gate(0, 0, theta / 2, qubit0)) + result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) + result.extend(u3_gate(0, 0, -theta / 2, qubit1)) + result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) + result.extend(u3_gate(0, 0, theta / 2, qubit1)) + + return result + + +def csx_gate(qubit0: IndexedIdentifier, qubit1: IndexedIdentifier) -> list[QuantumGate]: + """Implement the CSX gate as a decomposition of other gates. + + Used the following qiskit decomposition - + + In [19]: q = QuantumCircuit(2) + + In [20]: q.csx(0,1) + Out[20]: + + In [21]: q.draw() + Out[21]: + + q_0: ──■─── + ┌─┴──┐ + q_1: ┤ Sx ├ + └────┘ + + In [22]: q.decompose().decompose().draw() + Out[22]: + ┌─────────┐ + q_0: ┤ U1(π/4) ├──■────────────────■──────────────────────── + ├─────────┤┌─┴─┐┌──────────┐┌─┴─┐┌─────────┐┌─────────┐ + q_1: ┤ U2(0,π) ├┤ X ├┤ U1(-π/4) ├┤ X ├┤ U1(π/4) ├┤ U2(0,π) ├ + └─────────┘└───┘└──────────┘└───┘└─────────┘└─────────┘ + """ + result: list[QuantumGate] = [] + result.extend(phaseshift_gate(CONSTANTS_MAP["pi"] / 4, qubit0)) + result.extend(u2_gate(0, CONSTANTS_MAP["pi"], qubit1)) + result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) + result.extend(phaseshift_gate(-CONSTANTS_MAP["pi"] / 4, qubit1)) + result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) + result.extend(phaseshift_gate(CONSTANTS_MAP["pi"] / 4, qubit1)) + result.extend(u2_gate(0, CONSTANTS_MAP["pi"], qubit1)) + + return result + + +def rxx_gate( + theta: Union[int, float], qubit0: IndexedIdentifier, qubit1: IndexedIdentifier +) -> list[Union[QuantumGate, QuantumPhase]]: + """ + Implements the RXX gate as a decomposition of other gates. + """ + + result: list[Union[QuantumGate, QuantumPhase]] = [] + result.extend(global_phase_gate(-theta / 2, [qubit0, qubit1])) + result.extend(one_qubit_gate_op("h", qubit0)) + result.extend(one_qubit_gate_op("h", qubit1)) + result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) + result.extend(one_qubit_rotation_op("rz", theta, qubit1)) + result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) + result.extend(one_qubit_gate_op("h", qubit1)) + result.extend(one_qubit_gate_op("h", qubit0)) + + return result + + +def rccx_gate( + qubit0: IndexedIdentifier, qubit1: IndexedIdentifier, qubit2: IndexedIdentifier +) -> list[QuantumGate]: + result: list[QuantumGate] = [] + result.extend(u2_gate(0, CONSTANTS_MAP["pi"], qubit2)) + result.extend(phaseshift_gate(CONSTANTS_MAP["pi"] / 4, qubit2)) + result.extend(two_qubit_gate_op("cx", qubit1, qubit2)) + result.extend(phaseshift_gate(-CONSTANTS_MAP["pi"] / 4, qubit2)) + result.extend(two_qubit_gate_op("cx", qubit0, qubit2)) + result.extend(phaseshift_gate(CONSTANTS_MAP["pi"] / 4, qubit2)) + result.extend(two_qubit_gate_op("cx", qubit1, qubit2)) + result.extend(phaseshift_gate(-CONSTANTS_MAP["pi"] / 4, qubit2)) + result.extend(u2_gate(0, CONSTANTS_MAP["pi"], qubit2)) + + return result + + +def rzz_gate( + theta: Union[int, float], qubit0: IndexedIdentifier, qubit1: IndexedIdentifier +) -> list[Union[QuantumGate, QuantumPhase]]: + """ + Implements the RZZ gate as a decomposition of other gates. + + Used the following qiskit decomposition - + + In [32]: q.draw() + Out[32]: + + q_0: ─■────────── + │ZZ(theta) + q_1: ─■────────── + + + In [33]: q.decompose().decompose().decompose().draw() + Out[33]: + global phase: -theta/2 + + q_0: ──■─────────────────────■── + ┌─┴─┐┌───────────────┐┌─┴─┐ + q_1: ┤ X ├┤ U3(0,0,theta) ├┤ X ├ + └───┘└───────────────┘└───┘ + """ + result: list[Union[QuantumGate, QuantumPhase]] = [] + + result.extend(global_phase_gate(-theta / 2, [qubit0, qubit1])) + result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) + result.extend(u3_gate(0, 0, theta, qubit1)) + result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) + + return result + + +def cphaseshift_gate( + theta: Union[int, float], qubit0: IndexedIdentifier, qubit1: IndexedIdentifier +) -> list[QuantumGate]: + """ + Implements the controlled phase shift gate as a decomposition of other gates. + + Uses the following qiskit decomposition - + + In [11]: qc.draw() + Out[11]: + + q_0: ─■───────── + │P(theta) + q_1: ─■───────── + + + In [12]: qc.decompose().decompose().decompose().draw() + Out[12]: + ┌────────────────┐ + q_0: ┤ U(0,0,theta/2) ├──■───────────────────────■──────────────────── + └────────────────┘┌─┴─┐┌─────────────────┐┌─┴─┐┌────────────────┐ + q_1: ──────────────────┤ X ├┤ U(0,0,-theta/2) ├┤ X ├┤ U(0,0,theta/2) ├ + └───┘└─────────────────┘└───┘└────────────────┘ + """ + result: list[QuantumGate] = [] + result.extend(u3_gate(0, 0, theta / 2, qubit0)) + result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) + result.extend(u3_gate(0, 0, -theta / 2, qubit1)) + result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) + result.extend(u3_gate(0, 0, theta / 2, qubit1)) + + return result + + +def cphaseshift00_gate( + theta: Union[int, float], qubit0: IndexedIdentifier, qubit1: IndexedIdentifier +) -> list[QuantumGate]: + """ + Implements the controlled phase shift 00 gate as a decomposition of other gates. + """ + result: list[QuantumGate] = [] + result.extend(one_qubit_gate_op("x", qubit0)) + result.extend(one_qubit_gate_op("x", qubit1)) + result.extend(u3_gate(0, 0, theta / 2, qubit0)) + result.extend(u3_gate(0, 0, theta / 2, qubit1)) + result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) + result.extend(u3_gate(0, 0, -theta / 2, qubit1)) + result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) + result.extend(one_qubit_gate_op("x", qubit0)) + result.extend(one_qubit_gate_op("x", qubit1)) + return result + + +def cphaseshift01_gate( + theta: Union[int, float], qubit0: IndexedIdentifier, qubit1: IndexedIdentifier +) -> list[QuantumGate]: + """ + Implements the controlled phase shift 01 gate as a decomposition of other gates. + """ + result: list[QuantumGate] = [] + result.extend(one_qubit_gate_op("x", qubit0)) + result.extend(u3_gate(0, 0, theta / 2, qubit1)) + result.extend(u3_gate(0, 0, theta / 2, qubit0)) + result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) + result.extend(u3_gate(0, 0, -theta / 2, qubit1)) + result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) + result.extend(one_qubit_gate_op("x", qubit0)) + return result + + +def cphaseshift10_gate( + theta: Union[int, float], qubit0: IndexedIdentifier, qubit1: IndexedIdentifier +) -> list[QuantumGate]: + """ + Implements the controlled phase shift 10 gate as a decomposition of other gates. + """ + result: list[QuantumGate] = [] + result.extend(u3_gate(0, 0, theta / 2, qubit0)) + result.extend(one_qubit_gate_op("x", qubit1)) + result.extend(u3_gate(0, 0, theta / 2, qubit1)) + result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) + result.extend(u3_gate(0, 0, -theta / 2, qubit1)) + result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) + result.extend(one_qubit_gate_op("x", qubit1)) + return result + + +def gpi_gate(phi, qubit_id) -> list[QuantumGate]: + """ + Implements the gpi gate as a decomposition of other gates. + """ + theta_0 = CONSTANTS_MAP["pi"] + phi_0 = phi + lambda_0 = -phi_0 + CONSTANTS_MAP["pi"] + return u3_gate(theta_0, phi_0, lambda_0, qubit_id) + + +def gpi2_gate(phi, qubit_id) -> list[QuantumGate]: + """ + Implements the gpi2 gate as a decomposition of other gates. + """ + # Reference: + # https://amazon-braket-sdk-python.readthedocs.io/en/latest/_apidoc/braket.circuits.circuit.html#braket.circuits.circuit.Circuit.gpi2 + # https://docs.quantum.ibm.com/api/qiskit/qiskit.circuit.library.U3Gate#u3gate + theta_0 = CONSTANTS_MAP["pi"] / 2 + phi_0 = phi - CONSTANTS_MAP["pi"] / 2 + lambda_0 = CONSTANTS_MAP["pi"] / 2 - phi + return u3_gate(theta_0, phi_0, lambda_0, qubit_id) + + +# pylint: disable-next=too-many-arguments +def ms_gate(phi0, phi1, theta, qubit0, qubit1) -> list[QuantumGate]: + """ + Implements the Molmer Sorenson gate as a decomposition of other gates. + """ + mat = np.array( + [ + [ + np.cos(np.pi * theta), + 0, + 0, + -1j * np.exp(-1j * 2 * np.pi * (phi0 + phi1)) * np.sin(np.pi * theta), + ], + [ + 0, + np.cos(np.pi * theta), + -1j * np.exp(-1j * 2 * np.pi * (phi0 - phi1)) * np.sin(np.pi * theta), + 0, + ], + [ + 0, + -1j * np.exp(1j * 2 * np.pi * (phi0 - phi1)) * np.sin(np.pi * theta), + np.cos(np.pi * theta), + 0, + ], + [ + -1j * np.exp(1j * 2 * np.pi * (phi0 + phi1)) * np.sin(np.pi * theta), + 0, + 0, + np.cos(np.pi * theta), + ], + ] + ) + angles = kak_decomposition_angles(mat) + qubits = [qubit0, qubit1] + + result: list[QuantumGate] = [] + result.extend(u3_gate(angles[0][0], angles[0][1], angles[0][2], qubits[0])) + result.extend(u3_gate(angles[1][0], angles[1][1], angles[1][2], qubits[1])) + result.extend(one_qubit_gate_op("sx", qubits[0])) + result.extend(two_qubit_gate_op("cx", qubits[0], qubits[1])) + result.extend( + one_qubit_rotation_op("rx", ((1 / 2) - 2 * theta) * CONSTANTS_MAP["pi"], qubits[0]) + ) + result.extend(one_qubit_rotation_op("rx", CONSTANTS_MAP["pi"] / 2, qubits[1])) + result.extend(two_qubit_gate_op("cx", qubits[1], qubits[0])) + result.extend(sxdg_gate_op(qubits[1])) + result.extend(one_qubit_gate_op("s", qubits[1])) + result.extend(two_qubit_gate_op("cx", qubits[0], qubits[1])) + result.extend(u3_gate(angles[2][0], angles[2][1], angles[2][2], qubits[0])) + result.extend(u3_gate(angles[3][0], angles[3][1], angles[3][2], qubits[1])) + return result + + +def ccx_gate_op( + qubit0: IndexedIdentifier, qubit1: IndexedIdentifier, qubit2: IndexedIdentifier +) -> list[QuantumGate]: + return [ + QuantumGate( + modifiers=[], + name=Identifier(name="ccx"), + arguments=[], + qubits=[qubit0, qubit1, qubit2], + ) + ] + + +def ecr_gate(qubit0: IndexedIdentifier, qubit1: IndexedIdentifier) -> list[QuantumGate]: + """ + Implements the ECR gate as a decomposition of other gates. + """ + result: list[QuantumGate] = [] + result.extend(one_qubit_gate_op("s", qubit0)) + result.extend(one_qubit_rotation_op("rx", CONSTANTS_MAP["pi"] / 2, qubit1)) + result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) + result.extend(one_qubit_gate_op("x", qubit0)) + return result + + +def c3sx_gate( + qubit0: IndexedIdentifier, + qubit1: IndexedIdentifier, + qubit2: IndexedIdentifier, + qubit3: IndexedIdentifier, +) -> list[QuantumGate]: + """ + Implements the c3sx gate as a decomposition of other gates. + + Uses the following qiskit decomposition - + + In [15]: qc.draw() + Out[15]: + + q_0: ──■─── + │ + q_1: ──■─── + │ + q_2: ──■─── + ┌─┴──┐ + q_3: ┤ Sx ├ + └────┘ + + In [16]: qc.decompose().draw() + Out[16]: + + q_0: ──────■──────────■────────────────────■────────────────────────────────────────■──────── + │ ┌─┴─┐ ┌─┴─┐ │ + q_1: ──────┼────────┤ X ├──────■─────────┤ X ├──────■──────────■────────────────────┼──────── + │ └───┘ │ └───┘ │ ┌─┴─┐ ┌─┴─┐ + q_2: ──────┼───────────────────┼────────────────────┼────────┤ X ├──────■─────────┤ X ├────── + ┌───┐ │U1(π/8) ┌───┐┌───┐ │U1(-π/8) ┌───┐┌───┐ │U1(π/8) ├───┤┌───┐ │U1(-π/8) ├───┤┌───┐ + q_3: ┤ H ├─■────────┤ H ├┤ H ├─■─────────┤ H ├┤ H ├─■────────┤ H ├┤ H ├─■─────────┤ H ├┤ H ├─ + └───┘ └───┘└───┘ └───┘└───┘ └───┘└───┘ └───┘└───┘ + « + «q_0:─────────────────────────────────■────────────────────── + « │ + «q_1:────────────■────────────────────┼────────────────────── + « ┌─┴─┐ ┌─┴─┐ + «q_2:─■────────┤ X ├──────■─────────┤ X ├──────■───────────── + « │U1(π/8) ├───┤┌───┐ │U1(-π/8) ├───┤┌───┐ │U1(π/8) ┌───┐ + «q_3:─■────────┤ H ├┤ H ├─■─────────┤ H ├┤ H ├─■────────┤ H ├ + « └───┘└───┘ └───┘└───┘ └───┘ + """ + + result: list[QuantumGate] = [] + result.extend(one_qubit_gate_op("h", qubit3)) + result.extend(cu1_gate(CONSTANTS_MAP["pi"] / 8, qubit0, qubit3)) + result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) + # h(q[3]) * h (q[3]) = Identity + result.extend(cu1_gate(-CONSTANTS_MAP["pi"] / 8, qubit1, qubit3)) + result.extend(two_qubit_gate_op("cx", qubit0, qubit1)) + # h(q[3]) * h (q[3]) = Identity + result.extend(cu1_gate(CONSTANTS_MAP["pi"] / 8, qubit1, qubit3)) + result.extend(two_qubit_gate_op("cx", qubit1, qubit2)) + # h(q[3]) * h (q[3]) = Identity + result.extend(cu1_gate(-CONSTANTS_MAP["pi"] / 8, qubit2, qubit3)) + result.extend(two_qubit_gate_op("cx", qubit0, qubit2)) + # h(q[3]) * h (q[3]) = Identity + result.extend(cu1_gate(CONSTANTS_MAP["pi"] / 8, qubit2, qubit3)) + result.extend(two_qubit_gate_op("cx", qubit1, qubit2)) + # h(q[3]) * h (q[3]) = Identity + result.extend(cu1_gate(-CONSTANTS_MAP["pi"] / 8, qubit2, qubit3)) + result.extend(two_qubit_gate_op("cx", qubit0, qubit2)) + # h(q[3]) * h (q[3]) = Identity + result.extend(cu1_gate(CONSTANTS_MAP["pi"] / 8, qubit2, qubit3)) + result.extend(one_qubit_gate_op("h", qubit3)) + + return result + + +def c4x_gate( + qubit0: IndexedIdentifier, + qubit1: IndexedIdentifier, + qubit2: IndexedIdentifier, + qubit3: IndexedIdentifier, +) -> list[QuantumGate]: + """ + Implements the c4x gate + """ + return [ + QuantumGate( + modifiers=[], + name=Identifier(name="c4x"), + arguments=[], + qubits=[qubit0, qubit1, qubit2, qubit3], + ) + ] + + +def prx_gate(theta, phi, qubit_id) -> list[QuantumGate]: + """ + Implements the PRX gate as a decomposition of other gates. + """ + # Reference: + # https://amazon-braket-sdk-python.readthedocs.io/en/latest/_apidoc/braket.circuits.circuit.html#braket.circuits.circuit.Circuit.prx + # https://docs.quantum.ibm.com/api/qiskit/qiskit.circuit.library.U3Gate#u3gate + theta_0 = theta + phi_0 = phi - CONSTANTS_MAP["pi"] / 2 + lambda_0 = CONSTANTS_MAP["pi"] / 2 - phi + return u3_gate(theta_0, phi_0, lambda_0, qubit_id) + + +def one_qubit_gate_op(gate_name: str, qubit_id: IndexedIdentifier) -> list[QuantumGate]: + return [ + QuantumGate( + modifiers=[], + name=Identifier(name=gate_name), + arguments=[], + qubits=[qubit_id], + ) + ] + + +def one_qubit_rotation_op( + gate_name: str, rotation: float, qubit_id: IndexedIdentifier +) -> list[QuantumGate]: + return [ + QuantumGate( + modifiers=[], + name=Identifier(name=gate_name), + arguments=[FloatLiteral(value=rotation)], + qubits=[qubit_id], + ) + ] + + +def two_qubit_gate_op( + gate_name: str, qubit_id1: IndexedIdentifier, qubit_id2: IndexedIdentifier +) -> list[QuantumGate]: + return [ + QuantumGate( + modifiers=[], + name=Identifier(name=gate_name.lower()), + arguments=[], + qubits=[qubit_id1, qubit_id2], + ) + ] + + +ONE_QUBIT_OP_MAP = { + "id": lambda qubit_id: one_qubit_gate_op("id", qubit_id), + "h": lambda qubit_id: one_qubit_gate_op("h", qubit_id), + "x": lambda qubit_id: one_qubit_gate_op("x", qubit_id), + "not": lambda qubit_id: one_qubit_gate_op("x", qubit_id), + "y": lambda qubit_id: one_qubit_gate_op("y", qubit_id), + "z": lambda qubit_id: one_qubit_gate_op("z", qubit_id), + "s": lambda qubit_id: one_qubit_gate_op("s", qubit_id), + "t": lambda qubit_id: one_qubit_gate_op("t", qubit_id), + "sdg": lambda qubit_id: one_qubit_gate_op("sdg", qubit_id), + "si": lambda qubit_id: one_qubit_gate_op("sdg", qubit_id), + "tdg": lambda qubit_id: one_qubit_gate_op("tdg", qubit_id), + "ti": lambda qubit_id: one_qubit_gate_op("tdg", qubit_id), + "v": lambda qubit_id: one_qubit_gate_op("sx", qubit_id), + "sx": lambda qubit_id: one_qubit_gate_op("sx", qubit_id), + "vi": sxdg_gate_op, + "sxdg": sxdg_gate_op, +} + + +ONE_QUBIT_ROTATION_MAP = { + "rx": lambda rotation, qubit_id: one_qubit_rotation_op("rx", rotation, qubit_id), + "ry": lambda rotation, qubit_id: one_qubit_rotation_op("ry", rotation, qubit_id), + "rz": lambda rotation, qubit_id: one_qubit_rotation_op("rz", rotation, qubit_id), + "u1": phaseshift_gate, + "U1": phaseshift_gate, + "u": u3_gate, + "U": u3_gate, + "u3": u3_gate, + "U3": u3_gate, + "U2": u2_gate, + "u2": u2_gate, + "prx": prx_gate, + "phaseshift": phaseshift_gate, + "p": phaseshift_gate, + "gpi": gpi_gate, + "gpi2": gpi2_gate, +} + +TWO_QUBIT_OP_MAP = { + "cx": lambda qubit_id1, qubit_id2: two_qubit_gate_op("cx", qubit_id1, qubit_id2), + "CX": lambda qubit_id1, qubit_id2: two_qubit_gate_op("cx", qubit_id1, qubit_id2), + "cnot": lambda qubit_id1, qubit_id2: two_qubit_gate_op("cx", qubit_id1, qubit_id2), + "cz": lambda qubit_id1, qubit_id2: two_qubit_gate_op("cz", qubit_id1, qubit_id2), + "swap": lambda qubit_id1, qubit_id2: two_qubit_gate_op("swap", qubit_id1, qubit_id2), + "cv": csx_gate, + "cy": cy_gate, + "ch": ch_gate, + "xx": rxx_gate, + "rxx": rxx_gate, + "yy": ryy_gate, + "ryy": ryy_gate, + "zz": rzz_gate, + "rzz": rzz_gate, + "xy": xy_gate, + "xx_plus_yy": xx_plus_yy_gate, + "pswap": pswap_gate, + "iswap": iswap_gate, + "cp": cphaseshift_gate, + "crx": crx_gate, + "cry": cry_gate, + "crz": crz_gate, + "cu": cu_gate, + "cu3": cu3_gate, + "csx": csx_gate, + "cphaseshift": cphaseshift_gate, + "cu1": cu1_gate, + "cp00": cphaseshift00_gate, + "cphaseshift00": cphaseshift00_gate, + "cp01": cphaseshift01_gate, + "cphaseshift01": cphaseshift01_gate, + "cp10": cphaseshift10_gate, + "cphaseshift10": cphaseshift10_gate, + "ecr": ecr_gate, + "ms": ms_gate, +} + +THREE_QUBIT_OP_MAP = { + "ccx": ccx_gate_op, + "toffoli": ccx_gate_op, + "ccnot": ccx_gate_op, + "cswap": cswap_gate, + "rccx": rccx_gate, +} + +FOUR_QUBIT_OP_MAP = {"c3sx": c3sx_gate, "c3sqrtx": c3sx_gate} + +FIVE_QUBIT_OP_MAP = { + "c4x": c4x_gate, +} + + +def map_qasm_op_to_callable(op_name: str) -> tuple[Callable, int]: + """ + Map a QASM operation to a callable. + + Args: + op_name (str): The QASM operation name. + + Returns: + tuple: A tuple containing the callable and the number of qubits the operation acts on. + + Raises: + ValidationError: If the QASM operation is unsupported or undeclared. + """ + op_maps: list[tuple[dict, int]] = [ + (ONE_QUBIT_OP_MAP, 1), + (ONE_QUBIT_ROTATION_MAP, 1), + (TWO_QUBIT_OP_MAP, 2), + (THREE_QUBIT_OP_MAP, 3), + (FOUR_QUBIT_OP_MAP, 4), + (FIVE_QUBIT_OP_MAP, 5), + ] + + for op_map, qubit_count in op_maps: + try: + return op_map[op_name], qubit_count + except KeyError: + continue + + raise ValidationError(f"Unsupported / undeclared QASM operation: {op_name}") + + +SELF_INVERTING_ONE_QUBIT_OP_SET = {"id", "h", "x", "y", "z"} +ST_GATE_INV_MAP = { + "s": "sdg", + "t": "tdg", + "sdg": "s", + "tdg": "t", +} +ROTATION_INVERSION_ONE_QUBIT_OP_MAP = {"rx", "ry", "rz"} +U_INV_ROTATION_MAP = { + "U": u3_inv_gate, + "u3": u3_inv_gate, + "U3": u3_inv_gate, + "U2": u2_inv_gate, + "u2": u2_inv_gate, +} + + +def map_qasm_inv_op_to_callable(op_name: str): + """ + Map a QASM operation to a callable. + + Args: + op_name (str): The QASM operation name. + + Returns: + tuple: A tuple containing the callable, the number of qubits the operation acts on, + and what is to be done with the basic gate which we are trying to invert. + """ + if op_name in SELF_INVERTING_ONE_QUBIT_OP_SET: + return ONE_QUBIT_OP_MAP[op_name], 1, InversionOp.NO_OP + if op_name in ST_GATE_INV_MAP: + inv_gate_name = ST_GATE_INV_MAP[op_name] + return ONE_QUBIT_OP_MAP[inv_gate_name], 1, InversionOp.NO_OP + if op_name in TWO_QUBIT_OP_MAP: + return TWO_QUBIT_OP_MAP[op_name], 2, InversionOp.NO_OP + if op_name in THREE_QUBIT_OP_MAP: + return THREE_QUBIT_OP_MAP[op_name], 3, InversionOp.NO_OP + if op_name in U_INV_ROTATION_MAP: + # Special handling for U gate as it is composed of multiple + # basic gates and we need to invert each of them + return U_INV_ROTATION_MAP[op_name], 1, InversionOp.NO_OP + if op_name in ROTATION_INVERSION_ONE_QUBIT_OP_MAP: + return ( + ONE_QUBIT_ROTATION_MAP[op_name], + 1, + InversionOp.INVERT_ROTATION, + ) + raise ValidationError(f"Unsupported / undeclared QASM operation: {op_name}") + + +REV_CTRL_GATE_MAP = { + "cx": "x", + "cy": "y", + "cz": "z", + "crx": "rx", + "cry": "ry", + "crz": "rz", + "cp": "p", + "ch": "h", + "cu": "u", + "cswap": "swap", + "ccx": "cx", +} \ No newline at end of file diff --git a/src/pyqasm/modules/base.py b/src/pyqasm/modules/base.py index 488ced7..a013e76 100644 --- a/src/pyqasm/modules/base.py +++ b/src/pyqasm/modules/base.py @@ -551,3 +551,7 @@ def accept(self, visitor): Args: visitor (QasmVisitor): The visitor to accept """ + + @abstractmethod + def draw(self, idle_wires=True): + """Draw the module""" diff --git a/src/pyqasm/modules/qasm2.py b/src/pyqasm/modules/qasm2.py index f17e55e..2eed469 100644 --- a/src/pyqasm/modules/qasm2.py +++ b/src/pyqasm/modules/qasm2.py @@ -23,6 +23,7 @@ from pyqasm.exceptions import ValidationError from pyqasm.modules.base import QasmModule from pyqasm.modules.qasm3 import Qasm3Module +from pyqasm.printer import draw class Qasm2Module(QasmModule): @@ -105,3 +106,7 @@ def accept(self, visitor): final_stmt_list = visitor.finalize(unrolled_stmt_list) self.unrolled_ast.statements = final_stmt_list + + def draw(self): + """Draw the module""" + return draw(self.to_qasm3()) diff --git a/src/pyqasm/modules/qasm3.py b/src/pyqasm/modules/qasm3.py index fb193c4..6a805b3 100644 --- a/src/pyqasm/modules/qasm3.py +++ b/src/pyqasm/modules/qasm3.py @@ -16,6 +16,7 @@ from openqasm3.printer import dumps from pyqasm.modules.base import QasmModule +from pyqasm.printer import draw class Qasm3Module(QasmModule): @@ -48,3 +49,7 @@ def accept(self, visitor): final_stmt_list = visitor.finalize(unrolled_stmt_list) self._unrolled_ast.statements = final_stmt_list + + def draw(self, idle_wires=True): + """Draw the module""" + return draw(self, idle_wires=idle_wires) diff --git a/src/pyqasm/printer.py b/src/pyqasm/printer.py new file mode 100644 index 0000000..8e2dc46 --- /dev/null +++ b/src/pyqasm/printer.py @@ -0,0 +1,420 @@ +# Copyright (C) 2025 qBraid# +# This file is part of pyqasm +# +# Pyqasm is free software released under the GNU General Public License v3 +# or later. You can redistribute and/or modify it under the terms of the GPL v3. +# See the LICENSE file in the project root or . +# +# THERE IS NO WARRANTY for pyqasm, as per Section 15 of the GPL v3. + +""" +Module with analysis functions for QASM visitor + +""" +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +import openqasm3.ast as ast + +from pyqasm.expressions import Qasm3ExprEvaluator +from pyqasm.maps.gates import ( + ONE_QUBIT_OP_MAP, + ONE_QUBIT_ROTATION_MAP, + REV_CTRL_GATE_MAP, + TWO_QUBIT_OP_MAP, +) + +try: + from matplotlib import pyplot as plt + + mpl_installed = True +except ImportError as e: + mpl_installed = False + +if TYPE_CHECKING: + from pyqasm.modules.base import Qasm3Module + + +DEFAULT_GATE_COLOR = "#d4b6e8" +HADAMARD_GATE_COLOR = "#f0a6a6" + +FIG_MAX_WIDTH = 12 +GATE_BOX_WIDTH, GATE_BOX_HEIGHT = 0.6, 0.6 +GATE_SPACING = 0.2 +LINE_SPACING = 0.6 +TEXT_MARGIN = 0.6 +FRAME_PADDING = 0.2 + + +def draw(module: Qasm3Module, output="mpl", idle_wires=True): + if not mpl_installed: + raise ImportError( + "matplotlib needs to be installed prior to running pyqasm.draw(). You can install matplotlib with:\n'pip install pyqasm[visualization]'" + ) + + if output == "mpl": + plt.ioff() + plt.close("all") + return _draw_mpl(module, idle_wires=idle_wires) + else: + raise NotImplementedError(f"{output} drawing for Qasm3Module is unsupported") + + +def _draw_mpl(module: Qasm3Module, idle_wires=True) -> plt.Figure: + module.unroll() + module.remove_includes() + + statements = module._statements + + # compute line numbers per qubit + max depth + line_nums = dict() + sizes = dict() + line_num = -1 + max_depth = 0 + + # classical registers are condensed into a single line + for k in module._classical_registers.keys(): + line_num += 1 + line_nums[(k, -1)] = line_num + sizes[(k, -1)] = module._classical_registers[k] + + for qubit_reg in module._qubit_registers.keys(): + size = module._qubit_registers[qubit_reg] + line_num += size + for i in range(size): + line_nums[(qubit_reg, i)] = line_num + depth = module._qubit_depths[(qubit_reg, i)]._total_ops() + max_depth = max(max_depth, depth) + line_num -= 1 + line_num += size + + # compute moments + depths = dict() + for k in line_nums.keys(): + depths[k] = -1 + + global_phase = sum([Qasm3ExprEvaluator.evaluate_expression(s.argument)[0] for s in statements if isinstance(s, ast.QuantumPhase)]) + statements = [s for s in statements if not isinstance(s, ast.QuantumPhase)] + + moments = [] + for statement in statements: + if "Declaration" in str(type(statement)): + continue + if isinstance(statement, ast.QuantumGate): + qubits = [_identifier_to_key(q) for q in statement.qubits] + depth = 1 + max([depths[q] for q in qubits]) + for q in qubits: + depths[q] = depth + elif isinstance(statement, ast.QuantumMeasurementStatement): + qubit_key = _identifier_to_key(statement.measure.qubit) + target_key = _identifier_to_key(statement.target)[0], -1 + depth = 1 + max(depths[qubit_key], depths[target_key]) + for k in [qubit_key, target_key]: + depths[k] = depth + elif isinstance(statement, ast.QuantumBarrier): + qubits = [_identifier_to_key(q) for q in statement.qubits] + depth = 1 + max([depths[q] for q in qubits]) + for q in qubits: + depths[q] = depth + else: + raise NotImplementedError(f"Unsupported statement: {statement}") + + if depth >= len(moments): + moments.append([]) + moments[depth].append(statement) + + if not idle_wires: + # remove all lines that are not used + ks = sorted(line_nums.keys(), key=lambda k: line_nums[k]) + ks = [k for k in ks if depths[k] > 0] + line_nums = {k: i for i, k in enumerate(ks)} + + sections = [[]] + + width = TEXT_MARGIN + for moment in moments: + w = _mpl_get_moment_width(moment) + if width + w < FIG_MAX_WIDTH: + width += w + else: + width = TEXT_MARGIN + width = w + sections.append([]) + sections[-1].append(moment) + + if len(sections) > 1: + width = FIG_MAX_WIDTH + + n_lines = max(line_nums.values()) + 1 + + fig, axs = plt.subplots( + len(sections), + 1, + sharex=True, + figsize=(width, len(sections) * (n_lines * GATE_BOX_HEIGHT + LINE_SPACING * (n_lines - 1))), + ) + if len(sections) == 1: + axs = [axs] + + for ax in axs: + ax.set_ylim( + -GATE_BOX_HEIGHT / 2 - FRAME_PADDING / 2, + n_lines * GATE_BOX_HEIGHT + + LINE_SPACING * (n_lines - 1) + - GATE_BOX_HEIGHT / 2 + + FRAME_PADDING / 2, + ) + ax.set_xlim(-FRAME_PADDING / 2, width) + ax.axis("off") + + for sidx, moments in enumerate(sections): + ax = axs[sidx] + x = 0 + if sidx == 0: + if global_phase != 0: _mpl_draw_global_phase(global_phase, ax, x) + for k in module._qubit_registers.keys(): + for i in range(module._qubit_registers[k]): + if (k, i) in line_nums: + line_num = line_nums[(k, i)] + _mpl_draw_qubit_label((k, i), line_num, ax, x) + + for k in module._classical_registers.keys(): + _mpl_draw_creg_label(k, module._classical_registers[k], line_nums[(k, -1)], ax, x) + + x += TEXT_MARGIN + x0 = x + for i, moment in enumerate(moments): + dx = _mpl_get_moment_width(moment) + _mpl_draw_lines(dx, line_nums, sizes, ax, x, start=(i == 0 and sidx == 0)) + x += dx + x = x0 + for moment in moments: + dx = _mpl_get_moment_width(moment) + for statement in moment: + _mpl_draw_statement(statement, line_nums, ax, x) + x += dx + + return fig + + +def _identifier_to_key(identifier: ast.Identifier | ast.IndexedIdentifier) -> tuple[str, int]: + if isinstance(identifier, ast.Identifier): + return identifier.name, -1 + else: + return ( + identifier.name.name, + Qasm3ExprEvaluator.evaluate_expression(identifier.indices[0][0])[0], + ) + + +def _mpl_line_to_y(line_num: int) -> float: + return line_num * (GATE_BOX_HEIGHT + LINE_SPACING) + +def _mpl_draw_global_phase(global_phase: float, ax: plt.Axes, x: float): + ax.text(x, -0.75, f"Global Phase: {global_phase:.3f}", ha="left", va="center") + +def _mpl_draw_qubit_label(qubit: tuple[str, int], line_num: int, ax: plt.Axes, x: float): + ax.text(x, _mpl_line_to_y(line_num), f"{qubit[0]}[{qubit[1]}]", ha="right", va="center") +def _mpl_draw_creg_label(creg: str, size: int, line_num: int, ax: plt.Axes, x: float): + ax.text(x, _mpl_line_to_y(line_num), f"{creg[0]}", ha="right", va="center") + + +def _mpl_draw_lines( + width, + line_nums: dict[tuple[str, int], int], + sizes: dict[tuple[str, int], int], + ax: plt.Axes, + x: float, + start=True, +): + for k in line_nums.keys(): + y = _mpl_line_to_y(line_nums[k]) + if k[1] == -1: + gap = GATE_BOX_HEIGHT / 15 + ax.hlines( + xmin=x - width / 2, + xmax=x + width / 2, + y=y + gap / 2, + color="black", + linestyle="-", + zorder=-10, + ) + ax.hlines( + xmin=x - width / 2, + xmax=x + width / 2, + y=y - gap / 2, + color="black", + linestyle="-", + zorder=-10, + ) + if start: + ax.plot( + [x - width / 2 + gap, x - width / 2 + 2 * gap], + [y - 2 * gap, y + 2 * gap], + color="black", + zorder=-10, + ) + ax.text(x - width / 2 + 3 * gap, y + 3 * gap, f"{sizes[k]}", fontsize=8) + else: + ax.hlines( + xmin=x - width / 2, + xmax=x + width / 2, + y=y, + color="black", + linestyle="-", + zorder=-10, + ) + + +def _mpl_get_moment_width(moment: list[ast.QuantumStatement]) -> float: + return max([_mpl_get_statement_width(s) for s in moment]) + + +def _mpl_get_statement_width(statement: ast.QuantumStatement) -> float: + return GATE_BOX_WIDTH + GATE_SPACING + + +def _mpl_draw_statement( + statement: ast.QuantumStatement, line_nums: dict[tuple[str, int], int], ax: plt.Axes, x: float +): + if isinstance(statement, ast.QuantumGate): + args = [Qasm3ExprEvaluator.evaluate_expression(arg)[0] for arg in statement.arguments] + lines = [line_nums[_identifier_to_key(q)] for q in statement.qubits] + _mpl_draw_gate(statement, args, lines, ax, x) + elif isinstance(statement, ast.QuantumMeasurementStatement): + qubit_key = _identifier_to_key(statement.measure.qubit) + target_key = _identifier_to_key(statement.target) + _mpl_draw_measurement( + line_nums[qubit_key], line_nums[(target_key[0], -1)], target_key[1], ax, x + ) + elif isinstance(statement, ast.QuantumBarrier): + lines = [line_nums[_identifier_to_key(q)] for q in statement.qubits] + _mpl_draw_barrier(lines, ax, x) + else: + raise NotImplementedError(f"Unsupported statement: {statement}") + + +def _mpl_draw_gate( + gate: ast.QuantumGate, args: list[Any], lines: list[int], ax: plt.Axes, x: float +): + name = gate.name.name + if name in REV_CTRL_GATE_MAP: + i = 0 + while name in REV_CTRL_GATE_MAP: + name = REV_CTRL_GATE_MAP[name] + _draw_mpl_control(lines[i], lines[-1], ax, x) + i += 1 + lines = lines[i:] + gate.name.name = name + + if name in ONE_QUBIT_OP_MAP or name in ONE_QUBIT_ROTATION_MAP: + _draw_mpl_one_qubit_gate(gate, args, lines[0], ax, x) + elif name in TWO_QUBIT_OP_MAP: + if name == "swap": + _draw_mpl_swap(lines[0], lines[1], ax, x) + else: + raise NotImplementedError(f"Unsupported gate: {name}") + else: + raise NotImplementedError(f"Unsupported gate: {name}") + + +# TODO: switch to moment based system. go progressively, calculating required width for each moment, center the rest. this makes position calculations not to bad. if we overflow, start a new figure. + + +def _draw_mpl_one_qubit_gate( + gate: ast.QuantumGate, args: list[Any], line: int, ax: plt.Axes, x: float +): + color = DEFAULT_GATE_COLOR + if gate.name.name == "h": + color = HADAMARD_GATE_COLOR + text = gate.name.name.upper() + + y = _mpl_line_to_y(line) + rect = plt.Rectangle( + (x - GATE_BOX_WIDTH / 2, y - GATE_BOX_HEIGHT / 2), + GATE_BOX_WIDTH, + GATE_BOX_HEIGHT, + facecolor=color, + edgecolor="none", + ) + ax.add_patch(rect) + + if len(args) > 0: + args_text = f"{', '.join([f'{a:.3f}' if isinstance(a, float) else str(a) for a in args])}" + ax.text(x, y + GATE_BOX_HEIGHT / 8, text, ha="center", va="center", fontsize=12) + ax.text(x, y - GATE_BOX_HEIGHT / 4, args_text, ha="center", va="center", fontsize=8) + else: + ax.text(x, y, text, ha="center", va="center", fontsize=12) + + +def _draw_mpl_control(ctrl_line: int, target_line: int, ax: plt.Axes, x: float): + y1 = _mpl_line_to_y(ctrl_line) + y2 = _mpl_line_to_y(target_line) + ax.vlines(x=x, ymin=min(y1, y2), ymax=max(y1, y2), color="black", linestyle="-", zorder=-1) + ax.plot(x, y1, "ko", markersize=8, markerfacecolor="black") + + +def _draw_mpl_swap(line1: int, line2: int, ax: plt.Axes, x: float): + y1 = _mpl_line_to_y(line1) + y2 = _mpl_line_to_y(line2) + ax.vlines(x=x, ymin=min(y1, y2), ymax=max(y1, y2), color="black", linestyle="-") + ax.plot(x, y1, "x", markersize=8, color="black") + ax.plot(x, y2, "x", markersize=8, color="black") + + +def _mpl_draw_measurement(qbit_line: int, cbit_line: int, idx: int, ax: plt.Axes, x: float): + y1 = _mpl_line_to_y(qbit_line) + y2 = _mpl_line_to_y(cbit_line) + + color = "#A0A0A0" + gap = GATE_BOX_WIDTH / 3 + rect = plt.Rectangle( + (x - GATE_BOX_WIDTH / 2, y1 - GATE_BOX_HEIGHT / 2), + GATE_BOX_WIDTH, + GATE_BOX_HEIGHT, + facecolor=color, + edgecolor="none", + ) + ax.add_patch(rect) + ax.text(x, y1, "M", ha="center", va="center") + ax.vlines( + x=x - gap / 10, + ymin=min(y1, y2) + gap, + ymax=max(y1, y2), + color=color, + linestyle="-", + zorder=-1, + ) + ax.vlines( + x=x + gap / 10, + ymin=min(y1, y2) + gap, + ymax=max(y1, y2), + color=color, + linestyle="-", + zorder=-1, + ) + ax.plot(x, y2 + gap, "v", markersize=12, color=color) + ax.text(x + gap, y2 + gap, str(idx), color=color, ha="left", va="bottom", fontsize=8) + + +def _mpl_draw_barrier(lines: list[int], ax: plt.Axes, x: float): + for line in lines: + y = _mpl_line_to_y(line) + ax.vlines( + x=x, + ymin=y - GATE_BOX_HEIGHT / 2 - LINE_SPACING / 2, + ymax=y + GATE_BOX_HEIGHT / 2 + LINE_SPACING / 2, + color="black", + linestyle="--", + ) + rect = plt.Rectangle( + (x - GATE_BOX_WIDTH / 4, y - GATE_BOX_HEIGHT / 2 - LINE_SPACING / 2), + GATE_BOX_WIDTH / 2, + GATE_BOX_HEIGHT + LINE_SPACING, + facecolor="lightgray", + edgecolor="none", + alpha=0.5, + zorder=-1, + ) + ax.add_patch(rect) \ No newline at end of file diff --git a/tests/qasm3/test_printer.py b/tests/qasm3/test_printer.py new file mode 100644 index 0000000..198b45e --- /dev/null +++ b/tests/qasm3/test_printer.py @@ -0,0 +1,64 @@ +# Copyright (C) 2025 qBraid# +# This file is part of pyqasm +# +# Pyqasm is free software released under the GNU General Public License v3 +# or later. You can redistribute and/or modify it under the terms of the GPL v3. +# See the LICENSE file in the project root or . +# +# THERE IS NO WARRANTY for pyqasm, as per Section 15 of the GPL v3. + +import random + +import pytest +from qbraid import random_circuit, transpile + +from pyqasm.entrypoint import loads + + +def _check_fig(circ, fig): + ax = fig.gca() + # plt.savefig("test.png") + assert len(ax.texts) > 0 + # assert False + + +def test_simple(): + qasm = """OPENQASM 3.0; + include "stdgates.inc"; + qubit[3] q; + bit[3] b; + h q[0]; + z q[1]; + rz(pi/1.1) q[0]; + cx q[0], q[1]; + swap q[0], q[1]; + ccx q[0], q[1], q[2]; + b = measure q; + """ + circ = loads(qasm) + fig = circ.draw() + _check_fig(circ, fig) + + +def test_custom_gate(): + qasm = """OPENQASM 3.0; + include "stdgates.inc"; + qubit q; + gate custom a { + h a; + z a; + } + custom q; + """ + circ = loads(qasm) + fig = circ.draw() + _check_fig(circ, fig) + + +@pytest.mark.parametrize("_", range(100)) +def test_random(_): + circ = random_circuit("qiskit", measure=random.choice([True, False])) + qasm_str = transpile(circ, random.choice(["qasm2", "qasm3"])) + module = loads(qasm_str) + fig = module.draw() + _check_fig(circ, fig) \ No newline at end of file