# !/usr/bin/env python3 # Copyright (c) 2022 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. # # 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. r""" The source file of the Circuit class. """ import warnings import paddle from .container import Sequential from ..gate import Gate, H, S, Sdg, T, Tdg, X, Y, Z, P, RX, RY, RZ, U3 from ..gate import CNOT, CX, CY, CZ, SWAP from ..gate import CP, CRX, CRY, CRZ, CU, RXX, RYY, RZZ from ..gate import MS, CSWAP, Toffoli from ..gate import UniversalTwoQubits, UniversalThreeQubits from ..gate import Oracle, ControlOracle from ..gate import SuperpositionLayer, WeakSuperpositionLayer, LinearEntangledLayer from ..gate import RealBlockLayer, RealEntangledLayer, ComplexBlockLayer, ComplexEntangledLayer from ..gate import QAOALayer from ..gate import AmplitudeEncoding from ..channel import BitFlip, PhaseFlip, BitPhaseFlip, AmplitudeDamping, GeneralizedAmplitudeDamping, PhaseDamping from ..channel import Depolarizing, GeneralizedDepolarizing, PauliChannel, ResetChannel, ThermalRelaxation from ..channel import MixedUnitaryChannel, KrausRepr from ..intrinsic import _get_float_dtype from ..state import zero_state from ..operator import Collapse from typing import Union, Iterable, Optional, Dict, List, Tuple from paddle_quantum import State, get_backend, get_dtype, Backend from math import pi import numpy as np import matplotlib import matplotlib.pyplot as plt from ..gate.functional.visual import _circuit_plot class Circuit(Sequential): r"""Quantum circuit. Args: num_qubits: Number of qubits. Defaults to None. """ def __init__(self, num_qubits: Optional[int] = None): super().__init__() self.__num_qubits = num_qubits # whether the circuit is a dynamic quantum circuit self.__isdynamic = num_qubits is None # alias for ccx self.toffoli = self.ccx @property def num_qubits(self) -> int: r"""Number of qubits. """ return self.__num_qubits @property def isdynamic(self) -> bool: r"""Whether the circuit is dynamic """ return self.__dynamic @num_qubits.setter def num_qubits(self, value: int) -> None: assert isinstance(value, int) self.__num_qubits = value @property def param(self) -> paddle.Tensor: r"""Flattened parameters in the circuit. """ if len(self.parameters()) == 0: return [] return paddle.concat([paddle.flatten(param) for param in self.parameters()]) @property def grad(self) -> np.ndarray: r"""Gradients with respect to the flattened parameters. """ grad_list = [] for param in self.parameters(): assert param.grad is not None, 'the gradient is None, run the backward first before calling this property,' + \ ' otherwise check where the gradient chain is broken' grad_list.append(paddle.flatten(param.grad)) return paddle.concat(grad_list).numpy() def update_param(self, theta: Union[paddle.Tensor, np.ndarray, float], idx: int = None) -> None: r"""Replace parameters of all/one layer(s) by ``theta``. Args: theta: New parameters idx: Index of replacement. Defaults to None, referring to all layers. """ if not isinstance(theta, paddle.Tensor): theta = paddle.to_tensor(theta, dtype='float32') theta = paddle.flatten(theta) backend_dtype = _get_float_dtype(get_dtype()) if backend_dtype != 'float32': warnings.warn( f"\ndtype of parameters will be float32 instead of {backend_dtype}", UserWarning) if idx is None: assert self.param.shape == theta.shape, \ f"the shape of input parameters is not correct: expect {self.param.shape}, received {theta.shape}" for layer in self.sublayers(): for name, _ in layer.named_parameters(): param = getattr(layer, name) num_param = int(paddle.numel(param)) param = paddle.create_parameter( shape=param.shape, dtype='float32', default_initializer=paddle.nn.initializer.Assign(theta[:num_param].reshape(param.shape)), ) setattr(layer, 'theta', param) if num_param == theta.shape[0]: return theta = theta[num_param:] elif isinstance(idx, int): assert idx < len(self.sublayers()), f"the index is out of range, expect below {len(self.sublayers())}" layer = self.sublayers()[idx] assert theta.shape == paddle.concat([paddle.flatten(param) for param in layer.parameters()]).shape, \ "the shape of input parameters is not correct," for name, _ in layer.named_parameters(): param = getattr(layer, name) num_param = int(paddle.numel(param)) param = paddle.create_parameter( shape=param.shape, dtype='float32', default_initializer=paddle.nn.initializer.Assign(theta[:num_param].reshape(param.shape)), ) setattr(layer, 'theta', param) if num_param == theta.shape[0]: return theta = theta[num_param:] else: raise ValueError("idx must be an integer or None") def transfer_static(self) -> None: r""" set ``stop_gradient`` of all parameters of the circuit as ``True`` """ for layer in self.sublayers(): for name, _ in layer.named_parameters(): param = getattr(layer, name) param.stop_gradient = True setattr(layer, 'theta', param) def randomize_param(self, low: float = 0, high: Optional[float] = 2 * pi) -> None: r"""Randomize parameters of the circuit in a range from low to high. Args: low: Lower bound. high: Upper bound. """ for layer in self.sublayers(): for name, _ in layer.named_parameters(): param = getattr(layer, name) param = paddle.create_parameter( shape=param.shape, dtype=param.dtype, default_initializer=paddle.nn.initializer.Uniform(low=low, high=high), ) setattr(layer, 'theta', param) def __num_qubits_update(self, qubits_idx: Union[Iterable[int], int, str]) -> None: r"""Update ``self.num_qubits`` according to ``qubits_idx``, or report error. Args: qubits_idx: Input qubit indices of a quantum gate. """ num_qubits = self.__num_qubits if isinstance(qubits_idx, str): assert num_qubits is not None, "The qubit idx cannot be full when the number of qubits is unknown" return if isinstance(qubits_idx, Iterable): max_idx = np.max(qubits_idx) else: max_idx = qubits_idx if num_qubits is None: self.__num_qubits = max_idx + 1 return assert max_idx + 1 <= num_qubits or self.__isdynamic, \ f"The circuit is not a dynamic quantum circuit. Invalid input qubit idx: {max_idx} num_qubit: {self.__num_qubits}" self.__num_qubits = int(max(max_idx + 1, num_qubits)) def h( self, qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: Optional[int] = None, depth: int = 1 ) -> None: r"""Add single-qubit Hadamard gates. The matrix form of such a gate is: .. math:: H = \frac{1}{\sqrt{2}} \begin{bmatrix} 1&1\\ 1&-1 \end{bmatrix} Args: qubits_idx: Indices of the qubits on which the gates are applied. Defaults to 'full'. num_qubits: Total number of qubits. Defaults to None. depth: Number of layers. Defaults to 1. """ self.__num_qubits_update(qubits_idx) self.append(H(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth)) def s( self, qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: Optional[int] = None, depth: int = 1 ) -> None: r"""Add single-qubit S gates. The matrix form of such a gate is: .. math:: S = \begin{bmatrix} 1&0\\ 0&i \end{bmatrix} Args: qubits_idx: Indices of the qubits on which the gates are applied. Defaults to 'full'. num_qubits: Total number of qubits. Defaults to None. depth: Number of layers. Defaults to 1. """ self.__num_qubits_update(qubits_idx) self.append( S(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth)) def sdg( self, qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: Optional[int] = None, depth: int = 1 ) -> None: r"""Add single-qubit S dagger (S inverse) gates. The matrix form of such a gate is: .. math:: S^\dagger = \begin{bmatrix} 1&0\\ 0&-i \end{bmatrix} Args: qubits_idx: Indices of the qubits on which the gates are applied. Defaults to ``'full'``. num_qubits: Total number of qubits. Defaults to ``None``. depth: Number of layers. Defaults to ``1``. """ self.__num_qubits_update(qubits_idx) self.append( Sdg(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth)) def t( self, qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: Optional[int] = None, depth: int = 1 ) -> None: r"""Add single-qubit T gates. The matrix form of such a gate is: .. math:: T = \begin{bmatrix} 1&0\\ 0&e^\frac{i\pi}{4} \end{bmatrix} Args: qubits_idx: Indices of the qubits on which the gates are applied. Defaults to 'full'. num_qubits: Total number of qubits. Defaults to None. depth: Number of layers. Defaults to 1. """ self.__num_qubits_update(qubits_idx) self.append( T(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth)) def tdg( self, qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: Optional[int] = None, depth: int = 1 ) -> None: r"""Add single-qubit T dagger (T inverse) gates. The matrix form of such a gate is: .. math:: T^\dagger = \begin{bmatrix} 1&0\\ 0&e^{-\frac{i\pi}{4}} \end{bmatrix} Args: qubits_idx: Indices of the qubits on which the gates are applied. Defaults to ``'full'``. num_qubits: Total number of qubits. Defaults to None. depth: Number of layers. Defaults to 1. """ self.__num_qubits_update(qubits_idx) self.append( Tdg(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth)) def x( self, qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: Optional[int] = None, depth: int = 1 ) -> None: r"""Add single-qubit X gates. The matrix form of such a gate is: .. math:: X = \begin{bmatrix} 0 & 1 \\ 1 & 0 \end{bmatrix} Args: qubits_idx: Indices of the qubits on which the gates are applied. Defaults to 'full'. num_qubits: Total number of qubits. Defaults to None. depth: Number of layers. Defaults to 1. """ self.__num_qubits_update(qubits_idx) self.append( X(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth)) def y( self, qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: Optional[int] = None, depth: int = 1 ) -> None: r"""Add single-qubit Y gates. The matrix form of such a gate is: .. math:: Y = \begin{bmatrix} 0 & -i \\ i & 0 \end{bmatrix} Args: qubits_idx: Indices of the qubits on which the gates are applied. Defaults to 'full'. num_qubits: Total number of qubits. Defaults to None. depth: Number of layers. Defaults to 1. """ self.__num_qubits_update(qubits_idx) self.append( Y(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth)) def z( self, qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: Optional[int] = None, depth: int = 1 ) -> None: r"""Add single-qubit Z gates. The matrix form of such a gate is: .. math:: Z = \begin{bmatrix} 1 & 0 \\ 0 & -1 \end{bmatrix} Args: qubits_idx: Indices of the qubits on which the gates are applied. Defaults to 'full'. num_qubits: Total number of qubits. Defaults to None. depth: Number of layers. Defaults to 1. """ self.__num_qubits_update(qubits_idx) self.append( Z(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth)) def p( self, qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: Optional[int] = None, depth: int = 1, param: Union[paddle.Tensor, float] = None, param_sharing: bool = False ) -> None: r"""Add single-qubit P gates. The matrix form of such a gate is: .. math:: P(\theta) = \begin{bmatrix} 1 & 0 \\ 0 & e^{i\theta} \end{bmatrix} Args: qubits_idx: Indices of the qubits on which the gates are applied. Defaults to 'full'. num_qubits: Total number of qubits. Defaults to None. depth: Number of layers. Defaults to 1. param: Parameters of the gates. Defaults to None. param_sharing: Whether gates in the same layer share a parameter. Defaults to False. """ self.__num_qubits_update(qubits_idx) self.append( P(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth, param, param_sharing)) def rx( self, qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: Optional[int] = None, depth: int = 1, param: Union[paddle.Tensor, float] = None, param_sharing: bool = False ) -> None: r"""Add single-qubit rotation gates about the x-axis. The matrix form of such a gate is: .. math:: R_X(\theta) = \begin{bmatrix} \cos\frac{\theta}{2} & -i\sin\frac{\theta}{2} \\ -i\sin\frac{\theta}{2} & \cos\frac{\theta}{2} \end{bmatrix} Args: qubits_idx: Indices of the qubits on which the gates are applied. Defaults to 'full'. num_qubits: Total number of qubits. Defaults to None. depth: Number of layers. Defaults to 1. param: Parameters of the gates. Defaults to None. param_sharing: Whether gates in the same layer share a parameter. Defaults to False. """ self.__num_qubits_update(qubits_idx) self.append(RX(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth, param, param_sharing)) def ry( self, qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: Optional[int] = None, depth: int = 1, param: Union[paddle.Tensor, float] = None, param_sharing: bool = False ) -> None: r"""Add single-qubit rotation gates about the y-axis. The matrix form of such a gate is: .. math:: R_Y(\theta) = \begin{bmatrix} \cos\frac{\theta}{2} & -\sin\frac{\theta}{2} \\ \sin\frac{\theta}{2} & \cos\frac{\theta}{2} \end{bmatrix} Args: qubits_idx: Indices of the qubits on which the gates are applied. Defaults to 'full'. num_qubits: Total number of qubits. Defaults to None. depth: Number of layers. Defaults to 1. param: Parameters of the gates. Defaults to None. param_sharing: Whether gates in the same layer share a parameter. Defaults to False. """ self.__num_qubits_update(qubits_idx) self.append(RY(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth, param, param_sharing)) def rz( self, qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: Optional[int] = None, depth: int = 1, param: Union[paddle.Tensor, float] = None, param_sharing: bool = False ) -> None: r"""Add single-qubit rotation gates about the z-axis. The matrix form of such a gate is: .. math:: R_Z(\theta) = \begin{bmatrix} e^{-i\frac{\theta}{2}} & 0 \\ 0 & e^{i\frac{\theta}{2}} \end{bmatrix} Args: qubits_idx: Indices of the qubits on which the gates are applied. Defaults to 'full'. num_qubits: Total number of qubits. Defaults to None. depth: Number of layers. Defaults to 1. param: Parameters of the gates. Defaults to None. param_sharing: Whether gates in the same layer share a parameter. Defaults to False. """ self.__num_qubits_update(qubits_idx) self.append(RZ(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth, param, param_sharing)) def u3( self, qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None, depth: int = 1, param: Union[paddle.Tensor, Iterable[float]] = None, param_sharing: bool = False ) -> None: r"""Add single-qubit rotation gates. The matrix form of such a gate is: .. math:: \begin{align} U_3(\theta, \phi, \lambda) = \begin{bmatrix} \cos\frac\theta2&-e^{i\lambda}\sin\frac\theta2\\ e^{i\phi}\sin\frac\theta2&e^{i(\phi+\lambda)}\cos\frac\theta2 \end{bmatrix} \end{align} Args: qubits_idx: Indices of the qubits on which the gates are applied. Defaults to 'full'. num_qubits: Total number of qubits. Defaults to None. depth: Number of layers. Defaults to 1. param: Parameters of the gates. Defaults to None. param_sharing: Whether gates in the same layer share a parameter. Defaults to False. """ self.__num_qubits_update(qubits_idx) self.append(U3(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth, param, param_sharing)) def cnot( self, qubits_idx: Union[Iterable[int], str] = 'cycle', num_qubits: int = None, depth: int = 1 ) -> None: r"""Add CNOT gates. For a 2-qubit quantum circuit, when `qubits_idx` is `[0, 1]`, the matrix form of such a gate is: .. math:: \begin{align} \mathit{CNOT} &=|0\rangle \langle 0|\otimes I + |1 \rangle \langle 1|\otimes X\\ &= \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 0 & 1 \\ 0 & 0 & 1 & 0 \end{bmatrix} \end{align} Args: qubits_idx: Indices of the qubits on which the gates are applied. Defaults to 'cycle'. num_qubits: Total number of qubits. Defaults to None. depth: Number of layers. Defaults to 1. """ self.__num_qubits_update(qubits_idx) self.append( CNOT(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth)) def cx( self, qubits_idx: Union[Iterable[int], str] = 'cycle', num_qubits: int = None, depth: int = 1 ) -> None: r"""Same as cnot. Args: qubits_idx: Indices of the qubits on which the gates are applied. Defaults to 'cycle'. num_qubits: Total number of qubits. Defaults to None. depth: Number of layers. Defaults to 1. """ self.__num_qubits_update(qubits_idx) self.append( CX(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth)) def cy( self, qubits_idx: Union[Iterable[int], str] = 'cycle', num_qubits: int = None, depth: int = 1 ) -> None: r"""Add controlled Y gates. For a 2-qubit quantum circuit, when `qubits_idx` is `[0, 1]`, the matrix form of such a gate is: .. math:: \begin{align} \mathit{CY} &=|0\rangle \langle 0|\otimes I + |1 \rangle \langle 1|\otimes Y\\ &= \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 0 & -1j \\ 0 & 0 & 1j & 0 \end{bmatrix} \end{align} Args: qubits_idx: Indices of the qubits on which the gates are applied. Defaults to 'cycle'. num_qubits: Total number of qubits. Defaults to None. depth: Number of layers. Defaults to 1. """ self.__num_qubits_update(qubits_idx) self.append( CY(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth)) def cz( self, qubits_idx: Union[Iterable[int], str] = 'cycle', num_qubits: int = None, depth: int = 1 ) -> None: r"""Add controlled Z gates. For a 2-qubit quantum circuit, when `qubits_idx` is `[0, 1]`, the matrix form of such a gate is: .. math:: \begin{align} \mathit{CZ} &=|0\rangle \langle 0|\otimes I + |1 \rangle \langle 1|\otimes Z\\ &= \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & -1 \end{bmatrix} \end{align} Args: qubits_idx: Indices of the qubits on which the gates are applied. Defaults to 'cycle'. num_qubits: Total number of qubits. Defaults to None. depth: Number of layers. Defaults to 1. """ self.__num_qubits_update(qubits_idx) self.append( CZ(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth)) def swap( self, qubits_idx: Union[Iterable[int], str] = 'cycle', num_qubits: int = None, depth: int = 1 ) -> None: r"""Add SWAP gates. The matrix form of such a gate is: .. math:: \begin{align} \mathit{SWAP} = \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \end{align} Args: qubits_idx: Indices of the qubits on which the gates are applied. Defaults to 'cycle'. num_qubits: Total number of qubits. Defaults to None. depth: Number of layers. Defaults to 1. """ self.__num_qubits_update(qubits_idx) self.append( SWAP(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth)) def cp( self, qubits_idx: Union[Iterable[int], str] = 'cycle', num_qubits: int = None, depth: int = 1, param: Union[paddle.Tensor, float] = None, param_sharing: bool = False ) -> None: r"""Add controlled P gates. For a 2-qubit quantum circuit, when `qubits_idx` is `[0, 1]`, the matrix form of such a gate is: .. math:: \begin{align} \mathit{CP}(\theta) = \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & e^{i\theta} \end{bmatrix} \end{align} Args: qubits_idx: Indices of the qubits on which the gates are applied. Defaults to 'cycle'. num_qubits: Total number of qubits. Defaults to None. depth: Number of layers. Defaults to 1. param: Parameters of the gates. Defaults to None. param_sharing: Whether gates in the same layer share a parameter. Defaults to False. """ self.__num_qubits_update(qubits_idx) self.append(CP(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth, param, param_sharing)) def crx( self, qubits_idx: Union[Iterable[int], str] = 'cycle', num_qubits: int = None, depth: int = 1, param: Union[paddle.Tensor, float] = None, param_sharing: bool = False ) -> None: r"""Add controlled rotation gates about the x-axis. For a 2-qubit quantum circuit, when `qubits_idx` is `[0, 1]`, the matrix form of such a gate is: .. math:: \begin{align} \mathit{CR_X} &=|0\rangle \langle 0|\otimes I + |1 \rangle \langle 1|\otimes R_X\\ &= \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & \cos\frac{\theta}{2} & -i\sin\frac{\theta}{2} \\ 0 & 0 & -i\sin\frac{\theta}{2} & \cos\frac{\theta}{2} \end{bmatrix} \end{align} Args: qubits_idx: Indices of the qubits on which the gates are applied. Defaults to 'cycle'. num_qubits: Total number of qubits. Defaults to None. depth: Number of layers. Defaults to 1. param: Parameters of the gates. Defaults to None. param_sharing: Whether gates in the same layer share a parameter. Defaults to False. """ self.__num_qubits_update(qubits_idx) self.append(CRX(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth, param, param_sharing)) def cry( self, qubits_idx: Union[Iterable[int], str] = 'cycle', num_qubits: int = None, depth: int = 1, param: Union[paddle.Tensor, float] = None, param_sharing: bool = False ) -> None: r"""Add controlled rotation gates about the y-axis. For a 2-qubit quantum circuit, when `qubits_idx` is `[0, 1]`, the matrix form of such a gate is: .. math:: \begin{align} \mathit{CR_Y} &=|0\rangle \langle 0|\otimes I + |1 \rangle \langle 1|\otimes R_Y\\ &= \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & \cos\frac{\theta}{2} & -\sin\frac{\theta}{2} \\ 0 & 0 & \sin\frac{\theta}{2} & \cos\frac{\theta}{2} \end{bmatrix} \end{align} Args: qubits_idx: Indices of the qubits on which the gates are applied. Defaults to 'cycle'. num_qubits: Total number of qubits. Defaults to None. depth: Number of layers. Defaults to 1. param: Parameters of the gates. Defaults to None. param_sharing: Whether gates in the same layer share a parameter. Defaults to False. """ self.__num_qubits_update(qubits_idx) self.append(CRY(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth, param, param_sharing)) def crz( self, qubits_idx: Union[Iterable[int], str] = 'cycle', num_qubits: int = None, depth: int = 1, param: Union[paddle.Tensor, float] = None, param_sharing: bool = False ) -> None: r"""Add controlled rotation gates about the z-axis. For a 2-qubit quantum circuit, when `qubits_idx` is `[0, 1]`, the matrix form of such a gate is: .. math:: \begin{align} \mathit{CR_Z} &= |0\rangle \langle 0|\otimes I + |1 \rangle \langle 1|\otimes R_Z\\ &= \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & e^{-i\frac{\theta}{2}} & 0 \\ 0 & 0 & 0 & e^{i\frac{\theta}{2}} \end{bmatrix} \end{align} Args: qubits_idx: Indices of the qubits on which the gates are applied. Defaults to 'cycle'. num_qubits: Total number of qubits. Defaults to None. depth: Number of layers. Defaults to 1. param: Parameters of the gates. Defaults to None. param_sharing: Whether gates in the same layer share a parameter. Defaults to False. """ self.__num_qubits_update(qubits_idx) self.append(CRZ(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth, param, param_sharing)) def cu( self, qubits_idx: Union[Iterable[int], str] = 'cycle', num_qubits: int = None, depth: int = 1, param: Union[paddle.Tensor, float] = None, param_sharing: bool = False ) -> None: r"""Add controlled single-qubit rotation gates. For a 2-qubit quantum circuit, when `qubits_idx` is `[0, 1]`, the matrix form of such a gate is: .. math:: \begin{align} \mathit{CU} &= \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & \cos\frac\theta2 &-e^{i\lambda}\sin\frac\theta2 \\ 0 & 0 & e^{i\phi}\sin\frac\theta2&e^{i(\phi+\lambda)}\cos\frac\theta2 \end{bmatrix} \end{align} Args: qubits_idx: Indices of the qubits on which the gates are applied. Defaults to 'cycle'. num_qubits: Total number of qubits. Defaults to None. depth: Number of layers. Defaults to 1. param: Parameters of the gates. Defaults to None. param_sharing: Whether gates in the same layer share a parameter. Defaults to False. """ self.__num_qubits_update(qubits_idx) self.append(CU(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth, param, param_sharing)) def rxx( self, qubits_idx: Union[Iterable[int], str] = 'cycle', num_qubits: int = None, depth: int = 1, param: Union[paddle.Tensor, float] = None, param_sharing: bool = False ) -> None: r"""Add RXX gates. The matrix form of such a gate is: .. math:: \begin{align} \mathit{R_{XX}}(\theta) = \begin{bmatrix} \cos\frac{\theta}{2} & 0 & 0 & -i\sin\frac{\theta}{2} \\ 0 & \cos\frac{\theta}{2} & -i\sin\frac{\theta}{2} & 0 \\ 0 & -i\sin\frac{\theta}{2} & \cos\frac{\theta}{2} & 0 \\ -i\sin\frac{\theta}{2} & 0 & 0 & \cos\frac{\theta}{2} \end{bmatrix} \end{align} Args: qubits_idx: Indices of the qubits on which the gates are applied. Defaults to 'cycle'. num_qubits: Total number of qubits. Defaults to None. depth: Number of layers. Defaults to 1. param: Parameters of the gates. Defaults to None. param_sharing: Whether gates in the same layer share a parameter. Defaults to False. """ self.__num_qubits_update(qubits_idx) self.append(RXX(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth, param, param_sharing)) def ryy( self, qubits_idx: Union[Iterable[int], str] = 'cycle', num_qubits: int = None, depth: int = 1, param: Union[paddle.Tensor, float] = None, param_sharing: bool = False ) -> None: r"""Add RYY gates. The matrix form of such a gate is: .. math:: \begin{align} \mathit{R_{YY}}(\theta) = \begin{bmatrix} \cos\frac{\theta}{2} & 0 & 0 & i\sin\frac{\theta}{2} \\ 0 & \cos\frac{\theta}{2} & -i\sin\frac{\theta}{2} & 0 \\ 0 & -i\sin\frac{\theta}{2} & \cos\frac{\theta}{2} & 0 \\ i\sin\frac{\theta}{2} & 0 & 0 & cos\frac{\theta}{2} \end{bmatrix} \end{align} Args: qubits_idx: Indices of the qubits on which the gates are applied. Defaults to 'cycle'. num_qubits: Total number of qubits. Defaults to None. depth: Number of layers. Defaults to 1. param: Parameters of the gates. Defaults to None. param_sharing: Whether gates in the same layer share a parameter. Defaults to False. """ self.__num_qubits_update(qubits_idx) self.append(RYY(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth, param, param_sharing)) def rzz( self, qubits_idx: Union[Iterable[int], str] = 'cycle', num_qubits: int = None, depth: int = 1, param: Union[paddle.Tensor, float] = None, param_sharing: bool = False ) -> None: r"""Add RZZ gates. The matrix form of such a gate is: .. math:: \begin{align} \mathit{R_{ZZ}}(\theta) = \begin{bmatrix} e^{-i\frac{\theta}{2}} & 0 & 0 & 0 \\ 0 & e^{i\frac{\theta}{2}} & 0 & 0 \\ 0 & 0 & e^{i\frac{\theta}{2}} & 0 \\ 0 & 0 & 0 & e^{-i\frac{\theta}{2}} \end{bmatrix} \end{align} Args: qubits_idx: Indices of the qubits on which the gates are applied. Defaults to 'cycle'. num_qubits: Total number of qubits. Defaults to None. depth: Number of layers. Defaults to 1. param: Parameters of the gates. Defaults to None. param_sharing: Whether gates in the same layer share a parameter. Defaults to False. """ self.__num_qubits_update(qubits_idx) self.append(RZZ(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth, param, param_sharing)) def ms( self, qubits_idx: Union[Iterable[int], str] = 'cycle', num_qubits: int = None, depth: int = 1 ) -> None: r"""Add Mølmer-Sørensen (MS) gates. The matrix form of such a gate is: .. math:: \begin{align} \mathit{MS} = \mathit{R_{XX}}(-\frac{\pi}{2}) = \frac{1}{\sqrt{2}} \begin{bmatrix} 1 & 0 & 0 & i \\ 0 & 1 & i & 0 \\ 0 & i & 1 & 0 \\ i & 0 & 0 & 1 \end{bmatrix} \end{align} Args: qubits_idx: Indices of the qubits on which the gates are applied. Defaults to 'cycle'. num_qubits: Total number of qubits. Defaults to None. depth: Number of layers. Defaults to 1. """ self.__num_qubits_update(qubits_idx) self.append( MS(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth)) def cswap( self, qubits_idx: Union[Iterable[int], str] = 'cycle', num_qubits: int = None, depth: int = 1 ) -> None: r"""Add CSWAP (Fredkin) gates. The matrix form of such a gate is: .. math:: \begin{align} \mathit{CSWAP} = \begin{bmatrix} 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 \end{bmatrix} \end{align} Args: qubits_idx: Indices of the qubits on which the gates are applied. Defaults to 'cycle'. num_qubits: Total number of qubits. Defaults to None. depth: Number of layers. Defaults to 1. """ self.__num_qubits_update(qubits_idx) self.append( CSWAP(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth)) def ccx( self, qubits_idx: Union[Iterable[int], str] = 'cycle', num_qubits: int = None, depth: int = 1 ) -> None: r"""Add CCX gates. The matrix form of such a gate is: .. math:: \begin{align} \mathit{CCX} = \begin{bmatrix} 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{bmatrix} \end{align} Args: qubits_idx: Indices of the qubits on which the gates are applied. Defaults to 'cycle'. num_qubits: Total number of qubits. Defaults to None. depth: Number of layers. Defaults to 1. """ self.__num_qubits_update(qubits_idx) self.append( Toffoli(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth)) def universal_two_qubits( self, qubits_idx: Union[Iterable[int], str] = 'cycle', num_qubits: int = None, depth: int = 1, param: Union[paddle.Tensor, float] = None, param_sharing: bool = False ) -> None: r"""Add universal two-qubit gates. One of such a gate requires 15 parameters. Args: qubits_idx: Indices of the qubits on which the gates are applied. Defaults to 'cycle'. num_qubits: Total number of qubits. Defaults to None. depth: Number of layers. Defaults to 1. param: Parameters of the gates. Defaults to None. param_sharing: Whether gates in the same layer share a parameter. Defaults to False. """ self.__num_qubits_update(qubits_idx) self.append(UniversalTwoQubits(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth, param, param_sharing)) def universal_three_qubits( self, qubits_idx: Union[Iterable[int], str] = 'cycle', num_qubits: int = None, depth: int = 1, param: Union[paddle.Tensor, float] = None, param_sharing: bool = False ) -> None: r"""Add universal three-qubit gates. One of such a gate requires 81 parameters. Args: qubits_idx: Indices of the qubits on which the gates are applied. Defaults to 'cycle'. num_qubits: Total number of qubits. Defaults to None. depth: Number of layers. Defaults to 1. param: Parameters of the gates. Defaults to None. param_sharing: Whether gates in the same layer share a parameter. Defaults to False. Raises: ValueError: The ``param`` must be paddle.Tensor or float. """ self.__num_qubits_update(qubits_idx) self.append(UniversalThreeQubits(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth, param, param_sharing)) def oracle( self, oracle: paddle.tensor, qubits_idx: Union[Iterable[Iterable[int]], Iterable[int], int], num_qubits: int = None, depth: int = 1, gate_name: Optional[str] = 'O', latex_name: Optional[str] = None, plot_width: Optional[float] = None ) -> None: """Add an oracle gate. Args: oracle: Unitary oracle to be implemented. qubits_idx: Indices of the qubits on which the gates are applied. num_qubits: Total number of qubits. Defaults to None. depth: Number of layers. Defaults to 1. gate_name: name of this oracle. latex_name: latex name of this oracle, default to be the gate name. plot_width: width of this gate in circuit plot, default to be proportional with the gate name. """ self.__num_qubits_update(qubits_idx) gate_info = {'gatename': gate_name, 'texname': f"${gate_name}$" if latex_name is None else latex_name, 'plot_width': 0.6 * len(gate_name) if plot_width is None else plot_width} self.append(Oracle(oracle, qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth, gate_info)) def control_oracle( self, oracle: paddle.Tensor, qubits_idx: Union[Iterable[Iterable[int]], Iterable[int]], num_qubits: int = None, depth: int = 1, gate_name: Optional[str] = 'O', latex_name: Optional[str] = None, plot_width: Optional[float] = None ) -> None: """Add a controlled oracle gate. Args: oracle: Unitary oracle to be implemented. qubits_idx: Indices of the qubits on which the gates are applied. num_qubits: Total number of qubits. Defaults to None. depth: Number of layers. Defaults to 1. gate_name: name of this oracle. latex_name: latex name of this oracle, default to be the gate name. plot_width: width of this gate in circuit plot, default to be proportional with the gate name. """ self.__num_qubits_update(qubits_idx) gate_info = {'gatename': f"c{gate_name}", 'texname': f"${gate_name}$" if latex_name is None else latex_name, 'plot_width': 0.6 * len(gate_name) if plot_width is None else plot_width} self.append(ControlOracle(oracle, qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth, gate_info)) def collapse(self, qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None, desired_result: Union[int, str] = None, if_print: bool = False, measure_basis: Union[Iterable[paddle.Tensor], str] = 'z') -> None: r""" Args: qubits_idx: list of qubits to be collapsed. Defaults to ``'full'``. num_qubits: Total number of qubits. Defaults to ``None``. desired_result: The desired result you want to collapse. Defaults to ``None`` meaning randomly choose one. if_print: whether print the information about the collapsed state. Defaults to ``False``. measure_basis: The basis of the measurement. The quantum state will collapse to the corresponding eigenstate. Raises: NotImplementedError: If the basis of measurement is not z. Other bases will be implemented in future. TypeError: cannot get probability of state when the backend is unitary_matrix. Note: When desired_result is `None`, Collapse does not support gradient calculation """ self.__num_qubits_update(qubits_idx) self.append(Collapse(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, desired_result, if_print, measure_basis)) def superposition_layer( self, qubits_idx: Iterable[int] = 'full', num_qubits: int = None, depth: int = 1 ) -> None: r"""Add layers of Hadamard gates. Args: qubits_idx: Indices of the qubits on which the gates are applied. Defaults to 'full'. num_qubits: Total number of qubits. Defaults to None. depth: Number of layers. Defaults to 1. """ self.__num_qubits_update(qubits_idx) self.append( SuperpositionLayer(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth)) def weak_superposition_layer( self, qubits_idx: Iterable[int] = 'full', num_qubits: int = None, depth: int = 1 ) -> None: r"""Add layers of Ry gates with a rotation angle :math:`\pi/4`. Args: qubits_idx: Indices of the qubits on which the gates are applied. Defaults to 'full'. num_qubits: Total number of qubits. Defaults to None. depth: Number of layers. Defaults to 1. """ self.__num_qubits_update(qubits_idx) self.append( WeakSuperpositionLayer(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth)) def linear_entangled_layer( self, qubits_idx: Iterable[int] = 'full', num_qubits: int = None, depth: int = 1 ) -> None: r"""Add linear entangled layers consisting of Ry gates, Rz gates, and CNOT gates. Args: qubits_idx: Indices of the qubits on which the gates are applied. Defaults to 'full'. num_qubits: Total number of qubits. Defaults to None. depth: Number of layers. Defaults to 1. """ self.__num_qubits_update(qubits_idx) self.append( LinearEntangledLayer(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth)) def real_entangled_layer( self, qubits_idx: Iterable[int] = 'full', num_qubits: int = None, depth: int = 1 ) -> None: r"""Add strongly entangled layers consisting of Ry gates and CNOT gates. Args: qubits_idx: Indices of the qubits on which the gates are applied. Defaults to 'full'. num_qubits: Total number of qubits. Defaults to None. depth: Number of layers. Defaults to 1. """ self.__num_qubits_update(qubits_idx) self.append( RealEntangledLayer(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth)) def complex_entangled_layer( self, qubits_idx: Iterable[int] = 'full', num_qubits: int = None, depth: int = 1 ) -> None: r"""Add strongly entangled layers consisting of single-qubit rotation gates and CNOT gates. Args: qubits_idx: Indices of the qubits on which the gates are applied. Defaults to 'full'. num_qubits: Total number of qubits. Defaults to None. depth: Number of layers. Defaults to 1. """ self.__num_qubits_update(qubits_idx) self.append( ComplexEntangledLayer(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth)) def real_block_layer( self, qubits_idx: Iterable[int] = 'full', num_qubits: int = None, depth: int = 1 ) -> None: r"""Add weakly entangled layers consisting of Ry gates and CNOT gates. Args: qubits_idx: Indices of the qubits on which the gates are applied. Defaults to 'full'. num_qubits: Total number of qubits. Defaults to None. depth: Number of layers. Defaults to 1. """ self.__num_qubits_update(qubits_idx) self.append( RealBlockLayer(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth)) def complex_block_layer( self, qubits_idx: Iterable[int] = 'full', num_qubits: int = None, depth: int = 1 ) -> None: r"""Add weakly entangled layers consisting of single-qubit rotation gates and CNOT gates. Args: qubits_idx: Indices of the qubits on which the gates are applied. Defaults to 'full'. num_qubits: Total number of qubits. Defaults to None. depth: Number of layers. Defaults to 1. """ self.__num_qubits_update(qubits_idx) self.append( ComplexBlockLayer(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth)) def bit_flip( self, prob: Union[paddle.Tensor, float], qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None ) -> None: r"""Add bit flip channels. Args: prob: Probability of a bit flip. qubits_idx: Indices of the qubits on which the channels are applied. Defaults to 'full'. num_qubits: Total number of qubits. Defaults to None. """ self.__num_qubits_update(qubits_idx) self.append(BitFlip(prob, qubits_idx, self.num_qubits if num_qubits is None else num_qubits)) def phase_flip( self, prob: Union[paddle.Tensor, float], qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None ) -> None: r"""Add phase flip channels. Args: prob: Probability of a phase flip. qubits_idx: Indices of the qubits on which the channels are applied. Defaults to 'full'. num_qubits: Total number of qubits. Defaults to None. """ self.__num_qubits_update(qubits_idx) self.append(PhaseFlip(prob, qubits_idx, self.num_qubits if num_qubits is None else num_qubits)) def bit_phase_flip( self, prob: Union[paddle.Tensor, float], qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None ) -> None: r"""Add bit phase flip channels. Args: prob: Probability of a bit phase flip. qubits_idx: Indices of the qubits on which the channels are applied. Defaults to 'full'. num_qubits: Total number of qubits. Defaults to None. """ self.__num_qubits_update(qubits_idx) self.append(BitPhaseFlip(prob, qubits_idx, self.num_qubits if num_qubits is None else num_qubits)) def amplitude_damping( self, gamma: Union[paddle.Tensor, float], qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None ) -> None: r"""Add amplitude damping channels. Args: gamma: Damping probability. qubits_idx: Indices of the qubits on which the channels are applied. Defaults to 'full'. num_qubits: Total number of qubits. Defaults to None. """ self.__num_qubits_update(qubits_idx) self.append(AmplitudeDamping(gamma, qubits_idx, self.num_qubits if num_qubits is None else num_qubits)) def generalized_amplitude_damping( self, gamma: Union[paddle.Tensor, float], prob: Union[paddle.Tensor, float], qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None ) -> None: r"""Add generalized amplitude damping channels. Args: gamma: Damping probability. Its value should be in the range :math:`[0, 1]`. prob: Excitation probability. Its value should be in the range :math:`[0, 1]`. qubits_idx: Indices of the qubits on which the channels are applied. Defaults to 'full'. num_qubits: Total number of qubits. Defaults to None. """ self.__num_qubits_update(qubits_idx) self.append(GeneralizedAmplitudeDamping(gamma, prob, qubits_idx, self.num_qubits if num_qubits is None else num_qubits)) def phase_damping( self, gamma: Union[paddle.Tensor, float], qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None ) -> None: r"""Add phase damping channels. Args: gamma: Parameter of the phase damping channel. qubits_idx: Indices of the qubits on which the channels are applied. Defaults to 'full'. num_qubits: Total number of qubits. Defaults to None. """ self.__num_qubits_update(qubits_idx) self.append(PhaseDamping(gamma, qubits_idx, self.num_qubits if num_qubits is None else num_qubits)) def depolarizing( self, prob: Union[paddle.Tensor, float], qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None ) -> None: r"""Add depolarizing channels. Args: prob: Parameter of the depolarizing channel. qubits_idx: Indices of the qubits on which the channels are applied. Defaults to 'full'. num_qubits: Total number of qubits. Defaults to None. """ self.__num_qubits_update(qubits_idx) self.append(Depolarizing(prob, qubits_idx, self.num_qubits if num_qubits is None else num_qubits)) def generalized_depolarizing( self, prob: Union[paddle.Tensor, float], qubits_idx: Union[Iterable[int], int, str], num_qubits: int = None ) -> None: r"""Add a general depolarizing channel. Args: prob: Probabilities corresponding to the Pauli basis. qubits_idx: Indices of the qubits on which the channel is applied. num_qubits: Total number of qubits. Defaults to None. """ self.__num_qubits_update(qubits_idx) self.append(PauliChannel(prob, qubits_idx, self.num_qubits if num_qubits is None else num_qubits)) def pauli_channel( self, prob: Union[paddle.Tensor, float], qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None ) -> None: r"""Add Pauli channels. Args: prob: Probabilities corresponding to the Pauli X, Y, and Z operators. qubits_idx: Indices of the qubits on which the channels are applied. Defaults to 'full'. num_qubits: Total number of qubits. Defaults to None. """ self.__num_qubits_update(qubits_idx) self.append(PauliChannel(prob, qubits_idx, self.num_qubits if num_qubits is None else num_qubits)) def reset_channel( self, prob: Union[paddle.Tensor, float], qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None ) -> None: r"""Add reset channels. Args: prob: Probabilities of resetting to :math:`|0\rangle` and to :math:`|1\rangle`. qubits_idx: Indices of the qubits on which the channels are applied. Defaults to 'full'. num_qubits: Total number of qubits. Defaults to None. """ self.__num_qubits_update(qubits_idx) self.append(ResetChannel(prob, qubits_idx, self.num_qubits if num_qubits is None else num_qubits)) def thermal_relaxation( self, const_t: Union[paddle.Tensor, Iterable[float]], exec_time: Union[paddle.Tensor, float], qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None ) -> None: r"""Add thermal relaxation channels. Args: const_t: :math:`T_1` and :math:`T_2` relaxation time in microseconds. exec_time: Quantum gate execution time in the process of relaxation in nanoseconds. qubits_idx: Indices of the qubits on which the channels are applied. Defaults to 'full'. num_qubits: Total number of qubits. Defaults to None. """ self.__num_qubits_update(qubits_idx) self.append(ThermalRelaxation(const_t, exec_time, qubits_idx, self.num_qubits if num_qubits is None else num_qubits)) def mixed_unitary_channel( self, num_unitary: Union[paddle.Tensor, int], qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None ) -> None: r"""Add mixed random unitary channels Args: num_unitary: The amount of random unitaries to be generated. qubits_idx: Indices of the qubits on which the channels act. Defaults to ``'full'``. num_qubits: Total number of qubits. Defaults to ``None``. """ self.__num_qubits_update(qubits_idx) self.append(MixedUnitaryChannel(num_unitary, qubits_idx, self.num_qubits if num_qubits is None else num_qubits)) def kraus_repr( self, kraus_oper: Iterable[paddle.Tensor], qubits_idx: Union[Iterable[Iterable[int]], Iterable[int], int], num_qubits: int = None ) -> None: r"""Add custom channels in the Kraus representation. Args: kraus_oper: Kraus operators of this channel. qubits_idx: Indices of the qubits on which the channels are applied. Defaults to 'full'. num_qubits: Total number of qubits. Defaults to None. """ self.__num_qubits_update(qubits_idx) self.append(KrausRepr(kraus_oper, qubits_idx, self.num_qubits if num_qubits is None else num_qubits)) def qaoa_layer(self, edges: Iterable, nodes: Iterable, depth: Optional[int] = 1) -> None: # TODO: see qaoa layer in layer.py self.__num_qubits_update(edges) self.__num_qubits_update(nodes) self.append(QAOALayer(edges, nodes, depth)) def unitary_matrix(self, num_qubits: Optional[int] = None) -> paddle.Tensor: r"""Get the unitary matrix form of the circuit. Args: num_qubits: Total number of qubits. Defaults to None. Returns: Unitary matrix form of the circuit. """ if num_qubits is None: num_qubits = self.__num_qubits else: assert num_qubits >= self.__num_qubits backend = get_backend() self.to(backend=Backend.UnitaryMatrix) unitary = State(paddle.eye(2 ** num_qubits).cast(get_dtype()), backend=Backend.UnitaryMatrix) for layer in self._sub_layers.values(): unitary = layer(unitary) self.to(backend=backend) return unitary.data @property def gate_history(self) -> List[Dict[str, Union[str, List[int], paddle.Tensor]]]: r""" list of gates information of circuit Returns: history of quantum gates of circuit """ gate_history = [] for gate in self.sublayers(): if gate.gate_info['gatename'] is None: raise NotImplementedError(f"Gate {type(gate)} has no gate name and hence cannot be recorded into history.") else: gate.gate_history_generation() gate_history.extend(gate.gate_history) return gate_history def __count_history(self, history): # Record length of each section length = [5] n = self.__num_qubits # Record current section number for every qubit qubit = [0] * n # Number of sections qubit_max = max(qubit) # Record section number for each gate gate = [] for current_gate in history: # Single-qubit gates with no params to print if current_gate['gate'] in {'h', 's', 't', 'x', 'y', 'z', 'u', 'sdg', 'tdg'}: curr_qubit = current_gate['which_qubits'] gate.append(qubit[curr_qubit]) qubit[curr_qubit] = qubit[curr_qubit] + 1 # A new section is added if qubit[curr_qubit] > qubit_max: length.append(5) qubit_max = qubit[curr_qubit] elif current_gate['gate'] in {'p', 'rx', 'ry', 'rz'}: curr_qubit = current_gate['which_qubits'] gate.append(qubit[curr_qubit]) if length[qubit[curr_qubit]] == 5: length[qubit[curr_qubit]] = 13 qubit[curr_qubit] = qubit[curr_qubit] + 1 if qubit[curr_qubit] > qubit_max: length.append(5) qubit_max = qubit[curr_qubit] elif current_gate['gate'] in {'cnot', 'swap', 'rxx', 'ryy', 'rzz', 'ms', 'cy', 'cz', 'cu', 'cp', 'crx', 'cry', 'crz', 'cswap', 'ccx'}: a = max(current_gate['which_qubits']) b = min(current_gate['which_qubits']) ind = max(qubit[b: a + 1]) gate.append(ind) if length[ind] < 13 and current_gate['gate'] in {'rxx', 'ryy', 'rzz', 'cp', 'crx', 'cry', 'crz'}: length[ind] = 13 for j in range(b, a + 1): qubit[j] = ind + 1 if ind + 1 > qubit_max: length.append(5) qubit_max = ind + 1 return length, gate @property def qubit_history(self) -> List[List[Tuple[Dict[str, Union[str, List[int], paddle.Tensor]], int]]]: r""" gate information on each qubit Returns: list of gate history on each qubit Note: The entry ``qubit_history[i][j][0/1]`` returns the gate information / gate index of the j-th gate applied on the i-th qubit. """ history_qubit = [[] for _ in range(self.num_qubits)] for idx, i in enumerate(self.gate_history): qubits = i["which_qubits"] if not isinstance(qubits, Iterable): history_qubit[qubits].append([i, idx]) else: for j in qubits: history_qubit[j].append([i, idx]) return history_qubit def __str__(self) -> str: history = self.gate_history num_qubits = self.__num_qubits length, gate = self.__count_history(history) # Ignore the unused section total_length = sum(length) - 5 print_list = [['-' if i % 2 == 0 else ' '] * total_length for i in range(num_qubits * 2)] for i, current_gate in enumerate(history): if current_gate['gate'] in {'h', 's', 't', 'x', 'y', 'z', 'u'}: # Calculate starting position ind of current gate sec = gate[i] ind = sum(length[:sec]) print_list[current_gate['which_qubits'] * 2][ind + length[sec] // 2] = current_gate['gate'].upper() elif current_gate['gate'] in {'sdg'}: sec = gate[i] ind = sum(length[:sec]) print_list[current_gate['which_qubits'] * 2][ ind + length[sec] // 2 - 1: ind + length[sec] // 2 + 2] = current_gate['gate'].upper() elif current_gate['gate'] in {'tdg'}: sec = gate[i] ind = sum(length[:sec]) print_list[current_gate['which_qubits'] * 2][ ind + length[sec] // 2 - 1: ind + length[sec] // 2 + 2] = current_gate['gate'].upper() elif current_gate['gate'] in {'p', 'rx', 'ry', 'rz'}: sec = gate[i] ind = sum(length[:sec]) line = current_gate['which_qubits'] * 2 # param = self.__param[current_gate['theta'][2 if current_gate['gate'] == 'rz' else 0]] param = current_gate['theta'] if current_gate['gate'] == 'p': print_list[line][ind + 2] = 'P' print_list[line][ind + 3] = ' ' else: print_list[line][ind + 2] = 'R' print_list[line][ind + 3] = current_gate['gate'][1] print_list[line][ind + 4] = '(' print_list[line][ind + 5: ind + 10] = format(float(param.numpy()), '.3f')[:5] print_list[line][ind + 10] = ')' # Two-qubit gates elif current_gate['gate'] in {'cnot', 'swap', 'rxx', 'ryy', 'rzz', 'ms', 'cz', 'cy', 'cu', 'crx', 'cry', 'crz'}: sec = gate[i] ind = sum(length[:sec]) cqubit = current_gate['which_qubits'][0] tqubit = current_gate['which_qubits'][1] if current_gate['gate'] in {'cnot', 'swap', 'cy', 'cz', 'cu'}: print_list[cqubit * 2][ind + length[sec] // 2] = \ '*' if current_gate['gate'] in {'cnot', 'cy', 'cz', 'cu'} else 'x' print_list[tqubit * 2][ind + length[sec] // 2] = \ 'x' if current_gate['gate'] in {'swap', 'cnot'} else current_gate['gate'][1] elif current_gate['gate'] == 'ms': for qubit in {cqubit, tqubit}: print_list[qubit * 2][ind + length[sec] // 2 - 1] = 'M' print_list[qubit * 2][ind + length[sec] // 2] = '_' print_list[qubit * 2][ind + length[sec] // 2 + 1] = 'S' elif current_gate['gate'] in {'rxx', 'ryy', 'rzz'}: # param = self.__param[current_gate['theta'][0]] param = current_gate['theta'] for line in {cqubit * 2, tqubit * 2}: print_list[line][ind + 2] = 'R' print_list[line][ind + 3: ind + 5] = current_gate['gate'][1:3].lower() print_list[line][ind + 5] = '(' print_list[line][ind + 6: ind + 10] = format(float(param.numpy()), '.2f')[:4] print_list[line][ind + 10] = ')' elif current_gate['gate'] in {'crx', 'cry', 'crz'}: # param = self.__param[current_gate['theta'][2 if current_gate['gate'] == 'crz' else 0]] param = current_gate['theta'] print_list[cqubit * 2][ind + length[sec] // 2] = '*' print_list[tqubit * 2][ind + 2] = 'R' print_list[tqubit * 2][ind + 3] = current_gate['gate'][2] print_list[tqubit * 2][ind + 4] = '(' print_list[tqubit * 2][ind + 5: ind + 10] = format(float(param.numpy()), '.3f')[:5] print_list[tqubit * 2][ind + 10] = ')' start_line = min(cqubit, tqubit) end_line = max(cqubit, tqubit) for k in range(start_line * 2 + 1, end_line * 2): print_list[k][ind + length[sec] // 2] = '|' # Three-qubit gates elif current_gate['gate'] in {'cswap'}: sec = gate[i] ind = sum(length[:sec]) cqubit = current_gate['which_qubits'][0] tqubit1 = current_gate['which_qubits'][1] tqubit2 = current_gate['which_qubits'][2] start_line = min(current_gate['which_qubits']) end_line = max(current_gate['which_qubits']) for k in range(start_line * 2 + 1, end_line * 2): print_list[k][ind + length[sec] // 2] = '|' if current_gate['gate'] in {'cswap'}: print_list[cqubit * 2][ind + length[sec] // 2] = '*' print_list[tqubit1 * 2][ind + length[sec] // 2] = 'x' print_list[tqubit2 * 2][ind + length[sec] // 2] = 'x' elif current_gate['gate'] in {'ccx'}: sec = gate[i] ind = sum(length[:sec]) cqubit1 = current_gate['which_qubits'][0] cqubit2 = current_gate['which_qubits'][1] tqubit = current_gate['which_qubits'][2] start_line = min(current_gate['which_qubits']) end_line = max(current_gate['which_qubits']) for k in range(start_line * 2 + 1, end_line * 2): print_list[k][ind + length[sec] // 2] = '|' if current_gate['gate'] in {'ccx'}: print_list[cqubit1 * 2][ind + length[sec] // 2] = '*' print_list[cqubit2 * 2][ind + length[sec] // 2] = '*' print_list[tqubit * 2][ind + length[sec] // 2] = 'X' else: raise NotImplementedError(f"Not support to print the gate {current_gate['gate']}.") print_list = list(map(''.join, print_list)) return_str = '\n'.join(print_list) return return_str def plot(self, save_path: Optional[str] = None, dpi: Optional[int] = 100, show: Optional[bool] = True, output: Optional[bool] = False, scale: Optional[float] = 1.0, tex: Optional[bool] = False, ) -> Union[None, matplotlib.figure.Figure]: r'''display the circuit using matplotlib Args: save_path: the save path of image dpi: dots per inches, here is resolution ratio show: whether execute ``plt.show()`` output: whether return the ``matplotlib.figure.Figure`` instance scale: scale coefficient of figure, default to 1.0 tex: a bool flag which controls latex fonts of gate display, default to ``False``. Returns: a ``matplotlib.figure.Figure`` instance or ``None`` depends on ``output`` Note: Using ``plt.show()`` may cause a distortion, but it will not happen in the figure saved. If the depth is too long, there will be some patches unable to display. Setting ``tex = True`` requires that you have TeX and the other dependencies properly installed on your system. See https://matplotlib.org/stable/gallery/text_labels_and_annotations/tex_demo.html for more details. ''' _fig = _circuit_plot(self, dpi=dpi, scale=scale, tex = tex) if save_path: plt.savefig(save_path, dpi=dpi,) if show: # whether display in window plt.show() if output: return _fig # return the ``matplotlib.pyplot.figure`` instance def extend(self, cir): r""" extend for quantum circuit Args: cir: a Circuit or a Sequential Returns: concatenation of two quantum circuits """ if isinstance(cir, Circuit): if self.__num_qubits is None: self.__num_qubits = cir.num_qubits else: self.__num_qubits = self.__num_qubits if cir.num_qubits is None else max(self.__num_qubits, cir.num_qubits) super().extend(cir) elif isinstance(cir, Sequential): super().extend(cir) else: raise TypeError("the input type must be Circuit or Sequential") def forward(self, state: Optional[State] = None) -> State: r""" forward the input Args: state: initial state Returns: output quantum state """ assert self.__num_qubits is not None, "Information about num_qubits is required before running the circuit" if state is None: state = zero_state(self.__num_qubits, self.backend, self.dtype) else: assert self.__num_qubits == state.num_qubits, \ f"num_qubits does not agree: expected {self.__num_qubits}, received {state.num_qubits}" return super().forward(state)