Source code for jet.gate

"""Module containing the ``Gate`` and ``GateFactory`` classes in addition to all
``Gate`` subclasses.
"""
from abc import ABC, abstractmethod
from cmath import exp
from functools import lru_cache
from math import cos, sin, sqrt
from typing import Callable, Dict, List, Optional, Sequence

import numpy as np
from thewalrus.fock_gradients import (
    beamsplitter,
    displacement,
    squeezing,
    two_mode_squeezing,
)

from .factory import Tensor, TensorType

__all__ = [
    "Gate",
    "GateFactory",
    # Decorator gates
    "Adjoint",
    "Scale",
    # CV Fock gates
    "FockGate",
    "Displacement",
    "Squeezing",
    "TwoModeSqueezing",
    "Beamsplitter",
    # Qubit gates
    "QubitGate",
    "Hadamard",
    "PauliX",
    "PauliY",
    "PauliZ",
    "S",
    "T",
    "SX",
    "CX",
    "CY",
    "CZ",
    "SWAP",
    "ISWAP",
    "CSWAP",
    "Toffoli",
    "RX",
    "RY",
    "RZ",
    "PhaseShift",
    "CPhaseShift",
    "Rot",
    "CRX",
    "CRY",
    "CRZ",
    "CRot",
    "U1",
    "U2",
    "U3",
]


INV_SQRT2 = 1 / sqrt(2)


[docs]class Gate(ABC): """Gate represents a quantum gate. Args: name (str): Name of the gate. num_wires (int): Number of wires the gate is applied to. dim (int): Dimension of the gate. This should match the dimension of the qudits the gate can be applied to. params (List[float] or None): Parameters of the gate. Raises: ValueError: If the dimension is invalid. """ def __init__(self, name: str, num_wires: int, dim: int, params: Optional[List[float]] = None): self.name = name self._indices = None self._num_wires = num_wires self._params = params self._validate_dimension(dim) self._dim = dim @property def dimension(self) -> int: """Returns the dimension of this gate.""" return self._dim @dimension.setter def dimension(self, dim: int) -> None: """Sets the dimension of this gate. Args: dim (int): New dimension of this gate. Raises: ValueError: If the dimension is invalid. """ self._validate_dimension(dim) self._dim = dim @abstractmethod def _validate_dimension(self, dim: int) -> None: """Validates a candidate dimension for this gate. Args: dim (int): Dimension to be validated. Raises: ValueError: If the dimension is invalid. """ @property def indices(self) -> Optional[Sequence[str]]: """Returns the indices of this gate. An index is a label associated with an axis of the tensor representation of a gate; the indices of a tensor determine its connectivity in the context of a tensor network. """ return self._indices @indices.setter def indices(self, indices: Optional[Sequence[str]]) -> None: """Sets the indices of this gate. If the indices of a gate are not ``None``, they are used to construct the tensor representation of that gate. See @indices.getter for more information about tensor indices. Raises: ValueError: If the given indices are not a sequence of unique strings or the number of provided indices is invalid. Args: indices (Sequence[str] or None): New indices of the gate. """ # Skip the sequence property checks if `indices` is None. if indices is None: pass # Check that `indices` is a sequence of unique strings. elif ( not isinstance(indices, Sequence) or not all(isinstance(idx, str) for idx in indices) or len(set(indices)) != len(indices) ): raise ValueError("Indices must be a sequence of unique strings.") # Check that `indices` has the correct length. elif len(indices) != 2 * self._num_wires: raise ValueError( f"Gates must have two indices per wire; received {len(indices)} " f"indices for {self._num_wires} wires." ) self._indices = indices @property def num_wires(self) -> int: """Returns the number of wires this gate acts on.""" return self._num_wires @property def params(self) -> Optional[List[float]]: """Returns the parameters of this gate.""" return self._params @abstractmethod def _data(self) -> np.ndarray: """Returns the matrix representation of this gate."""
[docs] def tensor(self, dtype: np.dtype = np.complex128) -> TensorType: """Returns the tensor representation of this gate. Args: dtype (np.dtype): Data type of the tensor. """ data = self._data().flatten() indices = self.indices if indices is None: indices = list(map(str, range(2 * self._num_wires))) dimension = int(round(len(data) ** (1 / len(indices)))) shape = [dimension] * len(indices) return Tensor(indices=indices, shape=shape, data=data, dtype=dtype)
[docs]class GateFactory: """GateFactory is an implementation of the factory design pattern for the Gate class. The create() method constructs a Gate instance from a name that has been registered by a Gate subclass using the @register decorator. """ registry: Dict[str, type] = {} """Map that associates names with concrete Gate subclasses."""
[docs] @staticmethod def create( name: str, *params: float, adjoint: bool = False, scalar: float = 1, **kwargs ) -> Gate: """Constructs a gate by name. Raises: KeyError: If there is no entry for the given name in the registry. Args: name (str): Registered name of the desired gate. params (float): Parameters to pass to the gate constructor. adjoint (bool): Whether to take the adjoint of the gate. scalar (float): Scaling factor to apply to the gate. kwargs: Keyword arguments to pass to the gate constructor. Returns: Gate: The constructed gate. """ if name not in GateFactory.registry: raise KeyError(f"The name '{name}' does not exist in the gate registry.") subclass = GateFactory.registry[name] gate = subclass(*params, **kwargs) if adjoint: gate = Adjoint(gate=gate) if scalar != 1: gate = Scale(gate=gate, scalar=scalar) return gate
[docs] @staticmethod def register(names: Sequence[str]) -> Callable[[type], type]: """Registers a set of names with a class type. Raises: ValueError: If the provided class does not inherit from Gate. KeyError: If a name is already registered to another class. """ def wrapper(subclass: type) -> type: if not issubclass(subclass, Gate): raise ValueError(f"The type '{subclass.__name__}' is not a subclass of Gate.") # Let the caller specify duplicate keys if they wish. conflicts = set(names) & set(GateFactory.registry) if conflicts: raise KeyError(f"The names {conflicts} already exist in the gate registry.") for name in set(names): GateFactory.registry[name] = subclass return subclass return wrapper
# pylint: disable=bad-staticmethod-argument
[docs] @staticmethod def unregister(cls: type) -> None: """Unregisters a class type. Args: cls (type): Class type to remove from the registry. """ for key in {key for key, val in GateFactory.registry.items() if val == cls}: del GateFactory.registry[key]
#################################################################################################### # Decorator gates ####################################################################################################
[docs]class Adjoint(Gate): """Adjoint is a decorator which computes the conjugate transpose of an existing ``Gate``. Args: gate (Gate): Gate to take the adjoint of. """ def __init__(self, gate: Gate): self._gate = gate super().__init__( name=gate.name, num_wires=gate.num_wires, dim=gate.dimension, params=gate.params ) def _data(self): # pylint: disable=protected-access return self._gate._data().conj().T def _validate_dimension(self, dim): # pylint: disable=protected-access self._gate._validate_dimension(dim)
[docs]class Scale(Gate): """Scale is a decorator which linearly scales an existing ``Gate``. Args: gate (Gate): Gate to scale. scalar (float): Scaling factor. """ def __init__(self, gate: Gate, scalar: float): self._gate = gate self._scalar = scalar super().__init__( name=gate.name, num_wires=gate.num_wires, dim=gate.dimension, params=gate.params ) def _data(self): # pylint: disable=protected-access return self._scalar * self._gate._data() def _validate_dimension(self, dim): # pylint: disable=protected-access self._gate._validate_dimension(dim)
#################################################################################################### # Continuous variable Fock gates ####################################################################################################
[docs]class FockGate(Gate): """FockGate represents a (continuous variable) Fock gate. Args: name (str): Name of the gate. num_wires (int): Number of wires the gate is applied to. cutoff (int): Fock ladder cutoff. params (List[float] or None): Parameters of the gate. """ def __init__( self, name: str, num_wires: int, cutoff: int, params: Optional[List[float]] = None ): super().__init__(name=name, num_wires=num_wires, dim=cutoff, params=params) def _validate_dimension(self, dim): if dim < 2: raise ValueError("The dimension of a Fock gate must be greater than one.")
[docs]@GateFactory.register(names=["Displacement", "displacement", "D", "d"]) class Displacement(FockGate): """Displacement represents a displacement gate. See `thewalrus.displacement <https://the-walrus.readthedocs.io/en/latest/code/api/thewalrus.fock_gradients.displacement.html>`__ for more details. Args: r (float): Displacement magnitude. phi (float): Displacement angle. cutoff (int): Fock ladder cutoff. """ def __init__(self, r: float, phi: float, cutoff: int = 2): super().__init__(name="Displacement", num_wires=1, cutoff=cutoff, params=[r, phi]) @lru_cache() def _data(self): return displacement(*self.params, cutoff=self.dimension)
[docs]@GateFactory.register(names=["Squeezing", "squeezing"]) class Squeezing(FockGate): """Squeezing represents a squeezing gate. See `thewalrus.squeezing <https://the-walrus.readthedocs.io/en/latest/code/api/thewalrus.fock_gradients.squeezing.html>`__ for more details. Args: r (float): Squeezing magnitude. theta (float): Squeezing angle. cutoff (int): Fock ladder cutoff. """ def __init__(self, r: float, theta: float, cutoff: int = 2): super().__init__(name="Squeezing", num_wires=1, cutoff=cutoff, params=[r, theta]) @lru_cache() def _data(self): return squeezing(*self.params, cutoff=self.dimension)
[docs]@GateFactory.register(names=["TwoModeSqueezing", "twomodesqueezing"]) class TwoModeSqueezing(FockGate): """TwoModeSqueezing represents a two-mode squeezing gate. See `thewalrus.two_mode_squeezing <https://the-walrus.readthedocs.io/en/latest/code/api/thewalrus.fock_gradients.two_mode_squeezing.html>`__ for more details. Args: r (float): Squeezing magnitude. theta (float): Squeezing angle. cutoff (int): Fock ladder cutoff. """ def __init__(self, r: float, theta: float, cutoff: int = 2): super().__init__(name="TwoModeSqueezing", num_wires=2, cutoff=cutoff, params=[r, theta]) @lru_cache() def _data(self): return two_mode_squeezing(*self.params, cutoff=self.dimension)
[docs]@GateFactory.register( names=[ "Beamsplitter", "beamsplitter", "BS", "bs", ] ) class Beamsplitter(FockGate): """Beamsplitter represents a beamsplitter gate. See `thewalrus.beamsplitter <https://the-walrus.readthedocs.io/en/latest/code/api/thewalrus.fock_gradients.beamsplitter.html>`__ for more details. Args: theta (float): Transmissivity angle of the beamsplitter. The transmissivity is :math:`t=\\cos(\\theta)`. phi (float): Reflection phase of the beamsplitter. cutoff (int): Fock ladder cutoff. """ def __init__(self, theta: float, phi: float, cutoff: int = 2): super().__init__(name="Beamsplitter", num_wires=2, cutoff=cutoff, params=[theta, phi]) @lru_cache() def _data(self): return beamsplitter(*self.params, cutoff=self.dimension)
#################################################################################################### # Qubit gates ####################################################################################################
[docs]class QubitGate(Gate): """QubitGate represents a qubit gate. Args: name (str): Name of the gate. num_wires (int): Number of wires the gate is applied to. params (List[float] or None): Parameters of the gate. """ def __init__(self, name: str, num_wires: int, params: Optional[List[float]] = None): super().__init__(name=name, num_wires=num_wires, dim=2, params=params) def _validate_dimension(self, dim): if dim != 2: raise ValueError("The dimension of a qubit gate must be exactly two.")
[docs]@GateFactory.register(names=["Hadamard", "hadamard", "H", "h"]) class Hadamard(QubitGate): """Hadamard represents a Hadamard gate.""" def __init__(self): super().__init__(name="Hadamard", num_wires=1) @lru_cache() def _data(self): mat = [[INV_SQRT2, INV_SQRT2], [INV_SQRT2, -INV_SQRT2]] return np.array(mat)
[docs]@GateFactory.register(names=["PauliX", "paulix", "X", "x", "NOT", "not"]) class PauliX(QubitGate): """PauliX represents a Pauli-X gate.""" def __init__(self): super().__init__(name="PauliX", num_wires=1) @lru_cache() def _data(self): mat = [[0, 1], [1, 0]] return np.array(mat)
[docs]@GateFactory.register(names=["PauliY", "pauliy", "Y", "y"]) class PauliY(QubitGate): """PauliY represents a Pauli-Y gate.""" def __init__(self): super().__init__(name="PauliY", num_wires=1) @lru_cache() def _data(self): mat = [[0, -1j], [1j, 0]] return np.array(mat)
[docs]@GateFactory.register(names=["PauliZ", "pauliz", "Z", "z"]) class PauliZ(QubitGate): """PauliZ represents a Pauli-Z gate.""" def __init__(self): super().__init__(name="PauliZ", num_wires=1) @lru_cache() def _data(self): mat = [[1, 0], [0, -1]] return np.array(mat)
[docs]@GateFactory.register(names=["S", "s"]) class S(QubitGate): """S represents a single-qubit phase gate.""" def __init__(self): super().__init__(name="S", num_wires=1) @lru_cache() def _data(self): mat = [[1, 0], [0, 1j]] return np.array(mat)
[docs]@GateFactory.register(names=["T", "t"]) class T(QubitGate): """T represents a single-qubit T gate.""" def __init__(self): super().__init__(name="T", num_wires=1) @lru_cache() def _data(self): mat = [[1, 0], [0, exp(0.25j * np.pi)]] return np.array(mat)
[docs]@GateFactory.register(names=["SX", "sx"]) class SX(QubitGate): """SX represents a single-qubit Square-Root X gate.""" def __init__(self): super().__init__(name="SX", num_wires=1) @lru_cache() def _data(self): mat = [[0.5 + 0.5j, 0.5 - 0.5j], [0.5 - 0.5j, 0.5 + 0.5j]] return np.array(mat)
[docs]@GateFactory.register(names=["PhaseShift", "phaseshift"]) class PhaseShift(QubitGate): """PhaseShift represents a single-qubit local phase shift gate. Args: phi (float): Phase shift angle. """ def __init__(self, phi: float): super().__init__(name="PhaseShift", num_wires=1, params=[phi]) @lru_cache() def _data(self): phi = self.params[0] mat = [[1, 0], [0, exp(1j * phi)]] return np.array(mat)
[docs]@GateFactory.register(names=["CPhaseShift", "cphaseshift"]) class CPhaseShift(QubitGate): """CPhaseShift represents a controlled phase shift gate. Args: phi (float): Phase shift angle. """ def __init__(self, phi: float): super().__init__(name="CPhaseShift", num_wires=2, params=[phi]) @lru_cache() def _data(self): phi = self.params[0] mat = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, exp(1j * phi)]] return np.array(mat)
[docs]@GateFactory.register(names=["CX", "cx", "CNOT", "cnot"]) class CX(QubitGate): """CX represents a controlled-X gate.""" def __init__(self): super().__init__(name="CX", num_wires=2) @lru_cache() def _data(self): mat = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]] return np.array(mat)
[docs]@GateFactory.register(names=["CY", "cy"]) class CY(QubitGate): """CY represents a controlled-Y gate.""" def __init__(self): super().__init__(name="CY", num_wires=2) @lru_cache() def _data(self): mat = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, -1j], [0, 0, 1j, 0]] return np.array(mat)
[docs]@GateFactory.register(names=["CZ", "cz"]) class CZ(QubitGate): """CZ represents a controlled-Z gate.""" def __init__(self): super().__init__(name="CZ", num_wires=2) @lru_cache() def _data(self): mat = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, -1]] return np.array(mat)
[docs]@GateFactory.register(names=["SWAP", "swap"]) class SWAP(QubitGate): """SWAP represents a SWAP gate.""" def __init__(self): super().__init__(name="SWAP", num_wires=2) @lru_cache() def _data(self): mat = [[1, 0, 0, 0], [0, 0, 1, 0], [0, 1, 0, 0], [0, 0, 0, 1]] return np.array(mat)
[docs]@GateFactory.register(names=["ISWAP", "iswap"]) class ISWAP(QubitGate): """ISWAP represents a ISWAP gate.""" def __init__(self): super().__init__(name="ISWAP", num_wires=2) @lru_cache() def _data(self): mat = [[1, 0, 0, 0], [0, 0, 1j, 0], [0, 1j, 0, 0], [0, 0, 0, 1]] return np.array(mat)
[docs]@GateFactory.register(names=["CSWAP", "cswap"]) class CSWAP(QubitGate): """CSWAP represents a CSWAP gate.""" def __init__(self): super().__init__(name="CSWAP", num_wires=3) @lru_cache() def _data(self): mat = [ [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, 0], [0, 0, 0, 0, 0, 0, 0, 1], ] return np.array(mat)
[docs]@GateFactory.register(names=["Toffoli", "toffoli"]) class Toffoli(QubitGate): """Toffoli represents a Toffoli gate.""" def __init__(self): super().__init__(name="Toffoli", num_wires=3) @lru_cache() def _data(self): mat = [ [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], ] return np.array(mat)
[docs]@GateFactory.register(names=["RX", "rx"]) class RX(QubitGate): """RX represents a single-qubit X rotation gate. Args: theta (float): Rotation angle around the X-axis. """ def __init__(self, theta: float): super().__init__(name="RX", num_wires=1, params=[theta]) @lru_cache() def _data(self): theta = self.params[0] c = cos(theta / 2) js = 1j * sin(-theta / 2) mat = [[c, js], [js, c]] return np.array(mat)
[docs]@GateFactory.register(names=["RY", "ry"]) class RY(QubitGate): """RY represents a single-qubit Y rotation gate. Args: theta (float): Rotation angle around the Y-axis. """ def __init__(self, theta: float): super().__init__(name="RY", num_wires=1, params=[theta]) @lru_cache() def _data(self): theta = self.params[0] c = cos(theta / 2) s = sin(theta / 2) mat = [[c, -s], [s, c]] return np.array(mat)
[docs]@GateFactory.register(names=["RZ", "rz"]) class RZ(QubitGate): """RZ represents a single-qubit Z rotation gate. Args: theta (float): Rotation angle around the Z-axis. """ def __init__(self, theta: float): super().__init__(name="RZ", num_wires=1, params=[theta]) @lru_cache() def _data(self): theta = self.params[0] p = exp(-0.5j * theta) mat = [[p, 0], [0, np.conj(p)]] return np.array(mat)
[docs]@GateFactory.register(names=["Rot", "rot"]) class Rot(QubitGate): """Rot represents an arbitrary single-qubit rotation gate with three Euler angles. Each Pauli rotation gate can be recovered by fixing two of the three parameters: >>> assert RX(theta).tensor() == Rot(pi/2, -pi/2, theta).tensor() >>> assert RY(theta).tensor() == Rot(0, 0, theta).tensor() >>> assert RZ(theta).tensor() == Rot(theta, 0, 0).tensor() See `qml.Rot <https://pennylane.readthedocs.io/en/stable/code/api/pennylane.Rot.html>`__ for more details. Args: phi (float): First rotation angle. theta (float): Second rotation angle. omega (float): Third rotation angle. """ def __init__(self, phi: float, theta: float, omega: float): super().__init__(name="Rot", num_wires=1, params=[phi, theta, omega]) @lru_cache() def _data(self): phi, theta, omega = self.params c = cos(theta / 2) s = sin(theta / 2) mat = [ [exp(-0.5j * (phi + omega)) * c, -exp(0.5j * (phi - omega)) * s], [exp(-0.5j * (phi - omega)) * s, exp(0.5j * (phi + omega)) * c], ] return np.array(mat)
[docs]@GateFactory.register(names=["CRX", "crx"]) class CRX(QubitGate): """CRX represents a controlled-RX gate. Args: theta (float): Rotation angle around the X-axis. """ def __init__(self, theta: float): super().__init__(name="CRX", num_wires=2, params=[theta]) @lru_cache() def _data(self): theta = self.params[0] c = cos(theta / 2) js = 1j * sin(-theta / 2) mat = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, c, js], [0, 0, js, c]] return np.array(mat)
[docs]@GateFactory.register(names=["CRY", "cry"]) class CRY(QubitGate): """CRY represents a controlled-RY gate. Args: theta (float): Rotation angle around the Y-axis. """ def __init__(self, theta: float): super().__init__(name="CRY", num_wires=2, params=[theta]) @lru_cache() def _data(self): theta = self.params[0] c = cos(theta / 2) s = sin(theta / 2) mat = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, c, -s], [0, 0, s, c]] return np.array(mat)
[docs]@GateFactory.register(names=["CRZ", "crz"]) class CRZ(QubitGate): """CRZ represents a controlled-RZ gate. Args: theta (float): Rotation angle around the Z-axis. """ def __init__(self, theta: float): super().__init__(name="CRZ", num_wires=2, params=[theta]) @lru_cache() def _data(self): theta = self.params[0] mat = [ [1, 0, 0, 0], [0, 1, 0, 0], [0, 0, exp(-0.5j * theta), 0], [0, 0, 0, exp(0.5j * theta)], ] return np.array(mat)
[docs]@GateFactory.register(names=["CRot", "crot"]) class CRot(QubitGate): """CRot represents a controlled-rotation gate. Args: phi (float): First rotation angle. theta (float): Second rotation angle. omega (float): Third rotation angle. """ def __init__(self, phi: float, theta: float, omega: float): super().__init__(name="CRot", num_wires=2, params=[phi, theta, omega]) @lru_cache() def _data(self): phi, theta, omega = self.params c = cos(theta / 2) s = sin(theta / 2) mat = [ [1, 0, 0, 0], [0, 1, 0, 0], [0, 0, exp(-0.5j * (phi + omega)) * c, -exp(0.5j * (phi - omega)) * s], [0, 0, exp(-0.5j * (phi - omega)) * s, exp(0.5j * (phi + omega)) * c], ] return np.array(mat)
[docs]@GateFactory.register(names=["U1", "u1"]) class U1(QubitGate): """U1 represents a U1 gate. Args: phi (float): Rotation angle. """ def __init__(self, phi: float): super().__init__(name="U1", num_wires=1, params=[phi]) @lru_cache() def _data(self): phi = self.params[0] mat = [[1, 0], [0, exp(1j * phi)]] return np.array(mat)
[docs]@GateFactory.register(names=["U2", "u2"]) class U2(QubitGate): """U2 represents a U2 gate. Args: phi (float): First rotation angle. lam (float): Second rotation angle. """ def __init__(self, phi: float, lam: float): super().__init__(name="U2", num_wires=1, params=[phi, lam]) @lru_cache() def _data(self): phi, lam = self.params mat = [ [INV_SQRT2, -INV_SQRT2 * exp(1j * lam)], [INV_SQRT2 * exp(1j * phi), INV_SQRT2 * exp(1j * (phi + lam))], ] return np.array(mat)
[docs]@GateFactory.register(names=["U3", "u3"]) class U3(QubitGate): """U3 represents a U3 gate. Args: theta (float): First rotation angle. phi (float): Second rotation angle. lam (float): Third rotation angle. """ def __init__(self, theta: float, phi: float, lam: float): super().__init__(name="U3", num_wires=1, params=[theta, phi, lam]) @lru_cache() def _data(self): theta, phi, lam = self.params c = cos(theta / 2) s = sin(theta / 2) mat = [ [c, -s * exp(1j * lam)], [s * exp(1j * phi), c * exp(1j * (phi + lam))], ] return np.array(mat)