Source code for mpqp.core.instruction.gates.native_gates

"""Native gates is the set of all gates natively supported in OpenQASM. Since we
rely on this standard, all of them are indeed implemented. In addition, this
module contains a few abstract classes used to factorize the behaviors common to
a lot of gates.

You will find bellow the list of available native gates:

.. container::
    :name: native-gates-list
    
    ``to-be-generated``
"""

from __future__ import annotations

import inspect
import sys
from abc import abstractmethod
from numbers import Integral
from typing import TYPE_CHECKING, Optional

if TYPE_CHECKING:
    from sympy import Expr
    from qiskit.circuit import Parameter

import numpy as np
import numpy.typing as npt

# pylance doesn't handle well Expr, so a lot of "type:ignore" will happen in
# this file :/
from typeguard import typechecked

from mpqp.core.instruction.gates.controlled_gate import ControlledGate
from mpqp.core.instruction.gates.gate import Gate, InvolutionGate, SingleQubitGate
from mpqp.core.instruction.gates.gate_definition import UnitaryMatrix
from mpqp.core.instruction.gates.parametrized_gate import ParametrizedGate
from mpqp.core.languages import Language
from mpqp.tools.generics import Matrix, SimpleClassReprABC, classproperty
from mpqp.tools.maths import cos, exp, sin

# from sympy import Expr, pi


@typechecked
def _qiskit_parameter_adder(
    param: Expr | float, qiskit_parameters: set["Parameter"]
) -> "Parameter | float | int":
    """To avoid having several parameters in qiskit for the same value we keep
    track of them in a set. This function takes care of this, this way you can
    directly call `QiskitGate(_qiskit_parameter_adder(<param>, <q_params_set>))`
    without having to manually take care of the de-duping.

    This process is a form of memoization.

    Args:
        param: The parameter you need for your qiskit gate.
        qiskit_parameters: The set of previously set qiskit parameters. This set
        is updated inplace.

    Returns:
        The memoized parameter
    """
    from sympy import Expr

    if isinstance(param, Expr):
        name = str(param)
        previously_set_param = list(
            filter(lambda elt: elt.name == name, qiskit_parameters)
        )
        if len(previously_set_param) > 1:
            raise ReferenceError(
                "Somehow two parameter got the same name, this shouldn't be "
                "possible. For help on this error please contact the authors of"
                " this library"
            )
        elif len(previously_set_param) == 1:
            qiskit_param = previously_set_param[0]
        else:
            from qiskit.circuit import Parameter

            qiskit_param = Parameter(name)
            qiskit_parameters.add(qiskit_param)
    else:
        qiskit_param = param
    return qiskit_param


[docs]@typechecked class NativeGate(Gate, SimpleClassReprABC): """The standard on which we rely, OpenQASM, comes with a set of gates supported by default. More complicated gates can be defined by the user. This abstract class represent all those gates supported by default. Args: targets: List of indices referring to the qubits on which the gate will be applied. label: Label used to identify the gate. """ qlm_aqasm_keyword: str """Keyword(s) corresponding to the gate in ``myQLM``. This needs to be available at the class level and is not enforced by the type checker so be careful about it!""" qiskit_string: str """Keyword corresponding to the gate in ``qiskit``. This needs to be available at the class level and is not enforced by the type checker so be careful about it!""" @classproperty def qasm2_gate(cls) -> str: """Keyword(s) corresponding to the gate in ``QASM2``.""" return cls.qiskit_string native_gate_options = {"disable_symbol_warn": True} if TYPE_CHECKING: from braket.circuits import gates from qiskit.circuit.library import ( CCXGate, CPhaseGate, CXGate, CZGate, HGate, IGate, PhaseGate, RXGate, RYGate, RZGate, SGate, SwapGate, TGate, XGate, YGate, ZGate, ) @classproperty @abstractmethod def qiskit_gate( cls, ) -> type[ XGate | YGate | ZGate | HGate | TGate | SGate | SwapGate | CXGate | CZGate | CCXGate | IGate | RXGate | RYGate | RZGate | PhaseGate | CPhaseGate ]: pass @classproperty @abstractmethod def braket_gate( cls, ) -> type[ gates.X | gates.Y | gates.Z | gates.H | gates.T | gates.S | gates.Swap | gates.CNot | gates.CZ | gates.CCNot | gates.I | gates.Rx | gates.Ry | gates.Rz | gates.PhaseShift | gates.CPhaseShift ]: pass
[docs]@typechecked class RotationGate(NativeGate, ParametrizedGate, SimpleClassReprABC): """Many gates can be classified as a simple rotation gate, around a specific axis (and potentially with a control qubit). All those gates have in common a single parameter: ``theta``. This abstract class helps up factorize this behavior, and simply having to tweak the matrix semantics and qasm translation of the specific gate. Args: theta: Angle of the rotation. target: Index referring to the qubits on which the gate will be applied. """ def __init__(self, theta: Expr | float, target: int): self.parameters = [theta] definition = UnitaryMatrix( self.to_canonical_matrix(), **self.native_gate_options ) ParametrizedGate.__init__( self, definition, [target], [self.theta], type(self).__name__.capitalize() ) @property def theta(self): """Rotation angle (in radians).""" return self.parameters[0] def __repr__(self): return f"{type(self).__name__}({self.theta}, {self.targets[0]})"
[docs] def to_other_language( self, language: Language = Language.QISKIT, qiskit_parameters: Optional[set["Parameter"]] = None, ): if qiskit_parameters is None: qiskit_parameters = set() try: theta = float(self.theta) except: theta = self.theta if language == Language.QISKIT: return self.qiskit_gate(_qiskit_parameter_adder(theta, qiskit_parameters)) elif language == Language.BRAKET: from sympy import Expr # TODO: handle symbolic parameters for Braket if isinstance(theta, Expr): raise NotImplementedError( "Symbolic expressions are not yet supported for braket " "export, this feature is coming very soon!" ) return self.braket_gate(theta) if language == Language.QASM2: from mpqp.qasm.mpqp_to_qasm import float_to_qasm_str instruction_str = self.qasm2_gate instruction_str += ( "(" + ",".join(float_to_qasm_str(float(param)) for param in self.parameters) + ")" ) qubits = "" if isinstance(self, ControlledGate): qubits = ",".join([f"q[{j}]" for j in self.controls]) + "," qubits += ",".join([f"q[{j}]" for j in self.targets]) return instruction_str + " " + qubits + ";" else: raise NotImplementedError(f"Error: {language} is not supported")
[docs] def inverse(self) -> Gate: return self.__class__(-self.parameters[0], self.targets[0])
[docs]@typechecked class NoParameterGate(NativeGate, SimpleClassReprABC): """Abstract class describing native gates that do not depend on parameters. Args: targets: List of indices referring to the qubits on which the gate will be applied. label: Label used to identify the gate. """ qlm_aqasm_keyword: str if TYPE_CHECKING: from braket.circuits import gates from qiskit.circuit.library import ( CCXGate, CXGate, CZGate, HGate, IGate, SGate, SwapGate, TGate, XGate, YGate, ZGate, ) @classproperty @abstractmethod def qiskit_gate( cls, ) -> type[ XGate | YGate | ZGate | HGate | TGate | SGate | SwapGate | CXGate | CZGate | CCXGate | IGate ]: """Returns the corresponding ``qiskit`` class for this gate.""" pass @classproperty @abstractmethod def braket_gate( cls, ) -> type[ gates.X | gates.Y | gates.Z | gates.H | gates.T | gates.S | gates.Swap | gates.CNot | gates.CZ | gates.CCNot | gates.I ]: """Returns the corresponding ``braket`` class for this gate.""" pass """Corresponding ``qiskit``'s gate class.""" matrix: npt.NDArray[np.complex64] """Matricial semantics of the gate."""
[docs] def to_other_language( self, language: Language = Language.QISKIT, qiskit_parameters: Optional[set["Parameter"]] = None, ): if language == Language.QISKIT: return self.qiskit_gate() elif language == Language.BRAKET: return self.braket_gate() elif language == Language.QASM2: instruction_str = self.qasm2_gate qubits = "" if isinstance(self, ControlledGate): qubits = ",".join([f"q[{j}]" for j in self.controls]) + "," qubits += ",".join([f"q[{j}]" for j in self.targets]) return instruction_str + " " + qubits + ";" else: raise NotImplementedError(f"Error: {language} is not supported")
[docs] def to_canonical_matrix(self) -> Matrix: return self.matrix
[docs]@typechecked class OneQubitNoParamGate(SingleQubitGate, NoParameterGate, SimpleClassReprABC): """Abstract Class describing one-qubit native gates that do not depend on parameters. Args: target: Index referring to the qubits on which the gate will be applied. """ def __init__(self, target: int): SingleQubitGate.__init__(self, target, type(self).__name__)
[docs]class Id(OneQubitNoParamGate, InvolutionGate): r"""One qubit identity gate. `\begin{pmatrix}1&0\\0&1\end{pmatrix}` Args: target: Index referring to the qubit on which the gate will be applied. Example: >>> pprint(Id(0).to_matrix()) [[1, 0], [0, 1]] """ @classproperty def braket_gate(cls): from braket.circuits import gates return gates.I @classproperty def qiskit_gate(cls): from qiskit.circuit.library import IGate return IGate qlm_aqasm_keyword = "I" qiskit_string = "id" def __init__(self, target: int, label: Optional[str] = None): super().__init__(target) self.label = label self.matrix = np.eye(2, dtype=np.complex64)
[docs] def to_other_language( self, language: Language = Language.QISKIT, qiskit_parameters: Optional[set["Parameter"]] = None, ): if language == Language.QISKIT: if self.label: return self.qiskit_gate(label=self.label) return self.qiskit_gate() elif language == Language.BRAKET: return self.braket_gate() elif language == Language.QASM2: instruction_str = self.qasm2_gate qubits = ",".join([f"q[{j}]" for j in self.targets]) return instruction_str + " " + qubits + ";" else: raise NotImplementedError(f"Error: {language} is not supported")
[docs]class X(OneQubitNoParamGate, InvolutionGate): r"""One qubit X (NOT) Pauli gate. `\begin{pmatrix}0&1\\1&0\end{pmatrix}` Args: target: Index referring to the qubit on which the gate will be applied. Example: >>> pprint(X(0).to_matrix()) [[0, 1], [1, 0]] """ @classproperty def braket_gate(cls): from braket.circuits import gates return gates.X @classproperty def qiskit_gate(cls): from qiskit.circuit.library import XGate return XGate qlm_aqasm_keyword = "X" qiskit_string = "x" def __init__(self, target: int): super().__init__(target) self.matrix = np.array([[0, 1], [1, 0]])
[docs]class Y(OneQubitNoParamGate, InvolutionGate): r"""One qubit Y Pauli gate. `\begin{pmatrix}0&-i\\i&0\end{pmatrix}` Args: target: Index referring to the qubit on which the gate will be applied. Example: >>> pprint(Y(0).to_matrix()) [[0 , -1j], [1j, 0 ]] """ @classproperty def braket_gate(cls): from braket.circuits import gates return gates.Y @classproperty def qiskit_gate(cls): from qiskit.circuit.library import YGate return YGate qlm_aqasm_keyword = "Y" qiskit_string = "y" def __init__(self, target: int): super().__init__(target) self.matrix = np.array([[0, -1j], [1j, 0]])
[docs]class Z(OneQubitNoParamGate, InvolutionGate): r"""One qubit Z Pauli gate. `\begin{pmatrix}1&0\\0&-1\end{pmatrix}` Args: target: Index referring to the qubit on which the gate will be applied. Example: >>> pprint(Z(0).to_matrix()) [[1, 0 ], [0, -1]] """ @classproperty def braket_gate(cls): from braket.circuits import gates return gates.Z @classproperty def qiskit_gate(cls): from qiskit.circuit.library import ZGate return ZGate qlm_aqasm_keyword = "Z" qiskit_string = "z" def __init__(self, target: int): super().__init__(target) self.matrix = np.array([[1, 0], [0, -1]])
[docs]class H(OneQubitNoParamGate, InvolutionGate): r"""One qubit Hadamard gate. `\frac{1}{\sqrt{2}}\begin{pmatrix}1&1\\1&-1\end{pmatrix}` Args: target: Index referring to the qubit on which the gate will be applied. Example: >>> pprint(H(0).to_matrix()) [[0.70711, 0.70711 ], [0.70711, -0.70711]] """ @classproperty def braket_gate(cls): from braket.circuits import gates return gates.H @classproperty def qiskit_gate(cls): from qiskit.circuit.library import HGate return HGate qlm_aqasm_keyword = "H" qiskit_string = "h" def __init__(self, target: int): super().__init__(target) self.matrix = np.array([[1, 1], [1, -1]]) / np.sqrt(2)
[docs]class P(RotationGate, SingleQubitGate): r"""One qubit parametrized Phase gate. Consist in a rotation around Z axis. `\begin{pmatrix}1&0\\0&e^{i\theta}\end{pmatrix}` Args: theta: Parameter representing the phase to apply. target: Index referring to the qubit on which the gate will be applied. Example: >>> pprint(P(np.pi/3, 1).to_matrix()) [[1, 0 ], [0, 0.5+0.86603j]] """ @classproperty def braket_gate(cls): from braket.circuits import gates return gates.PhaseShift @classproperty def qiskit_gate(cls): from qiskit.circuit.library import PhaseGate return PhaseGate qlm_aqasm_keyword = "PH" qiskit_string = "p" def __init__(self, theta: Expr | float, target: int): super().__init__(theta, target)
[docs] def to_canonical_matrix(self) -> Matrix: return np.array( # pyright: ignore[reportCallIssue] [ [1, 0], [ 0, exp( self.parameters[0] * 1j # pyright: ignore[reportOperatorIssue] ), ], ] )
[docs]class CP(RotationGate, ControlledGate): """Two-qubit Controlled-P gate. Args: theta: Parameter representing the phase to apply. control: Index referring to the qubit used to control the gate. target: Index referring to the qubit on which the gate will be applied. Example: >>> pprint(CP(0.5, 0, 1).to_matrix()) [[1, 0, 0, 0 ], [0, 1, 0, 0 ], [0, 0, 1, 0 ], [0, 0, 0, 0.87758+0.47943j]] """ @classproperty def braket_gate(cls): from braket.circuits import gates return gates.CPhaseShift @classproperty def qiskit_gate(cls): from qiskit.circuit.library import CPhaseGate return CPhaseGate # TODO: this is a special case, see if it needs to be generalized qlm_aqasm_keyword = "CNOT;PH" qiskit_string = "cp" def __init__(self, theta: Expr | float, control: int, target: int): self.parameters = [theta] ControlledGate.__init__(self, [control], [target], P(theta, target), "CP") definition = UnitaryMatrix( self.to_canonical_matrix(), **self.native_gate_options ) ParametrizedGate.__init__(self, definition, [target], [theta], "CP")
[docs] def to_canonical_matrix(self): e = exp(self.theta * 1j) # pyright: ignore[reportOperatorIssue] return np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, e]])
def __repr__(self) -> str: theta = int(self.theta) if self.theta == int(self.theta) else self.theta return f"{type(self).__name__}({theta}, {self.controls[0]}, {self.targets[0]})"
[docs] def inverse(self) -> Gate: return self.__class__(-self.parameters[0], self.controls[0], self.targets[0])
nb_qubits = ( # pyright: ignore[reportAssignmentType,reportIncompatibleMethodOverride] 2 )
[docs]class S(OneQubitNoParamGate): r"""One qubit S gate. It's equivalent to ``P(pi/2)``. It can also be defined as the square-root of the Z (Pauli) gate. `\begin{pmatrix}1&0\\0&i\end{pmatrix}` Args: target: Index referring to the qubit on which the gate will be applied. Example: >>> pprint(S(0).to_matrix()) [[1, 0 ], [0, 1j]] """ @classproperty def braket_gate(cls): from braket.circuits import gates return gates.S @classproperty def qiskit_gate(cls): from qiskit.circuit.library import SGate return SGate qlm_aqasm_keyword = "S" qiskit_string = "s" def __init__(self, target: int): super().__init__(target) self.matrix = np.array([[1, 0], [0, 1j]])
[docs]class T(OneQubitNoParamGate): r"""One qubit T gate. It is also referred to as the `\pi/4` gate because it consists in applying the phase gate with a phase of `\pi/4`. `\begin{pmatrix}1&0\\0&e^{i\pi/4}\end{pmatrix}` The T gate can also be defined as the fourth-root of the Z (Pauli) gate. Args: target: Index referring to the qubit on which the gate will be applied. Example: >>> pprint(T(0).to_matrix()) [[1, 0 ], [0, 1.0*exp(0.25*I*pi)]] """ @classproperty def braket_gate(cls): from braket.circuits import gates return gates.T @classproperty def qiskit_gate(cls): from qiskit.circuit.library import TGate return TGate qlm_aqasm_keyword = "T" qiskit_string = "t" def __init__(self, target: int): super().__init__(target)
[docs] def to_canonical_matrix(self): from sympy import pi return np.array([[1, 0], [0, exp((pi / 4) * 1j)]])
[docs]class SWAP(InvolutionGate, NoParameterGate): r"""Two-qubit SWAP gate. `\begin{pmatrix}1&0&0&0\\0&0&1&0\\0&1&0&0\\0&0&0&1\end{pmatrix}` Args: a: First target of the swapping operation. b: Second target of the swapping operation. Example: >>> pprint(SWAP(0, 1).to_matrix()) [[1, 0, 0, 0], [0, 0, 1, 0], [0, 1, 0, 0], [0, 0, 0, 1]] """ @classproperty def braket_gate(cls): from braket.circuits import gates return gates.Swap @classproperty def qiskit_gate(cls): from qiskit.circuit.library import SwapGate return SwapGate qlm_aqasm_keyword = "SWAP" qiskit_string = "swap" def __init__(self, a: int, b: int): super().__init__([a, b], "SWAP") self.matrix = np.array([[1, 0, 0, 0], [0, 0, 1, 0], [0, 1, 0, 0], [0, 0, 0, 1]]) def __repr__(self) -> str: return f"{type(self).__name__}({self.targets[0]}, {self.targets[1]})" nb_qubits = ( # pyright: ignore[reportAssignmentType,reportIncompatibleMethodOverride] 2 ) """Size of the gate."""
[docs] def to_matrix(self, desired_gate_size: int = 0) -> npt.NDArray[np.complex64]: """Constructs the matrix representation of a SWAP gate for two qubits. Args: nb_qubits: The total number for qubits gate representation. If not provided, the minimum number of qubits required to generate the matrix will be used. Returns: The matrix representation of the SWAP gate. """ control, target = self.targets[0], self.targets[1] max_qubits = max(control, target) + 1 if desired_gate_size != 0 and desired_gate_size < max_qubits: raise ValueError( f"The number of qubits in the system must be at least {max_qubits}." ) nb_qubits_swap = abs(control - target) + 1 min_nb_qubits = min(control, target) swap_matrix = np.eye(2**nb_qubits_swap, dtype=np.complex64) for i in range(2**nb_qubits_swap): binary_state = list(format(i, f"0{nb_qubits_swap}b")) ( binary_state[nb_qubits_swap - control + min_nb_qubits - 1], binary_state[nb_qubits_swap - target + min_nb_qubits - 1], ) = ( binary_state[nb_qubits_swap - target + min_nb_qubits - 1], binary_state[nb_qubits_swap - control + min_nb_qubits - 1], ) swapped_index = int("".join(binary_state), 2) swap_matrix[i, i] = 0 swap_matrix[swapped_index, i] = 1 if desired_gate_size != 0: swap_matrix = np.kron(np.eye(2**min_nb_qubits), swap_matrix) swap_matrix = np.kron( swap_matrix, np.eye(2 ** (desired_gate_size - max_qubits)) ) return swap_matrix
[docs]class U(NativeGate, ParametrizedGate, SingleQubitGate): r"""Generic one qubit unitary gate. It is parametrized by 3 Euler angles. `\begin{pmatrix}\cos(\theta/2)&-e^{i\gamma}\sin(\theta/2)\\e^{i\phi}\sin(\theta/2)&e^{i(\gamma+\phi)}\cos(\theta/2)\end{pmatrix}` Args: theta: Parameter representing the first angle of the gate U. phi: Parameter representing the second angle of the gate U. gamma: Parameter representing the third angle of the gate U. target: Index referring to the qubit on which the gate will be applied. Example: >>> pprint(U(np.pi/3, 0, np.pi/4, 0).to_matrix()) [[0.86603, -0.35355-0.35355j], [0.5 , 0.61237+0.61237j ]] """ @classproperty def braket_gate(cls): from braket.circuits.gates import U as braket_U return braket_U @classproperty def qiskit_gate(cls): from qiskit.circuit.library import UGate return UGate qlm_aqasm_keyword = "U" qiskit_string = "u" def __init__( self, theta: Expr | float, phi: Expr | float, gamma: Expr | float, target: int, ): self.parameters = [theta, phi, gamma] definition = UnitaryMatrix( self.to_canonical_matrix(), **self.native_gate_options ) ParametrizedGate.__init__(self, definition, [target], [theta, phi, gamma], "U") @property def theta(self): """See corresponding argument.""" return self.parameters[0] @property def phi(self): """See corresponding argument.""" return self.parameters[1] @property def gamma(self): """See corresponding argument.""" return self.parameters[2]
[docs] def to_other_language( self, language: Language = Language.QISKIT, qiskit_parameters: Optional[set["Parameter"]] = None, ): if language == Language.QISKIT: if qiskit_parameters is None: qiskit_parameters = set() return self.qiskit_gate( theta=_qiskit_parameter_adder(self.theta, qiskit_parameters), phi=_qiskit_parameter_adder(self.phi, qiskit_parameters), lam=_qiskit_parameter_adder(self.gamma, qiskit_parameters), ) elif language == Language.BRAKET: from sympy import Expr # TODO handle symbolic parameters if ( isinstance(self.theta, Expr) or isinstance(self.phi, Expr) or isinstance(self.gamma, Expr) ): raise NotImplementedError( "Symbolic expressions are not yet supported for braket " "export, this feature is coming very soon!" ) return self.braket_gate(self.theta, self.phi, self.gamma) if language == Language.QASM2: from mpqp.qasm.mpqp_to_qasm import float_to_qasm_str instruction_str = self.qasm2_gate instruction_str += ( "(" + ",".join(float_to_qasm_str(float(param)) for param in self.parameters) + ")" ) qubits = ",".join([f"q[{j}]" for j in self.targets]) return instruction_str + " " + qubits + ";" else: raise NotImplementedError(f"Error: {language} is not supported")
[docs] def to_canonical_matrix(self): c, s, eg, ep = ( cos(self.theta / 2), # pyright: ignore[reportOperatorIssue] sin(self.theta / 2), # pyright: ignore[reportOperatorIssue] exp(self.gamma * 1j), # pyright: ignore[reportOperatorIssue] exp(self.phi * 1j), # pyright: ignore[reportOperatorIssue] ) return np.array( # pyright: ignore[reportCallIssue] [ [c, -eg * s], # pyright: ignore[reportOperatorIssue] [ep * s, eg * ep * c], # pyright: ignore[reportOperatorIssue] ] )
def __repr__(self) -> str: return f"{type(self).__name__}({self.theta}, {self.phi}, {self.gamma}, {self.targets[0]})"
[docs]class Rx(RotationGate, SingleQubitGate): r"""One qubit rotation around the X axis. `\begin{pmatrix}\cos(\theta/2)&-i\sin(\theta/2)\\-i\sin(\theta/2)&\cos(\theta/2)\end{pmatrix}` Args: theta: Parameter representing the angle of the gate. target: Index referring to the qubit on which the gate will be applied. Example: >>> pprint(Rx(np.pi/5, 1).to_matrix()) [[0.95106 , -0.30902j], [-0.30902j, 0.95106 ]] """ @classproperty def braket_gate(cls): from braket.circuits import gates return gates.Rx @classproperty def qiskit_gate(cls): from qiskit.circuit.library import RXGate return RXGate qlm_aqasm_keyword = "RX" qiskit_string = "rx" def __init__(self, theta: Expr | float, target: int): super().__init__(theta, target)
[docs] def to_canonical_matrix(self): c = cos(self.parameters[0] / 2) # pyright: ignore[reportOperatorIssue] s = sin(self.parameters[0] / 2) # pyright: ignore[reportOperatorIssue] return np.array( # pyright: ignore[reportCallIssue] [[c, -1j * s], [-1j * s, c]] # pyright: ignore[reportOperatorIssue] )
[docs]class Ry(RotationGate, SingleQubitGate): r"""One qubit rotation around the Y axis. `\begin{pmatrix}\cos(\theta/2)&-\sin(\theta/2)\\\sin(\theta/2)&\cos(\theta/2)\end{pmatrix}` Args: theta: Parameter representing the angle of the gate. target: Index referring to the qubit on which the gate will be applied. Example: >>> pprint(Ry(np.pi/5, 1).to_matrix()) [[0.95106, -0.30902], [0.30902, 0.95106 ]] """ @classproperty def braket_gate(cls): from braket.circuits import gates return gates.Ry @classproperty def qiskit_gate(cls): from qiskit.circuit.library import RYGate return RYGate qlm_aqasm_keyword = "RY" qiskit_string = "ry" def __init__(self, theta: Expr | float, target: int): super().__init__(theta, target)
[docs] def to_canonical_matrix(self): c = cos(self.parameters[0] / 2) # pyright: ignore[reportOperatorIssue] s = sin(self.parameters[0] / 2) # pyright: ignore[reportOperatorIssue] return np.array([[c, -s], [s, c]])
[docs]class Rz(RotationGate, SingleQubitGate): r"""One qubit rotation around the Z axis. `\begin{pmatrix}e^{i\theta/2}&0\\0&e^{-i\theta/2}\end{pmatrix}` Args: theta: Parameter representing the angle of the gate. target: Index referring to the qubit on which the gate will be applied. Example: >>> pprint(Rz(np.pi/5, 1).to_matrix()) [[0.95106-0.30902j, 0 ], [0 , 0.95106+0.30902j]] """ @classproperty def braket_gate(cls): from braket.circuits import gates return gates.Rz @classproperty def qiskit_gate(cls): from qiskit.circuit.library import RZGate return RZGate qlm_aqasm_keyword = "RZ" qiskit_string = "rz" def __init__(self, theta: Expr | float, target: int): super().__init__(theta, target)
[docs] def to_canonical_matrix(self): e = exp(-1j * self.parameters[0] / 2) # pyright: ignore[reportOperatorIssue] return np.array( # pyright: ignore[reportCallIssue] [[e, 0], [0, 1 / e]] # pyright: ignore[reportOperatorIssue] )
[docs]class Rk(RotationGate, SingleQubitGate): r"""One qubit Phase gate of angle `\frac{2i\pi}{2^k}`. `\begin{pmatrix}1&0\\0&e^{i\pi/2^{k-1}}\end{pmatrix}` Args: k: Parameter used in the definition of the phase to apply. target: Index referring to the qubit on which the gate will be applied. Examples: >>> pprint(Rk(5, 0).to_matrix()) [[1, 0 ], [0, 0.98079+0.19509j]] >>> pprint(Rk(k, 0).to_matrix()) [[1, 0 ], [0, 1.0*exp(2.0*I*pi/2**k)]] """ @classproperty def braket_gate(cls): from braket.circuits import gates return gates.PhaseShift @classproperty def qiskit_gate(cls): from qiskit.circuit.library import PhaseGate return PhaseGate qlm_aqasm_keyword = "PH" qiskit_string = "p" def __init__(self, k: Expr | int, target: int): self.parameters = [k] definition = UnitaryMatrix( self.to_canonical_matrix(), **self.native_gate_options ) ParametrizedGate.__init__(self, definition, [target], [self.k], "Rk") @property def theta(self) -> Expr | float: r"""Value of the rotation angle, parametrized by ``k`` with the relation `\theta = \frac{\pi}{2^{k-1}}`.""" from sympy import pi p = np.pi if isinstance(self.k, Integral) else pi return p / 2 ** (self.k - 1) # pyright: ignore[reportOperatorIssue] @property def k(self) -> Expr | int: """See corresponding argument.""" return self.parameters[0]
[docs] def to_canonical_matrix(self): e = exp(self.theta * 1j) # pyright: ignore[reportOperatorIssue] return np.array([[1, 0], [0, e]])
def __repr__(self): return f"{type(self).__name__}({self.k}, {self.targets[0]})"
[docs] def inverse(self) -> Gate: return Rk_dagger(self.k, self.targets[0])
[docs] def to_other_language( self, language: Language = Language.QISKIT, qiskit_parameters: Optional[set["Parameter"]] = None, ): if language == Language.QASM2: from mpqp.qasm.mpqp_to_qasm import float_to_qasm_str instruction_str = self.qasm2_gate instruction_str += ( f"({float_to_qasm_str(2 * np.pi / (2 ** float(self.k)))})" ) qubits = ",".join([f"q[{j}]" for j in self.targets]) return instruction_str + " " + qubits + ";" else: return super().to_other_language(language, qiskit_parameters)
[docs]class Rk_dagger(RotationGate, SingleQubitGate): r"""One qubit Phase gate of angle `-\frac{2i\pi}{2^k}`. `\begin{pmatrix}1&0\\0&e^{-i\pi/2^{k-1}}\end{pmatrix}` Args: k: Parameter used in the definition of the phase to apply. target: Index referring to the qubit on which the gate will be applied. Example: >>> pprint(Rk_dagger(5, 0).to_matrix()) [[1, 0 ], [0, 0.98079-0.19509j]] """ @classproperty def braket_gate(cls): from braket.circuits import gates return gates.PhaseShift @classproperty def qiskit_gate(cls): from qiskit.circuit.library import PhaseGate return PhaseGate qlm_aqasm_keyword = "PH" qiskit_string = "p" def __init__(self, k: Expr | int, target: int): self.parameters = [k] definition = UnitaryMatrix( self.to_canonical_matrix(), **self.native_gate_options ) ParametrizedGate.__init__(self, definition, [target], [self.k], "Rk†") @property def theta(self) -> Expr | float: r"""Value of the rotation angle, parametrized by ``k`` with the relation `\theta = -\frac{\pi}{2^{k-1}}`.""" from sympy import pi # TODO study the relevance of having pi from sympy p = np.pi if isinstance(self.k, Integral) else pi return -(p / 2 ** (self.k - 1)) # pyright: ignore[reportOperatorIssue] @property def k(self) -> Expr | float: """See corresponding argument.""" return self.parameters[0]
[docs] def to_canonical_matrix(self): e = exp(self.theta * 1j) # pyright: ignore[reportOperatorIssue] return np.array([[1, 0], [0, e]])
[docs] def to_other_language( self, language: Language = Language.QISKIT, qiskit_parameters: Optional[set["Parameter"]] = None, ): if language == Language.QASM2: from mpqp.qasm.mpqp_to_qasm import float_to_qasm_str instruction_str = self.qasm2_gate instruction_str += ( f"({float_to_qasm_str(-2 * np.pi / (2 ** float(self.k)))})" ) qubits = ",".join([f"q[{j}]" for j in self.targets]) return instruction_str + " " + qubits + ";" else: return super().to_other_language(language, qiskit_parameters)
def __repr__(self): return f"{type(self).__name__}({self.k}, {self.targets[0]})"
[docs] def inverse(self) -> Gate: return Rk(self.parameters[0], self.targets[0])
[docs]class CNOT(InvolutionGate, ControlledGate, NoParameterGate): r"""Two-qubit Controlled-NOT gate. `\begin{pmatrix}1&0&0&0\\0&1&0&0\\0&0&0&1\\0&0&1&0\end{pmatrix}` Args: control: index referring to the qubit used to control the gate target: index referring to the qubit on which the gate will be applied Example: >>> pprint(CNOT(0, 1).to_matrix()) [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]] """ @classproperty def braket_gate(cls): from braket.circuits import gates return gates.CNot @classproperty def qiskit_gate(cls): from qiskit.circuit.library import CXGate return CXGate qlm_aqasm_keyword = "CNOT" qiskit_string = "cx" def __init__(self, control: int, target: int): ControlledGate.__init__(self, [control], [target], X(target), "CNOT")
[docs] def to_canonical_matrix(self): return np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]])
nb_qubits = ( # pyright: ignore[reportAssignmentType,reportIncompatibleMethodOverride] 2 ) """Size of the gate."""
[docs]class CZ(InvolutionGate, ControlledGate, NoParameterGate): r"""Two-qubit Controlled-Z gate. `\begin{pmatrix}1&0&0&0\\0&1&0&0\\0&0&1&0\\0&0&0&-1\end{pmatrix}` Args: k: Parameter used in the definition of the phase to apply. control: Index referring to the qubit used to control the gate. target: Index referring to the qubit on which the gate will be applied. Examples: >>> pprint(CZ(0, 1).to_matrix()) [[1, 0, 0, 0 ], [0, 1, 0, 0 ], [0, 0, 1, 0 ], [0, 0, 0, -1]] """ @classproperty def braket_gate(cls): from braket.circuits import gates return gates.CZ @classproperty def qiskit_gate(cls): from qiskit.circuit.library import CZGate return CZGate qiskit_string = "cz" qlm_aqasm_keyword = "CSIGN" def __init__(self, control: int, target: int): ControlledGate.__init__(self, [control], [target], Z(target), "CZ")
[docs] def to_canonical_matrix(self): m = np.eye(4, dtype=complex) m[-1, -1] = -1 return m
nb_qubits = ( # pyright: ignore[reportAssignmentType,reportIncompatibleMethodOverride] 2 ) """Size of the gate."""
[docs]class CRk(RotationGate, ControlledGate): r"""Two-qubit Controlled-Rk gate. `\begin{pmatrix}1&0&0&0\\0&1&0&0\\0&0&1&0\\0&0&0&e^{i\pi/2^{k-1}}\end{pmatrix}` Args: k: Parameter used in the definition of the phase to apply. control: Index referring to the qubit used to control the gate. target: Index referring to the qubit on which the gate will be applied. Examples: >>> pprint(CRk(4, 0, 1).to_matrix()) [[1, 0, 0, 0 ], [0, 1, 0, 0 ], [0, 0, 1, 0 ], [0, 0, 0, 0.92388+0.38268j]] >>> k = symbols("k") >>> pprint(CRk(k, 0, 1).to_matrix()) [[1, 0, 0, 0 ], [0, 1, 0, 0 ], [0, 0, 1, 0 ], [0, 0, 0, 1.0*exp(2.0*I*pi/2**k)]] """ @classproperty def braket_gate(cls): from braket.circuits import gates return gates.CPhaseShift @classproperty def qiskit_gate(cls): from qiskit.circuit.library import CPhaseGate return CPhaseGate # TODO: this is a special case, see if it needs to be generalized qlm_aqasm_keyword = "CNOT;PH" qiskit_string = "cp" def __init__(self, k: Expr | int, control: int, target: int): self.parameters = [k] ControlledGate.__init__(self, [control], [target], Rk(k, target), "CRk") definition = UnitaryMatrix( self.to_canonical_matrix(), **self.native_gate_options ) ParametrizedGate.__init__(self, definition, [target], [k], "CRk") @property def theta(self) -> Expr | float: r"""Value of the rotation angle, parametrized by ``k`` with the relation `\theta = \frac{\pi}{2^{k-1}}`.""" from sympy import pi p = np.pi if isinstance(self.k, Integral) else pi return p / 2 ** (self.k - 1) # pyright: ignore[reportOperatorIssue] @property def k(self) -> Expr | float: """See corresponding argument.""" return self.parameters[0]
[docs] def to_canonical_matrix(self): e = exp(self.theta * 1j) # pyright: ignore[reportOperatorIssue] return np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, e]])
[docs] def to_other_language( self, language: Language = Language.QISKIT, qiskit_parameters: Optional[set["Parameter"]] = None, ): if language == Language.QASM2: from mpqp.qasm.mpqp_to_qasm import float_to_qasm_str instruction_str = self.qasm2_gate instruction_str += ( f"({float_to_qasm_str(2 * np.pi / (2 ** float(self.k)))})" ) qubits = ",".join([f"q[{j}]" for j in self.controls]) + "," qubits += ",".join([f"q[{j}]" for j in self.targets]) return instruction_str + " " + qubits + ";" else: return super().to_other_language(language, qiskit_parameters)
def __repr__(self) -> str: return f"{type(self).__name__}({self.k}, {self.controls[0]}, {self.targets[0]})" nb_qubits = ( # pyright: ignore[reportAssignmentType,reportIncompatibleMethodOverride] 2 ) """Size of the gate."""
[docs] def inverse(self) -> Gate: return CRk_dagger(self.parameters[0], self.controls[0], self.targets[0])
[docs]class CRk_dagger(RotationGate, ControlledGate): r"""Two-qubit Controlled-Rk-dagger gate. `\begin{pmatrix}1&0&0&0\\0&1&0&0\\0&0&1&0\\0&0&0&e^{-i\pi/2^{k-1}}\end{pmatrix}` Args: k: Parameter used in the definition of the phase to apply. control: Index referring to the qubit used to control the gate. target: Index referring to the qubit on which the gate will be applied. Example: >>> pprint(CRk_dagger(4, 0, 1).to_matrix()) [[1, 0, 0, 0 ], [0, 1, 0, 0 ], [0, 0, 1, 0 ], [0, 0, 0, 0.92388-0.38268j]] """ @classproperty def braket_gate(cls): from braket.circuits import gates return gates.CPhaseShift @classproperty def qiskit_gate(cls): from qiskit.circuit.library import CPhaseGate return CPhaseGate # TODO: this is a special case, see if it needs to be generalized qlm_aqasm_keyword = "CNOT;PH" qiskit_string = "cp" def __init__(self, k: Expr | int, control: int, target: int): self.parameters = [k] ControlledGate.__init__(self, [control], [target], Rk_dagger(k, target), "CRk†") definition = UnitaryMatrix( self.to_canonical_matrix(), **self.native_gate_options ) ParametrizedGate.__init__(self, definition, [target], [k], "CRk†") @property def theta(self) -> Expr | float: r"""Value of the rotation angle, parametrized by ``k`` with the relation `\theta = -\frac{\pi}{2^{k-1}}`.""" from sympy import pi p = np.pi if isinstance(self.k, Integral) else pi return -(p / 2 ** (self.k - 1)) # pyright: ignore[reportOperatorIssue] @property def k(self) -> Expr | int: """See corresponding argument.""" return self.parameters[0]
[docs] def to_canonical_matrix(self): e = exp(self.theta * 1j) # pyright: ignore[reportOperatorIssue] return np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, e]])
def __repr__(self) -> str: return f"{type(self).__name__}({self.k}, {self.controls[0]}, {self.targets[0]})" nb_qubits = ( # pyright: ignore[reportAssignmentType,reportIncompatibleMethodOverride] 2 ) """Size of the gate."""
[docs] def inverse(self) -> Gate: return CRk(self.k, self.controls[0], self.targets[0])
[docs] def to_other_language( self, language: Language = Language.QISKIT, qiskit_parameters: Optional[set["Parameter"]] = None, ): if language == Language.QASM2: from mpqp.qasm.mpqp_to_qasm import float_to_qasm_str instruction_str = self.qasm2_gate instruction_str += ( f"({float_to_qasm_str(-2 * np.pi / (2 ** float(self.k)))})" ) qubits = ",".join([f"q[{j}]" for j in self.controls]) + "," qubits += ",".join([f"q[{j}]" for j in self.targets]) return instruction_str + " " + qubits + ";" else: return super().to_other_language(language, qiskit_parameters)
[docs]class TOF(InvolutionGate, ControlledGate, NoParameterGate): r"""Three-qubit Controlled-Controlled-NOT gate, also known as Toffoli Gate. `\begin{pmatrix}1&0&0&0&0&0&0&0\\0&1&0&0&0&0&0&0\\0&0&1&0&0&0&0&0\\0&0&0&1&0&0&0&0\\0&0&0&0&1&0&0&0\\0&0&0&0&0&1&0&0\\0&0&0&0&0&0&0&1\\0&0&0&0&0&0&1&0\end{pmatrix}` Args: control: List of indices referring to the qubits used to control the gate. target: Index referring to the qubit on which the gate will be applied. Examples: >>> pprint(TOF([0, 1], 2).to_matrix()) [[1, 0, 0, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0, 0], [0, 0, 1, 0, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0, 0], [0, 0, 0, 0, 1, 0, 0, 0], [0, 0, 0, 0, 0, 1, 0, 0], [0, 0, 0, 0, 0, 0, 0, 1], [0, 0, 0, 0, 0, 0, 1, 0]] """ @classproperty def braket_gate(cls): from braket.circuits import gates return gates.CCNot @classproperty def qiskit_gate(cls): from qiskit.circuit.library import CCXGate return CCXGate qlm_aqasm_keyword = "CCNOT" qiskit_string = "ccx" def __init__(self, control: list[int], target: int): if len(control) != 2: raise ValueError("A Toffoli gate must have exactly 2 control qubits.") ControlledGate.__init__(self, control, [target], X(target), "TOF")
[docs] def to_canonical_matrix(self): m = np.identity(8, dtype=complex) m[-2:, -2:] = np.ones(2) - np.identity(2) return m
nb_qubits = ( # pyright: ignore[reportAssignmentType,reportIncompatibleMethodOverride] 3 ) """Size of the gate."""
NATIVE_GATES = [ cls for _, cls in inspect.getmembers(sys.modules[__name__], inspect.isclass) if issubclass(cls, NativeGate) and not any("ABC" in base.__name__ for base in cls.__bases__) ] """All concrete native gates."""