Source code for blueqat.gate

# Copyright 2019-2026 The Blueqat Developers
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
`gate` module implements quantum gate operations.
Modernized for PyTorch Tensor Network integration in 2026.
"""

import cmath
import math
from typing import Any, cast, Callable, Iterable, Iterator, List, NoReturn, Optional, Tuple, Type, TypeVar, Union

import torch

from .typing import Targets

_Op = TypeVar('_Op', bound='Operation')


[docs] class Operation: """Abstract quantum circuit operation class.""" lowername: str = '' """Lower name of the operation.""" @property def uppername(self) -> str: """Upper name of the operation.""" return self.lowername.upper() def __init__(self, targets: Targets, params=()) -> None: if self.lowername == '': raise ValueError( f"{self.__class__.__name__}.lowername is not defined.") self.params = params self.targets = targets
[docs] def target_iter(self, n_qubits: int) -> Iterator[int]: """The generator which yields the target qubits.""" return slicing(self.targets, n_qubits)
[docs] @classmethod def create(cls: Type[_Op], targets: Targets, params: tuple, options: Optional[dict]) -> _Op: """Create an operation.""" raise NotImplementedError(f"{cls.__name__}.create() is not defined.")
def _str_args(self) -> str: """Returns printable string of args.""" if not self.params: return '' return '(' + ', '.join(str(param) for param in self.params) + ')' def _str_targets(self) -> str: """Returns printable string of targets.""" def _slice_to_str(obj): if isinstance(obj, slice): start = '' if obj.start is None else str(obj.start.__index__()) stop = '' if obj.stop is None else str(obj.stop.__index__()) if obj.step is None: return f'{start}:{stop}' step = str(obj.step.__index__()) return f'{start}:{stop}:{step}' return str(obj.__index__()) if isinstance(self.targets, tuple): return f"[{', '.join(_slice_to_str(target) for target in self.targets)}]" return f"[{_slice_to_str(self.targets)}]" def __str__(self) -> str: str_args = self._str_args() str_targets = self._str_targets() return f'{self.lowername}{str_args}{str_targets}'
[docs] class IFallbackOperation(Operation): """The interface of `fallback`"""
[docs] def fallback(self, n_qubits: int) -> List['Operation']: """Get alternative operations""" raise NotImplementedError( f"fallback of {self.lowername} is not defined.")
[docs] class Gate(Operation): """Abstract quantum gate class.""" @property def n_qargs(self) -> int: """Number of qubit arguments of this gate.""" raise NotImplementedError()
[docs] def dagger(self) -> 'Gate': """Returns the Hermitian conjugate of `self`.""" raise NotImplementedError( "Hermitian conjugate of this gate is not provided.")
[docs] def matrix(self) -> torch.Tensor: """Returns the matrix of implementations as a PyTorch Tensor.""" raise NotImplementedError()
[docs] class OneQubitGate(Gate): """Abstract quantum gate class for 1 qubit gate.""" @property def n_qargs(self) -> int: return 1 def _make_fallback_for_target_iter( self, n_qubits: int, fallback: Callable[[int], List['Gate']]) -> List['Gate']: gates = [] for t in self.target_iter(n_qubits): gates += fallback(t) return gates
[docs] class TwoQubitGate(Gate): """Abstract quantum gate class for 2 qubits gate.""" @property def n_qargs(self): return 2
[docs] def control_target_iter(self, n_qubits: int) -> Iterator[Tuple[int, int]]: """The generator which yields the tuples of (control, target) qubits.""" return qubit_pairs(self.targets, n_qubits)
def _make_fallback_for_control_target_iter( self, n_qubits: int, fallback: Callable[[int, int], List['Gate']]) -> List['Gate']: gates = [] for c, t in self.control_target_iter(n_qubits): gates += fallback(c, t) return gates
[docs] class HGate(OneQubitGate): """Hadamard gate""" lowername = "h"
[docs] @classmethod def create(cls, targets: Targets, params: tuple, options: Optional[dict] = None) -> 'HGate': if params or options: raise ValueError(f"{cls.__name__} doesn't take parameters") return cls(targets)
[docs] def dagger(self): return self
[docs] def matrix(self): val = 1.0 / math.sqrt(2) return torch.tensor([[val, val], [val, -val]], dtype=torch.complex128)
[docs] class IGate(OneQubitGate, IFallbackOperation): """Identity gate""" lowername = "i"
[docs] @classmethod def create(cls, targets: Targets, params: tuple, options: Optional[dict] = None) -> 'IGate': if params or options: raise ValueError(f"{cls.__name__} doesn't take parameters") return cls(targets)
[docs] def fallback(self, _): return []
[docs] def dagger(self): return self
[docs] def matrix(self): return torch.eye(2, dtype=torch.complex128)
[docs] class Mat1Gate(OneQubitGate): """Arbitrary 2x2 matrix gate""" lowername = "mat1" def __init__(self, targets, mat: torch.Tensor): super().__init__(targets, (mat, )) self.mat = torch.as_tensor(mat, dtype=torch.complex128)
[docs] @classmethod def create(cls, targets: Targets, params: tuple, options: Optional[dict] = None) -> 'Mat1Gate': if options: raise ValueError(f"{cls.__name__} doesn't take options") return cls(targets, params[0])
[docs] def dagger(self): # mish() を .mT に修正し、テンソルの随伴行列を正しく取得 return Mat1Gate(self.targets, self.mat.mT.conj())
[docs] def matrix(self): return self.mat
[docs] class PhaseGate(OneQubitGate): """Phase gate""" lowername = "phase" def __init__(self, targets, theta): super().__init__(targets, (theta, )) self.theta = theta
[docs] @classmethod def create(cls, targets: Targets, params: tuple, options: Optional[dict] = None) -> 'PhaseGate': if options: raise ValueError(f"{cls.__name__} doesn't take options") return cls(targets, params[0])
[docs] def dagger(self): return PhaseGate(self.targets, -self.theta)
[docs] def matrix(self): theta = torch.as_tensor(self.theta, dtype=torch.complex128) # ones_like/zeros_like inherit theta's device, unlike a bare # torch.tensor(...) literal (always CPU), which would crash torch.stack # with a device mismatch if theta lives on e.g. CUDA/MPS. one = torch.ones_like(theta) zero = torch.zeros_like(theta) elements = torch.stack([one, zero, zero, torch.exp(1j * theta)]) return elements.reshape(2, 2)
[docs] class RXGate(OneQubitGate): """Rotate-X gate""" lowername = "rx" def __init__(self, targets, theta): super().__init__(targets, (theta, )) self.theta = theta
[docs] @classmethod def create(cls, targets: Targets, params: tuple, options: Optional[dict] = None) -> 'RXGate': if options: raise ValueError(f"{cls.__name__} doesn't take options") return cls(targets, params[0])
[docs] def dagger(self): return RXGate(self.targets, -self.theta)
[docs] def matrix(self): t = torch.as_tensor(self.theta, dtype=torch.complex128) * 0.5 cos_t = torch.cos(t) isin_t = -1j * torch.sin(t) elements = torch.stack([cos_t, isin_t, isin_t, cos_t]) return elements.reshape(2, 2)
[docs] class RYGate(OneQubitGate): """Rotate-Y gate""" lowername = "ry" def __init__(self, targets, theta): super().__init__(targets, (theta, )) self.theta = theta
[docs] @classmethod def create(cls, targets: Targets, params: tuple, options: Optional[dict] = None) -> 'RYGate': if options: raise ValueError(f"{cls.__name__} doesn't take options") return cls(targets, params[0])
[docs] def dagger(self): return RYGate(self.targets, -self.theta)
[docs] def matrix(self): t = torch.as_tensor(self.theta, dtype=torch.complex128) * 0.5 cos_t = torch.cos(t) sin_t = torch.sin(t) elements = torch.stack([cos_t, -sin_t, sin_t, cos_t]) return elements.reshape(2, 2)
[docs] class RZGate(OneQubitGate): """Rotate-Z gate""" lowername = "rz" def __init__(self, targets, theta): super().__init__(targets, (theta, )) self.theta = theta
[docs] @classmethod def create(cls, targets: Targets, params: tuple, options: Optional[dict] = None) -> 'RZGate': if options: raise ValueError(f"{cls.__name__} doesn't take options") return cls(targets, params[0])
[docs] def dagger(self): return RZGate(self.targets, -self.theta)
[docs] def matrix(self): t = torch.as_tensor(self.theta, dtype=torch.complex128) * 0.5 a = torch.exp(-1j * t) b = torch.exp(1j * t) zero = torch.zeros_like(t) elements = torch.stack([a, zero, zero, b]) return elements.reshape(2, 2)
[docs] class SGate(OneQubitGate, IFallbackOperation): """S gate""" lowername = "s"
[docs] @classmethod def create(cls, targets: Targets, params: tuple, options: Optional[dict] = None) -> 'SGate': if params or options: raise ValueError(f"{cls.__name__} doesn't take parameters") return cls(targets)
[docs] def dagger(self): return SDagGate(self.targets)
[docs] def fallback(self, n_qubits): return self._make_fallback_for_target_iter( n_qubits, lambda t: [PhaseGate(t, math.pi / 2)])
[docs] def matrix(self): return torch.tensor([[1, 0], [0, 1j]], dtype=torch.complex128)
[docs] class SDagGate(OneQubitGate, IFallbackOperation): """Dagger of S gate""" lowername = "sdg"
[docs] @classmethod def create(cls, targets: Targets, params: tuple, options: Optional[dict] = None) -> 'SDagGate': if params or options: raise ValueError(f"{cls.__name__} doesn't take parameters") return cls(targets)
[docs] def dagger(self): return SGate(self.targets)
[docs] def fallback(self, n_qubits): return self._make_fallback_for_target_iter( n_qubits, lambda t: [PhaseGate(t, -math.pi / 2)])
[docs] def matrix(self): return torch.tensor([[1, 0], [0, -1j]], dtype=torch.complex128)
[docs] class SXGate(OneQubitGate): """sqrt(X) gate""" lowername = "sx"
[docs] @classmethod def create(cls, targets: Targets, params: tuple, options: Optional[dict] = None) -> 'SXGate': if params or options: raise ValueError(f"{cls.__name__} doesn't take parameters") return cls(targets)
[docs] def dagger(self): return SXDagGate(self.targets)
[docs] def matrix(self): return 0.5 * torch.tensor([[1 + 1j, 1 - 1j], [1 - 1j, 1 + 1j]], dtype=torch.complex128)
[docs] class SXDagGate(OneQubitGate): """sqrt(X)† gate""" lowername = "sxdg"
[docs] @classmethod def create(cls, targets: Targets, params: tuple, options: Optional[dict] = None) -> 'SXDagGate': if params or options: raise ValueError(f"{cls.__name__} doesn't take parameters") return cls(targets)
[docs] def dagger(self): return SXGate(self.targets)
[docs] def matrix(self): return 0.5 * torch.tensor([[1 - 1j, 1 + 1j], [1 + 1j, 1 - 1j]], dtype=torch.complex128)
[docs] class TGate(OneQubitGate, IFallbackOperation): """T gate""" lowername = "t"
[docs] @classmethod def create(cls, targets: Targets, params: tuple, options: Optional[dict] = None) -> 'TGate': if params or options: raise ValueError(f"{cls.__name__} doesn't take parameters") return cls(targets)
[docs] def dagger(self): return TDagGate(self.targets)
[docs] def fallback(self, _): return [PhaseGate(self.targets, math.pi / 4)]
[docs] def matrix(self): # cmath.exp on a Python complex keeps full double precision; wrapping the # angle in torch.tensor(...) first creates a complex64 intermediate (torch's # default complex dtype for a scalar built from a Python complex), losing # ~8 significant digits before torch.exp ever runs. return torch.tensor([[1, 0], [0, cmath.exp(math.pi * 0.25j)]], dtype=torch.complex128)
[docs] class TDagGate(OneQubitGate, IFallbackOperation): """Dagger of T gate""" lowername = "tdg"
[docs] @classmethod def create(cls, targets: Targets, params: tuple, options: Optional[dict] = None) -> 'TDagGate': if params or options: raise ValueError(f"{cls.__name__} doesn't take parameters") return cls(targets)
[docs] def dagger(self): return TGate(self.targets)
[docs] def fallback(self, _): return [PhaseGate(self.targets, -math.pi / 4)]
[docs] def matrix(self): return torch.tensor([[1, 0], [0, cmath.exp(math.pi * -0.25j)]], dtype=torch.complex128)
[docs] class ToffoliGate(Gate, IFallbackOperation): """Toffoli (CCX) gate""" lowername = "ccx" @property def n_qargs(self): return 3
[docs] @classmethod def create(cls, targets: Targets, params: tuple, options: Optional[dict] = None) -> 'ToffoliGate': if params or options: raise ValueError(f"{cls.__name__} doesn't take parameters") return cls(targets)
[docs] def dagger(self): return self
[docs] def fallback(self, n_qubits): c1, c2, t = self.targets return [HGate(t), CCZGate((c1, c2, t)), HGate(t)]
[docs] def matrix(self): # targets = (c1, c2, t) uses the same control=least-significant-bit # convention as CXGate.matrix() (index = t*4 + c2*2 + c1), so the flipped # pair is where both controls are 1 (indices 3 and 7), not 6 and 7. m = torch.eye(8, dtype=torch.complex128) m[3, 3], m[3, 7] = 0, 1 m[7, 3], m[7, 7] = 1, 0 return m
[docs] class UGate(OneQubitGate): """Arbitrary 1 qubit unitary gate""" lowername = "u" def __init__(self, targets, theta, phi, lam, gamma=0.0): super().__init__(targets, (theta, phi, lam, gamma)) self.theta = theta self.phi = phi self.lam = lam self.gamma = gamma
[docs] @classmethod def create(cls, targets: Targets, params: tuple, options: Optional[dict] = None) -> 'UGate': return cls(targets, *params)
[docs] def dagger(self): return UGate(self.targets, -self.theta, -self.lam, -self.phi, -self.gamma)
[docs] def matrix(self): t = torch.as_tensor(self.theta, dtype=torch.complex128) p = torch.as_tensor(self.phi, dtype=torch.complex128) l = torch.as_tensor(self.lam, dtype=torch.complex128) g = torch.as_tensor(self.gamma, dtype=torch.complex128) gphase = torch.exp(1j * g) cos_t = torch.cos(0.5 * t) sin_t = torch.sin(0.5 * t) elements = torch.stack([ cos_t, -torch.exp(1j * l) * sin_t, torch.exp(1j * p) * sin_t, torch.exp(1j * (p + l)) * cos_t ]) return elements.reshape(2, 2) * gphase
[docs] class XGate(OneQubitGate): """Pauli's X gate""" lowername = "x"
[docs] @classmethod def create(cls, targets: Targets, params: tuple, options: Optional[dict] = None) -> 'XGate': if params or options: raise ValueError(f"{cls.__name__} doesn't take parameters") return cls(targets)
[docs] def dagger(self): return self
[docs] def matrix(self): return torch.tensor([[0, 1], [1, 0]], dtype=torch.complex128)
[docs] class YGate(OneQubitGate): """Pauli's Y gate""" lowername = "y"
[docs] @classmethod def create(cls, targets: Targets, params: tuple, options: Optional[dict] = None) -> 'YGate': if params or options: raise ValueError(f"{cls.__name__} doesn't take parameters") return cls(targets)
[docs] def dagger(self): return self
[docs] def matrix(self): return torch.tensor([[0, -1j], [1j, 0]], dtype=torch.complex128)
[docs] class ZGate(OneQubitGate): """Pauli's Z gate""" lowername = "z"
[docs] @classmethod def create(cls, targets: Targets, params: tuple, options: Optional[dict] = None) -> 'ZGate': if params or options: raise ValueError(f"{cls.__name__} doesn't take parameters") return cls(targets)
[docs] def dagger(self): return self
[docs] def matrix(self): return torch.tensor([[1, 0], [0, -1]], dtype=torch.complex128)
[docs] class CCZGate(Gate, IFallbackOperation): """2-Controlled Z gate""" lowername = "ccz" @property def n_qargs(self): return 3
[docs] @classmethod def create(cls, targets: Targets, params: tuple, options: Optional[dict] = None) -> 'CCZGate': if params or options: raise ValueError(f"{cls.__name__} doesn't take parameters") return cls(targets)
[docs] def fallback(self, n_qubits): c1, c2, t = self.targets return [ CXGate((c2, t)), TDagGate(t), CXGate((c1, t)), TGate(t), CXGate((c2, t)), TDagGate(t), CXGate((c1, t)), TGate(c2), TGate(t), CXGate((c1, c2)), TGate(c1), TDagGate(c2), CXGate((c1, c2)), ]
[docs] def dagger(self): return self
[docs] def matrix(self): m = torch.eye(8, dtype=torch.complex128) m[7, 7] = -1 return m
[docs] class CHGate(TwoQubitGate): """Controlled-H gate""" lowername = "ch"
[docs] @classmethod def create(cls, targets: Targets, params: tuple, options: Optional[dict] = None) -> 'CHGate': if params or options: raise ValueError(f"{cls.__name__} doesn't take parameters") return cls(targets)
[docs] def dagger(self): return self
[docs] def matrix(self): a = 1.0 / math.sqrt(2) return torch.tensor([ [1, 0, 0, 0], [0, a, 0, a], [0, 0, 1, 0], [0, a, 0, -a] ], dtype=torch.complex128)
[docs] class CPhaseGate(TwoQubitGate): """Controlled Phase gate""" lowername = "cphase" def __init__(self, targets, theta): super().__init__(targets, (theta, )) self.theta = theta
[docs] @classmethod def create(cls, targets: Targets, params: tuple, options: Optional[dict] = None) -> 'CPhaseGate': return cls(targets, params[0])
[docs] def dagger(self): return CPhaseGate(self.targets, -self.theta)
[docs] def matrix(self): theta = torch.as_tensor(self.theta, dtype=torch.complex128) one = torch.ones_like(theta) return torch.diag(torch.stack([one, one, one, torch.exp(1j * theta)]))
[docs] class CRXGate(TwoQubitGate): """Controlled RX gate""" lowername = "crx" def __init__(self, targets, theta): super().__init__(targets, (theta, )) self.theta = theta
[docs] @classmethod def create(cls, targets: Targets, params: tuple, options: Optional[dict] = None) -> 'CRXGate': return cls(targets, params[0])
[docs] def dagger(self): return CRXGate(self.targets, -self.theta)
[docs] def matrix(self): t = torch.as_tensor(self.theta, dtype=torch.complex128) * 0.5 cos_t = torch.cos(t) isin_t = -1j * torch.sin(t) zero = torch.zeros_like(t) one = torch.ones_like(t) elements = torch.stack([ one, zero, zero, zero, zero, cos_t, zero, isin_t, zero, zero, one, zero, zero, isin_t, zero, cos_t ]) return elements.reshape(4, 4)
[docs] class CRYGate(TwoQubitGate): """Controlled RY gate""" lowername = "cry" def __init__(self, targets, theta): super().__init__(targets, (theta, )) self.theta = theta
[docs] @classmethod def create(cls, targets: Targets, params: tuple, options: Optional[dict] = None) -> 'CRYGate': return cls(targets, params[0])
[docs] def dagger(self): return CRYGate(self.targets, -self.theta)
[docs] def matrix(self): t = torch.as_tensor(self.theta, dtype=torch.complex128) * 0.5 cos_t = torch.cos(t) sin_t = torch.sin(t) zero = torch.zeros_like(t) one = torch.ones_like(t) elements = torch.stack([ one, zero, zero, zero, zero, cos_t, zero, -sin_t, zero, zero, one, zero, zero, sin_t, zero, cos_t ]) return elements.reshape(4, 4)
[docs] class CRZGate(TwoQubitGate): """Controlled RZ gate""" lowername = "crz" def __init__(self, targets, theta): super().__init__(targets, (theta, )) self.theta = theta
[docs] @classmethod def create(cls, targets: Targets, params: tuple, options: Optional[dict] = None) -> 'CRZGate': return cls(targets, params[0])
[docs] def dagger(self): return CRZGate(self.targets, -self.theta)
[docs] def matrix(self): t = torch.as_tensor(self.theta, dtype=torch.complex128) * 0.5 a = torch.exp(-1j * t) b = torch.exp(1j * t) one = torch.ones_like(t) return torch.diag(torch.stack([one, a, one, b]))
[docs] class CSwapGate(Gate, IFallbackOperation): """Controlled SWAP gate""" lowername = "cswap" @property def n_qargs(self): return 3
[docs] @classmethod def create(cls, targets: Targets, params: tuple, options: Optional[dict] = None) -> 'CSwapGate': if params or options: raise ValueError(f"{cls.__name__} doesn't take parameters") return cls(targets)
[docs] def dagger(self): return self
[docs] def fallback(self, n_qubits): c, t1, t2 = self.targets return [CXGate((t2, t1)), ToffoliGate((c, t1, t2)), CXGate((t2, t1))]
[docs] def matrix(self): # targets = (c, t1, t2), same LSB-first convention as ToffoliGate.matrix() # (index = t2*4 + t1*2 + c): the swapped pair is where c=1 and exactly one # of t1/t2 is set, i.e. indices 3 (t1=1,t2=0) and 5 (t1=0,t2=1). m = torch.eye(8, dtype=torch.complex128) m[3, 3], m[3, 5] = 0, 1 m[5, 3], m[5, 5] = 1, 0 return m
[docs] class CUGate(TwoQubitGate): """Controlled-U gate""" lowername = "cu" def __init__(self, targets, theta, phi, lam, gamma=0.0): super().__init__(targets, (theta, phi, lam, gamma)) self.theta = theta self.phi = phi self.lam = lam self.gamma = gamma
[docs] @classmethod def create(cls, targets: Targets, params: tuple, options: Optional[dict] = None) -> 'CUGate': return cls(targets, *params)
[docs] def dagger(self): return CUGate(self.targets, -self.theta, -self.lam, -self.phi, -self.gamma)
[docs] def matrix(self): t = torch.as_tensor(self.theta, dtype=torch.complex128) p = torch.as_tensor(self.phi, dtype=torch.complex128) l = torch.as_tensor(self.lam, dtype=torch.complex128) g = torch.as_tensor(self.gamma, dtype=torch.complex128) cos_t = torch.cos(0.5 * t) sin_t = torch.sin(0.5 * t) zero = torch.zeros_like(t) one = torch.ones_like(t) elements = torch.stack([ one, zero, zero, zero, zero, torch.exp(1j * g) * cos_t, zero, -torch.exp(1j * (g + l)) * sin_t, zero, zero, one, zero, zero, torch.exp(1j * (g + p)) * sin_t, zero, torch.exp(1j * (g + p + l)) * cos_t ]) return elements.reshape(4, 4)
[docs] class CXGate(TwoQubitGate): """Controlled-X (CNOT) gate""" lowername = "cx"
[docs] @classmethod def create(cls, targets: Targets, params: tuple, options: Optional[dict] = None) -> 'CXGate': if params or options: raise ValueError(f"{cls.__name__} doesn't take parameters") return cls(targets)
[docs] def dagger(self): return self
[docs] def matrix(self): return torch.tensor([ [1, 0, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0], [0, 1, 0, 0] ], dtype=torch.complex128)
[docs] class CYGate(TwoQubitGate): """Controlled-Y gate""" lowername = "cy"
[docs] @classmethod def create(cls, targets: Targets, params: tuple, options: Optional[dict] = None) -> 'CYGate': if params or options: raise ValueError(f"{cls.__name__} doesn't take parameters") return cls(targets)
[docs] def dagger(self): return self
[docs] def matrix(self): return torch.tensor([ [1, 0, 0, 0], [0, 0, 0, -1j], [0, 0, 1, 0], [0, 1j, 0, 0] ], dtype=torch.complex128)
[docs] class CZGate(TwoQubitGate): """Controlled-Z gate""" lowername = "cz"
[docs] @classmethod def create(cls, targets: Targets, params: tuple, options: Optional[dict] = None) -> 'CZGate': if params or options: raise ValueError(f"{cls.__name__} doesn't take parameters") return cls(targets)
[docs] def dagger(self): return self
[docs] def matrix(self): return torch.tensor([ [1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, -1] ], dtype=torch.complex128)
[docs] class RXXGate(TwoQubitGate): """Rotate-XX gate""" lowername = "rxx" def __init__(self, targets, theta): super().__init__(targets, (theta, )) self.theta = theta
[docs] @classmethod def create(cls, targets: Targets, params: tuple, options: Optional[dict] = None) -> 'RXXGate': return cls(targets, *params)
[docs] def dagger(self): return RXXGate(self.targets, -self.theta)
[docs] def matrix(self): t = torch.as_tensor(self.theta, dtype=torch.complex128) * 0.5 cos_t = torch.cos(t) isin_t = -1j * torch.sin(t) zero = torch.zeros_like(t) elements = torch.stack([ cos_t, zero, zero, isin_t, zero, cos_t, isin_t, zero, zero, isin_t, cos_t, zero, isin_t, zero, zero, cos_t ]) return elements.reshape(4, 4)
[docs] class RYYGate(TwoQubitGate): """Rotate-YY gate""" lowername = "ryy" def __init__(self, targets, theta): super().__init__(targets, (theta, )) self.theta = theta
[docs] @classmethod def create(cls, targets: Targets, params: tuple, options: Optional[dict] = None) -> 'RYYGate': return cls(targets, *params)
[docs] def dagger(self): return RYYGate(self.targets, -self.theta)
[docs] def matrix(self): t = torch.as_tensor(self.theta, dtype=torch.complex128) * 0.5 cos_t = torch.cos(t) isin_t = 1j * torch.sin(t) zero = torch.zeros_like(t) elements = torch.stack([ cos_t, zero, zero, isin_t, zero, cos_t, -isin_t, zero, zero, -isin_t, cos_t, zero, isin_t, zero, zero, cos_t ]) return elements.reshape(4, 4)
[docs] class RZZGate(TwoQubitGate): """Rotate-ZZ gate""" lowername = "rzz" def __init__(self, targets, theta): super().__init__(targets, (theta, )) self.theta = theta
[docs] @classmethod def create(cls, targets: Targets, params: tuple, options: Optional[dict] = None) -> 'RZZGate': return cls(targets, *params)
[docs] def dagger(self): return RZZGate(self.targets, -self.theta)
[docs] def matrix(self): t = torch.as_tensor(self.theta, dtype=torch.complex128) * 0.5 a = torch.exp(1j * t) return torch.diag(torch.stack([a.conj(), a, a, a.conj()]))
[docs] class SwapGate(TwoQubitGate): """Swap gate""" lowername = "swap"
[docs] def dagger(self): return self
[docs] @classmethod def create(cls, targets: Targets, params: tuple, options: Optional[dict] = None) -> 'SwapGate': if params or options: raise ValueError(f"{cls.__name__} doesn't take parameters") return cls(targets)
[docs] def matrix(self): return torch.tensor([ [1, 0, 0, 0], [0, 0, 1, 0], [0, 1, 0, 0], [0, 0, 0, 1] ], dtype=torch.complex128)
[docs] class ZZGate(TwoQubitGate): """ZZ gate""" lowername = "zz" def __init__(self, targets): super().__init__(targets, ())
[docs] @classmethod def create(cls, targets: Targets, params: tuple, options: Optional[dict] = None) -> 'ZZGate': if params or options: raise ValueError(f"{cls.__name__} doesn't take parameters") return cls(targets)
[docs] def dagger(self): # matrix() is diag(1, 1j, 1j, 1), which is not Hermitian (its own conjugate # transpose is diag(1, -1j, -1j, 1) != itself), so the dagger is a distinct gate. return ZZDagGate(self.targets)
[docs] def matrix(self): return torch.diag(torch.tensor([1, 1j, 1j, 1], dtype=torch.complex128))
[docs] class ZZDagGate(TwoQubitGate): """Dagger of ZZ gate""" lowername = "zzdg" def __init__(self, targets): super().__init__(targets, ())
[docs] @classmethod def create(cls, targets: Targets, params: tuple, options: Optional[dict] = None) -> 'ZZDagGate': if params or options: raise ValueError(f"{cls.__name__} doesn't take parameters") return cls(targets)
[docs] def dagger(self): return ZZGate(self.targets)
[docs] def matrix(self): return torch.diag(torch.tensor([1, -1j, -1j, 1], dtype=torch.complex128))
[docs] class ISwapGate(TwoQubitGate, IFallbackOperation): """iSWAP gate: swaps two qubits and phases the swapped amplitudes by i.""" lowername = "iswap"
[docs] @classmethod def create(cls, targets: Targets, params: tuple, options: Optional[dict] = None) -> 'ISwapGate': if params or options: raise ValueError(f"{cls.__name__} doesn't take parameters") return cls(targets)
[docs] def dagger(self): return ISwapDagGate(self.targets)
[docs] def fallback(self, n_qubits): return self._make_fallback_for_control_target_iter( n_qubits, lambda c, t: [SGate(c), SGate(t), HGate(c), CXGate((c, t)), CXGate((t, c)), HGate(t)])
[docs] def matrix(self): # Symmetric in its two qubits, so the control/target bit convention # doesn't matter: |01> <-> i|10>. return torch.tensor([ [1, 0, 0, 0], [0, 0, 1j, 0], [0, 1j, 0, 0], [0, 0, 0, 1] ], dtype=torch.complex128)
[docs] class ISwapDagGate(TwoQubitGate, IFallbackOperation): """Dagger of iSWAP gate.""" lowername = "iswapdg"
[docs] @classmethod def create(cls, targets: Targets, params: tuple, options: Optional[dict] = None) -> 'ISwapDagGate': if params or options: raise ValueError(f"{cls.__name__} doesn't take parameters") return cls(targets)
[docs] def dagger(self): return ISwapGate(self.targets)
[docs] def fallback(self, n_qubits): return self._make_fallback_for_control_target_iter( n_qubits, lambda c, t: [HGate(t), CXGate((t, c)), CXGate((c, t)), HGate(c), SDagGate(t), SDagGate(c)])
[docs] def matrix(self): return torch.tensor([ [1, 0, 0, 0], [0, 0, -1j, 0], [0, -1j, 0, 0], [0, 0, 0, 1] ], dtype=torch.complex128)
[docs] class ExchangeGate(TwoQubitGate, IFallbackOperation): """Heisenberg exchange pulse, the native primitive of exchange-only (EO) spin-qubit hardware: U(theta) = exp(-i theta/2 (SWAP - I)), i.e. identity on the triplet (symmetric) subspace and phase e^{i theta} on the singlet. theta = J*t is the integrated pulse area (exchange integral x duration); theta = pi gives an exact SWAP, theta = pi/2 a sqrt-SWAP up to phase. Symmetric in its two qubits.""" lowername = "exch" def __init__(self, targets, theta): super().__init__(targets, (theta, )) self.theta = theta
[docs] @classmethod def create(cls, targets: Targets, params: tuple, options: Optional[dict] = None) -> 'ExchangeGate': if options: raise ValueError(f"{cls.__name__} doesn't take options") return cls(targets, params[0])
[docs] def dagger(self): return ExchangeGate(self.targets, -self.theta)
[docs] def fallback(self, n_qubits): # SWAP - I = (XX + YY + ZZ - I)/2, so U(theta) equals # rxx(theta/2) ryy(theta/2) rzz(theta/2) up to global phase e^{i theta/4}. return self._make_fallback_for_control_target_iter( n_qubits, lambda c, t: [RXXGate((c, t), self.theta * 0.5), RYYGate((c, t), self.theta * 0.5), RZZGate((c, t), self.theta * 0.5)])
[docs] def matrix(self): theta = torch.as_tensor(self.theta, dtype=torch.complex128) one = torch.ones_like(theta) zero = torch.zeros_like(theta) a = (one + torch.exp(1j * theta)) * 0.5 b = (one - torch.exp(1j * theta)) * 0.5 elements = torch.stack([ one, zero, zero, zero, zero, a, b, zero, zero, b, a, zero, zero, zero, zero, one ]) return elements.reshape(4, 4)
[docs] class Barrier(IFallbackOperation): """Barrier: a no-op marker separating circuit sections (as in Qiskit and OpenQASM). Simulation backends treat it as the identity via its empty fallback; the QASM output backend emits a real `barrier` statement.""" lowername = "barrier"
[docs] @classmethod def create(cls, targets: Targets, params: tuple, options: Optional[dict] = None) -> 'Barrier': if params or options: raise ValueError(f"{cls.__name__} doesn't take parameters") return cls(targets)
[docs] def fallback(self, _): return []
[docs] def dagger(self): return self
[docs] class GateBlock(IFallbackOperation): """A named group of operations, nestable to arbitrary depth. Blocks give circuits the hierarchical structure of real algorithms (Shor = init + modular exponentiation + inverse QFT, each built from smaller blocks) without changing how they execute: every backend sees the inner operations through `fallback()`, so simulation, QASM output and transpilation are unaffected. The structure shows up in `repr()` and in `Circuit.tree()`. Build blocks with `Circuit.block(name)` (a context manager) or `Circuit.append_block(name, subcircuit)`. """ lowername = "block" def __init__(self, name: str, ops: Optional[List['Operation']] = None): super().__init__((), ()) self.name = str(name) self.ops: List['Operation'] = list(ops) if ops is not None else []
[docs] @classmethod def create(cls, targets: Targets, params: tuple, options: Optional[dict] = None) -> 'GateBlock': raise ValueError( "GateBlock is structural and can't be created from the gate set; " "use Circuit.block(name) or Circuit.append_block(...).")
[docs] def fallback(self, _): return self.ops
[docs] def dagger(self): inner = [] for op in reversed(self.ops): if not hasattr(op, 'dagger'): raise ValueError( f"Cannot invert block '{self.name}': it contains a " f"non-invertible operation `{op.lowername}`.") inner.append(op.dagger()) name = self.name[:-1] if self.name.endswith('†') else self.name + '†' return GateBlock(name, inner)
[docs] def target_iter(self, n_qubits: int): seen = set() for op in self.ops: if isinstance(op, GateBlock): targets = op.target_iter(n_qubits) else: targets = op.target_iter(n_qubits) for t in targets: if t not in seen: seen.add(t) yield t
def __str__(self) -> str: return f"block({self.name!r}, <{len(self.ops)} ops>)"
[docs] class Measurement(Operation): """Measurement operation""" lowername = "measure" def __init__(self, targets: Targets, options: Optional[dict]): super().__init__(targets, ()) if options is None: options = {} key = options.get("key") self.key = str(key) if key is not None else None duplicated = options.get("duplicated") self.duplicated = str(duplicated) if duplicated is not None else None
[docs] @classmethod def create(cls, targets: Targets, params: tuple, options: Optional[dict] = None) -> 'Measurement': if params: raise ValueError(f"{cls.__name__} doesn't take params") return cls(targets, options)
[docs] def target_iter(self, n_qubits): return slicing(self.targets, n_qubits)
[docs] class Reset(Operation): """Reset operation""" lowername = "reset"
[docs] @classmethod def create(cls, targets: Targets, params: tuple, options: Optional[dict] = None) -> 'Reset': if params or options: raise ValueError(f"{cls.__name__} doesn't take params") return cls(targets)
[docs] def target_iter(self, n_qubits): return slicing(self.targets, n_qubits)
def slicing_singlevalue(arg: Union[slice, int], length: int) -> Iterator[int]: if isinstance(arg, slice): start, stop, step = arg.indices(length) i = start if step > 0: while i < stop: yield i i += step else: while i > stop: yield i i += step else: try: i = arg.__index__() except AttributeError: raise TypeError("indices must be integers or slices, not " + arg.__class__.__name__) from None if i < 0: i += length yield i def slicing(args: Targets, length: int) -> Iterator[int]: if isinstance(args, tuple): for arg in args: yield from slicing_singlevalue(arg, length) else: yield from slicing_singlevalue(args, length) def qubit_pairs(args: Tuple[Targets, Targets], length: int) -> Iterator[Tuple[int, int]]: if not isinstance(args, tuple) or len(args) != 2: raise ValueError("Control and target qubits pair(s) are required.") controls = list(slicing(args[0], length)) targets = list(slicing(args[1], length)) if len(controls) != len(targets): raise ValueError("The number of control qubits and target qubits must be the same.") for c, z in zip(controls, targets): if c == z: raise ValueError("Control qubit and target qubit must be different.") return zip(controls, targets) def get_maximum_index(indices: Targets) -> int: def _maximum_idx_single(idx: int): if isinstance(idx, slice): start = -1 stop = 0 if idx.start is not None: start = idx.start.__index__() if idx.stop is not None: stop = idx.stop.__index__() return max(start, stop - 1) return idx.__index__() if isinstance(indices, tuple): return max((_maximum_idx_single(i) for i in indices), default=-1) return _maximum_idx_single(indices)
[docs] def find_n_qubits(gates: Iterable[Operation]) -> int: return max((get_maximum_index(g.targets) for g in gates), default=-1) + 1