From eadb998d966510a7ab76380b6a20b501c39bdf6d Mon Sep 17 00:00:00 2001 From: Christopher Wood Date: Wed, 4 Dec 2019 18:35:19 -0500 Subject: [PATCH 1/6] PR 3703 Add RR basis to OneQubitEulerDecomposer Change simplify atol to simplify_tolerance --- qiskit/extensions/quantum_initializer/ucg.py | 4 +- qiskit/extensions/unitary.py | 8 +- qiskit/quantum_info/__init__.py | 2 +- qiskit/quantum_info/operators/operator.py | 1 + qiskit/quantum_info/synthesis/__init__.py | 4 +- .../synthesis/one_qubit_decompose.py | 428 ++++++++++++------ .../synthesis/two_qubit_decompose.py | 9 +- .../one-qubit-synthesis-c5e323717b8dfe4d.yaml | 16 + test/python/quantum_info/test_synthesis.py | 19 + 9 files changed, 347 insertions(+), 144 deletions(-) create mode 100644 releasenotes/notes/one-qubit-synthesis-c5e323717b8dfe4d.yaml diff --git a/qiskit/extensions/quantum_initializer/ucg.py b/qiskit/extensions/quantum_initializer/ucg.py index 80f0737aa227..c22ce1655b92 100644 --- a/qiskit/extensions/quantum_initializer/ucg.py +++ b/qiskit/extensions/quantum_initializer/ucg.py @@ -47,7 +47,7 @@ from qiskit.circuit.quantumregister import QuantumRegister from qiskit.circuit.quantumcircuit import QuantumCircuit from qiskit.exceptions import QiskitError -from qiskit.quantum_info.synthesis import euler_angles_1q +from qiskit.quantum_info.synthesis import OneQubitEulerDecomposer _EPS = 1e-10 # global variable used to chop very small numbers to zero @@ -121,7 +121,7 @@ def _dec_ucg(self): circuit = QuantumCircuit(q) # If there is no control, we use the ZYZ decomposition if not q_controls: - theta, phi, lamb = euler_angles_1q(self.params[0]) + theta, phi, lamb, _ = OneQubitEulerDecomposer._params_u3(self.params[0]) circuit.u3(theta, phi, lamb, q) return circuit, diag # If there is at least one control, first, diff --git a/qiskit/extensions/unitary.py b/qiskit/extensions/unitary.py index a5247c57a6ed..376c75e2f8de 100644 --- a/qiskit/extensions/unitary.py +++ b/qiskit/extensions/unitary.py @@ -25,8 +25,8 @@ from qiskit.extensions.standard import U3Gate from qiskit.quantum_info.operators.predicates import matrix_equal from qiskit.quantum_info.operators.predicates import is_unitary_matrix -from qiskit.quantum_info.synthesis import euler_angles_1q -from qiskit.quantum_info.synthesis import two_qubit_cnot_decompose +from qiskit.quantum_info.synthesis.one_qubit_decompose import OneQubitEulerDecomposer +from qiskit.quantum_info.synthesis.two_qubit_decompose import two_qubit_cnot_decompose from qiskit.extensions.exceptions import ExtensionError @@ -103,8 +103,8 @@ def _define(self): """Calculate a subcircuit that implements this unitary.""" if self.num_qubits == 1: q = QuantumRegister(1, "q") - angles = euler_angles_1q(self.to_matrix()) - self.definition = [(U3Gate(*angles), [q[0]], [])] + theta, phi, lam, _ = OneQubitEulerDecomposer._params_u3(self.to_matrix()) + self.definition = [(U3Gate(theta, phi, lam), [q[0]], [])] elif self.num_qubits == 2: self.definition = two_qubit_cnot_decompose(self.to_matrix()) else: diff --git a/qiskit/quantum_info/__init__.py b/qiskit/quantum_info/__init__.py index 540ee9354ef8..015a3c63aa37 100644 --- a/qiskit/quantum_info/__init__.py +++ b/qiskit/quantum_info/__init__.py @@ -104,7 +104,7 @@ euler_angles_1q two_qubit_cnot_decompose TwoQubitBasisDecomposer - + OneQubitEulerDecomposer """ from .operators.operator import Operator diff --git a/qiskit/quantum_info/operators/operator.py b/qiskit/quantum_info/operators/operator.py index bb492f33863e..278c758ce5ab 100644 --- a/qiskit/quantum_info/operators/operator.py +++ b/qiskit/quantum_info/operators/operator.py @@ -178,6 +178,7 @@ def to_operator(self): def to_instruction(self): """Convert to a UnitaryGate instruction.""" + # pylint: disable=cyclic-import from qiskit.extensions.unitary import UnitaryGate return UnitaryGate(self.data) diff --git a/qiskit/quantum_info/synthesis/__init__.py b/qiskit/quantum_info/synthesis/__init__.py index 7c874c90c1fa..8b3df9767700 100644 --- a/qiskit/quantum_info/synthesis/__init__.py +++ b/qiskit/quantum_info/synthesis/__init__.py @@ -14,4 +14,6 @@ """State and Unitary synthesis methods.""" -from .two_qubit_decompose import TwoQubitBasisDecomposer, euler_angles_1q, two_qubit_cnot_decompose +from .two_qubit_decompose import TwoQubitBasisDecomposer, two_qubit_cnot_decompose +from .one_qubit_decompose import OneQubitEulerDecomposer +from .two_qubit_decompose import euler_angles_1q # DEPRECATED diff --git a/qiskit/quantum_info/synthesis/one_qubit_decompose.py b/qiskit/quantum_info/synthesis/one_qubit_decompose.py index e71bf88e83be..4a9e69be8f4e 100644 --- a/qiskit/quantum_info/synthesis/one_qubit_decompose.py +++ b/qiskit/quantum_info/synthesis/one_qubit_decompose.py @@ -14,7 +14,7 @@ # pylint: disable=invalid-name """ -Decompose single-qubit unitary into Euler angles. +Decompose a single-qubit unitary via Euler angles. """ import math @@ -22,36 +22,89 @@ import scipy.linalg as la from qiskit.circuit.quantumcircuit import QuantumCircuit -from qiskit.extensions.standard import HGate, U3Gate, U1Gate, RXGate, RYGate, RZGate +from qiskit.extensions.standard import (U3Gate, U1Gate, RXGate, RYGate, RZGate, + RGate) from qiskit.exceptions import QiskitError -from qiskit.quantum_info.operators import Operator from qiskit.quantum_info.operators.predicates import is_unitary_matrix -DEFAULT_ATOL = 1e-12 +DEFAULT_SIMPLIFY_TOLERANCE = 1e-12 +DEFAULT_ATOL = 1e-7 class OneQubitEulerDecomposer: - """A class for decomposing 1-qubit unitaries into Euler angle rotations. - - Allowed basis and their decompositions are: - U3: U -> exp(1j*phase) * U3(theta, phi, lam) - U1X: U -> exp(1j*phase) * U1(lam).RX(pi/2).U1(theta+pi).RX(pi/2).U1(phi+pi) - ZYZ: U -> exp(1j*phase) * RZ(phi).RY(theta).RZ(lam) - ZXZ: U -> exp(1j*phase) * RZ(phi).RX(theta).RZ(lam) - XYX: U -> exp(1j*phase) * RX(phi).RY(theta).RX(lam) + r"""A class for decomposing 1-qubit unitaries into Euler angle rotations. + + The resulting decomposition is parameterized by 3 Euler rotation angle + parameters :math:`(\theta, \phi\ lambda)`, and a phase parameter + :math:`\gamma`. The value of the parameters for an input unitary depends + on the decomposition basis. Allowed bases and the resulting circuits are + shown in the following table. Note that for the non-Euler bases (U3, U1X, + RR), the ZYZ euler parameters are used. + + .. list-table:: Supported circuit bases + :widths: auto + :header-rows: 1 + + * - Basis + - Euler Angle Basis + - Decomposition Circuit + * - 'ZYZ' + - :math:`Z(\phi) Y(\theta) Z(\lambda)` + - :math:`e^{i\gamma} R_Z(\phi).R_Y(\theta).R_Z(\lambda)` + * - 'ZXZ' + - :math:`Z(\phi) X(\theta) Z(\lambda)` + - :math:`e^{i\gamma} R_Z(\phi).R_X(\theta).R_Z(\lambda)` + * - 'XYX' + - :math:`X(\phi) Y(\theta) X(\lambda)` + - :math:`e^{i\gamma} R_X(\phi).R_Y(\theta).R_X(\lambda)` + * - 'U3' + - :math:`Z(\phi) Y(\theta) Z(\lambda)` + - :math:`e^{i\gamma}{2}\right)\right)} U_3(\theta,\phi,\lambda)` + * - 'U1X' + - :math:`Z(\phi) Y(\theta) Z(\lambda)` + - :math:`e^{i \gamma}{2}\right)\right)} + :math:`U_1(\phi+\pi).R_X\left(\frac{\pi}{2}\right).U_1(\theta+\pi).` + :math:`R_X\left(\frac{\pi}{2}\right).U_1(\lambda)` + * - 'RR' + - :math:`Z(\phi) Y(\theta) Z(\lambda)` + - :math:`e^{i\gamma} R\left(-\pi,\frac{\phi-\lambda+\pi}{2}\right).` + :math:`R\left(\theta+\pi,\frac{\pi}{2}-\lambda\right)` """ def __init__(self, basis='U3'): - if basis not in ['U3', 'U1X', 'ZYZ', 'ZXZ', 'XYX']: - raise QiskitError("OneQubitEulerDecomposer: unsupported basis") - self._basis = basis + """Initialize decomposer + + Supported bases are: 'U3', 'U1X', 'RR', 'ZYZ', 'ZXZ', 'XYX'. + + Args: + basis (str): the decomposition basis [Default: 'U3] + + Raises: + QiskitError: If input basis is not recognized. + """ + # Default values + self._basis = 'U3' + self._params = self._params_u3 + self._circuit = self._circuit_u3 + # Set basis + self.basis = basis - def __call__(self, unitary_mat, simplify=True, atol=DEFAULT_ATOL): + def __call__(self, + unitary, + simplify=True, + simplify_tolerance=DEFAULT_SIMPLIFY_TOLERANCE, + phase_equal=False, + atol=DEFAULT_ATOL): """Decompose single qubit gate into a circuit. Args: - unitary_mat (array_like): 1-qubit unitary matrix - simplify (bool): remove zero-angle rotations [Default: True] - atol (float): absolute tolerance for checking angles zero. + unitary (Operator or Gate or array): 1-qubit unitary matrix + simplify (bool): reduce gate count in decomposition [Default: True]. + simplify_tolerance (float): absolute tolerance for checking + angles in simplify [Default: 1e-12]. + phase_equal (bool): verify the output circuit is phase equal + to the input matrix [Default: False]. + atol (bool): absolute tolerance for comparing synthesised circuit + matrix to input [Default: 1e-7]. Returns: QuantumCircuit: the decomposed single-qubit gate circuit @@ -59,188 +112,295 @@ def __call__(self, unitary_mat, simplify=True, atol=DEFAULT_ATOL): Raises: QiskitError: if input is invalid or synthesis fails. """ - if hasattr(unitary_mat, 'to_operator'): + if hasattr(unitary, 'to_operator'): # If input is a BaseOperator subclass this attempts to convert # the object to an Operator so that we can extract the underlying # numpy matrix from `Operator.data`. - unitary_mat = unitary_mat.to_operator().data - if hasattr(unitary_mat, 'to_matrix'): + unitary = unitary.to_operator().data + elif hasattr(unitary, 'to_matrix'): # If input is Gate subclass or some other class object that has # a to_matrix method this will call that method. - unitary_mat = unitary_mat.to_matrix() + unitary = unitary.to_matrix() # Convert to numpy array incase not already an array - unitary_mat = np.asarray(unitary_mat, dtype=complex) + unitary = np.asarray(unitary, dtype=complex) # Check input is a 2-qubit unitary - if unitary_mat.shape != (2, 2): + if unitary.shape != (2, 2): raise QiskitError("OneQubitEulerDecomposer: " "expected 2x2 input matrix") - if not is_unitary_matrix(unitary_mat): + if not is_unitary_matrix(unitary): raise QiskitError("OneQubitEulerDecomposer: " "input matrix is not unitary.") - circuit = self._circuit(unitary_mat, simplify=simplify, atol=atol) + theta, phi, lam, _ = self._params(unitary) + circuit = self._circuit(theta, phi, lam, + simplify=simplify, + simplify_tolerance=simplify_tolerance) # Check circuit is correct - if not Operator(circuit).equiv(Operator(unitary_mat)): - raise QiskitError("OneQubitEulerDecomposer: " - "synthesis failed within required accuracy.") + self.check_equiv(unitary, circuit, + phase_equal=phase_equal, + atol=atol) return circuit - def _angles(self, unitary_mat): - """Return Euler angles for given basis.""" - if self._basis in ['U3', 'U1X', 'ZYZ']: - return self._angles_zyz(unitary_mat) - if self._basis == 'ZXZ': - return self._angles_zxz(unitary_mat) - if self._basis == 'XYX': - return self._angles_xyx(unitary_mat) - raise QiskitError("OneQubitEulerDecomposer: invalid basis") - - def _circuit(self, unitary_mat, simplify=True, atol=DEFAULT_ATOL): - # NOTE: The 4th variable is phase to be used later - theta, phi, lam, _ = self._angles(unitary_mat) - if self._basis == 'U3': - return self._circuit_u3(theta, phi, lam) - if self._basis == 'U1X': - return self._circuit_u1x(theta, - phi, - lam, - simplify=simplify, - atol=atol) - if self._basis == 'ZYZ': - return self._circuit_zyz(theta, - phi, - lam, - simplify=simplify, - atol=atol) - if self._basis == 'ZXZ': - return self._circuit_zxz(theta, - phi, - lam, - simplify=simplify, - atol=atol) - if self._basis == 'XYX': - return self._circuit_xyx(theta, - phi, - lam, - simplify=simplify, - atol=atol) - raise QiskitError("OneQubitEulerDecomposer: invalid basis") + @property + def basis(self): + """The decomposition basis.""" + return self._basis + + @basis.setter + def basis(self, basis): + """Set the decomposition basis.""" + basis_methods = { + 'U3': (self._params_u3, self._circuit_u3), + 'U1X': (self._params_u1x, self._circuit_u1x), + 'RR': (self._params_zyz, self._circuit_rr), + 'ZYZ': (self._params_zyz, self._circuit_zyz), + 'ZXZ': (self._params_zxz, self._circuit_zxz), + 'XYX': (self._params_xyx, self._circuit_xyx) + } + if basis not in basis_methods: + raise QiskitError("OneQubitEulerDecomposer: unsupported basis {}".format(basis)) + self._basis = basis + self._params, self._circuit = basis_methods[self._basis] + + def angles(self, unitary): + """Return the Euler angles for input array. + + Args: + unitary (np.ndarray): 2x2 unitary matrix. + + Returns: + tuple: (theta, phi, lambda). + """ + theta, phi, lam, _ = self._params(unitary) + return theta, phi, lam + + def angles_and_phase(self, unitary): + """Return the Euler angles and phase for input array. + + Args: + unitary (np.ndarray): 2x2 unitary matrix. + + Returns: + tuple: (theta, phi, lambda, phase). + """ + return self._params(unitary) + + def circuit(self, theta, phi, lam, simplify=True, + simplify_tolerance=DEFAULT_SIMPLIFY_TOLERANCE): + """Return the basis circuit for the input parameters. + + Args: + theta (float): euler angle parameter + phi (float): euler angle parameter + lam (float): euler angle parameter + simplify (bool): simplify output circuit [Default: True] + simplify_tolerance (float): absolute tolerance for checking + angles zero [Default: 1e-12]. + + Returns: + QuantumCircuit: the basis circuits. + """ + return self._circuit(theta, phi, lam, + simplify=simplify, + simplify_tolerance=simplify_tolerance) @staticmethod - def _angles_zyz(unitary_mat): - """Return euler angles for special unitary matrix in ZYZ basis. + def check_equiv(unitary, circuit, phase_equal=False, atol=DEFAULT_ATOL): + """Check a circuit is equivalent to a unitary. + + Args: + unitary (Operator or Gate or array): unitary operator. + circuit (QuantumCircuit or Instruction): decomposition circuit. + phase_equal (bool): require the decomposition to be global phase + equal [Default: False] + atol (float): absolute tolerance for checking matrix entries. + [Default: 1e-7] - In this representation U = exp(1j * phase) * Rz(phi).Ry(theta).Rz(lam) + Raises: + QiskitError: if the input unitary and circuit are not equivalent. """ + # NOTE: this function isn't specific to this class so could be + # moved to another location for more general use. + + # pylint: disable=cyclic-import + from qiskit.quantum_info.operators import Operator + if phase_equal and not np.allclose(Operator(circuit).data, unitary, atol=atol): + raise QiskitError( + "Phase equal circuit synthesis failed within required accuracy." + ) + if not phase_equal and not Operator(circuit).equiv(unitary, atol=atol): + raise QiskitError( + "Circuit synthesis failed within required accuracy.") + + @staticmethod + def _params_zyz(mat): + """Return the euler angles and phase for the ZYZ basis.""" # We rescale the input matrix to be special unitary (det(U) = 1) # This ensures that the quaternion representation is real - coeff = la.det(unitary_mat)**(-0.5) + coeff = la.det(mat)**(-0.5) phase = -np.angle(coeff) - U = coeff * unitary_mat # U in SU(2) + su_mat = coeff * mat # U in SU(2) # OpenQASM SU(2) parameterization: # U[0, 0] = exp(-i(phi+lambda)/2) * cos(theta/2) # U[0, 1] = -exp(-i(phi-lambda)/2) * sin(theta/2) # U[1, 0] = exp(i(phi-lambda)/2) * sin(theta/2) # U[1, 1] = exp(i(phi+lambda)/2) * cos(theta/2) - theta = 2 * math.atan2(abs(U[1, 0]), abs(U[0, 0])) - phiplambda = 2 * np.angle(U[1, 1]) - phimlambda = 2 * np.angle(U[1, 0]) + theta = 2 * math.atan2(abs(su_mat[1, 0]), abs(su_mat[0, 0])) + phiplambda = 2 * np.angle(su_mat[1, 1]) + phimlambda = 2 * np.angle(su_mat[1, 0]) phi = (phiplambda + phimlambda) / 2.0 lam = (phiplambda - phimlambda) / 2.0 return theta, phi, lam, phase @staticmethod - def _angles_zxz(unitary_mat): - """Return euler angles for special unitary matrix in ZXZ basis. - - In this representation U = exp(1j * phase) * Rz(phi).Rx(theta).Rz(lam) - """ - theta, phi, lam, phase = OneQubitEulerDecomposer._angles_zyz(unitary_mat) + def _params_zxz(mat): + """Return the euler angles and phase for the ZXZ basis.""" + theta, phi, lam, phase = OneQubitEulerDecomposer._params_zyz(mat) return theta, phi + np.pi / 2, lam - np.pi / 2, phase @staticmethod - def _angles_xyx(unitary_mat): - """Return euler angles for special unitary matrix in XYX basis. - - In this representation U = exp(1j * phase) * Rx(phi).Ry(theta).Rx(lam) - """ + def _params_xyx(mat): + """Return the euler angles and phase for the XYX basis.""" # We use the fact that # Rx(a).Ry(b).Rx(c) = H.Rz(a).Ry(-b).Rz(c).H - had = HGate().to_matrix() - mat_zyz = np.dot(np.dot(had, unitary_mat), had) - theta, phi, lam, phase = OneQubitEulerDecomposer._angles_zyz(mat_zyz) + mat_zyz = 0.5 * np.array( + [[ + mat[0, 0] + mat[0, 1] + mat[1, 0] + mat[1, 1], + mat[0, 0] - mat[0, 1] + mat[1, 0] - mat[1, 1] + ], + [ + mat[0, 0] + mat[0, 1] - mat[1, 0] - mat[1, 1], + mat[0, 0] - mat[0, 1] - mat[1, 0] + mat[1, 1] + ]], + dtype=complex) + theta, phi, lam, phase = OneQubitEulerDecomposer._params_zyz(mat_zyz) return -theta, phi, lam, phase @staticmethod - def _circuit_u3(theta, phi, lam): - circuit = QuantumCircuit(1) - circuit.append(U3Gate(theta, phi, lam), [0]) - return circuit + def _params_u3(mat): + """Return the euler angles and phase for the U3 basis.""" + # The determinant of U3 gate depends on its params + # via det(u3(theta, phi, lam)) = exp(1j*(phi+lam)) + # Since the phase is wrt to a SU matrix we must rescale + # phase to correct this + theta, phi, lam, phase = OneQubitEulerDecomposer._params_zyz(mat) + return theta, phi, lam, phase - 0.5 * (phi + lam) @staticmethod - def _circuit_u1x(theta, phi, lam, simplify=True, atol=DEFAULT_ATOL): - # Check for U1 and U2 decompositions into minimimal - # required X90 pulses - if simplify and np.allclose([theta, phi], [0., 0.], atol=atol): - # zero X90 gate decomposition - circuit = QuantumCircuit(1) - circuit.append(U1Gate(lam), [0]) - return circuit - if simplify and np.isclose(theta, np.pi / 2, atol=atol): - # single X90 gate decomposition - circuit = QuantumCircuit(1) - circuit.append(U1Gate(lam - np.pi / 2), [0]) - circuit.append(RXGate(np.pi / 2), [0]) - circuit.append(U1Gate(phi + np.pi / 2), [0]) - return circuit - # General two-X90 gate decomposition - circuit = QuantumCircuit(1) - circuit.append(U1Gate(lam), [0]) - circuit.append(RXGate(np.pi / 2), [0]) - circuit.append(U1Gate(theta + np.pi), [0]) - circuit.append(RXGate(np.pi / 2), [0]) - circuit.append(U1Gate(phi + np.pi), [0]) - return circuit + def _params_u1x(mat): + """Return the euler angles and phase for the U1X basis.""" + # The determinant of this decomposition depends on its params + # Since the phase is wrt to a SU matrix we must rescale + # phase to correct this + theta, phi, lam, phase = OneQubitEulerDecomposer._params_zyz(mat) + return theta, phi, lam, phase - 0.5 * (theta + phi + lam) @staticmethod - def _circuit_zyz(theta, phi, lam, simplify=True, atol=DEFAULT_ATOL): + def _circuit_zyz(theta, + phi, + lam, + simplify=True, + simplify_tolerance=DEFAULT_SIMPLIFY_TOLERANCE): circuit = QuantumCircuit(1) - if simplify and np.isclose(theta, 0.0, atol=atol): + if simplify and np.isclose(theta, 0.0, atol=simplify_tolerance): circuit.append(RZGate(phi + lam), [0]) return circuit - if not simplify or not np.isclose(lam, 0.0, atol=atol): + if not simplify or not np.isclose(lam, 0.0, atol=simplify_tolerance): circuit.append(RZGate(lam), [0]) - if not simplify or not np.isclose(theta, 0.0, atol=atol): + if not simplify or not np.isclose(theta, 0.0, atol=simplify_tolerance): circuit.append(RYGate(theta), [0]) - if not simplify or not np.isclose(phi, 0.0, atol=atol): + if not simplify or not np.isclose(phi, 0.0, atol=simplify_tolerance): circuit.append(RZGate(phi), [0]) return circuit @staticmethod - def _circuit_zxz(theta, phi, lam, simplify=False, atol=DEFAULT_ATOL): - if simplify and np.isclose(theta, 0.0, atol=atol): + def _circuit_zxz(theta, + phi, + lam, + simplify=False, + simplify_tolerance=DEFAULT_SIMPLIFY_TOLERANCE): + if simplify and np.isclose(theta, 0.0, atol=simplify_tolerance): circuit = QuantumCircuit(1) circuit.append(RZGate(phi + lam), [0]) return circuit circuit = QuantumCircuit(1) - if not simplify or not np.isclose(lam, 0.0, atol=atol): + if not simplify or not np.isclose(lam, 0.0, atol=simplify_tolerance): circuit.append(RZGate(lam), [0]) - if not simplify or not np.isclose(theta, 0.0, atol=atol): + if not simplify or not np.isclose(theta, 0.0, atol=simplify_tolerance): circuit.append(RXGate(theta), [0]) - if not simplify or not np.isclose(phi, 0.0, atol=atol): + if not simplify or not np.isclose(phi, 0.0, atol=simplify_tolerance): circuit.append(RZGate(phi), [0]) return circuit @staticmethod - def _circuit_xyx(theta, phi, lam, simplify=True, atol=DEFAULT_ATOL): + def _circuit_xyx(theta, + phi, + lam, + simplify=True, + simplify_tolerance=DEFAULT_SIMPLIFY_TOLERANCE): circuit = QuantumCircuit(1) - if simplify and np.isclose(theta, 0.0, atol=atol): + if simplify and np.isclose(theta, 0.0, atol=simplify_tolerance): circuit.append(RXGate(phi + lam), [0]) return circuit - if not simplify or not np.isclose(lam, 0.0, atol=atol): + if not simplify or not np.isclose(lam, 0.0, atol=simplify_tolerance): circuit.append(RXGate(lam), [0]) - if not simplify or not np.isclose(theta, 0.0, atol=atol): + if not simplify or not np.isclose(theta, 0.0, atol=simplify_tolerance): circuit.append(RYGate(theta), [0]) - if not simplify or not np.isclose(phi, 0.0, atol=atol): + if not simplify or not np.isclose(phi, 0.0, atol=simplify_tolerance): circuit.append(RXGate(phi), [0]) return circuit + + @staticmethod + def _circuit_u3(theta, + phi, + lam, + simplify=True, + simplify_tolerance=DEFAULT_SIMPLIFY_TOLERANCE): + # pylint: disable=unused-argument + circuit = QuantumCircuit(1) + circuit.append(U3Gate(theta, phi, lam), [0]) + return circuit + + @staticmethod + def _circuit_u1x(theta, + phi, + lam, + simplify=True, + simplify_tolerance=DEFAULT_SIMPLIFY_TOLERANCE): + # Shift theta and phi so decomposition is + # U1(phi).X90.U1(theta).X90.U1(lam) + theta += np.pi + phi += np.pi + # Check for decomposition into minimimal number required X90 pulses + if simplify and np.isclose(abs(theta), np.pi, atol=simplify_tolerance): + # Zero X90 gate decomposition + circuit = QuantumCircuit(1) + circuit.append(U1Gate(lam + phi + theta), [0]) + return circuit + if simplify and np.isclose(abs(theta), np.pi/2, atol=simplify_tolerance): + # Single X90 gate decomposition + circuit = QuantumCircuit(1) + circuit.append(U1Gate(lam + theta), [0]) + circuit.append(RXGate(np.pi / 2), [0]) + circuit.append(U1Gate(phi + theta), [0]) + return circuit + # General two-X90 gate decomposition + circuit = QuantumCircuit(1) + circuit.append(U1Gate(lam), [0]) + circuit.append(RXGate(np.pi / 2), [0]) + circuit.append(U1Gate(theta), [0]) + circuit.append(RXGate(np.pi / 2), [0]) + circuit.append(U1Gate(phi), [0]) + return circuit + + @staticmethod + def _circuit_rr(theta, + phi, + lam, + simplify=True, + simplify_tolerance=DEFAULT_SIMPLIFY_TOLERANCE): + circuit = QuantumCircuit(1) + if not simplify or not np.isclose(theta, -np.pi, atol=simplify_tolerance): + circuit.append(RGate(theta + np.pi, np.pi / 2 - lam), [0]) + circuit.append(RGate(-np.pi, 0.5 * (phi - lam + np.pi)), [0]) + return circuit diff --git a/qiskit/quantum_info/synthesis/two_qubit_decompose.py b/qiskit/quantum_info/synthesis/two_qubit_decompose.py index a921d9addad3..e62c9536693e 100644 --- a/qiskit/quantum_info/synthesis/two_qubit_decompose.py +++ b/qiskit/quantum_info/synthesis/two_qubit_decompose.py @@ -38,12 +38,14 @@ from qiskit.exceptions import QiskitError from qiskit.quantum_info.operators.predicates import is_unitary_matrix from qiskit.quantum_info.synthesis.weyl import weyl_coordinates +from qiskit.quantum_info.synthesis.one_qubit_decompose import OneQubitEulerDecomposer _CUTOFF_PRECISION = 1e-12 +_DECOMP1Q = OneQubitEulerDecomposer('U3') def euler_angles_1q(unitary_matrix): - """Compute Euler angles for a single-qubit gate. + """DEPRECATED: Compute Euler angles for a single-qubit gate. Find angles (theta, phi, lambda) such that unitary_matrix = phase * Rz(phi) * Ry(theta) * Rz(lambda) @@ -57,6 +59,9 @@ def euler_angles_1q(unitary_matrix): Raises: QiskitError: if unitary_matrix not 2x2, or failure """ + warnings.warn("euler_angles_q1` is deprecated. " + "Use `synthesis.OneQubitEulerDecomposer().angles instead.", + DeprecationWarning) if unitary_matrix.shape != (2, 2): raise QiskitError("euler_angles_1q: expected 2x2 matrix") phase = la.det(unitary_matrix)**(-1.0/2.0) @@ -446,7 +451,7 @@ def __call__(self, target, basis_fidelity=None): best_nbasis = np.argmax(expected_fidelities) decomposition = self.decomposition_fns[best_nbasis](target_decomposed) - decomposition_angles = [euler_angles_1q(x) for x in decomposition] + decomposition_angles = [_DECOMP1Q.angles(x) for x in decomposition] q = QuantumRegister(2) return_circuit = QuantumCircuit(q) diff --git a/releasenotes/notes/one-qubit-synthesis-c5e323717b8dfe4d.yaml b/releasenotes/notes/one-qubit-synthesis-c5e323717b8dfe4d.yaml new file mode 100644 index 000000000000..ed149d345677 --- /dev/null +++ b/releasenotes/notes/one-qubit-synthesis-c5e323717b8dfe4d.yaml @@ -0,0 +1,16 @@ +--- +features: + - | + Improves the `quantum_info.synthesis.OneQubitEulerDecomposer` class by + adding methods `angles`, `angles_and_phase`, `circuits` methods for + returning relevant parameters without validation, and the `__call__` + method which performs full synthesis with validation. + - | + Adds `RR` decomposition basis to the + `quantum_info.synthesis.OneQubitEulerDecomposer` for decomposiing an + arbitrary 2x2 unitary into a two `RGate` circuit. +deprecations: + - | + Deprecates `quantum_info.synthesis.euler_angles_1q` It is superseded by the + `qauntum_info.synthesis.OneQubitEulerDecomposer` class which provides the + same functionality though `OneQubitEulerDecomposer().angles(mat)`. diff --git a/test/python/quantum_info/test_synthesis.py b/test/python/quantum_info/test_synthesis.py index 8e729630e9a6..e5b950e66a25 100644 --- a/test/python/quantum_info/test_synthesis.py +++ b/test/python/quantum_info/test_synthesis.py @@ -216,6 +216,25 @@ def test_one_qubit_random_xyx_basis(self, nsamples=50): unitary = random_unitary(2) self.check_one_qubit_euler_angles(unitary, 'XYX') + # R, R basis + def test_one_qubit_clifford_rr_basis(self): + """Verify for r, r basis and all Cliffords.""" + for clifford in ONEQ_CLIFFORDS: + self.check_one_qubit_euler_angles(clifford, 'RR') + + def test_one_qubit_hard_thetas_rr_basis(self): + """Verify for r, r basis and close-to-degenerate theta.""" + # We lower tolerance for this test since decomposition since it + # appears to be less numerically accurate. + for gate in HARD_THETA_ONEQS: + self.check_one_qubit_euler_angles(Operator(gate), 'RR', 1e-7) + + def test_one_qubit_random_rr_basis(self, nsamples=50): + """Verify for r, r basis and random unitaries.""" + for _ in range(nsamples): + unitary = random_unitary(2) + self.check_one_qubit_euler_angles(unitary, 'RR') + # FIXME: streamline the set of test cases class TestTwoQubitWeylDecomposition(QiskitTestCase): From 31c000a77f8c6339727e96345822b81545a8c7ea Mon Sep 17 00:00:00 2001 From: "Christopher J. Wood" Date: Thu, 14 Nov 2019 14:07:14 -0500 Subject: [PATCH 2/6] add phase property to gates * Added phase to base Gate class * Added phase to base ControlledGate class * Added phase to standard gate extensions --- qiskit/circuit/add_control.py | 4 ++ qiskit/circuit/controlledgate.py | 16 +++-- qiskit/circuit/gate.py | 40 ++++++++++- qiskit/extensions/standard/h.py | 71 +++++++++++------- qiskit/extensions/standard/iden.py | 22 ++++-- qiskit/extensions/standard/ms.py | 19 ++--- qiskit/extensions/standard/r.py | 37 ++++++---- qiskit/extensions/standard/rx.py | 82 ++++++++++++++------- qiskit/extensions/standard/rxx.py | 56 ++++++++------- qiskit/extensions/standard/ry.py | 81 +++++++++++++++------ qiskit/extensions/standard/rz.py | 84 ++++++++++++++++------ qiskit/extensions/standard/rzz.py | 46 ++++++++---- qiskit/extensions/standard/s.py | 62 ++++++++++------ qiskit/extensions/standard/swap.py | 83 ++++++++++++++------- qiskit/extensions/standard/t.py | 62 ++++++++++------ qiskit/extensions/standard/u1.py | 84 +++++++++++++++------- qiskit/extensions/standard/u2.py | 35 ++++++--- qiskit/extensions/standard/u3.py | 76 +++++++++++++++----- qiskit/extensions/standard/x.py | 112 ++++++++++++++++++++--------- qiskit/extensions/standard/y.py | 79 +++++++++++++------- qiskit/extensions/standard/z.py | 75 ++++++++++++------- 21 files changed, 858 insertions(+), 368 deletions(-) diff --git a/qiskit/circuit/add_control.py b/qiskit/circuit/add_control.py index ae85a063b62e..580f5a3c864d 100644 --- a/qiskit/circuit/add_control.py +++ b/qiskit/circuit/add_control.py @@ -32,6 +32,10 @@ def add_control(operation, num_ctrl_qubits, label): num_qubits + 2*num_ctrl_qubits - 1. """ import qiskit.extensions.standard as standard + if operation.phase: + # If gate has a global phase set we convert to unitary gate before + # making the controled version + operation = UnitaryGate(operation.to_matrix()) if isinstance(operation, standard.RZGate) or operation.name == 'rz': # num_ctrl_qubits > 1 # the condition matching 'name' above is to catch a test case, diff --git a/qiskit/circuit/controlledgate.py b/qiskit/circuit/controlledgate.py index 5d67261d1b6d..e196a5ed167c 100644 --- a/qiskit/circuit/controlledgate.py +++ b/qiskit/circuit/controlledgate.py @@ -23,21 +23,22 @@ class ControlledGate(Gate): """Controlled unitary gate.""" - def __init__(self, name, num_qubits, params, label=None, num_ctrl_qubits=1, - definition=None): + def __init__(self, name, num_qubits, params, phase=0, label=None, + num_ctrl_qubits=1, definition=None): """Create a new gate. Args: name (str): The Qobj name of the gate. num_qubits (int): The number of qubits the gate acts on. params (list): A list of parameters. - label (str or None): An optional label for the gate [Default: None] + phase (float): set the gate phase (Default: 0). + label (str or None): An optional label for the gate (Default: None). num_ctrl_qubits (int): Number of control qubits. definition (list): list of gate rules for implementing this gate. Raises: CircuitError: num_ctrl_qubits >= num_qubits """ - super().__init__(name, num_qubits, params, label=label) + super().__init__(name, num_qubits, params, phase=phase, label=label) if num_ctrl_qubits < num_qubits: self.num_ctrl_qubits = num_ctrl_qubits else: @@ -46,7 +47,7 @@ def __init__(self, name, num_qubits, params, label=None, num_ctrl_qubits=1, self.definition = definition if len(definition) == 1: base_gate = definition[0][0] - if isinstance(base_gate, ControlledGate): + if isinstance(base_gate, ControlledGate) and not base_gate.phase: self.base_gate = base_gate.base_gate else: self.base_gate = base_gate @@ -61,4 +62,7 @@ def __eq__(self, other): def inverse(self): """Invert this gate by calling inverse on the base gate.""" - return self.base_gate.inverse().control(self.num_ctrl_qubits) + inv = self.base_gate.inverse().control(self.num_ctrl_qubits) + if self.phase: + inv.phase = -self.phase + return inv diff --git a/qiskit/circuit/gate.py b/qiskit/circuit/gate.py index a8095fded48c..3f69c1012be3 100644 --- a/qiskit/circuit/gate.py +++ b/qiskit/circuit/gate.py @@ -14,6 +14,8 @@ """Unitary gate.""" +import math +import cmath import numpy as np from scipy.linalg import schur @@ -24,27 +26,58 @@ class Gate(Instruction): """Unitary gate.""" - def __init__(self, name, num_qubits, params, label=None): + def __init__(self, name, num_qubits, params, phase=0, label=None): """Create a new gate. Args: name (str): the Qobj name of the gate num_qubits (int): the number of qubits the gate acts on. params (list): a list of parameters. - label (str or None): An optional label for the gate [Default: None] + phase (float): set the gate phase (Default: 0). + label (str or None): An optional label for the gate (Default: None). """ self._label = label self.definition = None + self._phase = phase super().__init__(name, num_qubits, 0, params) + @property + def phase(self): + """Return the phase of the gate.""" + return self._phase + + @phase.setter + def phase(self, angle): + """Set the phase of the gate.""" + # Set the phase to the [-2 * pi, 2 * pi] interval + angle = float(angle) + if not angle: + self._phase = 0 + elif angle < 0: + self._phase = angle % (-2 * math.pi) + else: + self._phase = angle % (2 * math.pi) + + def _matrix_definition(self): + """Return the Numpy.array matrix definition of the gate.""" + # This should be set in classes that derive from Gate. + return None + def to_matrix(self): """Return a Numpy.array for the gate unitary matrix. + Returns: + ndarray: The matrix representation of the gate. + Raises: CircuitError: If a Gate subclass does not implement this method an exception will be raised when this base class method is called. """ - raise CircuitError("to_matrix not defined for this {}".format(type(self))) + # pylint: disable=assignment-from-none + mat = self._matrix_definition() + if mat is None: + raise CircuitError("to_matrix not defined for this {}".format(type(self))) + return cmath.exp(1j * self._phase) * mat if self._phase else mat def power(self, exponent): """Creates a unitary gate as `gate^exponent`. @@ -122,6 +155,7 @@ def control(self, num_ctrl_qubits=1, label=None): QiskitError: unrecognized mode """ # pylint: disable=cyclic-import + # TODO: Check base control methods if phase != 0 from .add_control import add_control return add_control(self, num_ctrl_qubits, label) diff --git a/qiskit/extensions/standard/h.py b/qiskit/extensions/standard/h.py index 002bca37b813..7d9e04d20e09 100644 --- a/qiskit/extensions/standard/h.py +++ b/qiskit/extensions/standard/h.py @@ -26,25 +26,34 @@ # pylint: disable=cyclic-import class HGate(Gate): - """Hadamard gate.""" + r"""Hadamard gate. - def __init__(self, label=None): + **Matrix Definition** + + The matrix for this gate is given by: + + .. math:: + + U_{\text{H}} = \frac{1}{\sqrt{2}} + \begin{bmatrix} + 1 & 1 \\ + 1 & -1 + \end{bmatrix} + """ + + def __init__(self, phase=0, label=None): """Create new Hadamard gate.""" - super().__init__("h", 1, [], label=label) + super().__init__("h", 1, [], phase=phase, label=label) def _define(self): """ gate h a { u2(0,pi) a; } """ from qiskit.extensions.standard.u2 import U2Gate - definition = [] q = QuantumRegister(1, "q") - rule = [ - (U2Gate(0, pi), [q[0]], []) + self.definition = [ + (U2Gate(0, pi, phase=self.phase), [q[0]], []) ] - for inst in rule: - definition.append(inst) - self.definition = definition def control(self, num_ctrl_qubits=1, label=None): """Controlled version of this gate. @@ -56,15 +65,15 @@ def control(self, num_ctrl_qubits=1, label=None): Returns: ControlledGate: controlled version of this gate. """ - if num_ctrl_qubits == 1: - return CHGate() + if num_ctrl_qubits == 1 and not self.phase: + return CHGate(label=label) return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label) def inverse(self): """Invert this gate.""" - return HGate() # self-inverse + return HGate(phase=-self.phase) # self-inverse - def to_matrix(self): + def _matrix_definition(self): """Return a Numpy.array for the H gate.""" return numpy.array([[1, 1], [1, -1]], dtype=complex) / numpy.sqrt(2) @@ -102,11 +111,29 @@ def h(self, qubit, *, q=None): # pylint: disable=invalid-name,unused-argument class CHGate(ControlledGate): - """controlled-H gate.""" + r"""Controlled-Hadamard gate. + + **Matrix Definition** + + The matrix for this gate is given by: + + .. math:: + + U_{\text{CH}} = + I \otimes |0 \rangle\!\langle 0| + + U_{\text{H}} \otimes |1 \rangle\!\langle 1| + = \begin{bmatrix} + 1 & 0 & 0 & 0 \\ + 0 & \frac{1}{\sqrt{2}} & 0 & \frac{1}{\sqrt{2}} \\ + 0 & 0 & 1 & 0 \\ + 0 & \frac{1}{\sqrt{2}} & 0 & -\frac{1}{\sqrt{2}} + \end{bmatrix} + """ - def __init__(self): + def __init__(self, phase=0, label=None): """Create new CH gate.""" - super().__init__("ch", 2, [], num_ctrl_qubits=1) + super().__init__("ch", 2, [], phase=phase, label=label, + num_ctrl_qubits=1) self.base_gate = HGate() def _define(self): @@ -124,10 +151,9 @@ def _define(self): from qiskit.extensions.standard.s import SGate, SdgGate from qiskit.extensions.standard.t import TGate, TdgGate from qiskit.extensions.standard.x import CnotGate - definition = [] q = QuantumRegister(2, "q") - rule = [ - (SGate(), [q[1]], []), + self.definition = [ + (SGate(phase=self.phase), [q[1]], []), (HGate(), [q[1]], []), (TGate(), [q[1]], []), (CnotGate(), [q[0], q[1]], []), @@ -135,15 +161,12 @@ def _define(self): (HGate(), [q[1]], []), (SdgGate(), [q[1]], []) ] - for inst in rule: - definition.append(inst) - self.definition = definition def inverse(self): """Invert this gate.""" - return CHGate() # self-inverse + return CHGate(phase=-self.phase) # self-inverse - def to_matrix(self): + def _matrix_definition(self): """Return a Numpy.array for the Ch gate.""" return numpy.array([[1, 0, 0, 0], [0, 1/numpy.sqrt(2), 0, 1/numpy.sqrt(2)], diff --git a/qiskit/extensions/standard/iden.py b/qiskit/extensions/standard/iden.py index bfcafe17d473..e9cb50465ebc 100644 --- a/qiskit/extensions/standard/iden.py +++ b/qiskit/extensions/standard/iden.py @@ -22,21 +22,33 @@ class IdGate(Gate): - """Identity gate. + r"""Identity gate. Identity gate corresponds to a single-qubit gate wait cycle, and should not be optimized or unrolled (it is an opaque gate). + + **Matrix Definition** + + The matrix for this gate is given by: + + .. math:: + + U_{\text{I}} = + \begin{bmatrix} + 1 & 0 \\ + 0 & 1 + \end{bmatrix} """ - def __init__(self, label=None): + def __init__(self, phase=0, label=None): """Create new Identity gate.""" - super().__init__("id", 1, [], label=label) + super().__init__("id", 1, [], phase=phase, label=label) def inverse(self): """Invert this gate.""" - return IdGate() # self-inverse + return IdGate(phase=-self.phase) # self-inverse - def to_matrix(self): + def _matrix_definition(self): """Return a Numpy.array for the Id gate.""" return numpy.array([[1, 0], [0, 1]], dtype=complex) diff --git a/qiskit/extensions/standard/ms.py b/qiskit/extensions/standard/ms.py index 161704ce1f45..b4456a98e563 100755 --- a/qiskit/extensions/standard/ms.py +++ b/qiskit/extensions/standard/ms.py @@ -33,23 +33,26 @@ class MSGate(Gate): """Global Molmer-Sorensen gate.""" - def __init__(self, n_qubits, theta): + def __init__(self, n_qubits, theta, phase=0, label=None): """Create new MS gate.""" - super().__init__("ms", n_qubits, [theta]) + super().__init__("ms", n_qubits, [theta], + phase=phase, label=label) def _define(self): from qiskit.extensions.standard.rxx import RXXGate - definition = [] q = QuantumRegister(self.num_qubits, "q") - rule = [] + definition = [] for i in range(self.num_qubits): + phase = self.phase if i == 0 else 0 for j in range(i+1, self.num_qubits): - rule += [(RXXGate(self.params[0]), [q[i], q[j]], [])] - - for inst in rule: - definition.append(inst) + definition += [(RXXGate(self.params[0], phase=phase), + [q[i], q[j]], [])] self.definition = definition + def inverse(self): + """Invert this gate.""" + return MSGate(self.num_qubits, -self.params[0], phase=self.phase) + def ms(self, theta, qubits): """Apply MS to q1 and q2.""" diff --git a/qiskit/extensions/standard/r.py b/qiskit/extensions/standard/r.py index 4e189d23b43b..a8f2f11720f7 100644 --- a/qiskit/extensions/standard/r.py +++ b/qiskit/extensions/standard/r.py @@ -25,36 +25,49 @@ class RGate(Gate): - """Rotation θ around the cos(φ)x + sin(φ)y axis.""" + r"""Rotation θ around the cos(φ)x + sin(φ)y axis. - def __init__(self, theta, phi): - """Create new r single-qubit gate.""" - super().__init__("r", 1, [theta, phi]) + **Matrix Definition** + + The matrix for this gate is given by: + + .. math:: + + U_{\text{R}}(\theta, \phi) + = \exp\left(-i (\cos(\phi)\sigma_X + \sin(\phi)\sigma_Y) \right) + = \begin{bmatrix} + \cos(\theta/2) & -i e^{-i\phi}\sin(\theta/2) \\ + -i e^{i\phi}\sin(\theta/2) & \cos(\theta/2) + \end{bmatrix} + """ + + def __init__(self, theta, phi, phase=0, label=None): + """Create new r single qubit gate.""" + super().__init__("r", 1, [theta, phi], + phase=phase, label=label) def _define(self): """ gate r(θ, φ) a {u3(θ, φ - π/2, -φ + π/2) a;} """ from qiskit.extensions.standard.u3 import U3Gate - definition = [] q = QuantumRegister(1, "q") theta = self.params[0] phi = self.params[1] - rule = [ - (U3Gate(theta, phi - pi / 2, -phi + pi / 2), [q[0]], []) + self.definition = [ + (U3Gate(theta, phi - pi / 2, -phi + pi / 2, + phase=self.phase), [q[0]], []) ] - for inst in rule: - definition.append(inst) - self.definition = definition def inverse(self): """Invert this gate. r(θ, φ)^dagger = r(-θ, φ) """ - return RGate(-self.params[0], self.params[1]) + return RGate(-self.params[0], self.params[1], + phase=-self.phase) - def to_matrix(self): + def _matrix_definition(self): """Return a Numpy.array for the R gate.""" cos = math.cos(self.params[0] / 2) sin = math.sin(self.params[0] / 2) diff --git a/qiskit/extensions/standard/rx.py b/qiskit/extensions/standard/rx.py index c14335e654a8..dc93462715cb 100644 --- a/qiskit/extensions/standard/rx.py +++ b/qiskit/extensions/standard/rx.py @@ -26,25 +26,37 @@ class RXGate(Gate): - """rotation around the x-axis.""" + r"""rotation around the x-axis. - def __init__(self, theta): + **Matrix Definition** + + The matrix for this gate is given by: + + .. math:: + + U_{\text{RX}}(\theta) + = \exp\left(-i \frac{\theta}{2} \sigma_X \right) + = \begin{bmatrix} + \cos(\theta / 2) & -i \sin(\theta / 2) \\ + -i \sin(\theta / 2) & \cos(\theta / 2) + \end{bmatrix} + """ + + def __init__(self, theta, phase=0, label=None): """Create new rx single qubit gate.""" - super().__init__("rx", 1, [theta]) + super().__init__("rx", 1, [theta], + phase=phase, label=label) def _define(self): """ gate rx(theta) a {r(theta, 0) a;} """ from qiskit.extensions.standard.r import RGate - definition = [] q = QuantumRegister(1, "q") - rule = [ - (RGate(self.params[0], 0), [q[0]], []) + self.definition = [ + (RGate(self.params[0], 0, phase=self.phase), + [q[0]], []) ] - for inst in rule: - definition.append(inst) - self.definition = definition def control(self, num_ctrl_qubits=1, label=None): """Controlled version of this gate. @@ -56,8 +68,8 @@ def control(self, num_ctrl_qubits=1, label=None): Returns: ControlledGate: controlled version of this gate. """ - if num_ctrl_qubits == 1: - return CrxGate(self.params[0]) + if num_ctrl_qubits == 1 and not self.phase: + return CrxGate(self.params[0], label=label) return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label) def inverse(self): @@ -65,9 +77,9 @@ def inverse(self): rx(theta)^dagger = rx(-theta) """ - return RXGate(-self.params[0]) + return RXGate(-self.params[0], phase=-self.phase) - def to_matrix(self): + def _matrix_definition(self): """Return a Numpy.array for the RX gate.""" cos = math.cos(self.params[0] / 2) sin = math.sin(self.params[0] / 2) @@ -109,11 +121,29 @@ def rx(self, theta, qubit, *, q=None): # pylint: disable=invalid-name,unused-ar class CrxGate(ControlledGate): - """controlled-rx gate.""" + r"""Controlled rotation around the z axis. + + **Matrix Definition** - def __init__(self, theta): + The matrix for this gate is given by: + + .. math:: + + U_{\text{Crx}}(\theta) = + I \otimes |0 \rangle\!\langle 0| + + U_{\text{RZ}}(\theta) \otimes |1 \rangle\!\langle 1| + = \begin{bmatrix} + 1 & 0 & 0 & 0 \\ + 0 & \cos\left(\frac{\theta}{2}\right) & 0 & -i\sin\left(\frac{\theta}{2}\right) \\ + 0 & 0 & 1 & 0 \\ + 0 & -i\sin\left(\frac{\theta}{2}\right) & 0 & \cos\left(\frac{\theta}{2}\right) + \end{bmatrix} + """ + + def __init__(self, theta, phase=0, label=None): """Create new crx gate.""" - super().__init__('crx', 2, [theta], num_ctrl_qubits=1) + super().__init__('crx', 2, [theta], phase=phase, label=label, + num_ctrl_qubits=1) self.base_gate = RXGate(theta) def _define(self): @@ -129,23 +159,27 @@ def _define(self): from qiskit.extensions.standard.u1 import U1Gate from qiskit.extensions.standard.u3 import U3Gate from qiskit.extensions.standard.x import CnotGate - definition = [] q = QuantumRegister(2, 'q') - rule = [ - (U1Gate(pi / 2), [q[1]], []), + self.definition = [ + (U1Gate(pi / 2, phase=self.phase), [q[1]], []), (CnotGate(), [q[0], q[1]], []), (U3Gate(-self.params[0] / 2, 0, 0), [q[1]], []), (CnotGate(), [q[0], q[1]], []), (U3Gate(self.params[0] / 2, -pi / 2, 0), [q[1]], []) ] - for inst in rule: - definition.append(inst) - self.definition = definition def inverse(self): """Invert this gate.""" - return CrxGate(-self.params[0]) - + return CrxGate(-self.params[0], phase=-self.phase) + + def _matrix_definition(self): + """Return a Numpy.array for the Controlled-Rx gate.""" + theta = float(self.params[0]) + return numpy.array([[1, 0, 0, 0], + [0, numpy.cos(theta / 2), 0, -1j * numpy.sin(theta / 2)], + [0, 0, 1, 0], + [0, -1j * numpy.sin(theta / 2), 0, numpy.cos(theta / 2)]], + dtype=complex) @deprecate_arguments({'ctl': 'control_qubit', 'tgt': 'target_qubit'}) diff --git a/qiskit/extensions/standard/rxx.py b/qiskit/extensions/standard/rxx.py index 5c5dcf726956..b8df3e97a0fd 100644 --- a/qiskit/extensions/standard/rxx.py +++ b/qiskit/extensions/standard/rxx.py @@ -22,53 +22,61 @@ class RXXGate(Gate): - """Two-qubit XX-rotation gate. + r"""Two-qubit XX-rotation gate. - This gate corresponds to the rotation U(θ) = exp(-1j * θ * X⊗X / 2) + **Matrix Definition** + + The matrix for this gate is given by: + + .. math:: + + U_{\text{RZ}}(\theta) + = \exp\left(-i \frac{\theta}{2} + (\sigma_X\otimes\sigma_X) \right) + = \begin{bmatrix} + \cos(\theta / 2) & 0 & 0 & -i \sin(\theta / 2) \\ + 0 & \cos(\theta / 2) & -i \sin(\theta / 2) & 0 \\ + 0 & -i \sin(\theta / 2) & \cos(\theta / 2) & 0 \\ + -i \sin(\theta / 2) & 0 & 0 & \cos(\theta / 2) + \end{bmatrix} """ - def __init__(self, theta): + def __init__(self, theta, phase=0, label=None): """Create new rxx gate.""" - super().__init__("rxx", 2, [theta]) + super().__init__("rxx", 2, [theta], + phase=phase, label=label) def _define(self): """Calculate a subcircuit that implements this unitary.""" from qiskit.extensions.standard.x import CnotGate - from qiskit.extensions.standard.u1 import U1Gate + from qiskit.extensions.standard.rz import RZGate from qiskit.extensions.standard.u2 import U2Gate from qiskit.extensions.standard.u3 import U3Gate from qiskit.extensions.standard.h import HGate - definition = [] q = QuantumRegister(2, "q") theta = self.params[0] - rule = [ + self.definition = [ (U3Gate(np.pi / 2, theta, 0), [q[0]], []), (HGate(), [q[1]], []), (CnotGate(), [q[0], q[1]], []), - (U1Gate(-theta), [q[1]], []), + (RZGate(-theta, phase=self.phase), [q[1]], []), (CnotGate(), [q[0], q[1]], []), (HGate(), [q[1]], []), (U2Gate(-np.pi, np.pi - theta), [q[0]], []), ] - for inst in rule: - definition.append(inst) - self.definition = definition def inverse(self): """Invert this gate.""" - return RXXGate(-self.params[0]) - - # NOTE: we should use the following as the canonical matrix - # definition but we don't include it yet since it differs from - # the circuit decomposition matrix by a global phase - # def to_matrix(self): - # """Return a Numpy.array for the RXX gate.""" - # theta = float(self.params[0]) - # return np.array([ - # [np.cos(theta / 2), 0, 0, -1j * np.sin(theta / 2)], - # [0, np.cos(theta / 2), -1j * np.sin(theta / 2), 0], - # [0, -1j * np.sin(theta / 2), np.cos(theta / 2), 0], - # [-1j * np.sin(theta / 2), 0, 0, np.cos(theta / 2)]], dtype=complex) + return RXXGate(-self.params[0], phase=-self.phase) + + def _matrix_definition(self): + """Return a Numpy.array for the RXX gate.""" + theta = float(self.params[0]) + return np.array([ + [np.cos(theta / 2), 0, 0, -1j * np.sin(theta / 2)], + [0, np.cos(theta / 2), -1j * np.sin(theta / 2), 0], + [0, -1j * np.sin(theta / 2), np.cos(theta / 2), 0], + [-1j * np.sin(theta / 2), 0, 0, np.cos(theta / 2)]], dtype=complex) def rxx(self, theta, qubit1, qubit2): diff --git a/qiskit/extensions/standard/ry.py b/qiskit/extensions/standard/ry.py index f9d86892e47c..f2ee12270c81 100644 --- a/qiskit/extensions/standard/ry.py +++ b/qiskit/extensions/standard/ry.py @@ -26,25 +26,37 @@ class RYGate(Gate): - """rotation around the y-axis.""" + r"""rotation around the y-axis. - def __init__(self, theta): + **Matrix Definition** + + The matrix for this gate is given by: + + .. math:: + + U_{\text{RY}}(\theta) + = \exp\left(-i \frac{\theta}{2} \sigma_Y \right) + = \begin{bmatrix} + \cos(\theta / 2) & -\sin(\theta / 2) \\ + \sin(\theta / 2) & \cos(\theta / 2) + \end{bmatrix} + """ + + def __init__(self, theta, phase=0, label=None): """Create new ry single qubit gate.""" - super().__init__("ry", 1, [theta]) + super().__init__("ry", 1, [theta], + phase=phase, label=label) def _define(self): """ gate ry(theta) a { r(theta, pi/2) a; } """ from qiskit.extensions.standard.r import RGate - definition = [] q = QuantumRegister(1, "q") - rule = [ - (RGate(self.params[0], pi/2), [q[0]], []) + self.definition = [ + (RGate(self.params[0], pi/2, phase=self.phase), + [q[0]], []) ] - for inst in rule: - definition.append(inst) - self.definition = definition def control(self, num_ctrl_qubits=1, label=None): """Controlled version of this gate. @@ -56,8 +68,8 @@ def control(self, num_ctrl_qubits=1, label=None): Returns: ControlledGate: controlled version of this gate. """ - if num_ctrl_qubits == 1: - return CryGate(self.params[0]) + if num_ctrl_qubits == 1 and not self.phase: + return CryGate(self.params[0], label=label) return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label) def inverse(self): @@ -65,9 +77,9 @@ def inverse(self): ry(theta)^dagger = ry(-theta) """ - return RYGate(-self.params[0]) + return RYGate(-self.params[0], phase=-self.phase) - def to_matrix(self): + def _matrix_definition(self): """Return a Numpy.array for the RY gate.""" cos = math.cos(self.params[0] / 2) sin = math.sin(self.params[0] / 2) @@ -109,11 +121,29 @@ def ry(self, theta, qubit, *, q=None): # pylint: disable=invalid-name,unused-ar class CryGate(ControlledGate): - """controlled-ry gate.""" + r"""Controlled rotation around the z axis. + + **Matrix Definition** + + The matrix for this gate is given by: + + .. math:: + + U_{\text{Cry}}(\theta) = + I \otimes |0 \rangle\!\langle 0| + + U_{\text{RZ}}(\theta) \otimes |1 \rangle\!\langle 1| + = \begin{bmatrix} + 1 & 0 & 0 & 0 \\ + 0 & \cos\left(\frac{\theta}{2}\right) & 0 & -\sin\left(\frac{\theta}{2}\right) \\ + 0 & 0 & 1 & 0 \\ + 0 & \sin\left(\frac{\theta}{2}\right) & 0 & \cos\left(\frac{\theta}{2}\right) + \end{bmatrix} + """ - def __init__(self, theta): + def __init__(self, theta, phase=0, label=None): """Create new cry gate.""" - super().__init__("cry", 2, [theta], num_ctrl_qubits=1) + super().__init__("cry", 2, [theta], phase=phase, label=label, + num_ctrl_qubits=1) self.base_gate = RYGate(theta) def _define(self): @@ -126,21 +156,26 @@ def _define(self): """ from qiskit.extensions.standard.x import CnotGate from qiskit.extensions.standard.u3 import U3Gate - definition = [] q = QuantumRegister(2, "q") - rule = [ - (U3Gate(self.params[0] / 2, 0, 0), [q[1]], []), + self.definition = [ + (U3Gate(self.params[0] / 2, 0, 0, phase=self.phase), [q[1]], []), (CnotGate(), [q[0], q[1]], []), (U3Gate(-self.params[0] / 2, 0, 0), [q[1]], []), (CnotGate(), [q[0], q[1]], []) ] - for inst in rule: - definition.append(inst) - self.definition = definition def inverse(self): """Invert this gate.""" - return CryGate(-self.params[0]) + return CryGate(-self.params[0], phase=-self.phase) + + def _matrix_definition(self): + """Return a Numpy.array for the Controlled-Ry gate.""" + theta = float(self.params[0]) + return numpy.array([[1, 0, 0, 0], + [0, numpy.cos(theta / 2), 0, -numpy.sin(theta / 2)], + [0, 0, 1, 0], + [0, numpy.sin(theta / 2), 0, numpy.cos(theta / 2)]], + dtype=complex) @deprecate_arguments({'ctl': 'control_qubit', diff --git a/qiskit/extensions/standard/rz.py b/qiskit/extensions/standard/rz.py index f215c276a9a5..58b9ab71f8cf 100644 --- a/qiskit/extensions/standard/rz.py +++ b/qiskit/extensions/standard/rz.py @@ -15,6 +15,7 @@ """ Rotation around the z-axis. """ +import numpy from qiskit.circuit import Gate from qiskit.circuit import ControlledGate from qiskit.circuit import QuantumCircuit @@ -23,25 +24,36 @@ class RZGate(Gate): - """rotation around the z-axis.""" + r"""rotation around the z-axis. - def __init__(self, phi): + **Matrix Definition** + + The matrix for this gate is given by: + + .. math:: + + U_{\text{RZ}}(\theta) + = \exp\left(-i \frac{\theta}{2} \sigma_Z \right) + = \begin{bmatrix} + e^{-i \theta/2} & 0 \\ + 0 & e^{i \theta/2} + \end{bmatrix} + """ + + def __init__(self, phi, phase=0, label=None): """Create new rz single qubit gate.""" - super().__init__("rz", 1, [phi]) + super().__init__("rz", 1, [phi], + phase=phase, label=label) def _define(self): """ gate rz(phi) a { u1(phi) a; } """ from qiskit.extensions.standard.u1 import U1Gate - definition = [] q = QuantumRegister(1, "q") - rule = [ - (U1Gate(self.params[0]), [q[0]], []) + self.definition = [ + (U1Gate(self.params[0], phase=self.phase), [q[0]], []) ] - for inst in rule: - definition.append(inst) - self.definition = definition def control(self, num_ctrl_qubits=1, label=None): """Controlled version of this gate. @@ -53,8 +65,8 @@ def control(self, num_ctrl_qubits=1, label=None): Returns: ControlledGate: controlled version of this gate. """ - if num_ctrl_qubits == 1: - return CrzGate(self.params[0]) + if num_ctrl_qubits == 1 and not self.phase: + return CrzGate(self.params[0], label=label) return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label) def inverse(self): @@ -62,7 +74,13 @@ def inverse(self): rz(phi)^dagger = rz(-phi) """ - return RZGate(-self.params[0]) + return RZGate(-self.params[0], phase=-self.phase) + + def _matrix_definition(self): + """Return a Numpy.array for the RZ gate.""" + return numpy.array([[numpy.exp(-1j * self.params[0] / 2), 0], + [0, numpy.exp(1j * self.params[0] / 2)]], + dtype=complex) @deprecate_arguments({'q': 'qubit'}) @@ -91,11 +109,29 @@ def rz(self, phi, qubit, *, q=None): # pylint: disable=invalid-name,unused-argu class CrzGate(ControlledGate): - """controlled-rz gate.""" + r"""Controlled rotation around the z axis. + + **Matrix Definition** + + The matrix for this gate is given by: + + .. math:: + + U_{\text{Crz}}(\theta) = + I \otimes |0 \rangle\!\langle 0| + + U_{\text{RZ}}(\theta) \otimes |1 \rangle\!\langle 1| + = \begin{bmatrix} + 1 & 0 & 0 & 0 \\ + 0 & e^{-i \theta/2} & 0 & 0 \\ + 0 & 0 & 1 & 0 \\ + 0 & 0 & 0 & e^{i \theta/2} + \end{bmatrix} + """ - def __init__(self, theta): + def __init__(self, theta, phase=0, label=None): """Create new crz gate.""" - super().__init__("crz", 2, [theta], num_ctrl_qubits=1) + super().__init__("crz", 2, [theta], phase=phase, label=label, + num_ctrl_qubits=1) self.base_gate = RZGate(theta) def _define(self): @@ -107,21 +143,25 @@ def _define(self): """ from qiskit.extensions.standard.x import CnotGate from qiskit.extensions.standard.u1 import U1Gate - definition = [] q = QuantumRegister(2, "q") - rule = [ - (U1Gate(self.params[0] / 2), [q[1]], []), + self.definition = [ + (U1Gate(self.params[0] / 2, phase=self.phase), [q[1]], []), (CnotGate(), [q[0], q[1]], []), (U1Gate(-self.params[0] / 2), [q[1]], []), (CnotGate(), [q[0], q[1]], []) ] - for inst in rule: - definition.append(inst) - self.definition = definition def inverse(self): """Invert this gate.""" - return CrzGate(-self.params[0]) + return CrzGate(-self.params[0], phase=-self.phase) + + def _matrix_definition(self): + """Return a Numpy.array for the Controlled-Rz gate.""" + theta = float(self.params[0]) + return numpy.array([[1, 0, 0, 0], + [0, numpy.exp(-1j * theta / 2), 0, 0], + [0, 0, 1, 0], + [0, 0, 0, numpy.exp(1j * theta / 2)]], dtype=complex) @deprecate_arguments({'ctl': 'control_qubit', 'tgt': 'target_qubit'}) diff --git a/qiskit/extensions/standard/rzz.py b/qiskit/extensions/standard/rzz.py index 717fb264dc96..5059e398b3c8 100644 --- a/qiskit/extensions/standard/rzz.py +++ b/qiskit/extensions/standard/rzz.py @@ -15,38 +15,60 @@ """ two-qubit ZZ-rotation gate. """ +import numpy from qiskit.circuit import Gate from qiskit.circuit import QuantumCircuit from qiskit.circuit import QuantumRegister class RZZGate(Gate): - """Two-qubit ZZ-rotation gate.""" + r"""Two-qubit ZZ-rotation gate. - def __init__(self, theta): + **Matrix Definition** + + The matrix for this gate is given by: + + .. math:: + + U_{\text{RZ}}(\theta) + = \exp\left(-i \frac{\theta}{2} + (\sigma_Z\otimes\sigma_Z) \right) + = \begin{bmatrix} + e^{-i\theta/2} & 0 & 0 & 0 \\ + 0 & 0& e^{\theta/2} & 0 \\ + 0 & 0 & e^{i\theta/2} & 0 \\ + 0 & 0 & 0 & e^{-i\theta/2} + \end{bmatrix} + """ + + def __init__(self, theta, phase=0, label=None): """Create new rzz gate.""" - super().__init__("rzz", 2, [theta]) + super().__init__("rzz", 2, [theta], + phase=phase, label=label) def _define(self): """ - gate rzz(theta) a, b { cx a, b; u1(theta) b; cx a, b; } + gate rzz(theta) a, b { cx a, b; rz(theta) b; cx a, b; } """ - from qiskit.extensions.standard.u1 import U1Gate + from qiskit.extensions.standard.rz import RZGate from qiskit.extensions.standard.x import CnotGate - definition = [] q = QuantumRegister(2, "q") - rule = [ + self.definition = [ (CnotGate(), [q[0], q[1]], []), - (U1Gate(self.params[0]), [q[1]], []), + (RZGate(self.params[0], phase=self.phase), + [q[1]], []), (CnotGate(), [q[0], q[1]], []) ] - for inst in rule: - definition.append(inst) - self.definition = definition def inverse(self): """Invert this gate.""" - return RZZGate(-self.params[0]) + return RZZGate(-self.params[0], phase=-self.phase) + + def _matrix_definition(self): + """Return a Numpy.array for the RZZ gate.""" + exp_p = numpy.exp(1j * self.params[0] / 2) + exp_m = numpy.exp(-1j * self.params[0] / 2) + return numpy.diag([exp_m, exp_p, exp_p, exp_m]) def rzz(self, theta, qubit1, qubit2): diff --git a/qiskit/extensions/standard/s.py b/qiskit/extensions/standard/s.py index cea199c41a22..0ad2e27e988b 100644 --- a/qiskit/extensions/standard/s.py +++ b/qiskit/extensions/standard/s.py @@ -24,62 +24,80 @@ class SGate(Gate): - """S=diag(1,i) Clifford phase gate.""" + r"""S Clifford phase gate. - def __init__(self, label=None): + **Matrix Definition** + + The matrix for this gate is given by: + + .. math:: + + U_{\text{S}}(\theta, \phi) = + \begin{bmatrix} + 1 & 0 \\ + 0 & i + \end{bmatrix} + """ + + def __init__(self, phase=0, label=None): """Create new S gate.""" - super().__init__("s", 1, [], label=label) + super().__init__("s", 1, [], phase=phase, label=label) def _define(self): """ gate s a { u1(pi/2) a; } """ from qiskit.extensions.standard.u1 import U1Gate - definition = [] q = QuantumRegister(1, "q") - rule = [ - (U1Gate(pi / 2), [q[0]], []) + self.definition = [ + (U1Gate(pi / 2, phase=self.phase), [q[0]], []) ] - for inst in rule: - definition.append(inst) - self.definition = definition def inverse(self): """Invert this gate.""" - return SdgGate() + return SdgGate(phase=-self.phase) - def to_matrix(self): + def _matrix_definition(self): """Return a Numpy.array for the S gate.""" return numpy.array([[1, 0], [0, 1j]], dtype=complex) class SdgGate(Gate): - """Sdg=diag(1,-i) Clifford adjoint phase gate.""" + r"""Sdg Clifford adjoint phase gate. + + **Matrix Definition** + + The matrix for this gate is given by: + + .. math:: + + U_{\text{S}^\dagger} = + \begin{bmatrix} + 1 & 0 \\ + 0 & -i + \end{bmatrix} + """ - def __init__(self, label=None): + def __init__(self, phase=0, label=None): """Create new Sdg gate.""" - super().__init__("sdg", 1, [], label=label) + super().__init__("sdg", 1, [], phase=phase, label=label) def _define(self): """ gate sdg a { u1(-pi/2) a; } """ from qiskit.extensions.standard.u1 import U1Gate - definition = [] q = QuantumRegister(1, "q") - rule = [ - (U1Gate(-pi / 2), [q[0]], []) + self.definition = [ + (U1Gate(-pi / 2, phase=self.phase), [q[0]], []) ] - for inst in rule: - definition.append(inst) - self.definition = definition def inverse(self): """Invert this gate.""" - return SGate() + return SGate(phase=-self.phase) - def to_matrix(self): + def _matrix_definition(self): """Return a Numpy.array for the Sdg gate.""" return numpy.array([[1, 0], [0, -1j]], dtype=complex) diff --git a/qiskit/extensions/standard/swap.py b/qiskit/extensions/standard/swap.py index 1cfce87a4840..9185614e0a3e 100644 --- a/qiskit/extensions/standard/swap.py +++ b/qiskit/extensions/standard/swap.py @@ -24,27 +24,39 @@ class SwapGate(Gate): - """SWAP gate.""" + r"""Swap gate. - def __init__(self): - """Create new SWAP gate.""" - super().__init__("swap", 2, []) + **Matrix Definition** + + The matrix for this gate is given by: + + .. math:: + + U_{\text{Swap}} + = \begin{bmatrix} + 1 & 0 & 0 & 0 \\ + 0 & 0 & 1 & 0 \\ + 0 & 1 & 0 & 0 \\ + 0 & 0 & 0 & 1 + \end{bmatrix} + """ + + def __init__(self, phase=0, label=None): + """Create new Swap gate.""" + super().__init__("swap", 2, [], + phase=phase, label=label) def _define(self): """ gate swap a,b { cx a,b; cx b,a; cx a,b; } """ from qiskit.extensions.standard.x import CnotGate - definition = [] q = QuantumRegister(2, "q") - rule = [ - (CnotGate(), [q[0], q[1]], []), + self.definition = [ + (CnotGate(phase=self.phase), [q[0], q[1]], []), (CnotGate(), [q[1], q[0]], []), (CnotGate(), [q[0], q[1]], []) ] - for inst in rule: - definition.append(inst) - self.definition = definition def control(self, num_ctrl_qubits=1, label=None): """Controlled version of this gate. @@ -56,15 +68,15 @@ def control(self, num_ctrl_qubits=1, label=None): Returns: ControlledGate: controlled version of this gate. """ - if num_ctrl_qubits == 1: - return FredkinGate() + if num_ctrl_qubits == 1 and not self.phase: + return FredkinGate(label=label) return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label) def inverse(self): """Invert this gate.""" - return SwapGate() # self-inverse + return SwapGate(phase=-self.phase) # self-inverse - def to_matrix(self): + def _matrix_definition(self): """Return a Numpy.array for the Swap gate.""" return numpy.array([[1, 0, 0, 0], [0, 0, 1, 0], @@ -102,11 +114,34 @@ def swap(self, qubit1, qubit2): class FredkinGate(ControlledGate): - """Fredkin gate.""" + r"""Fredkin (Controlled-Swap) gate. + + **Matrix Definition** + + The matrix for this gate is given by: + + .. math:: + + U_{\text{CSwap}} =& + I \otimes |0 \rangle\!\langle 0| + + U_{\text{Swap}} \otimes |1 \rangle\!\langle 1| \\ + =& + \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 & 0 & 0 & 1 & 0 & 0 \\ + 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 \\ + 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 \\ + 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 \\ + 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 + \end{bmatrix} + """ - def __init__(self): + def __init__(self, phase=0, label=None): """Create new Fredkin gate.""" - super().__init__("cswap", 3, [], num_ctrl_qubits=1) + super().__init__("cswap", 3, [], phase=phase, label=label, + num_ctrl_qubits=1) self.base_gate = SwapGate() def _define(self): @@ -118,23 +153,19 @@ def _define(self): } """ from qiskit.extensions.standard.x import CnotGate, ToffoliGate - definition = [] q = QuantumRegister(3, "q") - rule = [ - (CnotGate(), [q[2], q[1]], []), + self.definition = [ + (CnotGate(phase=self.phase), [q[2], q[1]], []), (ToffoliGate(), [q[0], q[1], q[2]], []), (CnotGate(), [q[2], q[1]], []) ] - for inst in rule: - definition.append(inst) - self.definition = definition def inverse(self): """Invert this gate.""" - return FredkinGate() # self-inverse + return FredkinGate(phase=-self.phase) # self-inverse - def to_matrix(self): - """Return a Numpy.array for the Fredkin (CSWAP) gate.""" + def _matrix_definition(self): + """Return a Numpy.array for the Fredkin gate.""" return numpy.array([[1, 0, 0, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0, 0], [0, 0, 1, 0, 0, 0, 0, 0], diff --git a/qiskit/extensions/standard/t.py b/qiskit/extensions/standard/t.py index dd05b7ca5d92..2a436c21519d 100644 --- a/qiskit/extensions/standard/t.py +++ b/qiskit/extensions/standard/t.py @@ -24,62 +24,80 @@ class TGate(Gate): - """T Gate: pi/4 rotation around Z axis.""" + r"""T Gate: pi/4 rotation around Z axis. - def __init__(self, label=None): + **Matrix Definition** + + The matrix for this gate is given by: + + .. math:: + + U_{\text{T}} = + \begin{bmatrix} + 1 & 0 \\ + 0 & e^{i \pi / 4} + \end{bmatrix} + """ + + def __init__(self, phase=0, label=None): """Create new T gate.""" - super().__init__("t", 1, [], label=label) + super().__init__("t", 1, [], phase=phase, label=label) def _define(self): """ gate t a { u1(pi/4) a; } """ from qiskit.extensions.standard.u1 import U1Gate - definition = [] q = QuantumRegister(1, "q") - rule = [ - (U1Gate(pi/4), [q[0]], []) + self.definition = [ + (U1Gate(pi/4, phase=self.phase), [q[0]], []) ] - for inst in rule: - definition.append(inst) - self.definition = definition def inverse(self): """Invert this gate.""" - return TdgGate() + return TdgGate(phase=-self.phase) - def to_matrix(self): + def _matrix_definition(self): """Return a Numpy.array for the S gate.""" return numpy.array([[1, 0], [0, (1+1j) / numpy.sqrt(2)]], dtype=complex) class TdgGate(Gate): - """T Gate: -pi/4 rotation around Z axis.""" + r"""T Gate: -pi/4 rotation around Z axis + + **Matrix Definition** + + The matrix for this gate is given by: + + .. math:: + + U_{\text{T}^\dagger} = + \begin{bmatrix} + 1 & 0 \\ + 0 & e^{-i \pi / 4} + \end{bmatrix} + """ - def __init__(self, label=None): + def __init__(self, phase=0, label=None): """Create new Tdg gate.""" - super().__init__("tdg", 1, [], label=label) + super().__init__("tdg", 1, [], phase=phase, label=label) def _define(self): """ gate t a { u1(pi/4) a; } """ from qiskit.extensions.standard.u1 import U1Gate - definition = [] q = QuantumRegister(1, "q") - rule = [ - (U1Gate(-pi/4), [q[0]], []) + self.definition = [ + (U1Gate(-pi/4, phase=self.phase), [q[0]], []) ] - for inst in rule: - definition.append(inst) - self.definition = definition def inverse(self): """Invert this gate.""" - return TGate() + return TGate(phase=-self.phase) - def to_matrix(self): + def _matrix_definition(self): """Return a Numpy.array for the S gate.""" return numpy.array([[1, 0], [0, (1-1j) / numpy.sqrt(2)]], dtype=complex) diff --git a/qiskit/extensions/standard/u1.py b/qiskit/extensions/standard/u1.py index 014d24d90718..0b17b1306535 100644 --- a/qiskit/extensions/standard/u1.py +++ b/qiskit/extensions/standard/u1.py @@ -25,22 +25,32 @@ # pylint: disable=cyclic-import class U1Gate(Gate): - """Diagonal single-qubit gate.""" + r"""Diagonal single-qubit gate. - def __init__(self, theta, label=None): + **Matrix Definition** + + The matrix for this gate is given by: + + .. math:: + + U_1(\lambda) = \begin{bmatrix} + 1 & 0 \\ + 0 & e^{i \lambda} + \end{bmatrix} + """ + + def __init__(self, theta, phase=0, label=None): """Create new diagonal single-qubit gate.""" - super().__init__("u1", 1, [theta], label=label) + super().__init__("u1", 1, [theta], + phase=phase, label=label) def _define(self): from qiskit.extensions.standard.u3 import U3Gate - definition = [] q = QuantumRegister(1, "q") - rule = [ - (U3Gate(0, 0, self.params[0]), [q[0]], []) + self.definition = [ + (U3Gate(0, 0, self.params[0], phase=self.phase), + [q[0]], []) ] - for inst in rule: - definition.append(inst) - self.definition = definition def control(self, num_ctrl_qubits=1, label=None): """Controlled version of this gate. @@ -52,18 +62,17 @@ def control(self, num_ctrl_qubits=1, label=None): Returns: ControlledGate: controlled version of this gate. """ - if num_ctrl_qubits == 1: - return Cu1Gate(*self.params) + if num_ctrl_qubits == 1 and not self.phase: + return Cu1Gate(*self.params, label=label) return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label) def inverse(self): """Invert this gate.""" - return U1Gate(-self.params[0]) + return U1Gate(-self.params[0], phase=-self.phase) - def to_matrix(self): - """Return a Numpy.array for the U1 gate.""" - lam = self.params[0] - lam = float(lam) + def _matrix_definition(self): + """Return a Numpy.array for the U3 gate.""" + lam = float(self.params[0]) return numpy.array([[1, 0], [0, numpy.exp(1j * lam)]], dtype=complex) @@ -100,11 +109,30 @@ def u1(self, theta, qubit, *, q=None): # pylint: disable=invalid-name,unused-ar class Cu1Gate(ControlledGate): - """controlled-u1 gate.""" + """controlled-u1 gate."""r"""Controlled-Z gate. + + **Matrix Definition** + + The matrix for this gate is given by: + + .. math:: + + U_{\text{Cu1}}(\lambda) = + I \otimes |0 \rangle\!\langle 0| + + U_{1}(\lambda) \otimes |1 \rangle\!\langle 1| + = + \begin{bmatrix} + 1 & 0 & 0 & 0 \\ + 0 & 1 & 0 & 0 \\ + 0 & 0 & 1 & 0 \\ + 0 & 0 & 0 & e^{i \lambda} + \end{bmatrix} + """ - def __init__(self, theta): + def __init__(self, theta, phase=0, label=None): """Create new cu1 gate.""" - super().__init__("cu1", 2, [theta], num_ctrl_qubits=1) + super().__init__("cu1", 2, [theta], phase=0, label=None, + num_ctrl_qubits=1) self.base_gate = U1Gate(theta) def _define(self): @@ -116,22 +144,26 @@ def _define(self): } """ from qiskit.extensions.standard.x import CnotGate - definition = [] q = QuantumRegister(2, "q") - rule = [ - (U1Gate(self.params[0] / 2), [q[0]], []), + self.definition = [ + (U1Gate(self.params[0] / 2, phase=self.phase), [q[0]], []), (CnotGate(), [q[0], q[1]], []), (U1Gate(-self.params[0] / 2), [q[1]], []), (CnotGate(), [q[0], q[1]], []), (U1Gate(self.params[0] / 2), [q[1]], []) ] - for inst in rule: - definition.append(inst) - self.definition = definition def inverse(self): """Invert this gate.""" - return Cu1Gate(-self.params[0]) + return Cu1Gate(-self.params[0], phase=-self.phase) + + def _matrix_definition(self): + """Return a Numpy.array for the Cu1 gate.""" + lam = float(self.params[0]) + return numpy.array([[1, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, numpy.exp(1j * lam)]], dtype=complex) @deprecate_arguments({'ctl': 'control_qubit', diff --git a/qiskit/extensions/standard/u2.py b/qiskit/extensions/standard/u2.py index 50fcf4b35c13..5fbf11fbc258 100644 --- a/qiskit/extensions/standard/u2.py +++ b/qiskit/extensions/standard/u2.py @@ -24,30 +24,43 @@ class U2Gate(Gate): - """One-pulse single-qubit gate.""" + r"""One-pulse single-qubit gate. - def __init__(self, phi, lam, label=None): + **Matrix Definition** + + The matrix for this gate is given by: + + .. math:: + + U_2(\phi, \lambda) = \frac{1}{\sqrt{2}}\begin{bmatrix} + 1 & -e^{i \lambda} \\ + e^{i \phi} & e^{i (\phi+\lambda)} + \end{bmatrix} + """ + + def __init__(self, phi, lam, phase=0, label=None): """Create new one-pulse single-qubit gate.""" - super().__init__("u2", 1, [phi, lam], label=label) + super().__init__("u2", 1, [phi, lam], + phase=phase, label=label) def _define(self): from qiskit.extensions.standard.u3 import U3Gate - definition = [] q = QuantumRegister(1, "q") - rule = [(U3Gate(pi / 2, self.params[0], self.params[1]), [q[0]], [])] - for inst in rule: - definition.append(inst) - self.definition = definition + self.definition = [ + (U3Gate(pi / 2, self.params[0], self.params[1], phase=self.phase), + [q[0]], []) + ] def inverse(self): """Invert this gate. u2(phi,lamb)^dagger = u2(-lamb-pi,-phi+pi) """ - return U2Gate(-self.params[1] - pi, -self.params[0] + pi) + return U2Gate(-self.params[1] - pi, -self.params[0] + pi, + phase=-self.phase) - def to_matrix(self): - """Return a Numpy.array for the U2 gate.""" + def _matrix_definition(self): + """Return a Numpy.array for the U3 gate.""" isqrt2 = 1 / numpy.sqrt(2) phi, lam = self.params phi, lam = float(phi), float(lam) diff --git a/qiskit/extensions/standard/u3.py b/qiskit/extensions/standard/u3.py index 3a4449a69b28..65f26b1d3e81 100644 --- a/qiskit/extensions/standard/u3.py +++ b/qiskit/extensions/standard/u3.py @@ -26,18 +26,32 @@ # pylint: disable=cyclic-import class U3Gate(Gate): - """Two-pulse single-qubit gate.""" + r"""Two-pulse single-qubit gate. - def __init__(self, theta, phi, lam, label=None): + **Matrix Definition** + + The matrix for this gate is given by: + + .. math:: + + U_3(\theta, \phi, \lambda) = \begin{bmatrix} + \cos(\theta / 2) & -e^{i\lambda}\sin(\theta / 2) \\ + e^{i\phi}\sin(\theta / 2) & e^{i(\phi+\lambda)}\cos(\theta / 2) + \end{bmatrix} + """ + + def __init__(self, theta, phi, lam, phase=0, label=None): """Create new two-pulse single qubit gate.""" - super().__init__("u3", 1, [theta, phi, lam], label=label) + super().__init__("u3", 1, [theta, phi, lam], + phase=phase, label=label) def inverse(self): """Invert this gate. u3(theta, phi, lamb)^dagger = u3(-theta, -lam, -phi) """ - return U3Gate(-self.params[0], -self.params[2], -self.params[1]) + return U3Gate(-self.params[0], -self.params[2], -self.params[1], + phase=-self.phase) def control(self, num_ctrl_qubits=1, label=None): """Controlled version of this gate. @@ -49,11 +63,11 @@ def control(self, num_ctrl_qubits=1, label=None): Returns: ControlledGate: controlled version of this gate. """ - if num_ctrl_qubits == 1: - return Cu3Gate(*self.params) + if num_ctrl_qubits == 1 and not self.phase: + return Cu3Gate(*self.params, label=label) return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label) - def to_matrix(self): + def _matrix_definition(self): """Return a Numpy.array for the U3 gate.""" theta, phi, lam = self.params theta, phi, lam = float(theta), float(phi), float(lam) @@ -104,11 +118,30 @@ def u3(self, theta, phi, lam, qubit, *, q=None): # pylint: disable=invalid-name class Cu3Gate(ControlledGate): - """controlled-u3 gate.""" + r"""Controlled-u3 gate. + + **Matrix Definition** + + The matrix for this gate is given by: - def __init__(self, theta, phi, lam): + .. math:: + + U_{\text{CZ}} = + I \otimes |0 \rangle\!\langle 0| + + U_3(\theta, \phi, \lambda) \otimes |1 \rangle\!\langle 1| + = + \begin{bmatrix} + 1 & 0 & 0 & 0 \\ + 0 & \cos(\theta / 2) & 0 & -e^{i\lambda}\sin(\theta / 2) \\ + 0 & 0 & 1 & 0 \\ + 0 & e^{i\phi}\sin(\theta / 2) & 0 & e^{i(\phi+\lambda)}\cos(\theta / 2) + \end{bmatrix} + """ + + def __init__(self, theta, phi, lam, phase=0, label=None): """Create new cu3 gate.""" - super().__init__("cu3", 2, [theta, phi, lam], num_ctrl_qubits=1) + super().__init__("cu3", 2, [theta, phi, lam], phase=phase, label=label, + num_ctrl_qubits=1) self.base_gate = U3Gate(theta, phi, lam) def _define(self): @@ -124,23 +157,32 @@ def _define(self): """ from qiskit.extensions.standard.u1 import U1Gate from qiskit.extensions.standard.x import CnotGate - definition = [] q = QuantumRegister(2, "q") - rule = [ - (U1Gate((self.params[2] + self.params[1]) / 2), [q[0]], []), + self.definition = [ + (U1Gate((self.params[2] + self.params[1]) / 2, phase=self.phase), [q[0]], []), (U1Gate((self.params[2] - self.params[1]) / 2), [q[1]], []), (CnotGate(), [q[0], q[1]], []), (U3Gate(-self.params[0] / 2, 0, -(self.params[1] + self.params[2]) / 2), [q[1]], []), (CnotGate(), [q[0], q[1]], []), (U3Gate(self.params[0] / 2, self.params[1], 0), [q[1]], []) ] - for inst in rule: - definition.append(inst) - self.definition = definition def inverse(self): """Invert this gate.""" - return Cu3Gate(-self.params[0], -self.params[2], -self.params[1]) + return Cu3Gate(-self.params[0], -self.params[2], -self.params[1], + phase=-self.phase) + + def _matrix_definition(self): + """Return a Numpy.array for the Cu3 gate.""" + theta, phi, lam = self.params + theta, phi, lam = float(theta), float(phi), float(lam) + return numpy.array([[1, 0, 0, 0], + [0, numpy.cos(theta / 2), + 0, -numpy.exp(1j * lam) * numpy.sin(theta / 2)], + [0, 0, 1, 0], + [0, numpy.exp(1j * phi) * numpy.sin(theta / 2), + 0, numpy.exp(1j * (phi + lam)) * numpy.cos(theta / 2)] + ], dtype=complex) @deprecate_arguments({'ctl': 'control_qubit', diff --git a/qiskit/extensions/standard/x.py b/qiskit/extensions/standard/x.py index 93457b083fb3..785f912d4e6f 100644 --- a/qiskit/extensions/standard/x.py +++ b/qiskit/extensions/standard/x.py @@ -25,11 +25,24 @@ class XGate(Gate): - """Pauli X (bit-flip) gate.""" + r"""Pauli X (bit-flip) gate. - def __init__(self, label=None): + **Matrix Definition** + + The matrix for this gate is given by: + + .. math:: + + U_{\text{X}} = + \begin{bmatrix} + 0 & 1 \\ + 1 & 0 + \end{bmatrix} + """ + + def __init__(self, phase=0, label=None): """Create new X gate.""" - super().__init__("x", 1, [], label=label) + super().__init__("x", 1, [], phase=phase, label=label) def _define(self): """ @@ -38,14 +51,10 @@ def _define(self): } """ from qiskit.extensions.standard.u3 import U3Gate - definition = [] q = QuantumRegister(1, "q") - rule = [ - (U3Gate(pi, 0, pi), [q[0]], []) + self.definition = [ + (U3Gate(pi, 0, pi, phase=self.phase), [q[0]], []) ] - for inst in rule: - definition.append(inst) - self.definition = definition def control(self, num_ctrl_qubits=1, label=None): """Controlled version of this gate. @@ -57,17 +66,17 @@ def control(self, num_ctrl_qubits=1, label=None): Returns: ControlledGate: controlled version of this gate. """ - if num_ctrl_qubits == 1: - return CnotGate() - elif num_ctrl_qubits == 2: - return ToffoliGate() + if num_ctrl_qubits == 1 and not self.phase: + return CnotGate(label=label) + elif num_ctrl_qubits == 2 and not self.phase: + return ToffoliGate(label=label) return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label) def inverse(self): """Invert this gate.""" - return XGate() # self-inverse + return XGate(phase=-self.phase) # self-inverse - def to_matrix(self): + def _matrix_definition(self): """Return a Numpy.array for the X gate.""" return numpy.array([[0, 1], [1, 0]], dtype=complex) @@ -107,11 +116,30 @@ def x(self, qubit, *, q=None): # pylint: disable=unused-argument class CnotGate(ControlledGate): - """controlled-NOT gate.""" + r"""Controlled-CNOT gate. + + **Matrix Definition** - def __init__(self): + The matrix for this gate is given by: + + .. math:: + + U_{\text{CX}} = + I \otimes |0 \rangle\!\langle 0| + + U_{\text{X}} \otimes |1 \rangle\!\langle 1| + = + \begin{bmatrix} + 1 & 0 & 0 & 0 \\ + 0 & 0 & 0 & 1 \\ + 0 & 0 & 1 & 0 \\ + 0 & 1 & 0 & 0 + \end{bmatrix} + """ + + def __init__(self, phase=0, label=None): """Create new CNOT gate.""" - super().__init__("cx", 2, [], num_ctrl_qubits=1) + super().__init__("cx", 2, [], phase=phase, label=label, + num_ctrl_qubits=1) self.base_gate = XGate() def control(self, num_ctrl_qubits=1, label=None): @@ -130,9 +158,9 @@ def control(self, num_ctrl_qubits=1, label=None): def inverse(self): """Invert this gate.""" - return CnotGate() # self-inverse + return CnotGate(phase=-self.phase) # self-inverse - def to_matrix(self): + def _matrix_definition(self): """Return a Numpy.array for the Cx gate.""" return numpy.array([[1, 0, 0, 0], [0, 0, 0, 1], @@ -177,11 +205,35 @@ def cx(self, control_qubit, target_qubit, # pylint: disable=invalid-name class ToffoliGate(ControlledGate): - """Toffoli gate.""" - - def __init__(self): + r"""Toffoli (Controlled-CNOT) gate. + + **Matrix Definition** + + The matrix for this gate is given by: + + .. math:: + + U_{\text{CX}} =& + I \otimes |0, 0 \rangle\!\langle 0, 0| + + I \otimes |0, 1 \rangle\!\langle 0, 1| + + I \otimes |1, 0 \rangle\!\langle 1, 0| + + U_{\text{X}} \otimes |1, 1 \rangle\!\langle 1, 1| \\ + =& + \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 & 0 & 0 & 0 & 0 & 1 \\ + 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 & 1 & 0 & 0 & 0 & 0 + \end{bmatrix} + """ + def __init__(self, phase=0, label=None): """Create new Toffoli gate.""" - super().__init__("ccx", 3, [], num_ctrl_qubits=2) + super().__init__("ccx", 3, [], phase=0, label=None, + num_ctrl_qubits=2) self.base_gate = XGate() def _define(self): @@ -196,10 +248,9 @@ def _define(self): from qiskit.extensions.standard.h import HGate from qiskit.extensions.standard.t import TGate from qiskit.extensions.standard.t import TdgGate - definition = [] q = QuantumRegister(3, "q") - rule = [ - (HGate(), [q[2]], []), + self.definition = [ + (HGate(phase=self.phase), [q[2]], []), (CnotGate(), [q[1], q[2]], []), (TdgGate(), [q[2]], []), (CnotGate(), [q[0], q[2]], []), @@ -215,15 +266,12 @@ def _define(self): (TdgGate(), [q[1]], []), (CnotGate(), [q[0], q[1]], []) ] - for inst in rule: - definition.append(inst) - self.definition = definition def inverse(self): """Invert this gate.""" - return ToffoliGate() # self-inverse + return ToffoliGate(phase=-self.phase) # self-inverse - def to_matrix(self): + def _matrix_definition(self): """Return a Numpy.array for the Toffoli gate.""" return numpy.array([[1, 0, 0, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0, 0], diff --git a/qiskit/extensions/standard/y.py b/qiskit/extensions/standard/y.py index 25f85bd34586..4ff896f36691 100644 --- a/qiskit/extensions/standard/y.py +++ b/qiskit/extensions/standard/y.py @@ -25,22 +25,31 @@ class YGate(Gate): - """Pauli Y (bit-phase-flip) gate.""" + r"""Pauli Y (bit-phase-flip) gate. - def __init__(self, label=None): + **Matrix Definition** + + The matrix for this gate is given by: + + .. math:: + + U_{\text{Z}} = + \begin{bmatrix} + 0 & -i \\ + i & 0 + \end{bmatrix} + """ + + def __init__(self, phase=0, label=None): """Create new Y gate.""" - super().__init__("y", 1, [], label=label) + super().__init__("y", 1, [], phase=phase, label=label) def _define(self): from qiskit.extensions.standard.u3 import U3Gate - definition = [] q = QuantumRegister(1, "q") - rule = [ - (U3Gate(pi, pi/2, pi/2), [q[0]], []) + self.definition = [ + (U3Gate(pi, pi/2, pi/2, phase=self.phase), [q[0]], []) ] - for inst in rule: - definition.append(inst) - self.definition = definition def control(self, num_ctrl_qubits=1, label=None): """Controlled version of this gate. @@ -52,16 +61,16 @@ def control(self, num_ctrl_qubits=1, label=None): Returns: ControlledGate: controlled version of this gate. """ - if num_ctrl_qubits == 1: - return CyGate() + if num_ctrl_qubits == 1 and not self.phase: + return CyGate(label=label) return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label) def inverse(self): """Invert this gate.""" - return YGate() # self-inverse + return YGate(phase=-self.phase) # self-inverse - def to_matrix(self): - """Return a Numpy.array for the Y gate.""" + def _matrix_definition(self): + """Return a Numpy.array for the Z gate.""" return numpy.array([[0, -1j], [1j, 0]], dtype=complex) @@ -100,11 +109,30 @@ def y(self, qubit, *, q=None): # pylint: disable=unused-argument class CyGate(ControlledGate): - """controlled-Y gate.""" + r"""Controlled-Y gate. + + **Matrix Definition** + + The matrix for this gate is given by: + + .. math:: + + U_{\text{CT}} = + I \otimes |0 \rangle\!\langle 0| + + U_{\text{Y}} \otimes |1 \rangle\!\langle 1| + = + \begin{bmatrix} + 1 & 0 & 0 & 0 \\ + 0 & 0 & 0 & -i \\ + 0 & 0 & 1 & 0 \\ + 0 & i & 0 & 0 + \end{bmatrix} + """ - def __init__(self): + def __init__(self, phase=0, label=None): """Create new CY gate.""" - super().__init__("cy", 2, [], num_ctrl_qubits=1) + super().__init__("cy", 2, [], phase=phase, label=label, + num_ctrl_qubits=1) self.base_gate = YGate() def _define(self): @@ -114,20 +142,23 @@ def _define(self): from qiskit.extensions.standard.s import SGate from qiskit.extensions.standard.s import SdgGate from qiskit.extensions.standard.x import CnotGate - definition = [] q = QuantumRegister(2, "q") - rule = [ - (SdgGate(), [q[1]], []), + self.definition = [ + (SdgGate(phase=self.phase), [q[1]], []), (CnotGate(), [q[0], q[1]], []), (SGate(), [q[1]], []) ] - for inst in rule: - definition.append(inst) - self.definition = definition def inverse(self): """Invert this gate.""" - return CyGate() # self-inverse + return CyGate(phase=-self.phase) # self-inverse + + def _matrix_definition(self): + """Return a Numpy.array for the Cy gate.""" + return numpy.array([[1, 0, 0, 0], + [0, 0, 0, -1j], + [0, 0, 1, 0], + [0, 1j, 0, 0]], dtype=complex) @deprecate_arguments({'ctl': 'control_qubit', diff --git a/qiskit/extensions/standard/z.py b/qiskit/extensions/standard/z.py index 2c970b8781f5..2f9375aa3d53 100644 --- a/qiskit/extensions/standard/z.py +++ b/qiskit/extensions/standard/z.py @@ -25,22 +25,31 @@ class ZGate(Gate): - """Pauli Z (phase-flip) gate.""" + r"""Pauli Z (phase-flip) gate. - def __init__(self, label=None): + **Matrix Definition** + + The matrix for this gate is given by: + + .. math:: + + U_{\text{Z}} = + \begin{bmatrix} + 1 & 0 \\ + 0 & -1 + \end{bmatrix} + """ + + def __init__(self, phase=0, label=None): """Create new Z gate.""" - super().__init__("z", 1, [], label=label) + super().__init__("z", 1, [], phase=phase, label=label) def _define(self): from qiskit.extensions.standard.u1 import U1Gate - definition = [] q = QuantumRegister(1, "q") - rule = [ - (U1Gate(pi), [q[0]], []) + self.definition = [ + (U1Gate(pi, phase=self.phase), [q[0]], []) ] - for inst in rule: - definition.append(inst) - self.definition = definition def control(self, num_ctrl_qubits=1, label=None): """Controlled version of this gate. @@ -52,16 +61,17 @@ def control(self, num_ctrl_qubits=1, label=None): Returns: ControlledGate: controlled version of this gate. """ - if num_ctrl_qubits == 1: - return CzGate() + + if num_ctrl_qubits == 1 and not self.phase: + return CzGate(label=label) return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label) def inverse(self): """Invert this gate.""" - return ZGate() # self-inverse + return ZGate(phase=-self.phase) # self-inverse - def to_matrix(self): - """Return a Numpy.array for the X gate.""" + def _matrix_definition(self): + """Return a Numpy.array for the Z gate.""" return numpy.array([[1, 0], [0, -1]], dtype=complex) @@ -100,11 +110,30 @@ def z(self, qubit, *, q=None): # pylint: disable=unused-argument class CzGate(ControlledGate): - """controlled-Z gate.""" + r"""Controlled-Z gate. + + **Matrix Definition** + + The matrix for this gate is given by: + + .. math:: + + U_{\text{CZ}} = + I \otimes |0 \rangle\!\langle 0| + + U_{\text{Z}} \otimes |1 \rangle\!\langle 1| + = + \begin{bmatrix} + 1 & 0 & 0 & 0 \\ + 0 & 1 & 0 & 0 \\ + 0 & 0 & 1 & 0 \\ + 0 & 0 & 0 & -1 + \end{bmatrix} + """ - def __init__(self, label=None): + def __init__(self, phase=0, label=None): """Create new CZ gate.""" - super().__init__("cz", 2, [], label=label, num_ctrl_qubits=1) + super().__init__("cz", 2, [], phase=phase, label=label, + num_ctrl_qubits=1) self.base_gate = ZGate() def _define(self): @@ -113,22 +142,18 @@ def _define(self): """ from qiskit.extensions.standard.h import HGate from qiskit.extensions.standard.x import CnotGate - definition = [] q = QuantumRegister(2, "q") - rule = [ - (HGate(), [q[1]], []), + self.definition = [ + (HGate(phase=self.phase), [q[1]], []), (CnotGate(), [q[0], q[1]], []), (HGate(), [q[1]], []) ] - for inst in rule: - definition.append(inst) - self.definition = definition def inverse(self): """Invert this gate.""" - return CzGate() # self-inverse + return CzGate(phase=-self.phase) # self-inverse - def to_matrix(self): + def _matrix_definition(self): """Return a Numpy.array for the Cz gate.""" return numpy.array([[1, 0, 0, 0], [0, 1, 0, 0], From 1cd1331308031cdad97e949661722a2c5ee1765b Mon Sep 17 00:00:00 2001 From: Christopher Wood Date: Wed, 4 Dec 2019 18:35:19 -0500 Subject: [PATCH 3/6] add phase to 1-qubit synthesis and ion decompose --- .../quantum_info/synthesis/ion_decompose.py | 2 +- .../synthesis/one_qubit_decompose.py | 41 +++++++++++-------- test/python/quantum_info/test_synthesis.py | 5 --- 3 files changed, 26 insertions(+), 22 deletions(-) diff --git a/qiskit/quantum_info/synthesis/ion_decompose.py b/qiskit/quantum_info/synthesis/ion_decompose.py index cb2c175c381b..b98a793a88d9 100644 --- a/qiskit/quantum_info/synthesis/ion_decompose.py +++ b/qiskit/quantum_info/synthesis/ion_decompose.py @@ -51,7 +51,7 @@ def cnot_rxx_decompose(plus_ry=True, plus_rxx=True): sgn_rxx = -1 circuit = QuantumCircuit(2) - circuit.append(RYGate(sgn_ry * np.pi / 2), [0]) + circuit.append(RYGate(sgn_ry * np.pi / 2, phase=0.25*np.pi), [0]) circuit.append(RXXGate(sgn_rxx * np.pi / 2), [0, 1]) circuit.append(RXGate(-sgn_rxx * np.pi / 2), [0]) circuit.append(RXGate(-sgn_rxx * sgn_ry * np.pi / 2), [1]) diff --git a/qiskit/quantum_info/synthesis/one_qubit_decompose.py b/qiskit/quantum_info/synthesis/one_qubit_decompose.py index 4a9e69be8f4e..37d38e861e62 100644 --- a/qiskit/quantum_info/synthesis/one_qubit_decompose.py +++ b/qiskit/quantum_info/synthesis/one_qubit_decompose.py @@ -92,7 +92,7 @@ def __call__(self, unitary, simplify=True, simplify_tolerance=DEFAULT_SIMPLIFY_TOLERANCE, - phase_equal=False, + phase_equal=True, atol=DEFAULT_ATOL): """Decompose single qubit gate into a circuit. @@ -102,7 +102,7 @@ def __call__(self, simplify_tolerance (float): absolute tolerance for checking angles in simplify [Default: 1e-12]. phase_equal (bool): verify the output circuit is phase equal - to the input matrix [Default: False]. + to the input matrix [Default: True]. atol (bool): absolute tolerance for comparing synthesised circuit matrix to input [Default: 1e-7]. @@ -131,8 +131,9 @@ def __call__(self, if not is_unitary_matrix(unitary): raise QiskitError("OneQubitEulerDecomposer: " "input matrix is not unitary.") - theta, phi, lam, _ = self._params(unitary) + theta, phi, lam, phase = self._params(unitary) circuit = self._circuit(theta, phi, lam, + phase=phase, simplify=simplify, simplify_tolerance=simplify_tolerance) # Check circuit is correct @@ -185,7 +186,7 @@ def angles_and_phase(self, unitary): """ return self._params(unitary) - def circuit(self, theta, phi, lam, simplify=True, + def circuit(self, theta, phi, lam, phase=0, simplify=True, simplify_tolerance=DEFAULT_SIMPLIFY_TOLERANCE): """Return the basis circuit for the input parameters. @@ -193,6 +194,7 @@ def circuit(self, theta, phi, lam, simplify=True, theta (float): euler angle parameter phi (float): euler angle parameter lam (float): euler angle parameter + phase (float): gate phase parameter simplify (bool): simplify output circuit [Default: True] simplify_tolerance (float): absolute tolerance for checking angles zero [Default: 1e-12]. @@ -201,6 +203,7 @@ def circuit(self, theta, phi, lam, simplify=True, QuantumCircuit: the basis circuits. """ return self._circuit(theta, phi, lam, + phase=phase, simplify=simplify, simplify_tolerance=simplify_tolerance) @@ -299,16 +302,17 @@ def _params_u1x(mat): def _circuit_zyz(theta, phi, lam, + phase=0, simplify=True, simplify_tolerance=DEFAULT_SIMPLIFY_TOLERANCE): circuit = QuantumCircuit(1) if simplify and np.isclose(theta, 0.0, atol=simplify_tolerance): - circuit.append(RZGate(phi + lam), [0]) + circuit.append(RZGate(phi + lam, phase=phase), [0]) return circuit if not simplify or not np.isclose(lam, 0.0, atol=simplify_tolerance): circuit.append(RZGate(lam), [0]) if not simplify or not np.isclose(theta, 0.0, atol=simplify_tolerance): - circuit.append(RYGate(theta), [0]) + circuit.append(RYGate(theta, phase=phase), [0]) if not simplify or not np.isclose(phi, 0.0, atol=simplify_tolerance): circuit.append(RZGate(phi), [0]) return circuit @@ -317,17 +321,18 @@ def _circuit_zyz(theta, def _circuit_zxz(theta, phi, lam, + phase=0, simplify=False, simplify_tolerance=DEFAULT_SIMPLIFY_TOLERANCE): if simplify and np.isclose(theta, 0.0, atol=simplify_tolerance): circuit = QuantumCircuit(1) - circuit.append(RZGate(phi + lam), [0]) + circuit.append(RZGate(phi + lam, phase=phase), [0]) return circuit circuit = QuantumCircuit(1) if not simplify or not np.isclose(lam, 0.0, atol=simplify_tolerance): circuit.append(RZGate(lam), [0]) if not simplify or not np.isclose(theta, 0.0, atol=simplify_tolerance): - circuit.append(RXGate(theta), [0]) + circuit.append(RXGate(theta, phase=phase), [0]) if not simplify or not np.isclose(phi, 0.0, atol=simplify_tolerance): circuit.append(RZGate(phi), [0]) return circuit @@ -336,16 +341,17 @@ def _circuit_zxz(theta, def _circuit_xyx(theta, phi, lam, + phase=0, simplify=True, simplify_tolerance=DEFAULT_SIMPLIFY_TOLERANCE): circuit = QuantumCircuit(1) if simplify and np.isclose(theta, 0.0, atol=simplify_tolerance): - circuit.append(RXGate(phi + lam), [0]) + circuit.append(RXGate(phi + lam, phase=phase), [0]) return circuit if not simplify or not np.isclose(lam, 0.0, atol=simplify_tolerance): circuit.append(RXGate(lam), [0]) if not simplify or not np.isclose(theta, 0.0, atol=simplify_tolerance): - circuit.append(RYGate(theta), [0]) + circuit.append(RYGate(theta, phase=phase), [0]) if not simplify or not np.isclose(phi, 0.0, atol=simplify_tolerance): circuit.append(RXGate(phi), [0]) return circuit @@ -354,17 +360,19 @@ def _circuit_xyx(theta, def _circuit_u3(theta, phi, lam, + phase=0, simplify=True, simplify_tolerance=DEFAULT_SIMPLIFY_TOLERANCE): # pylint: disable=unused-argument circuit = QuantumCircuit(1) - circuit.append(U3Gate(theta, phi, lam), [0]) + circuit.append(U3Gate(theta, phi, lam, phase=phase), [0]) return circuit @staticmethod def _circuit_u1x(theta, phi, lam, + phase=0, simplify=True, simplify_tolerance=DEFAULT_SIMPLIFY_TOLERANCE): # Shift theta and phi so decomposition is @@ -375,18 +383,18 @@ def _circuit_u1x(theta, if simplify and np.isclose(abs(theta), np.pi, atol=simplify_tolerance): # Zero X90 gate decomposition circuit = QuantumCircuit(1) - circuit.append(U1Gate(lam + phi + theta), [0]) + circuit.append(U1Gate(lam + phi + theta, phase=phase), [0]) return circuit if simplify and np.isclose(abs(theta), np.pi/2, atol=simplify_tolerance): - # Single X90 gate decomposition + # single X90 gate decomposition circuit = QuantumCircuit(1) - circuit.append(U1Gate(lam + theta), [0]) + circuit.append(U1Gate(lam + theta, phase=phase), [0]) circuit.append(RXGate(np.pi / 2), [0]) circuit.append(U1Gate(phi + theta), [0]) return circuit # General two-X90 gate decomposition circuit = QuantumCircuit(1) - circuit.append(U1Gate(lam), [0]) + circuit.append(U1Gate(lam, phase=phase), [0]) circuit.append(RXGate(np.pi / 2), [0]) circuit.append(U1Gate(theta), [0]) circuit.append(RXGate(np.pi / 2), [0]) @@ -397,10 +405,11 @@ def _circuit_u1x(theta, def _circuit_rr(theta, phi, lam, + phase=0, simplify=True, simplify_tolerance=DEFAULT_SIMPLIFY_TOLERANCE): circuit = QuantumCircuit(1) if not simplify or not np.isclose(theta, -np.pi, atol=simplify_tolerance): circuit.append(RGate(theta + np.pi, np.pi / 2 - lam), [0]) - circuit.append(RGate(-np.pi, 0.5 * (phi - lam + np.pi)), [0]) + circuit.append(RGate(-np.pi, 0.5 * (phi - lam + np.pi), phase=phase), [0]) return circuit diff --git a/test/python/quantum_info/test_synthesis.py b/test/python/quantum_info/test_synthesis.py index e5b950e66a25..59e65cfe62d7 100644 --- a/test/python/quantum_info/test_synthesis.py +++ b/test/python/quantum_info/test_synthesis.py @@ -119,12 +119,7 @@ def check_one_qubit_euler_angles(self, operator, basis='U3', with self.subTest(operator=operator): target_unitary = operator.data decomp_unitary = Operator(decomposer(target_unitary)).data - # Add global phase to make special unitary - target_unitary *= la.det(target_unitary)**(-0.5) - decomp_unitary *= la.det(decomp_unitary)**(-0.5) maxdist = np.max(np.abs(target_unitary - decomp_unitary)) - if maxdist > 0.1: - maxdist = np.max(np.abs(target_unitary + decomp_unitary)) self.assertTrue(np.abs(maxdist) < tolerance, "Worst distance {}".format(maxdist)) From ea8361dbf620df69d1a11fd7c4578e250bcb54b1 Mon Sep 17 00:00:00 2001 From: Christopher Wood Date: Thu, 13 Feb 2020 12:39:17 -0800 Subject: [PATCH 4/6] Update swap mapper pickles --- .../pickles/TestsBasicSwap_a_cx_to_map.pickle | Bin 1518 -> 1545 bytes .../TestsBasicSwap_handle_measurement.pickle | Bin 1902 -> 1949 bytes .../TestsBasicSwap_initial_layout.pickle | Bin 1759 -> 1786 bytes .../TestsLookaheadSwap_a_cx_to_map.pickle | Bin 2143 -> 2170 bytes ...stsLookaheadSwap_handle_measurement.pickle | Bin 3336 -> 3386 bytes .../TestsLookaheadSwap_initial_layout.pickle | Bin 2843 -> 2870 bytes .../TestsStochasticSwap_a_cx_to_map.pickle | Bin 1348 -> 1375 bytes ...tsStochasticSwap_handle_measurement.pickle | Bin 1745 -> 1796 bytes .../TestsStochasticSwap_initial_layout.pickle | Bin 1549 -> 1576 bytes 9 files changed, 0 insertions(+), 0 deletions(-) diff --git a/test/python/pickles/TestsBasicSwap_a_cx_to_map.pickle b/test/python/pickles/TestsBasicSwap_a_cx_to_map.pickle index 2dc1af8cf87721d5df256e54ba6b750397c03828..78037d7d509ea326275d2d4b93513f42f475cfce 100644 GIT binary patch literal 1545 zcma)6X?N2`6txppg{_6KLz+NaL8;ky$`+P@z-32;3Nez0j6jxsEeQX4- z%u^aa`D%ySWwKe(kwZ)ejXF`2CO@Nbz~t3*Fjgm$64ODmPDF%*3;5X`^1lXCeh_`{ zG5y%_{6cKMYWTkL+G?V$tnlmt-iSkLQ!u7TT}6r>R#wB`d^*OIjBsQDzkiEs=*SDW>D?cF@dpuH*0{nmW*3e& z;OYpx$?9Dra8j7jud7L#)a**!G#6!=r6PBB^JdxI9&Ql+O^sVL^6#k`p)7guHr3B* z+;JXEaaWP`6d5k?)lJ3t_cZRarRJ=YOsuW(r;9Dxj9e%d@IZ0X4>cYUs9M(O0(#$x$MT@JjJziZxy{!XDRxuy-cW zNcpfaHNZE8?@>oXx)@y#6MLgE?_#zoVQ5lAqatLA^?IxEj%gN^%6+3rSEJzaw<8+X zuBXv=F{a@dGzKo_Xtt0y>iDgv)H-rMKW WVtmjTmWh1BKe!-qU;VsehYYIclKr zfG=^htDVu#j`VFfMv{L_ibkAthqyEHKEmB z&0orZv!W~7IYt7k@t0E|?qsbBac#4V0<5#ATp&eP&LnvuyQBpHI#^HV9M#z1`pXE# zX0h?#;tWDZdMp-?Y6~-rx&v{h6da3x*DGT(- GX^kI%3+GP& diff --git a/test/python/pickles/TestsBasicSwap_handle_measurement.pickle b/test/python/pickles/TestsBasicSwap_handle_measurement.pickle index 27eb154a547de1d8d5f76b86df5a4a50a8b765bb..90a05353104a6832a334ea5a0392228f3a010867 100644 GIT binary patch literal 1949 zcma)7>3`cq5Vf723Oxwr4y6JSizLhY<*Rd~cwHz?9n_jDs4FyF zZ&R&SOGIB{%A4W8B$8e%yL~Crd?@=e$*{@W%m>|`$OJa~*y36)Im%>`MuQ}6r5WSI zu-6(n3&|imATsG-E0b(f*zS5;bbwd{GKSOfrcF4x7$s50XiRqshh-9}#AWRoO?eIW z2}juBIs7Nd`(5Ngl%?3&ZcI#JY)re_HE*j9xh#gFpJKO<%f0OyKS+87!%WB9RmKUk zVQhn4(XJcEfO)%1oHO=Zx;C#U>`E@|Dut_Eaym5Kp(5ovGmLNz?QGYHb0W^;TAJzP zK_2+HZql`N6B^2;D9z-MN5s`QxSnIrD%{|D+c=&*#a^4(f5XkdkJ@m!kpt~hxXI;i z>ePV!X4q+TM&jmnIaSlaTYTK=(kyB2IfdI?+DrS))T5*)M{wKqJTRwCh1<<3x$Q=2 zn^(BQ+NSbAve|9d_&Fzi+&M;-3s??>?z^iwLQJh^wh`wG%tKS zFvf$Iil3em%g!-=6f6%h%fkwfR9UnJv?Cs6qJ|ybVV;mrz>OqAUtI$e@FO?a#E%4hKGLJ?arI15_%4&LGTU4?gf`Th^4 zjIyc_!T3NSvAh$_VoR*PSzCKnG{WW>-QQiW^-B40(kX9RGHh!G=$IIVDI1LFNv#0NCT9Ewgb z;zMZ!4sL3Svp&v|zA#o4KGG5P+eVysjqWcfe5}h`DW$D%icb{A)@JXAPZd71HhVvO zuJDDm6|E75IyBT@`uK|UBrYm^t*Iw7_{KGz{Z`>S4Zm1Y>u!Iq@PoD4sr;z$leO8Y z{H*YcwOQ)jyotr9Lh`lS%|sAO{JMUD%(p~4m~-c_{t U3V)?R@hOVM=|P_1Z{F+HKNbOTr2qf` literal 1902 zcma)7=bzg|5Vd`&x`Z_P5ki3DlmkrfodXgOF=>D(ido5<8xdK)XGz2fEL>=&h2GoW z%Ir#OoiC6dUu0``-Y#DpJemqE z*Wb`+G*Z!*nDKY>UlM6Ak=?!&SuvD-ndaEwZ{&q;Pvim{18j0_E;-6&n#F@OYiBv* z#IV;MIZNpvKPYnPU^6G#qOjHVH)(=IgffBC@n;R4JQb&L&S=bbXvS}GFdAW-@9>|r z=yy?sah_qj*P4W)#Dp&M8vbU@u_A_|pJ7LUo&HvhAEdpKa#zRSQSn6iFtKF2y{6e1 zicEHwStRycx){H#Hko~dt7yB|B-TT$;cD8|DZ(NQaLuGe>&7vZ z%W;;=A*rLL%Ha1g)lEVvL zgP-#ySh)av)at)7;@fd06KCbY@1Wnu4f}Ug$v6pBM zX=gm8@N~U%$`eZyPdww2Ynt@z7|)TGcTnL_4VzN+e5L9I3-ZQz(WpAC(5XRFs$QB_ zbwuH4y>m*{v5BhVtcm8h>=R?WY};#HS6u?m;qo$Y@CvJbRl(OT(+9BHfY++AHT5xg zUCWr|lMvtyn&Zj6xt=#lM3%*oNUY(VC4I0R1k8U@q00vQKbVT8J_T%L+W4GNz5LW3j^9Hn!I2VvN(I8<8mVIT1}2pVq|> z$mQZ!NHPKjF;vJdLgY1~Fh+lemKkxzAVvyjFG8HF5%1DG=Xj4|Mkr|xF}SH2-Vg8r z=}Tc%;k;(pZyWKUYtHsZ3Loq8R;%NyZ-Y-1KD9A>2aFXyvoU)Ie6H|?jg_qtCUs;| ze;ME_(v!HL@U>1onZY-%>Fl=(-)Z=TDz)zR_X3`cq5cSbhfwUZ!5M`Dw+x{ z*Wc4fV=#I9Eis0kh@@lyZdDr;2yi_UE*YN zBhCw%u{sRs;9lPLg2H{Se}GYJ7DsMhZnR#vaJZj=jw(Fh*8Kp-jLvy{S>i#j7S%NI zP=JSB(##!&M_f8WM~&ohI+Rl^dW|xq1(WY8JZkc^=(RYiYnBuqv$1v=N)9i2%`y}L z9-rX}yYZV1E59fv&RPDcM&>fwafK&ca{4sar)n759C{(1qIs`HtT?gCPgAAqGcIqf zBLnnic=lFYQ)eX|7ZuTSO!U0M@=l_b9@`Wz{FmlMrtuGPLfarJvc!7fCH8`zbI7h4 zY_Q|-@~zqZiux6%epTVMoz&WJQ@qa1P21obra^6;H(lybms@;mhPN$=wb4k5yv(F? zx_;svCU{rjJw12r%z6RdufdyIy%h#$g*EyIw7`@4a5rw0h&+!Ykyr~lE86i6K4Sb; zg%dpT|5H<=Z2R(Ku0N^ZSzkstMQlf66P|Wyl{9`|;S+7Cla-T9Y)#>ejcM!Yb?XXe zZEVYW>{cT(T?Un-)`Qg4mbY=^||6pQ}LC;*EduI+ZEqXmn#Oe#ueY1 z3ZW3*P!Vlc3~7lgB&~DBd1($7SkxRF0f_W5jTOGr2*<1t7pl{kD2#M_G0ULOpgwJ> z!q~?6keS#-0XAk&Wu}nZSS5`x)RCbs0+ggDv8ixTQ*X^+>KbJ)DO}d&n>Dpo`+J2S zY)n(@m{R!B#_UvnQn+GcmijO|vAQEAUv`5+go(t}-3Bt3s9s?5&YHec_3Jsk+`-RG z_>01?)rDPQ{HE}G9#(g7{k@o3*dh~YK8X{V^{SL}%6w5A{J}_nD*RP_dIpo_P5iC! skIQ$<5+A`1-!ltdo4TdBG6$Q>6I@fc?sB%l*(PUO)b-kB8J1!1Uv$VlE&u=k literal 1759 zcma)7>7Ud@5S@cF$f2$XsN;_75j;j+@IFviISf0nfEtfDb~4R&Ad{JwV;Rs^@ESz@ zpRMYocXaWGewj>Ob-mYJ^}2Gi9U;!I#zilRv#6veN|6@jsD5?Us@Ih&>Y#D-7MdC@ z*WcD?G*U5=X#4xJ6$^=f|-($GsWg{|9v8S)vev?3j`+SF+=9dOkwl*hQuRYT}5+X4~%oDSs_i(aD))q;t4HJ&hWwdl17<(wsrCoR@4LrL(W*DOO3;Hep&wj2Mq z?&R0R#JNa6Rgaz_Hdo`QtDL?vjbJD6B0Q`1dM!oOR#f5VR8`aGUD{Sh26$nH-o13D z&PqBiDv=jS?>-4y7S>y-Y5}y9!DavwsTh4S7J^RH_3*+yq7_ZaM)#(9ge-FV%a#s!OQ+Kn74X1J)h z5SKJQAtCjT!+$zI{Aa2|hxgSgWqfWjzR>vcu8gnd8G-6j#@Ff`WeiNlHyYxujBuV2 zsU^x7sx``x(p)A`QFB}lu&(%ODvg*Kj@Uw6sczs^jf9VzrQJ(p>Eu8sA!sr5XE9LtD&t z<$H}EEM}z-kQ1x9C~3eA3K1p}H@6zd3{GBP;?8-Vqx^b_mpiycfVI8@QwKyGt`=iLPLW=F5WD gR^8H!$-&0*1b=A!=~A>o(I!P(s_V7OGAzU1Uu&8+UH||9 diff --git a/test/python/pickles/TestsLookaheadSwap_a_cx_to_map.pickle b/test/python/pickles/TestsLookaheadSwap_a_cx_to_map.pickle index 1528506f8ca058bb6e67c49c1ae29ff97c77aebd..73d230a4181f05d555c0c5759c7bb50d4167b9b6 100644 GIT binary patch literal 2170 zcma)8_nQ<&5Z${Y4(OeTCt^ASIvCEZAm&p+7LKT{VO)D>hwG6|Z)ZjpumnsCVn)o0 zIbhEE-&xf?eYbml-{~(qGq0s&%+R;>;q*0qQ!O=XN&POx$mS(Iiem-~8^7XMcet`xF9a})uBjT5eNSf79{ zu69JCP8u~5u4^)$q^eO>?drJpHmp}^HXxYuu>0sq|7txN~mO!;_lZVU0=WTEfl3MryT=a0{!^E1iy8ePeSQ;dZfEXEUwW z8uSjroerJW!6Ky)YXaQW#l&24l0B;B?cg6P>TZsI58+;48ztJ(mkAU@tY-y-PQeCN zkW~WQXX$Mu6rF@kgw1^tCVjop(A`4VDps5FQb?z5BW!orv;^)Yy(J2;ql=w$3Fvqe zjb>`u-_On-AUxR5evsLR9h7>xKg8Ti!@M99AExb^8~cZO#$}`{T)grW7aEVu9lc~j zKgyv4!ejlRW!yR__tyP!lQpTlvU#fzLYKEHA?nN9E??Vun<8-gj_PVp-eSTN4(rQX zEaRvG?Czqb<;1HK8sgW>nG8q=Hl+hcOcSVOggM>q3LwS$yH|R5jXB|52UV6NX=ZwDR z2`~IhUw1~|VZ;0g;YD#HLw1z#QlGw;Z8csYyec+Fa{|(zuMu8%7)RAd>}khLMBm{6 zZy1)x2ycp+=sQk$t54tCR^K~>cg5y-?p{u!_XzJhOxz!vao@!Uk-a_m*i7(YfR7CO z6NHcZ*neW#e@ggFT%G7)|2g3chlzVp^d;ddhuMTG6DBlaz7Ft>QE`&+ZJ&zotcvdm zKgjlzJt}@A{NykRBg%dz{NgYdW`sw=UZ4?QcI8x6Y7u^QGv@vrm>X6M%(}N#E?oKC jDdrx2W9PpU{;(&l_3B+XF8X(x2H z=AT&1v`Yv}T|Me8=Dm3U9M6$7X|sy779}y3m5Y5SXc%ZYL$aqjtvae1<0Ky^drKJz z+O2w&MC}&FDn?_q9`1=+88luoBB44{F;4NZVy?!#5S?%;ij%OzmEdd+PGzMl38%T< z5>8=PvFg9Ko4W3>bU2-fRuj$;%SEOF)|i^c(M*Ul%cEHhjl;8iobBqBT5_L5IM>yy z^lGynwd!FP6XjxBGppwj&Nr)iqCAq;LWU>Gg|w#l%L*SC%vBjQR2)ZvYFH!Aq!iV` zg`8?F;UXscKYMbPS$7xnewVP$x(jfLW>cE2TVQ>bZ`}uTur#f^%hA_G*m5Y;M$5FujF;sxK48y z>E-2u=2p30k5x3E|9Vru4TKvcy&TR)!i_9nud%{!a!t(5gj*zLV^&(d!JxMiZnJ1# zIJ=U{ulI3#4|mL`Ki;RNot^wAb1}*J??hw2r&TA6z7AxueBFiAIG59k(P)X`39=1Xv{+~DM2!p2M8qe{egVSH8D>U4ob{{ysu{rdWi6hMf>#9*F8LI{5?l_{$KuHnDy5) z$`2D>6dD<G4E!s`~}tom?vrLhw6*Yoj)QF)Z`ri6*V zV}!Sc{Jov|dx!9@#2m}%<+^!~@V>=_{^%@y4}SF+7PD!_c_gw|GUOMnny6YM#IJU~?0*2}8I=ID?rf7+RetW0a0kCJ`R{~3 avimjT_>=HgT+3eJhT7jw6a3BhQt2NxqPxlf diff --git a/test/python/pickles/TestsLookaheadSwap_handle_measurement.pickle b/test/python/pickles/TestsLookaheadSwap_handle_measurement.pickle index 8b3dc7f56807c521e3a55e78ae128997fef9f9c6..51ae1385ffbc5337dda82dfb32fd2ecd0a1d707e 100644 GIT binary patch literal 3386 zcma)9cYG985Kf^kReCj2l#3-+5POM&dJ<6-4|R=umzBM6x$y1YNkB(I#j}Fg6|r~4 z-V2CgFDQ0J>=ne`>&)AImwTZ6{AY9f&G+7X-^{$-&CJRa(!rE;t|pcCQ+fW2ypzr4 zo1$Cm!0@&eY*@iGfkGe#+XrENh&*;s1#>*8k$$9H1vu5;Kzqdqa ztT!9$L+BgxtU9k#ofk?j=L{|A=XH&jd#uT09sRu?b@`;K9~^&jfB`lJ7VEcGuY7lM zI>@;`H;a8)7{ply6E=(`I`GaqiYhJfzn81lAEdX#M!eAw!q6DkRYU^ARNI5}3>U-W zQGZn}H@2}!%p2@U?h%Ao%&YQ-DE;YdgWH1YczHglqMH&nQ&F!vUJ+rN6SmM;Wj^UL zTpcgt>j?Qd&_+!GTdplX8Z~QNIC2)Y;@n#kwh_6~*NA{^qso>`Ww+zXO6j?vBbA;} zgzY7_m>o^nfmeIOJa*@?ryXO8vlC%w;f#)CiOXFGyJ}3ljZ)0r2)k>v=!9)L%ou57 zkF`ytGENYrQ%**=$*Ps%w6G_qs3q*hoBf|P(JrmM+nd*qA&k|%OJN^vYTuYAyYEMc zi_|fZDZ%z9jMJEys#VN7!T}mJOcgL*i~F}T?OFSTbAKRjeGp;7x^wU0;L^E2M7qPR znW&0+C?O$vr8ScXhZ(IoJZ#Mo1Y0RyO1`6Q&9$ zQ(_AQUt?mcQ57j5r$$4y40PYYF;l-o>4Oh$C_;8 z>2#d(BYmP!?eT;Yw4<4XS%#w%L(WNrlZ7+0#L;ZRDH;<;$16t#!l@cH9L<)Vk&V*| zINg)eeg@%8$z`1Iv&2hy!sn>{<`T}<>gEy7G1Q$KsymNxzHsJ6>gKAI7Z5JgsD4&m zM7UUEMRhD*#%PX>OVo*(Pq9KVDfD_X;TG9Wj@3fKtwud=3puwF?hwwxNZie; z9(NM%(x|S--GqBIRw~ZBljrZ=0`61ucR%3)Q=u_`51RA$5aD5yZOq>z#razls$EQY zR6ANic+7D0c*t2wctSW!B1emrqbCVZX;eFUn(&Oq3`a|4{ubGIwt(l<{5?;2L2?=M z_hRe(EmJvOBD}2CEhoHUsCzZ$y`)&L5ndP8a@HqjLEbTM5Z=_7R`(X+ZH*Px@w-yy zZ<&pE)WobHyelM`zm>+fA8!0g5BxTKZ~JZeD!%=8{Db`Vd@XhFcTfPg-0!Femb$NM6nZ2} zf}K>bvl)zr#qXkmUCm%rEx(%zb~l4z*&L-drPOY~1l#YyS1Fm_Q?2TyR<#b0-&+Av zkUomgSKU`N3ca<6em@oLZw7TK2dLmcGZ+n=KS%`!n?bF%JHKPYPj;6-dh2seGUNIi zGFQL7_4lp%tQU@=SmWg@Uv3YU6_#JcOW49jD%TLchW|{2`wdm-Fun%K@SlW?Grc*V M^M^CUpXN3H0P|8lhX4Qo literal 3336 zcma)9cbpSd5WYfPdhbV543@Kj*b66uMozl1tmh`T+y-(vzTHF)WCRqA4aBa9y(1Rv z4MedQ5W6Dw3Sw8pKJ#{8l11g`Kku?L-<$cqnR&bK=9DLp^rj{=m5HRA$nsxgt#l?^ zA3Pdk{l_ZbYoKKCT$B>ZBC#eVB_(OA-bQ(>BmZ=)bZyG6skbdJ>)Q2pI)f&$rtGMx zwK5i(nrIf$x$MS_o%WIqX|K}D@IK3}t!y-^(hZrBR>n5aoRhR5w2Z`>Nr03Uw^J~x zW35y;dvY?J%j_Wo%3|qIJyRAL(p;4YKiM#zUKES>BXLDO1~MNvyfVm}a?F zy@z%t+Q(YT{)Tj|AF@MrtX&W%k#SQxSjT9o0>&-RuBl^^=(KGyF<(h%R?>ygHR2f6 zj-p%2(HqgtX%j7Vn2*C8x;q`JvvE}{SZ=dAG0LC$F&ugF=;}BV5SyHnnr-OiKL_|kW28i zwYVvcYn#aAkagspc@klIo~F~fm|dgPo8MwiCr92%GYB&a$k5y9WTiuTGs|a5gj2MV z*@QVEC3Ahusf5!6GrLI1Ji_VPCQ3*tIfIbX){v5U(x+J_&dlK~M^5+IgmWZU=tQ3@ zI{XtoU&UKMI8U=%NH{;l?gF3Pg@lU)voK({K<&JkaEZ3+chjYW%d{=ej>*gD%r|kl zIw6Y)R|rT>-D1L(k}`kZuJX^@)r4yVvp7h(NbS6qaGkbFN;!qs6K>G9P|6%`bY%E$ zBHUc)RcPjJ31;qAU*{6SZ4ye3(NejbPU^71#Lr6M zX2LH5k~(Z5{2HpmZ~oc)o$!ZXwgf4q3V#y*(l(AMvD^~A=8#*;tM&+$&4uqp&Mjl7 z?cdD!o9(&f{A{{ScoorZQ~p72GhU0OyUmq>`|Y++fR^gHVv4{6PU19_vsKtBJ7S**)ykrR<@cJ;Tmm+T32s**omiY}@n4)W470e5<`){KU1#vRBezI#{x4Q38A AQvd(} diff --git a/test/python/pickles/TestsLookaheadSwap_initial_layout.pickle b/test/python/pickles/TestsLookaheadSwap_initial_layout.pickle index 90be0799bc1062ea7c0c34a7f0100a4ed5a5b47c..1f8f0d9224c0ae972c6f3b931008ce449637f626 100644 GIT binary patch literal 2870 zcma)8SCkZ05Zyp*5D*a&6Gj@;RWU1wx+>_vO4Kp5W6x}}Z(w$I?{qgTU;fnkqv*>NH8Xg$5rRI_SAJ$C4KX zuOaIPUV+WvY8EzPro9Lo+sRIx(L6=(|9)LtJI18LCY-1bVN;P@$*F+8+Ezb1&BJDi zl80L1<_@;7RWBvAwDGj8^i$O@QB}gWC2VJ~>M-pw z+%HiPrd|Hg)4}#@GtcB)-_K^;oDpZ$iqRH!;PU+mHYfW(@07B%DIUP@46^-gB=Vn;_&rJ^ryJZ)>!_CgdV}15`+=9(&|(sNfxjKVkE1oGOFrWRhV{gWJGxs zVYHDohA=iJYn-h{X$j*AM@iI}Qp;pYk0vAyW@H^hIM!gTvUm>Fc+bIcMI66AQ7u+& zzA1 zuIJJoVM4sLEZ-J1l+TCKPRfL2BQGZsCK)etgvOYcd0S1?UN#Y$B`R0uB?twB883Z8 zV6d2%kma3q5Ee06$*wwu&?2?061z&7-l;OZJ@f!i({-m4PBQF>aB_^@3|mdtQ8Nj% zB#O$|okBR(V20gkgwqXH&Q1n+nu9a+3N8}Plpxs^W)semlCAr}*|v^3hj6aM%r32& zqDIdnoNrJmDf__%gbNMUT2j_b5f^D6E+$;Ejt`f%`7lQ-oJ+V&(#S%dN4PxZ!xd2{ zuOwV0G4o2KWZthPTw^e-ca_n-2>$^F@SvC5`y7m~dar zhx;QR9w0m@F^kK5c!=<@!Gx4}^9bQlgGsv%l#D}GXCt^N4 z8Ts%O;c1CkTIRzugl7#VCB>WP2+tc#N-k+DS;Pxobn^4@=Ho>NFKLy_2rtJ}z7nZ? zmGGLRT2`j=b;274Gb-OCyk#&cDazj_ykoGa8Llp!(WT#Y@Saw?obY~3?FW(C4+$U1 z>&wg3eoXkpU`FkygwG5nrNx2I311k@lF5$xx&nT0(gp=L?>A?2UZEy>WR1|5L<>J~ zrXLAEMYqXR^PtK2nedCv2dtR4g7B*-kP%!-_$}6)-=pTNBK#pSE6bX*n((K=Omo%{ z{xX<2w4!w$_(WgD=ill?wOSME`&Vl6pl19{_{UZ&n93@SSj`b@64hat4%0RN0+*D} Az5oCK literal 2843 zcma)8XP6UJ6x~7{5D*a&8&(F?Rj~_VSrs&}R5g|{PLg3?B%9rnnSlkl3KoLBg4h+Y zR}i}*_J+M;@4X<3xatq@eUq0>;QP=&DQE87d(OG{m6^3EWcsE)lhea zK_Q(%vLZm7!h@nk3wOqmK<8^j@1|(Tlv- znDGNI&t`Bn3mY-hK7@_!co)uSo}%x6zpkwvZ_;5CPSlUEsYtHoR6u`itDl+fVY67# zL#=Rg2V2;xkCNJ361K8cU)4|3&*aixD+a`>!=!$?HDMe5R1JvL6tQgy+Zn7jOnM9t zh*gD2mw)tju>IQ1Q(4#dGbuM~#98%Xw1pkG{6K=u$^Oqf#VqZL2l4xZ2|F6aDeRJ*3wv^sy$E~T@lG-sSvToru}_2Q)llq*!oG^9O?8h|DITf)RL=&*b23!> zJdCivl$RzBCmg_%RDUDpKwHNgL^xPthDUa*VH&L`9AZ$@p+gCW8LT9R)k}wlIv7#F z$n|ORr@AfH1pbJW9nPGNAjE8C3G(>dA5XKrw;|Sv`Bu%i@ z7)^g7;Yf)ZSL~0BX<12oFlk zk}@A2B0Ov`Atl~CLU`0*(yqnPF~jV{heZw^)6AC=9+xojVHx3x3Ll<~e0YlRw8Shc z^Who7vj&rr;>~k}=M5$$m$sKI-~}%_-uVpk@uGv5w94g#mn&4h5~+NZ@S3DrUZ(PO z!W#xND&Hi$WiTlz%HJluW3Z?ht}dO@rQdb%o>sep@P38b41ZG629}3(M_v(&->n_4f1ZzZ^>l6d|mX&8l`W77JlGNKN5b5u8fJ6 zAye=(;TM|^RWWZR;a5>0Be;t2TSaqzkD9ZZ@Q1{#Dr?Rf!k-2+&1ob2WiWAQWoaJx q6kpBf-kMmgY76xxD=}qA3;rhjW2==+Wi>~v;fS_aZ5SrQWZl2yRme2} diff --git a/test/python/pickles/TestsStochasticSwap_a_cx_to_map.pickle b/test/python/pickles/TestsStochasticSwap_a_cx_to_map.pickle index 2cfd35193b9a3a8d9cc14859733ddd6864d49ba0..c14de933def2e9c214a77025be0c7134205fb1f2 100644 GIT binary patch literal 1375 zcma)6X;;%g6fG@lP(c(CHw3rfQbk4Fx2m9#%BIF8#z{IbIkstUlN_t(%sKk0f7>^c z1_V9mhc?N3^X{E@=f3$-@DW5`g1G1hO+TS05*Efut@CLtr=O)XYaloM6I~K{)9%jY zav`g66zoBIWv<_HS0ujVis0dvr-hs!aR$) zfj)xnml!bZUR7OX91<|6Te zIKn=wYcu3mb!f=S*?kJX!J4cVVc5Zbdw>FdJ*=bzBjv<%a3I_C4rQ8rBZy+&BxwlH zz(LyfsKg=D?kAKii?M%~YoS*%I2ZjC~0zIvGc@P z;y5Q^s!b``Itwr@NLw*PKN9e~7%7WEt=a`KLC_a>;`&t90hbW=uz>k~NY#8Qg7?jaH8XI?mMh*+R zv)d{4bE1ABVQr^Y6W7Aajtz8!i@HJ8Vp+8~MJ>K+V@W6vElaG>8HsJaS>5u@n)c0W zF-r&VMpwL*u>Yudw^i|8Op((*h&ihG$hG}J?uy_z_$0=ej`%v_I`}MRm_1B;PUoJ} z0J|~ssvIm6A)9l3F{lMPZ!D>MLVd2Oca@%`r{6O>(TBv*+li{%vPB z4OR4@AKJ~%?A)2TduP8FJowRPKQ4NH!%JvI!ooPIwYSDfx-F$%1G(uR=#t2rR(CF! z3t5e$U=7kIbG)V#*PR;MLbugJ7N^Q$h8`QeW|o9EV;)9+J&cM`%)%;bRExq`TwpOb z&_~ey5(B2ytD*zu@&HCfqdQf;;fH=qp%|=)g4IPw1ol`4tzlAgkhp#vVQ;x>+vEk> zw6C1A`V`<6Yp_~`AsfTi02#b`SWOW}Dv4`je^%uVDjIy#k7C{+E(p-T0jhIU;-G2u z6H4aA*uTRyv#S{#4iV6}#9_1D4{$`Y8u{Nij+P54i@J$pHjbM@WuA~YVTv&^uDyO( zK_q zC5oSwxSWyZ;ff%x1Tl2g6f;8M&q-WU9L%PzYumiU^~|=-3(>|}xRDj|H+e>1*p{(I zf2l}s68tTR+omuS14NRV19b>@h@m_&lv51dC5Du!?wMjjjB*?ITX?Wro~BU>>v5Xo zAtiYvQR*bgE4?=H_}3IqD1|jd4yAuDZUos7Jf$H}0(V)&olTl8EbN}yO4BY<+Gi5a zJ89LFHBoL~y>9GUt;UDdIiXk_Ra&!Ur3+7-!nzY0F0&j+kZEFx8ySI;Wv^V&(=MpE3fn u+18JNI-vc=vU(fTc3mAez^EHn!b_i`6r(D^BR73Z0`|3@#Mq!aF8&16$*!aT diff --git a/test/python/pickles/TestsStochasticSwap_handle_measurement.pickle b/test/python/pickles/TestsStochasticSwap_handle_measurement.pickle index 5491cda0cc0c620378c494806a53b0438bf9c291..d0d77dd61182dd494e92379a9abcb2c10007c09d 100644 GIT binary patch literal 1796 zcma)7>3`cq5Vf723OxwrE+vH$WCG^Ck3!NTRMhl>ibI%{yv>Npa-Jnox3JJcTNTRj zFEhK+CRJ1T@kN$q=e>D5Z+3lkCPJKDjPqU;PojcT5_7pG>gY+*2{9n ziOH}xb(Yg{eq7|z!FDdPLt&@uZ_@;c2xS7N@6Q@Kc_B{YoY9!=R|#t*GKu@V7R~q_ z&WWbj8+(v9_K?EEE;)Ug?NcSW%@wA2gm!yvVok(49;LZ{ z5f)*9$JV=cmqQb|8fUqjutmHZ2ahxNeuXDoe+To~RUELw{vB=%KV#|eBooam9CW#x zS{m?_F*}Q|NIdP;u9^-W3h<0e`$^YcP&vtW-UHk!!!C@bc!K5?;Bpr>~lx26&AYMS#~! zykYrmYwF>3PF6$g;7#U!OW|$Z3Oy-x0^X^4+uEylnU~GfX1zyUrhfl!-Y5}S7Dpnn zt~kqj0v&w7{D&1juUDE8w6prg2FV(Hpu@!}n zZA|-f*kqni@N9J54Yo>@_{564ml4?|vF4oRM`g!JF3?x_v}uRFEOa7HapyXA%1_sh zoN;M^x@`K{66Z)We5UYugMk{r^EJ@d!>6a^ixOXwzBay6___()b!Wvud&Q@h_K`k| z-zW^4*^N{9ttq386)r6rg<*-vC>$!tCbXgOLalJsr7kV9LMjm(h2JS$Yyuk!lcvIv zLfXu3C>*aVT+@YgD^OxWdT=v^oCkv%!&+$iYFNLg1s>K*bc`LI8pLIVE1M8MY#@F# zCr8l=BYrZ7pB1idLR{NG{9+EouXKVD*QL1`z=t5ijQ}@kUYJN^B4vPEv?Tn!JSx?V zEctdDz~s^6m7da1ISAdMO_i>5-$ z_182Sja2j{X8axeN+RtgvfGy;D~7T!(;RF3wY<>niCkc9fOW3jOOA4xX7M1++F8yx zG3>QR&QdzamqjietmjQOC~S27b($a%p-kX({7r^VUW?N>XEZi-XvS}GFdAXA@9;}n z^t&j+IM1-fYfVB?VnR=O4S&7nSQSIj&#*PXHh-hW57J&qxxM3Wt$3n*m{_tMUehFo zB9q+}7KxqzU5sB=@-$bnOJTQ5PKS)#CbN(53~lk6#CnJ|?4j+QA}qoH&rVwOh#f<@ z5@)#_vL9TDgXfrcufp@Lzk&JeD)w1X{|-0qp0#v%fr(}nUUa#WS{m?@aW;!@NxbY; z7Mkw865v&r_LAOvPT@6|_R*}dI8J+Vgn6%7gl5%M*l$*8-fLB{1%=mbY^De$hv&Tp zKj&nCH^w+%r9Wvn$T?B=v(0-SRCsf`pM*mX_w%snXMiKLAOgHK#@m+Lwxya<=TteZ z4&GtjcNN~#ZO{`^CE)#vx2b*lfO*+OZPbV4GWAD~@0AI<*!0M$k$4io4db(|x*fvC1I&;hf`A51hz2$6UWYFd?ZGUI{$vge^WpI1~zg z=GAC`Do#s{!$IqLD9K!CKJdc*mT@8rInp>1;nnaU2a<6<1dzkoRg<%iK%RU~sXCJ@<<3m0P&fT<+hxT0fE#afkoaz5NwaT{gJlld!H(-L-a# z>_L3Z&b8S&&pP)FJ7*hk-KQm5P!Yb}!gnvznu_&&T-HM0v(Qz8YdeM7w!f$N;h!=; zvP?M72|F)IS!B)71Uxi$AYvY`!3K{euD@J8tfg2ooziwPr?@$54OieTSD-EN zwk@&CCEnS>U9x@kv%xR?-DpQI{kq*tq3fmJ=q!JL-yPx)gL^L^qHV-|>T>rD=o};d zbO>n>zko=#5kp#GgrWTdRb<&$D+R+jdk%cc z-)44YVF(=P2V2(6-a9+jcjjF^M5JCtSu>2%FsCPSo@9A%^vWI;uZu;Np>q5^sv0%N z+fb=g65bQ2dsFl?N}?>{aVO?)`gw*8-bM;`x;*39=wp*p))B)@Br57Bs;M%bba~os zN_J<7XM$mZ*f(ozalB0i9rHlMU~O;Ga*1VIq@Q52E$d#Df)ZOjM$aVgb&v;9rm)Se zehh`N4NbWfZ^BRvc*=VU+kNctwooAKCtbQWG45>Vfsb9~E!U}$ij_!ZA|*M9kl_cq z=d{Ld$Jk*4yj#$iY9 zkuz4~DCvqJ=G;mi$Z4BzX#8mNa?Y($)L`Z{j+C)_9taB0xz#-2K8~*8*!qk21Xp<~ z*!|HHKM{wcaok+gNI5!ypC#!nr|3hX{vvm_<)l^egq$Vnlk0iIm@5^9JT5h3i{>w4 zXcGT|#;)xU8ARAs&qEU>kdA8#ApvGOabJwa06CB8``x zYWzw6mHf;Sj01p=U*URj1j(UkmFv;^MvSF#J?K+3q&wRgmRt` zU3rlZLfByf`BGuo$BLZcHWJ#1^byGx_jXX1#TF5?4mueRVuAlk$;z!^*Niz}^X#G7 zI_CAMInMBk`j0hw#gZ#H5{ Date: Thu, 13 Feb 2020 14:45:46 -0800 Subject: [PATCH 5/6] Fix to_matrix test --- test/python/circuit/test_controlled_gate.py | 7 +------ test/python/circuit/test_extensions_standard.py | 10 ++++++---- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/test/python/circuit/test_controlled_gate.py b/test/python/circuit/test_controlled_gate.py index d0ad34219a07..d48862f6ef17 100644 --- a/test/python/circuit/test_controlled_gate.py +++ b/test/python/circuit/test_controlled_gate.py @@ -473,12 +473,7 @@ def test_controlled_standard_gates(self, num_ctrl_qubits): # 'object has no attribute "control"' # skipping Id and Barrier continue - if gate.name == 'rz': - iden = Operator.from_label('I') - zgen = Operator.from_label('Z') - base_mat = (np.cos(0.5 * theta) * iden - 1j * np.sin(0.5 * theta) * zgen).data - else: - base_mat = Operator(gate).data + base_mat = Operator(gate).data target_mat = _compute_control_matrix(base_mat, num_ctrl_qubits) self.assertTrue(matrix_equal(Operator(cgate).data, target_mat, ignore_phase=True)) diff --git a/test/python/circuit/test_extensions_standard.py b/test/python/circuit/test_extensions_standard.py index ecf2020d3c69..929b0de36320 100644 --- a/test/python/circuit/test_extensions_standard.py +++ b/test/python/circuit/test_extensions_standard.py @@ -19,13 +19,13 @@ from inspect import signature from ddt import ddt, data, unpack -from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister, execute +from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister, transpile from qiskit.qasm import pi from qiskit.exceptions import QiskitError from qiskit.circuit.exceptions import CircuitError from qiskit.test import QiskitTestCase from qiskit.circuit import Gate, ControlledGate, ParameterVector -from qiskit import BasicAer +from qiskit.quantum_info.operators import Operator from qiskit.quantum_info.operators.predicates import matrix_equal, is_unitary_matrix from qiskit.extensions.standard import ( @@ -1354,7 +1354,6 @@ def test_to_matrix(self): definition.""" params = [0.1 * i for i in range(10)] gate_class_list = Gate.__subclasses__() + ControlledGate.__subclasses__() - simulator = BasicAer.get_backend('unitary_simulator') for gate_class in gate_class_list: sig = signature(gate_class.__init__) free_params = len(sig.parameters) - 1 # subtract "self" @@ -1376,7 +1375,10 @@ def test_to_matrix(self): self.log.info('to_matrix method FAILED for "%s" gate', gate.name) continue - definition_unitary = execute([circ], simulator).result().get_unitary() + # Unroll gate definition + ucirc = transpile(circ, optimization_level=0, basis_gates=['id', 'u3', 'cx']) + # Unrolled Unitary + definition_unitary = Operator(ucirc).data self.assertTrue(matrix_equal(definition_unitary, gate_matrix)) self.assertTrue(is_unitary_matrix(gate_matrix)) From becf84a0c54cfafef2eb9bb3b8d3e95494ff3a12 Mon Sep 17 00:00:00 2001 From: Erick Winston Date: Tue, 3 Mar 2020 23:04:54 -0500 Subject: [PATCH 6/6] resolve merge conflicts with master --- .github/CODEOWNERS | 4 +- azure-pipelines.yml | 10 +- docs/apidocs/qiskit.rst | 33 -- docs/index.rst | 2 +- qiskit/assembler/assemble_schedules.py | 398 +++++++------ qiskit/assembler/disassemble.py | 2 - qiskit/circuit/__init__.py | 8 + qiskit/circuit/add_control.py | 80 ++- qiskit/circuit/controlledgate.py | 88 ++- qiskit/circuit/gate.py | 54 +- qiskit/circuit/library/__init__.py | 17 + qiskit/circuit/library/boolean_logic.py | 124 +++++ qiskit/circuit/quantumcircuit.py | 36 +- qiskit/circuit/random/utils.py | 34 +- qiskit/compiler/assemble.py | 155 +++--- qiskit/compiler/schedule.py | 23 +- qiskit/compiler/transpile.py | 132 ++--- qiskit/converters/ast_to_dag.py | 52 +- qiskit/converters/circuit_to_gate.py | 4 + qiskit/converters/dag_to_circuit.py | 2 +- qiskit/extensions/__init__.py | 22 +- .../quantum_initializer/__init__.py | 10 +- qiskit/extensions/quantum_initializer/diag.py | 134 +---- .../quantum_initializer/diagonal.py | 176 ++++++ .../quantum_initializer/initializer.py | 8 +- .../quantum_initializer/isometry.py | 48 +- .../quantum_initializer/mcg_up_to_diagonal.py | 8 +- qiskit/extensions/quantum_initializer/squ.py | 8 +- qiskit/extensions/quantum_initializer/uc.py | 348 ++++++++++++ .../quantum_initializer/uc_pauli_rot.py | 188 +++++++ qiskit/extensions/quantum_initializer/ucg.py | 302 +--------- .../extensions/quantum_initializer/ucrot.py | 159 +----- qiskit/extensions/quantum_initializer/ucrx.py | 128 +++++ qiskit/extensions/quantum_initializer/ucry.py | 125 +++++ qiskit/extensions/quantum_initializer/ucrz.py | 126 +++++ qiskit/extensions/quantum_initializer/ucx.py | 86 +-- qiskit/extensions/quantum_initializer/ucy.py | 85 +-- qiskit/extensions/quantum_initializer/ucz.py | 86 +-- qiskit/extensions/standard/__init__.py | 38 +- qiskit/extensions/standard/ccx.py | 2 +- qiskit/extensions/standard/ch.py | 4 +- qiskit/extensions/standard/crx.py | 2 +- qiskit/extensions/standard/cry.py | 2 +- qiskit/extensions/standard/crz.py | 2 +- qiskit/extensions/standard/cswap.py | 2 +- qiskit/extensions/standard/cu1.py | 2 +- qiskit/extensions/standard/cu3.py | 2 +- qiskit/extensions/standard/cx.py | 2 +- qiskit/extensions/standard/cy.py | 2 +- qiskit/extensions/standard/cz.py | 2 +- qiskit/extensions/standard/h.py | 98 ++-- qiskit/extensions/standard/i.py | 123 ++++ qiskit/extensions/standard/iden.py | 62 +-- qiskit/extensions/standard/ms.py | 21 +- .../standard/multi_control_rotation_gates.py | 2 +- .../standard/multi_control_toffoli_gate.py | 3 +- qiskit/extensions/standard/r.py | 41 +- qiskit/extensions/standard/rcccx.py | 14 +- qiskit/extensions/standard/rccx.py | 8 +- qiskit/extensions/standard/rx.py | 95 +++- qiskit/extensions/standard/rxx.py | 79 +-- qiskit/extensions/standard/ry.py | 91 ++- qiskit/extensions/standard/rz.py | 121 ++-- qiskit/extensions/standard/rzz.py | 56 +- qiskit/extensions/standard/s.py | 72 ++- qiskit/extensions/standard/swap.py | 147 +++-- qiskit/extensions/standard/t.py | 76 ++- qiskit/extensions/standard/u1.py | 126 +++-- qiskit/extensions/standard/u2.py | 51 +- qiskit/extensions/standard/u3.py | 119 ++-- qiskit/extensions/standard/x.py | 224 ++++++-- qiskit/extensions/standard/y.py | 121 ++-- qiskit/extensions/standard/z.py | 122 ++-- qiskit/extensions/unitary.py | 89 ++- qiskit/providers/basejob.py | 53 +- qiskit/pulse/__init__.py | 111 ++-- qiskit/pulse/channels.py | 4 +- qiskit/pulse/cmd_def.py | 2 + qiskit/pulse/commands/__init__.py | 26 +- qiskit/pulse/commands/instruction.py | 256 +-------- qiskit/pulse/instruction_schedule_map.py | 90 ++- qiskit/pulse/instructions/__init__.py | 24 + qiskit/pulse/instructions/instruction.py | 286 ++++++++++ qiskit/pulse/interfaces.py | 13 +- qiskit/pulse/parser.py | 101 ++-- qiskit/pulse/pulse_lib/__init__.py | 2 +- qiskit/pulse/pulse_lib/continuous.py | 59 +- qiskit/pulse/pulse_lib/discrete.py | 296 +++++++--- qiskit/pulse/reschedule.py | 2 +- qiskit/pulse/schedule.py | 205 +++---- qiskit/quantum_info/__init__.py | 60 +- .../quantum_info/operators/base_operator.py | 327 ++++++----- qiskit/quantum_info/operators/channel/chi.py | 128 +---- qiskit/quantum_info/operators/channel/choi.py | 156 +----- .../quantum_info/operators/channel/kraus.py | 131 ++--- qiskit/quantum_info/operators/channel/ptm.py | 142 +---- .../operators/channel/quantum_channel.py | 123 ++-- .../operators/channel/stinespring.py | 102 +--- .../quantum_info/operators/channel/superop.py | 236 ++------ .../operators/channel/transformations.py | 7 +- qiskit/quantum_info/operators/operator.py | 271 +++++---- qiskit/quantum_info/operators/pauli.py | 4 +- qiskit/quantum_info/synthesis/__init__.py | 4 +- .../quantum_info/synthesis/ion_decompose.py | 2 +- .../synthesis/one_qubit_decompose.py | 449 ++++++++++----- .../synthesis/two_qubit_decompose.py | 13 +- qiskit/scheduler/methods/basic.py | 62 +-- .../backends/boeblingen/fake_boeblingen.py | 1 + .../johannesburg/fake_johannesburg.py | 1 + .../mock/backends/melbourne/fake_melbourne.py | 1 + .../mock/backends/ourense/fake_ourense.py | 1 + .../poughkeepsie/fake_poughkeepsie.py | 1 + .../mock/backends/rochester/fake_rochester.py | 1 + .../backends/rueschlikon/fake_rueschlikon.py | 1 + .../mock/backends/singapore/fake_singapore.py | 1 + .../mock/backends/tenerife/fake_tenerife.py | 1 + qiskit/test/mock/backends/tokyo/fake_tokyo.py | 1 + qiskit/test/mock/backends/vigo/fake_vigo.py | 1 + .../mock/backends/yorktown/fake_yorktown.py | 1 + qiskit/test/mock/fake_backend.py | 58 +- qiskit/transpiler/passes/__init__.py | 1 + .../passes/basis/ms_basis_decomposer.py | 6 +- qiskit/transpiler/passes/layout/csp_layout.py | 94 ++-- .../passes/optimization/consolidate_blocks.py | 4 +- .../crosstalk_adaptive_schedule.py | 6 +- .../remove_diagonal_gates_before_measure.py | 4 +- qiskit/transpiler/passes/routing/__init__.py | 1 + .../passes/routing/algorithms/__init__.py | 35 ++ .../routing/algorithms/token_swapper.py | 241 ++++++++ .../passes/routing/algorithms/types.py | 45 ++ .../passes/routing/algorithms/util.py | 103 ++++ .../passes/routing/layout_transformation.py | 108 ++++ .../passes/utils/check_cx_direction.py | 4 +- .../transpiler/passes/utils/cx_direction.py | 4 +- qiskit/transpiler/passmanager.py | 232 ++++---- .../transpiler/preset_passmanagers/level2.py | 3 + .../transpiler/preset_passmanagers/level3.py | 3 + qiskit/util.py | 17 + .../jsonschema/schema_validation.py | 45 +- qiskit/visualization/__init__.py | 9 + qiskit/visualization/gate_map.py | 8 + qiskit/visualization/matplotlib.py | 12 +- qiskit/visualization/qcstyle.py | 4 +- qiskit/visualization/text.py | 5 + .../visualization/transition_visualization.py | 316 +++++++++++ .../add-open-controls-bb21111b866ada43.yaml | 19 + ...base-operator-update-b0d824738b4bfd4d.yaml | 17 + ...ogic-circuit-library-0f913cc04063210b.yaml | 11 + ...eprecations-in-pulse-ffc384b325b077f1.yaml | 7 +- ...nsistent-gate-naming-efa669c7a8d3a654.yaml | 31 ++ ...defined-by-frequency-a86b4cc623a2e43b.yaml | 5 + ...-rhs-on-extending-qc-9e65804b6b0ab4da.yaml | 8 + ...splayout_level_2and3-df6b06d05a34263f.yaml | 11 + ...efault-schedule-name-51ba198cf08978cd.yaml | 6 + .../fake-backend-run-37d48dbf0e9de6f3.yaml | 12 + ...bstituted-parameters-78bc9ece47013dbb.yaml | 7 + .../notes/fix3684-69e230f98546deb6.yaml | 5 + ...wait-for-final-state-b9951d21aad5d3c2.yaml | 8 + .../one-qubit-synthesis-c5e323717b8dfe4d.yaml | 16 + .../operator-qargs-aeb2254b5a643013.yaml | 16 + ...sition-visualization-a62d0d119569fa05.yaml | 44 ++ ...uctions-and-commands-aaa6d8724b8a29d3.yaml | 24 + requirements.txt | 1 + setup.py | 1 + test/python/circuit/test_circuit_data.py | 22 +- .../circuit/test_circuit_load_from_qasm.py | 78 ++- .../python/circuit/test_circuit_operations.py | 16 + .../python/circuit/test_circuit_properties.py | 33 ++ test/python/circuit/test_controlled_gate.py | 523 +++++++++++++++--- .../{test_diag.py => test_diagonal_gate.py} | 5 +- .../circuit/test_extensions_standard.py | 48 +- test/python/circuit/test_gate_power.py | 58 +- .../python/circuit/test_instruction_repeat.py | 16 +- test/python/circuit/test_instructions.py | 18 +- test/python/circuit/test_isometry.py | 2 +- test/python/circuit/test_library.py | 52 ++ test/python/circuit/test_parameters.py | 69 ++- .../circuit/{test_ucg.py => test_uc.py} | 10 +- test/python/circuit/test_ucx_y_z.py | 10 +- test/python/compiler/test_assembler.py | 2 + test/python/compiler/test_transpiler.py | 6 +- .../pickles/TestsBasicSwap_a_cx_to_map.pickle | Bin 1518 -> 1563 bytes .../TestsBasicSwap_handle_measurement.pickle | Bin 1902 -> 1975 bytes .../TestsBasicSwap_initial_layout.pickle | Bin 1759 -> 1804 bytes .../TestsLookaheadSwap_a_cx_to_map.pickle | Bin 2143 -> 2188 bytes ...stsLookaheadSwap_handle_measurement.pickle | Bin 3336 -> 3415 bytes .../TestsLookaheadSwap_initial_layout.pickle | Bin 2843 -> 2888 bytes .../TestsStochasticSwap_a_cx_to_map.pickle | Bin 1348 -> 1393 bytes ...tsStochasticSwap_handle_measurement.pickle | Bin 1745 -> 1822 bytes .../TestsStochasticSwap_initial_layout.pickle | Bin 1549 -> 1594 bytes test/python/providers/test_jobmethods.py | 38 ++ test/python/pulse/test_continuous_pulses.py | 35 +- test/python/pulse/test_discrete_pulses.py | 42 +- test/python/pulse/test_schedule.py | 18 + test/python/qasm/all_gates.qasm | 55 ++ .../operators/channel/test_chi.py | 52 +- .../operators/channel/test_choi.py | 56 +- .../operators/channel/test_kraus.py | 39 +- .../operators/channel/test_linearops.py | 10 +- .../operators/channel/test_ptm.py | 56 +- .../operators/channel/test_stinespring.py | 39 +- .../operators/channel/test_superop.py | 72 ++- .../quantum_info/operators/test_operator.py | 83 ++- .../quantum_info/states/test_densitymatrix.py | 7 - .../quantum_info/states/test_statevector.py | 7 - test/python/quantum_info/test_synthesis.py | 51 +- test/python/test_dagcircuit.py | 74 +-- test/python/test_qasm_parser.py | 19 +- test/python/tools/jupyter/test_notebooks.py | 5 + test/python/tools/test_parallel.py | 15 +- .../transpiler/test_collect_2q_blocks.py | 22 +- .../transpiler/test_consolidate_blocks.py | 16 +- test/python/transpiler/test_csp_layout.py | 8 - test/python/transpiler/test_decompose.py | 4 +- .../transpiler/test_layout_transformation.py | 56 ++ .../transpiler/test_optimize_1q_gates.py | 4 +- .../python/transpiler/test_passmanager_run.py | 6 +- .../transpiler/test_preset_passmanagers.py | 84 ++- ...st_remove_diagonal_gates_before_measure.py | 8 +- test/python/transpiler/test_token_swapper.py | 120 ++++ test/python/transpiler/test_unroller.py | 4 +- .../references/circuit_text_ref.txt | 26 +- .../references/circuit_text_ref_original.txt | 12 + .../visualization/test_circuit_text_drawer.py | 6 +- .../test_circuit_visualization_output.py | 4 +- .../randomized/test_transpiler_equivalence.py | 10 +- 226 files changed, 8250 insertions(+), 4585 deletions(-) delete mode 100644 docs/apidocs/qiskit.rst create mode 100644 qiskit/circuit/library/__init__.py create mode 100644 qiskit/circuit/library/boolean_logic.py create mode 100644 qiskit/extensions/quantum_initializer/diagonal.py create mode 100644 qiskit/extensions/quantum_initializer/uc.py create mode 100644 qiskit/extensions/quantum_initializer/uc_pauli_rot.py create mode 100644 qiskit/extensions/quantum_initializer/ucrx.py create mode 100644 qiskit/extensions/quantum_initializer/ucry.py create mode 100644 qiskit/extensions/quantum_initializer/ucrz.py create mode 100644 qiskit/extensions/standard/i.py mode change 100755 => 100644 qiskit/extensions/standard/ms.py create mode 100644 qiskit/pulse/instructions/__init__.py create mode 100644 qiskit/pulse/instructions/instruction.py create mode 100644 qiskit/transpiler/passes/routing/algorithms/__init__.py create mode 100644 qiskit/transpiler/passes/routing/algorithms/token_swapper.py create mode 100644 qiskit/transpiler/passes/routing/algorithms/types.py create mode 100644 qiskit/transpiler/passes/routing/algorithms/util.py create mode 100644 qiskit/transpiler/passes/routing/layout_transformation.py create mode 100644 qiskit/visualization/transition_visualization.py create mode 100644 releasenotes/notes/add-open-controls-bb21111b866ada43.yaml create mode 100644 releasenotes/notes/base-operator-update-b0d824738b4bfd4d.yaml create mode 100644 releasenotes/notes/boolean-logic-circuit-library-0f913cc04063210b.yaml create mode 100644 releasenotes/notes/consistent-gate-naming-efa669c7a8d3a654.yaml create mode 100644 releasenotes/notes/continuous-waves-defined-by-frequency-a86b4cc623a2e43b.yaml create mode 100644 releasenotes/notes/copy-rhs-on-extending-qc-9e65804b6b0ab4da.yaml create mode 100644 releasenotes/notes/csplayout_level_2and3-df6b06d05a34263f.yaml create mode 100644 releasenotes/notes/default-schedule-name-51ba198cf08978cd.yaml create mode 100644 releasenotes/notes/fake-backend-run-37d48dbf0e9de6f3.yaml create mode 100644 releasenotes/notes/fix-propagation-of-substituted-parameters-78bc9ece47013dbb.yaml create mode 100644 releasenotes/notes/fix3684-69e230f98546deb6.yaml create mode 100644 releasenotes/notes/job-wait-for-final-state-b9951d21aad5d3c2.yaml create mode 100644 releasenotes/notes/one-qubit-synthesis-c5e323717b8dfe4d.yaml create mode 100644 releasenotes/notes/operator-qargs-aeb2254b5a643013.yaml create mode 100644 releasenotes/notes/quibit-transition-visualization-a62d0d119569fa05.yaml create mode 100644 releasenotes/notes/unify-instructions-and-commands-aaa6d8724b8a29d3.yaml rename test/python/circuit/{test_diag.py => test_diagonal_gate.py} (95%) create mode 100644 test/python/circuit/test_library.py rename test/python/circuit/{test_ucg.py => test_uc.py} (90%) create mode 100644 test/python/qasm/all_gates.qasm create mode 100644 test/python/transpiler/test_layout_transformation.py create mode 100644 test/python/transpiler/test_token_swapper.py create mode 100644 test/python/visualization/references/circuit_text_ref_original.txt diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index e1ae442b834a..ff2721cfd613 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -15,11 +15,11 @@ docs/ @nonhermitian @mtreinish circuit/ @ajavadia @kdk @ewinston @1ucian0 @mtreinish extensions/ @ajavadia @kdk @ewinston @1ucian0 @mtreinish dagcircuit/ @ajavadia @kdk @1ucian0 @maddy-tod -providers/ @ajavadia @mtreinish @diego-plan9 +providers/ @ajavadia @mtreinish @diego-plan9 @jyu00 @chriseclectic @ewinston basicaer/ @chriseclectic @ajavadia pulse/ @lcapelluto @taalexander @eggerdj @nkanazawa1989 @danpuzzuoli scheduler/ @lcapelluto @taalexander @eggerdj @nkanazawa1989 @danpuzzuoli -quantum_info/ @chriseclectic @ajavadia +quantum_info/ @chriseclectic @ajavadia @ewinston qi/ @chriseclectic @ajavadia result/ @diego-plan9 @jyu00 @taalexander @mtreinish @ajavadia schemas/ @diego-plan9 @dcmckayibm diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 3df2232abd56..e3004194fad5 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -62,7 +62,7 @@ stages: TWINE_PASSWORD: $(TWINE_PASSWORD) - job: 'macos' condition: startsWith(variables['Build.SourceBranch'], 'refs/tags') - pool: {vmImage: 'macOS-10.13'} + pool: {vmImage: 'macOS-10.15'} variables: python.version: '3.7' CIBW_BEFORE_BUILD: pip install -U Cython @@ -222,8 +222,8 @@ stages: inputs: pathtoPublish: 'docs/_build/html' artifactName: 'html_docs' - - job: 'MacOS_HighSierra_Tests' - pool: {vmImage: 'macOS-10.13'} + - job: 'MacOS_Catalina_Tests' + pool: {vmImage: 'macOS-10.15'} condition: not(startsWith(variables['Build.SourceBranch'], 'refs/tags')) strategy: matrix: @@ -386,8 +386,8 @@ stages: inputs: testResultsFiles: '**/test-*.xml' testRunTitle: 'Test results for Linux Python $(python.version)' - - job: 'MacOS_HighSierra_Tests' - pool: {vmImage: 'macOS-10.13'} + - job: 'MacOS_Catalina_Tests' + pool: {vmImage: 'macOS-10.15'} condition: not(startsWith(variables['Build.SourceBranch'], 'refs/tags')) strategy: matrix: diff --git a/docs/apidocs/qiskit.rst b/docs/apidocs/qiskit.rst deleted file mode 100644 index 933ce5e74c16..000000000000 --- a/docs/apidocs/qiskit.rst +++ /dev/null @@ -1,33 +0,0 @@ -.. module:: qiskit - -========================== -Qiskit Terra API Reference -========================== - -.. toctree:: - :maxdepth: 1 - - circuit - compiler - execute - visualization - converters - assembler - dagcircuit - extensions - providers_basicaer - providers - providers_models - pulse - scheduler - qasm - qobj - quantum_info - result - tools - tools_jupyter - transpiler - transpiler_passes - transpiler_preset - validation - visualization diff --git a/docs/index.rst b/docs/index.rst index 9b256d73dc39..27af89033ee7 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -6,7 +6,7 @@ Qiskit Terra documentation :maxdepth: 2 :hidden: - API References + API References Release Notes .. Hiding - Indices and tables diff --git a/qiskit/assembler/assemble_schedules.py b/qiskit/assembler/assemble_schedules.py index d5ced498a03b..0c9cf634d81a 100644 --- a/qiskit/assembler/assemble_schedules.py +++ b/qiskit/assembler/assemble_schedules.py @@ -13,213 +13,221 @@ # that they have been altered from the originals. """Assemble function for converting a list of circuits into a qobj.""" +from typing import Any, Dict, List, Tuple from collections import defaultdict from qiskit.exceptions import QiskitError -from qiskit.pulse.commands import (PulseInstruction, AcquireInstruction, +from qiskit.pulse import Schedule +from qiskit.pulse.commands import (Command, PulseInstruction, Acquire, AcquireInstruction, DelayInstruction, SamplePulse, ParametricInstruction) -from qiskit.qobj import (PulseQobj, QobjExperimentHeader, +from qiskit.qobj import (PulseQobj, QobjHeader, QobjExperimentHeader, PulseQobjInstruction, PulseQobjExperimentConfig, PulseQobjExperiment, PulseQobjConfig, PulseLibraryItem) from qiskit.qobj.converters import InstructionToQobjConverter, LoConfigConverter from qiskit.qobj.converters.pulse_instruction import ParametricPulseShapes from qiskit.qobj.utils import MeasLevel, MeasReturnType +from .run_config import RunConfig -def assemble_schedules(schedules, qobj_id, qobj_header, run_config): + +def assemble_schedules(schedules: List[Schedule], + qobj_id: int, + qobj_header: QobjHeader, + run_config: RunConfig) -> PulseQobj: """Assembles a list of schedules into a qobj that can be run on the backend. Args: - schedules (list[Schedule]): schedules to assemble - qobj_id (int): identifier for the generated qobj - qobj_header (QobjHeader): header to pass to the results - run_config (RunConfig): configuration of the runtime environment + schedules: Schedules to assemble. + qobj_id: Identifier for the generated qobj. + qobj_header: Header to pass to the results. + run_config: Configuration of the runtime environment. + Returns: - PulseQobj: the Qobj to be run on the backends + The Qobj to be run on the backends. + Raises: - QiskitError: when invalid schedules or configs are provided + QiskitError: when frequency settings are not supplied. """ - if hasattr(run_config, 'instruction_converter'): - instruction_converter = run_config.instruction_converter - else: - instruction_converter = InstructionToQobjConverter - - qobj_config = run_config.to_dict() - - qubit_lo_freq = qobj_config.get('qubit_lo_freq', None) - if qubit_lo_freq is None: + if not hasattr(run_config, 'qubit_lo_freq'): raise QiskitError('qubit_lo_freq must be supplied.') - - meas_lo_freq = qobj_config.get('meas_lo_freq', None) - if meas_lo_freq is None: + if not hasattr(run_config, 'meas_lo_freq'): raise QiskitError('meas_lo_freq must be supplied.') - meas_map = qobj_config.pop('meas_map', None) + lo_converter = LoConfigConverter(PulseQobjExperimentConfig, + **run_config.to_dict()) + experiments, experiment_config = _assemble_experiments(schedules, + lo_converter, + run_config) + qobj_config = _assemble_config(lo_converter, experiment_config, run_config) + + return PulseQobj(experiments=experiments, + qobj_id=qobj_id, + header=qobj_header, + config=qobj_config) + + +def _assemble_experiments( + schedules: List[Schedule], + lo_converter: LoConfigConverter, + run_config: RunConfig +) -> Tuple[List[PulseQobjExperiment], Dict[str, Any]]: + """Assembles a list of schedules into PulseQobjExperiments, and returns related metadata that + will be assembled into the Qobj configuration. - # convert enums to serialized values - meas_return = qobj_config.get('meas_return', 'avg') - if isinstance(meas_return, MeasReturnType): - qobj_config['meas_return'] = meas_return.value + Args: + schedules: Schedules to assemble. + lo_converter: The configured frequency converter and validator. + run_config: Configuration of the runtime environment. - meas_level = qobj_config.get('meas_return', 2) - if isinstance(meas_level, MeasLevel): - qobj_config['meas_level'] = meas_level.value + Returns: + The list of assembled experiments, and the dictionary of related experiment config. - instruction_converter = instruction_converter(PulseQobjInstruction, **qobj_config) + Raises: + QiskitError: when frequency settings are not compatible with the experiments. + """ + freq_configs = [lo_converter(lo_dict) for lo_dict in getattr(run_config, 'schedule_los', [])] - qubit_lo_range = qobj_config.pop('qubit_lo_range', None) - meas_lo_range = qobj_config.pop('meas_lo_range', None) - lo_converter = LoConfigConverter(PulseQobjExperimentConfig, - qubit_lo_range=qubit_lo_range, - meas_lo_range=meas_lo_range, - **qobj_config) + if len(schedules) > 1 and len(freq_configs) not in [0, 1, len(schedules)]: + raise QiskitError('Invalid frequency setting is specified. If the frequency is specified, ' + 'it should be configured the same for all schedules, configured for each ' + 'schedule, or a list of frequencies should be provided for a single ' + 'frequency sweep schedule.') - memory_slot_size = 0 + instruction_converter = getattr(run_config, 'instruction_converter', InstructionToQobjConverter) + instruction_converter = instruction_converter(PulseQobjInstruction, **run_config.to_dict()) - # Pack everything into the Qobj - qobj_schedules = [] user_pulselib = {} + experiments = [] for idx, schedule in enumerate(schedules): - # instructions - max_memory_slot = 0 - qobj_instructions = [] - acquire_instruction_map = defaultdict(list) - - # Instructions are returned as tuple of shifted time and instruction - for shift, instruction in schedule.instructions: - # TODO: support conditional gate - - if isinstance(instruction, ParametricInstruction): - pulse_shape = ParametricPulseShapes(type(instruction.command)).name - if pulse_shape not in run_config.parametric_pulses: - # Convert to SamplePulse if the backend does not support it - instruction = PulseInstruction(instruction.command.get_sample_pulse(), - instruction.channels[0], - name=instruction.name) - - if isinstance(instruction, PulseInstruction): - name = instruction.command.name - if name in user_pulselib and instruction.command != user_pulselib[name]: - name = "{0}-{1:x}".format(name, hash(instruction.command.samples.tostring())) - instruction = PulseInstruction( - command=SamplePulse(name=name, samples=instruction.command.samples), - name=instruction.name, - channel=instruction.channels[0]) - # add samples to pulse library - user_pulselib[name] = instruction.command - - if isinstance(instruction, AcquireInstruction): - max_memory_slot = max(max_memory_slot, - *[slot.index for slot in instruction.mem_slots]) - # Acquires have a single AcquireChannel per inst, but we have to bundle them - # together into the Qobj as one instruction with many channels - acquire_instruction_map[(shift, instruction.command)].append(instruction) - continue - - if isinstance(instruction, DelayInstruction): - # delay instructions are ignored as timing is explicit within qobj - continue - - qobj_instructions.append(instruction_converter(shift, instruction)) - - if acquire_instruction_map: - if meas_map: - _validate_meas_map(acquire_instruction_map, meas_map) - for (shift, _), instructions in acquire_instruction_map.items(): - qubits, mem_slots, reg_slots = _bundle_channel_indices(instructions) - qobj_instructions.append( - instruction_converter.convert_single_acquires( - shift, instructions[0], - qubits=qubits, memory_slot=mem_slots, register_slot=reg_slots)) - - # memory slot size is memory slot index + 1 because index starts from zero - exp_memory_slot_size = max_memory_slot + 1 - memory_slot_size = max(memory_slot_size, exp_memory_slot_size) - - # experiment header + qobj_instructions, user_pulses, max_memory_slot = _assemble_instructions( + schedule, + instruction_converter, + run_config) + user_pulselib.update(user_pulses) + # TODO: add other experimental header items (see circuit assembler) qobj_experiment_header = QobjExperimentHeader( - memory_slots=exp_memory_slot_size, - name=schedule.name or 'Experiment-%d' % idx - ) + memory_slots=max_memory_slot + 1, # Memory slots are 0 indexed + name=schedule.name or 'Experiment-%d' % idx) + + experiment = PulseQobjExperiment( + header=qobj_experiment_header, + instructions=qobj_instructions) + if freq_configs: + # This handles the cases where one frequency setting applies to all experiments and + # where each experiment has a different frequency + freq_idx = idx if len(freq_configs) != 1 else 0 + experiment.config = freq_configs[freq_idx] + + experiments.append(experiment) + + # Frequency sweep + if freq_configs and len(experiments) == 1: + experiment = experiments[0] + experiments = [] + for freq_config in freq_configs: + experiments.append(PulseQobjExperiment( + header=experiment.header, + instructions=experiment.instructions, + config=freq_config)) - qobj_schedules.append({ - 'header': qobj_experiment_header, - 'instructions': qobj_instructions - }) + # Top level Qobj configuration + experiment_config = { + 'pulse_library': [PulseLibraryItem(name=pulse.name, samples=pulse.samples) + for pulse in user_pulselib.values()], + 'memory_slots': max([exp.header.memory_slots for exp in experiments]) + } - # set number of memoryslots - qobj_config['memory_slots'] = memory_slot_size + return experiments, experiment_config - # setup pulse_library - qobj_config['pulse_library'] = [PulseLibraryItem(name=pulse.name, samples=pulse.samples) - for pulse in user_pulselib.values()] - # convert lo frequencies to GHz - qobj_config['qubit_lo_freq'] = [freq/1e9 for freq in qubit_lo_freq] - qobj_config['meas_lo_freq'] = [freq/1e9 for freq in meas_lo_freq] +def _assemble_instructions( + schedule: Schedule, + instruction_converter: InstructionToQobjConverter, + run_config: RunConfig +) -> Tuple[List[PulseQobjInstruction], Dict[str, Command], int]: + """Assembles the instructions in a schedule into a list of PulseQobjInstructions and returns + related metadata that will be assembled into the Qobj configuration. - # create qobj experiment field - experiments = [] - schedule_los = qobj_config.pop('schedule_los', []) - - if len(schedule_los) == 1: - lo_dict = schedule_los[0] - # update global config - q_los = lo_converter.get_qubit_los(lo_dict) - if q_los: - qobj_config['qubit_lo_freq'] = [freq/1e9 for freq in q_los] - m_los = lo_converter.get_meas_los(lo_dict) - if m_los: - qobj_config['meas_lo_freq'] = [freq/1e9 for freq in m_los] - - if schedule_los: - # multiple frequency setups - if len(qobj_schedules) == 1: - # frequency sweep - for lo_dict in schedule_los: - experiments.append(PulseQobjExperiment( - instructions=qobj_schedules[0]['instructions'], - header=qobj_schedules[0]['header'], - config=lo_converter(lo_dict) - )) - elif len(qobj_schedules) == len(schedule_los): - # n:n setup - for lo_dict, schedule in zip(schedule_los, qobj_schedules): - experiments.append(PulseQobjExperiment( - instructions=schedule['instructions'], - header=schedule['header'], - config=lo_converter(lo_dict) - )) - else: - raise QiskitError('Invalid LO setting is specified. ' - 'The LO should be configured for each schedule, or ' - 'single setup for all schedules (unique), or ' - 'multiple setups for a single schedule (frequency sweep),' - 'or no LO configured at all.') - else: - # unique frequency setup - for schedule in qobj_schedules: - experiments.append(PulseQobjExperiment( - instructions=schedule['instructions'], - header=schedule['header'], - )) + Args: + schedule: Schedule to assemble. + instruction_converter: A converter instance which can convert PulseInstructions to + PulseQobjInstructions. + run_config: Configuration of the runtime environment. - qobj_config = PulseQobjConfig(**qobj_config) + Returns: + A list of converted instructions, the user pulse library dictionary (from pulse name to + pulse command), and the maximum number of readout memory slots used by this Schedule. + """ + max_memory_slot = 0 + qobj_instructions = [] + user_pulselib = {} - return PulseQobj(qobj_id=qobj_id, - config=qobj_config, - experiments=experiments, - header=qobj_header) + acquire_instruction_map = defaultdict(list) + for time, instruction in schedule.instructions: + + if isinstance(instruction, ParametricInstruction): + pulse_shape = ParametricPulseShapes(type(instruction.command)).name + if pulse_shape not in run_config.parametric_pulses: + # Convert to SamplePulse if the backend does not support it + instruction = PulseInstruction(instruction.command.get_sample_pulse(), + instruction.channels[0], + name=instruction.name) + + if isinstance(instruction, PulseInstruction): + name = instruction.command.name + if name in user_pulselib and instruction.command != user_pulselib[name]: + name = "{0}-{1:x}".format(name, hash(instruction.command.samples.tostring())) + instruction = PulseInstruction( + command=SamplePulse(name=name, samples=instruction.command.samples), + name=instruction.name, + channel=instruction.channels[0]) + # add samples to pulse library + user_pulselib[name] = instruction.command + + if isinstance(instruction, AcquireInstruction): + max_memory_slot = max(max_memory_slot, + *[slot.index for slot in instruction.mem_slots]) + # Acquires have a single AcquireChannel per inst, but we have to bundle them + # together into the Qobj as one instruction with many channels + acquire_instruction_map[(time, instruction.command)].append(instruction) + continue + + if isinstance(instruction, DelayInstruction): + # delay instructions are ignored as timing is explicit within qobj + continue + + qobj_instructions.append(instruction_converter(time, instruction)) + + if acquire_instruction_map: + if hasattr(run_config, 'meas_map'): + _validate_meas_map(acquire_instruction_map, run_config.meas_map) + for (time, _), instructions in acquire_instruction_map.items(): + qubits, mem_slots, reg_slots = _bundle_channel_indices(instructions) + qobj_instructions.append( + instruction_converter.convert_single_acquires( + time, instructions[0], + qubits=qubits, memory_slot=mem_slots, register_slot=reg_slots)) + + return qobj_instructions, user_pulselib, max_memory_slot + + +def _validate_meas_map(instruction_map: Dict[Tuple[int, Acquire], List[AcquireInstruction]], + meas_map: List[List[int]]) -> None: + """Validate all qubits tied in ``meas_map`` are to be acquired. + Args: + instruction_map: A dictionary grouping AcquireInstructions according to their start time + and the command features (notably, their duration). + meas_map: List of groups of qubits that must be acquired together. -def _validate_meas_map(instruction_map, meas_map): - """Validate all qubits tied in meas_map are to be acquired.""" + Raises: + QiskitError: If the instructions do not satisfy the measurement map. + """ meas_map_sets = [set(m) for m in meas_map] # Check each acquisition time individually for _, instructions in instruction_map.items(): - measured_qubits = set() for inst in instructions: measured_qubits.update([acq.index for acq in inst.acquires]) @@ -231,9 +239,18 @@ def _validate_meas_map(instruction_map, meas_map): 'in measurement map: {1}'.format(measured_qubits, meas_set)) -def _bundle_channel_indices(instructions): +def _bundle_channel_indices( + instructions: List[AcquireInstruction] +) -> Tuple[List[int], List[int], List[int]]: """From the list of AcquireInstructions, bundle the indices of the acquire channels, - memory slots, and register slots into a 3-tuple of lists.""" + memory slots, and register slots into a 3-tuple of lists. + + Args: + instructions: A list of AcquireInstructions to be bundled. + + Returns: + The qubit indices, the memory slot indices, and register slot indices from instructions. + """ qubits = [] mem_slots = [] reg_slots = [] @@ -242,3 +259,52 @@ def _bundle_channel_indices(instructions): mem_slots.extend(mem_slot.index for mem_slot in inst.mem_slots) reg_slots.extend(reg.index for reg in inst.reg_slots) return qubits, mem_slots, reg_slots + + +def _assemble_config(lo_converter: LoConfigConverter, + experiment_config: Dict[str, Any], + run_config: RunConfig) -> PulseQobjConfig: + """Assembles the QobjConfiguration from experimental config and runtime config. + + Args: + lo_converter: The configured frequency converter and validator. + experiment_config: Schedules to assemble. + run_config: Configuration of the runtime environment. + + Returns: + The assembled PulseQobjConfig. + """ + qobj_config = run_config.to_dict() + qobj_config.update(experiment_config) + + # Run config not needed in qobj config + qobj_config.pop('meas_map', None) + qobj_config.pop('qubit_lo_range', None) + qobj_config.pop('meas_lo_range', None) + + # convert enums to serialized values + meas_return = qobj_config.get('meas_return', 'avg') + if isinstance(meas_return, MeasReturnType): + qobj_config['meas_return'] = meas_return.value + + meas_level = qobj_config.get('meas_level', 2) + if isinstance(meas_level, MeasLevel): + qobj_config['meas_level'] = meas_level.value + + # convert lo frequencies to Hz + qobj_config['qubit_lo_freq'] = [freq / 1e9 for freq in qobj_config['qubit_lo_freq']] + qobj_config['meas_lo_freq'] = [freq / 1e9 for freq in qobj_config['meas_lo_freq']] + + # frequency sweep config + schedule_los = qobj_config.pop('schedule_los', []) + if len(schedule_los) == 1: + lo_dict = schedule_los[0] + q_los = lo_converter.get_qubit_los(lo_dict) + # Hz -> GHz + if q_los: + qobj_config['qubit_lo_freq'] = [freq / 1e9 for freq in q_los] + m_los = lo_converter.get_meas_los(lo_dict) + if m_los: + qobj_config['meas_lo_freq'] = [freq / 1e9 for freq in m_los] + + return PulseQobjConfig(**qobj_config) diff --git a/qiskit/assembler/disassemble.py b/qiskit/assembler/disassemble.py index 3b66c559a1eb..132ebd30a216 100644 --- a/qiskit/assembler/disassemble.py +++ b/qiskit/assembler/disassemble.py @@ -50,8 +50,6 @@ def _experiments_to_circuits(qobj): conditional = {} for i in x.instructions: name = i.name - if i.name == 'id': - name = 'iden' qubits = [] params = getattr(i, 'params', []) try: diff --git a/qiskit/circuit/__init__.py b/qiskit/circuit/__init__.py index 6003ffbd20dd..72cde06fe2af 100644 --- a/qiskit/circuit/__init__.py +++ b/qiskit/circuit/__init__.py @@ -54,11 +54,19 @@ ParameterVector ParameterExpression +Random Circuits +=============== + +.. autosummary:: + :toctree: ../stubs/ + + random.random_circuit """ from .quantumcircuit import QuantumCircuit from .classicalregister import ClassicalRegister, Clbit from .quantumregister import QuantumRegister, Qubit from .gate import Gate +# pylint: disable=cyclic-import from .controlledgate import ControlledGate from .instruction import Instruction from .instructionset import InstructionSet diff --git a/qiskit/circuit/add_control.py b/qiskit/circuit/add_control.py index ae85a063b62e..bd743610349a 100644 --- a/qiskit/circuit/add_control.py +++ b/qiskit/circuit/add_control.py @@ -14,57 +14,83 @@ """ Add control to operation if supported. """ -from qiskit import QiskitError +from typing import Union, Optional + +from qiskit.circuit.exceptions import CircuitError from qiskit.extensions import UnitaryGate +from . import ControlledGate, Gate, QuantumRegister, QuantumCircuit + + +def add_control(operation: Union[Gate, ControlledGate], + num_ctrl_qubits: int, + label: Union[str, None], + ctrl_state: Union[int, str, None]) -> ControlledGate: + """For standard gates, if the controlled version already exists in the + library, it will be returned (e.g. XGate.control() = CnotGate(). + For more generic gates, this method implements the controlled + version by first decomposing into the ['u1', 'u3', 'cx'] basis, then + controlling each gate in the decomposition. -def add_control(operation, num_ctrl_qubits, label): - """Add num_ctrl_qubits controls to operation + Open controls are implemented by conjugating the control line with + X gates. Adds num_ctrl_qubits controls to operation. Args: - operation (Gate or ControlledGate): operation to add control to. - num_ctrl_qubits (int): number of controls to add to gate (default=1) - label (str): optional gate label + operation: Operation for which control will be added. + num_ctrl_qubits: The number of controls to add to gate (default=1). + label: Optional gate label. + ctrl_state (int or str or None): The control state in decimal or as + a bitstring (e.g. '111'). If specified as a bitstring the length + must equal num_ctrl_qubits, MSB on left. If None, use + 2**num_ctrl_qubits-1. Returns: - ControlledGate: controlled version of gate. This default algorithm - uses num_ctrl_qubits-1 ancillae qubits so returns a gate of size - num_qubits + 2*num_ctrl_qubits - 1. + Controlled version of gate. + """ + if operation.phase: + # If gate has a global phase set we convert to unitary gate before + # making the controled version + operation = UnitaryGate(operation.to_matrix()) import qiskit.extensions.standard as standard if isinstance(operation, standard.RZGate) or operation.name == 'rz': # num_ctrl_qubits > 1 # the condition matching 'name' above is to catch a test case, # 'TestControlledGate.test_rotation_gates', where the rz gate # gets converted to a circuit before becoming a generic Gate object. - cgate = standard.CrzGate(*operation.params) + cgate = standard.CRZGate(*operation.params) return cgate.control(num_ctrl_qubits - 1) if isinstance(operation, UnitaryGate): # attempt decomposition operation._define() - return control(operation, num_ctrl_qubits=num_ctrl_qubits, label=label) + return control(operation, num_ctrl_qubits=num_ctrl_qubits, label=label, + ctrl_state=ctrl_state) -def control(operation, num_ctrl_qubits=1, label=None): +def control(operation: Union[Gate, ControlledGate], + num_ctrl_qubits: Optional[int] = 1, + label: Optional[Union[None, str]] = None, + ctrl_state: Optional[Union[None, int, str]] = None) -> ControlledGate: """Return controlled version of gate using controlled rotations Args: - operation (Gate or Controlledgate): gate to create ControlledGate from - num_ctrl_qubits (int): number of controls to add to gate (default=1) - label (str): optional gate label + operation: gate to create ControlledGate from + num_ctrl_qubits: number of controls to add to gate (default=1) + label: optional gate label + ctrl_state: The control state in decimal or as + a bitstring (e.g. '111'). If specified as a bitstring the length + must equal num_ctrl_qubits, MSB on left. If None, use + 2**num_ctrl_qubits-1. + Returns: - ControlledGate: controlled version of gate. This default algorithm - uses num_ctrl_qubits-1 ancillae qubits so returns a gate of size - num_qubits + 2*num_ctrl_qubits - 1. + Controlled version of gate. Raises: - QiskitError: gate contains non-gate in definitionl + CircuitError: gate contains non-gate in definition """ from math import pi # pylint: disable=cyclic-import import qiskit.circuit.controlledgate as controlledgate - from qiskit.circuit.quantumregister import QuantumRegister - from qiskit.circuit.quantumcircuit import QuantumCircuit # pylint: disable=unused-import import qiskit.extensions.standard.multi_control_rotation_gates import qiskit.extensions.standard.multi_control_toffoli_gate @@ -73,7 +99,6 @@ def control(operation, num_ctrl_qubits=1, label=None): q_control = QuantumRegister(num_ctrl_qubits, name='control') q_target = QuantumRegister(operation.num_qubits, name='target') q_ancillae = None # TODO: add - qc = QuantumCircuit(q_control, q_target) if operation.name == 'x' or ( @@ -98,7 +123,7 @@ def control(operation, num_ctrl_qubits=1, label=None): for rule in bgate.definition: if rule[0].name == 'u3': theta, phi, lamb = rule[0].params - if phi == -pi/2 and lamb == pi/2: + if phi == -pi / 2 and lamb == pi / 2: qc.mcrx(theta, q_control, q_target[rule[1][0].index], use_basis_gates=True) elif phi == 0 and lamb == 0: @@ -122,7 +147,7 @@ def control(operation, num_ctrl_qubits=1, label=None): None, mode='noancilla') else: - raise QiskitError('gate contains non-controllable instructions') + raise CircuitError('gate contains non-controllable instructions') instr = qc.to_instruction() if isinstance(operation, controlledgate.ControlledGate): new_num_ctrl_qubits = num_ctrl_qubits + operation.num_ctrl_qubits @@ -146,20 +171,19 @@ def control(operation, num_ctrl_qubits=1, label=None): operation.params, label=label, num_ctrl_qubits=new_num_ctrl_qubits, - definition=instr.definition) + definition=instr.definition, + ctrl_state=ctrl_state) cgate.base_gate = base_gate return cgate def _gate_to_circuit(operation): - from qiskit.circuit.quantumcircuit import QuantumCircuit - from qiskit.circuit.quantumregister import QuantumRegister qr = QuantumRegister(operation.num_qubits) qc = QuantumCircuit(qr, name=operation.name) if hasattr(operation, 'definition') and operation.definition: for rule in operation.definition: if rule[0].name in {'id', 'barrier', 'measure', 'snapshot'}: - raise QiskitError('Cannot make controlled gate with {} instruction'.format( + raise CircuitError('Cannot make controlled gate with {} instruction'.format( rule[0].name)) qc.append(rule[0], qargs=[qr[bit.index] for bit in rule[1]], cargs=[]) else: diff --git a/qiskit/circuit/controlledgate.py b/qiskit/circuit/controlledgate.py index 5d67261d1b6d..31b6b63ab82e 100644 --- a/qiskit/circuit/controlledgate.py +++ b/qiskit/circuit/controlledgate.py @@ -15,29 +15,39 @@ """ Controlled unitary gate. """ - from qiskit.circuit.exceptions import CircuitError from .gate import Gate +from . import QuantumRegister class ControlledGate(Gate): """Controlled unitary gate.""" - def __init__(self, name, num_qubits, params, label=None, num_ctrl_qubits=1, - definition=None): - """Create a new gate. + def __init__(self, name, num_qubits, params, num_ctrl_qubits=1, phase=0, + label=None, definition=None, ctrl_state=None): + """Create a controlled gate. + + Attributes: + num_ctrl_qubits (int): The number of control qubits. + ctrl_state (int): The control state in decimal notation. Args: name (str): The Qobj name of the gate. num_qubits (int): The number of qubits the gate acts on. params (list): A list of parameters. - label (str or None): An optional label for the gate [Default: None] + phase (float): set the gate phase (Default: 0). + label (str or None): An optional label for the gate (Default: None). num_ctrl_qubits (int): Number of control qubits. definition (list): list of gate rules for implementing this gate. + ctrl_state (int or str or None): The control state in decimal or as + a bitstring (e.g. '111'). If specified as a bitstring the length + must equal num_ctrl_qubits, MSB on left. If None, use + 2**num_ctrl_qubits-1. Raises: CircuitError: num_ctrl_qubits >= num_qubits + CircuitError: ctrl_state < 0 or ctrl_state > 2**num_ctrl_qubits. """ - super().__init__(name, num_qubits, params, label=label) + super().__init__(name, num_qubits, params, phase=phase, label=label) if num_ctrl_qubits < num_qubits: self.num_ctrl_qubits = num_ctrl_qubits else: @@ -50,14 +60,76 @@ def __init__(self, name, num_qubits, params, label=None, num_ctrl_qubits=1, self.base_gate = base_gate.base_gate else: self.base_gate = base_gate + self._ctrl_state = None + self.ctrl_state = ctrl_state + + @property + def definition(self): + """Return definition in terms of other basic gates. If the gate has + open controls, as determined from `self.ctrl_state`, the returned + definition is conjugated with X.""" + if not self._definition: + self._define() + # pylint: disable=cyclic-import + from qiskit.extensions.standard import XGate, CnotGate + bit_ctrl_state = bin(self.ctrl_state)[2:].zfill(self.num_ctrl_qubits) + # hacky way to get register assuming single register + if self._definition: + qreg = self._definition[0][1][0].register + elif isinstance(self, CnotGate): + qreg = QuantumRegister(self.num_qubits, 'q') + self._definition = [(self, [qreg[0], qreg[1]], [])] + open_rules = [] + for qind, val in enumerate(bit_ctrl_state[::-1]): + if val == '0': + open_rules.append([XGate(), [qreg[qind]], []]) + return open_rules + self._definition + open_rules + + @definition.setter + def definition(self, excited_def): + """Set controlled gate definition with closed controls.""" + super(Gate, self.__class__).definition.fset(self, excited_def) + + @property + def ctrl_state(self): + """Return the control state of the gate as a decimal integer.""" + return self._ctrl_state + + @ctrl_state.setter + def ctrl_state(self, ctrl_state): + """Set the control state of this gate. + + Args: + ctrl_state (int or str or None): The control state of the gate. + + Raises: + CircuitError: ctrl_state is invalid. + """ + if isinstance(ctrl_state, str): + try: + assert len(ctrl_state) == self.num_ctrl_qubits + ctrl_state = int(ctrl_state, 2) + except ValueError: + raise CircuitError('invalid control bit string: ' + ctrl_state) + except AssertionError: + raise CircuitError('invalid control bit string: length != ' + 'num_ctrl_qubits') + if isinstance(ctrl_state, int): + if 0 <= ctrl_state < 2**self.num_ctrl_qubits: + self._ctrl_state = ctrl_state + else: + raise CircuitError('invalid control state specification') + elif ctrl_state is None: + self._ctrl_state = 2**self.num_ctrl_qubits - 1 + else: + raise CircuitError('invalid control state specification') def __eq__(self, other): if not isinstance(other, ControlledGate): return False else: return (other.num_ctrl_qubits == self.num_ctrl_qubits and - self.base_gate == other.base_gate and - super().__eq__(other)) + self.base_gate == other.base_gate) def inverse(self): """Invert this gate by calling inverse on the base gate.""" diff --git a/qiskit/circuit/gate.py b/qiskit/circuit/gate.py index a8095fded48c..b20074e46796 100644 --- a/qiskit/circuit/gate.py +++ b/qiskit/circuit/gate.py @@ -14,6 +14,8 @@ """Unitary gate.""" +import math +import cmath import numpy as np from scipy.linalg import schur @@ -24,19 +26,43 @@ class Gate(Instruction): """Unitary gate.""" - def __init__(self, name, num_qubits, params, label=None): + def __init__(self, name, num_qubits, params, phase=0, label=None): """Create a new gate. Args: name (str): the Qobj name of the gate num_qubits (int): the number of qubits the gate acts on. params (list): a list of parameters. - label (str or None): An optional label for the gate [Default: None] + phase (float): set the gate phase (Default: 0). + label (str or None): An optional label for the gate (Default: None). """ self._label = label self.definition = None + self._phase = phase super().__init__(name, num_qubits, 0, params) + @property + def phase(self): + """Return the phase of the gate.""" + return self._phase + + @phase.setter + def phase(self, angle): + """Set the phase of the gate.""" + # Set the phase to the [-2 * pi, 2 * pi] interval + angle = float(angle) + if not angle: + self._phase = 0 + elif angle < 0: + self._phase = angle % (-2 * math.pi) + else: + self._phase = angle % (2 * math.pi) + + def _matrix_definition(self): + """Return the Numpy.array matrix definition of the gate.""" + # This should be set in classes that derive from Gate. + return None + def to_matrix(self): """Return a Numpy.array for the gate unitary matrix. @@ -44,7 +70,10 @@ def to_matrix(self): CircuitError: If a Gate subclass does not implement this method an exception will be raised when this base class method is called. """ - raise CircuitError("to_matrix not defined for this {}".format(type(self))) + mat = self._matrix_definition() + if mat is None: + raise CircuitError("to_matrix not defined for this {}".format(type(self))) + return cmath.exp(1j * self._phase) * mat if self._phase else mat def power(self, exponent): """Creates a unitary gate as `gate^exponent`. @@ -58,9 +87,10 @@ def power(self, exponent): Raises: CircuitError: If Gate is not unitary """ + from qiskit.quantum_info.operators import Operator # pylint: disable=cyclic-import from qiskit.extensions.unitary import UnitaryGate # pylint: disable=cyclic-import # Should be diagonalized because it's a unitary. - decomposition, unitary = schur(self.to_matrix(), output='complex') + decomposition, unitary = schur(Operator(self).data, output='complex') # Raise the diagonal entries to the specified power decomposition_power = list() @@ -84,6 +114,8 @@ def assemble(self): instruction = super().assemble() if self.label: instruction.label = self.label + if self._phase: + instruction.phase = self._phase return instruction @property @@ -106,24 +138,24 @@ def label(self, name): else: raise TypeError('label expects a string or None') - def control(self, num_ctrl_qubits=1, label=None): + def control(self, num_ctrl_qubits=1, label=None, ctrl_state=None): """Return controlled version of gate Args: num_ctrl_qubits (int): number of controls to add to gate (default=1) - label (str): optional gate label + label (str or None): optional gate label + ctrl_state (int or str or None): The control state in decimal or as + a bitstring (e.g. '111'). If None, use 2**num_ctrl_qubits-1. Returns: - ControlledGate: controlled version of gate. This default algorithm - uses num_ctrl_qubits-1 ancillae qubits so returns a gate of size - num_qubits + 2*num_ctrl_qubits - 1. + ControlledGate: controlled version of gate. Raises: - QiskitError: unrecognized mode + QiskitError: unrecognized mode or invalid ctrl_state """ # pylint: disable=cyclic-import from .add_control import add_control - return add_control(self, num_ctrl_qubits, label) + return add_control(self, num_ctrl_qubits, label, ctrl_state) @staticmethod def _broadcast_single_argument(qarg): diff --git a/qiskit/circuit/library/__init__.py b/qiskit/circuit/library/__init__.py new file mode 100644 index 000000000000..701956717ebb --- /dev/null +++ b/qiskit/circuit/library/__init__.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Module for builtin library of circuits.""" + +from .boolean_logic import Permutation, XOR, InnerProduct diff --git a/qiskit/circuit/library/boolean_logic.py b/qiskit/circuit/library/boolean_logic.py new file mode 100644 index 000000000000..d6377d438304 --- /dev/null +++ b/qiskit/circuit/library/boolean_logic.py @@ -0,0 +1,124 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +# pylint: disable=no-member + +"""Implementations of boolean logic quantum circuits.""" + +from typing import List, Optional + +import numpy as np +from qiskit.circuit import QuantumRegister, QuantumCircuit +from qiskit.circuit.exceptions import CircuitError + + +class Permutation(QuantumCircuit): + """An n_qubit circuit that permutes qubits.""" + + def __init__(self, + n_qubits: int, + pattern: Optional[List[int]] = None, + seed: Optional[int] = None) -> QuantumCircuit: + """Return an n_qubit permutation circuit implemented using SWAPs. + + Args: + n_qubits: circuit width. + pattern: permutation pattern. If None, permute randomly. + seed: random seed in case a random permutation is requested. + + Returns: + A permutation circuit. + + Raises: + CircuitError: if permutation pattern is malformed. + """ + super().__init__(n_qubits, name="permutation") + + if pattern is not None: + if sorted(pattern) != list(range(n_qubits)): + raise CircuitError("Permutation pattern must be some " + "ordering of 0..n_qubits-1 in a list.") + pattern = np.array(pattern) + else: + rng = np.random.RandomState(seed) + pattern = np.arange(n_qubits) + rng.shuffle(pattern) + + for i in range(n_qubits): + if (pattern[i] != -1) and (pattern[i] != i): + self.swap(i, int(pattern[i])) + pattern[pattern[i]] = -1 + + +class XOR(QuantumCircuit): + """An n_qubit circuit for bitwise xor-ing the input with some integer ``amount``. + + The ``amount`` is xor-ed in bitstring form with the input. + + This circuit can also represent addition by ``amount`` over the finite field GF(2). + """ + + def __init__(self, + n_qubits: int, + amount: Optional[int] = None, + seed: Optional[int] = None) -> QuantumCircuit: + """Return a circuit implementing bitwise xor. + + Args: + n_qubits: the width of circuit. + amount: the xor amount in decimal form. + seed: random seed in case a random xor is requested. + + Returns: + A circuit for bitwise XOR. + + Raises: + CircuitError: if the xor bitstring exceeds available qubits. + """ + super().__init__(n_qubits, name="xor") + + if amount is not None: + if len(bin(amount)[2:]) > n_qubits: + raise CircuitError("Bits in 'amount' exceed circuit width") + else: + rng = np.random.RandomState(seed) + amount = rng.randint(0, 2**n_qubits) + + for i in range(n_qubits): + bit = amount & 1 + amount = amount >> 1 + if bit == 1: + self.x(i) + + +class InnerProduct(QuantumCircuit): + """An n_qubit circuit that computes the inner product of two registers.""" + + def __init__(self, n_qubits: int) -> QuantumCircuit: + """Return a circuit to compute the inner product of 2 n-qubit registers. + + This implementation uses CZ gates. + + Args: + n_qubits: width of top and bottom registers (half total circuit width) + + Returns: + A circuit computing inner product of two registers. + """ + qr_a = QuantumRegister(n_qubits) + qr_b = QuantumRegister(n_qubits) + super().__init__(qr_a, qr_b, name="inner_product") + + for i in range(n_qubits): + self.cz(qr_a[i], qr_b[i]) diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index 16d5810b773c..53e5c58b69f8 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -21,6 +21,7 @@ import multiprocessing as mp from collections import OrderedDict import numpy as np +from qiskit.util import is_main_process from qiskit.circuit.instruction import Instruction from qiskit.qasm.qasm import Qasm from qiskit.circuit.exceptions import CircuitError @@ -123,22 +124,17 @@ class QuantumCircuit: extension_lib = "include \"qelib1.inc\";" def __init__(self, *regs, name=None): + if any([not isinstance(reg, (QuantumRegister, ClassicalRegister)) for reg in regs]): + try: + regs = tuple(int(reg) for reg in regs) + except Exception: + raise CircuitError("Circuit args must be Registers or be castable to an int" + + "(%s '%s' was provided)" + % ([type(reg).__name__ for reg in regs], regs)) if name is None: name = self.cls_prefix() + str(self.cls_instances()) - # pylint: disable=not-callable - # (known pylint bug: https://github.com/PyCQA/pylint/issues/1699) - if sys.platform != "win32": - if isinstance(mp.current_process(), - (mp.context.ForkProcess, mp.context.SpawnProcess)): - name += '-{}'.format(mp.current_process().pid) - elif sys.version_info[0] == 3 \ - and (sys.version_info[1] == 5 or sys.version_info[1] == 6) \ - and mp.current_process().name != 'MainProcess': - # It seems condition of if-statement doesn't work in python 3.5 and 3.6 - # because processes created by "ProcessPoolExecutor" are not - # mp.context.ForkProcess or mp.context.SpawnProcess. As a workaround, - # "name" of the process is checked instead. - name += '-{}'.format(mp.current_process().pid) + if sys.platform != "win32" and not is_main_process(): + name += '-{}'.format(mp.current_process().pid) self._increment_instances() if not isinstance(name, str): @@ -334,8 +330,12 @@ def extend(self, rhs): if element not in self.cregs: self.cregs.append(element) + # Copy the circuit data if rhs and self are the same, otherwise the data of rhs is + # appended to both self and rhs resulting in an infinite loop + data = rhs.data.copy() if rhs is self else rhs.data + # Add new gates - for instruction_context in rhs.data: + for instruction_context in data: self._append(*instruction_context) return self @@ -1294,7 +1294,10 @@ def _rebind_definition(self, instruction, parameter, value): for op, _, _ in instruction._definition: for idx, param in enumerate(op.params): if isinstance(param, ParameterExpression) and parameter in param.parameters: - op.params[idx] = param.bind({parameter: value}) + if isinstance(value, ParameterExpression): + op.params[idx] = param.subs({parameter: value}) + else: + op.params[idx] = param.bind({parameter: value}) self._rebind_definition(op, parameter, value) def _substitute_parameters(self, parameter_map): @@ -1306,6 +1309,7 @@ def _substitute_parameters(self, parameter_map): for (instr, param_index) in self._parameter_table[old_parameter]: new_param = instr.params[param_index].subs({old_parameter: new_parameter}) instr.params[param_index] = new_param + self._rebind_definition(instr, old_parameter, new_parameter) self._parameter_table[new_parameter] = self._parameter_table.pop(old_parameter) diff --git a/qiskit/circuit/random/utils.py b/qiskit/circuit/random/utils.py index 71cad691a88b..e969c0a9a1f3 100644 --- a/qiskit/circuit/random/utils.py +++ b/qiskit/circuit/random/utils.py @@ -18,12 +18,12 @@ from qiskit.circuit import QuantumRegister, ClassicalRegister, QuantumCircuit from qiskit.circuit import Reset -from qiskit.extensions import (IdGate, U1Gate, U2Gate, U3Gate, XGate, +from qiskit.extensions import (IGate, U1Gate, U2Gate, U3Gate, XGate, YGate, ZGate, HGate, SGate, SdgGate, TGate, - TdgGate, RXGate, RYGate, RZGate, CnotGate, - CyGate, CzGate, CHGate, CrzGate, Cu1Gate, - Cu3Gate, SwapGate, RZZGate, - ToffoliGate, FredkinGate) + TdgGate, RXGate, RYGate, RZGate, CXGate, + CYGate, CZGate, CHGate, CRZGate, CU1Gate, + CU3Gate, SwapGate, RZZGate, + CCXGate, CSwapGate) from qiskit.circuit.exceptions import CircuitError @@ -31,6 +31,16 @@ def random_circuit(n_qubits, depth, max_operands=3, measure=False, conditional=False, reset=False, seed=None): """Generate random circuit of arbitrary size and form. + This function will generate a random circuit by randomly selecting gates + from the set of standard gates in :mod:`qiskit.extensions`. For example: + + .. jupyter-execute:: + + from qiskit.circuit.random import random_circuit + + circ = random_circuit(2, 2, measure=True) + circ.draw(output='mpl') + Args: n_qubits (int): number of quantum wires depth (int): layers of operations (i.e. critical path length) @@ -49,14 +59,14 @@ def random_circuit(n_qubits, depth, max_operands=3, measure=False, if max_operands < 1 or max_operands > 3: raise CircuitError("max_operands must be between 1 and 3") - one_q_ops = [IdGate, U1Gate, U2Gate, U3Gate, XGate, YGate, ZGate, + one_q_ops = [IGate, U1Gate, U2Gate, U3Gate, XGate, YGate, ZGate, HGate, SGate, SdgGate, TGate, TdgGate, RXGate, RYGate, RZGate] - one_param = [U1Gate, RXGate, RYGate, RZGate, RZZGate, Cu1Gate, CrzGate] + one_param = [U1Gate, RXGate, RYGate, RZGate, RZZGate, CU1Gate, CRZGate] two_param = [U2Gate] - three_param = [U3Gate, Cu3Gate] - two_q_ops = [CnotGate, CyGate, CzGate, CHGate, CrzGate, - Cu1Gate, Cu3Gate, SwapGate, RZZGate] - three_q_ops = [ToffoliGate, FredkinGate] + three_param = [U3Gate, CU3Gate] + two_q_ops = [CXGate, CYGate, CZGate, CHGate, CRZGate, + CU1Gate, CU3Gate, SwapGate, RZZGate] + three_q_ops = [CCXGate, CSwapGate] qr = QuantumRegister(n_qubits, 'q') qc = QuantumCircuit(n_qubits) @@ -96,7 +106,7 @@ def random_circuit(n_qubits, depth, max_operands=3, measure=False, num_angles = 3 else: num_angles = 0 - angles = [rng.uniform(0, 2*np.pi) for x in range(num_angles)] + angles = [rng.uniform(0, 2 * np.pi) for x in range(num_angles)] register_operands = [qr[i] for i in operands] op = operation(*angles) diff --git a/qiskit/compiler/assemble.py b/qiskit/compiler/assemble.py index 838504316213..cf4866228b5d 100644 --- a/qiskit/compiler/assemble.py +++ b/qiskit/compiler/assemble.py @@ -16,128 +16,103 @@ import uuid import copy -from qiskit.circuit import QuantumCircuit +from typing import Union, List, Dict, Optional +from qiskit.circuit import QuantumCircuit, Qubit, Parameter from qiskit.exceptions import QiskitError from qiskit.pulse import ScheduleComponent, LoConfig from qiskit.assembler.run_config import RunConfig from qiskit.assembler import assemble_circuits, assemble_schedules -from qiskit.qobj import QobjHeader +from qiskit.qobj import QobjHeader, Qobj from qiskit.qobj.utils import MeasLevel, MeasReturnType from qiskit.validation.jsonschema import SchemaValidationError +from qiskit.providers import BaseBackend +from qiskit.pulse.channels import PulseChannel +from qiskit.pulse import Schedule # TODO: parallelize over the experiments (serialize each separately, then add global header/config) -def assemble(experiments, - backend=None, - qobj_id=None, qobj_header=None, - shots=None, memory=False, max_credits=None, seed_simulator=None, - qubit_lo_freq=None, meas_lo_freq=None, - qubit_lo_range=None, meas_lo_range=None, - schedule_los=None, meas_level=MeasLevel.CLASSIFIED, - meas_return=MeasReturnType.AVERAGE, meas_map=None, - memory_slot_size=100, rep_time=None, parameter_binds=None, - parametric_pulses=None, - **run_config): - """Assemble a list of circuits or pulse schedules into a Qobj. +def assemble(experiments: Union[QuantumCircuit, List[QuantumCircuit], Schedule, List[Schedule]], + backend: Optional[BaseBackend] = None, + qobj_id: Optional[str] = None, + qobj_header: Optional[Union[QobjHeader, Dict]] = None, + shots: Optional[int] = None, memory: Optional[bool] = False, + max_credits: Optional[int] = None, + seed_simulator: Optional[int] = None, + qubit_lo_freq: Optional[List[int]] = None, + meas_lo_freq: Optional[List[int]] = None, + qubit_lo_range: Optional[List[int]] = None, + meas_lo_range: Optional[List[int]] = None, + schedule_los: Optional[Union[List[Union[Dict[PulseChannel, float], LoConfig]], + Union[Dict[PulseChannel, float], LoConfig]]] = None, + meas_level: Union[int, MeasLevel] = MeasLevel.CLASSIFIED, + meas_return: Union[str, MeasReturnType] = MeasReturnType.AVERAGE, + meas_map: Optional[List[List[Qubit]]] = None, + memory_slot_size: int = 100, + rep_time: Optional[float] = None, + parameter_binds: Optional[List[Dict[Parameter, float]]] = None, + parametric_pulses: Optional[List[str]] = None, + **run_config: Dict) -> Qobj: + """Assemble a list of circuits or pulse schedules into a ``Qobj``. This function serializes the payloads, which could be either circuits or schedules, - to create Qobj "experiments". It further annotates the experiment payload with + to create ``Qobj`` "experiments". It further annotates the experiment payload with header and configurations. Args: - experiments (QuantumCircuit or list[QuantumCircuit] or Schedule or list[Schedule]): - Circuit(s) or pulse schedule(s) to execute - - backend (BaseBackend): - If set, some runtime options are automatically grabbed from - backend.configuration() and backend.defaults(). - If any other option is explicitly set (e.g., rep_rate), it + experiments: Circuit(s) or pulse schedule(s) to execute + backend: If set, some runtime options are automatically grabbed from + ``backend.configuration()`` and ``backend.defaults()``. + If any other option is explicitly set (e.g., ``rep_time``), it will override the backend's. If any other options is set in the run_config, it will also override the backend's. - - qobj_id (str): - String identifier to annotate the Qobj - - qobj_header (QobjHeader or dict): - User input that will be inserted in Qobj header, and will also be + qobj_id: String identifier to annotate the ``Qobj`` + qobj_header: User input that will be inserted in ``Qobj`` header, and will also be copied to the corresponding Result header. Headers do not affect the run. - - shots (int): - Number of repetitions of each circuit, for sampling. Default: 1024 - or max_shots from the backend configuration, whichever is smaller - - memory (bool): - If True, per-shot measurement bitstrings are returned as well + shots: Number of repetitions of each circuit, for sampling. Default: 1024 + or ``max_shots`` from the backend configuration, whichever is smaller + memory: If ``True``, per-shot measurement bitstrings are returned as well (provided the backend supports it). For OpenPulse jobs, only - measurement level 2 supports this option. Default: False - - max_credits (int): - Maximum credits to spend on job. Default: 10 - - seed_simulator (int): - Random seed to control sampling, for when backend is a simulator - - qubit_lo_freq (list): - List of default qubit LO frequencies in Hz. Will be overridden by - `schedule_los` if set. - - meas_lo_freq (list): - List of default measurement LO frequencies in Hz. Will be overridden - by `schedule_los` if set. - - qubit_lo_range (list): - List of drive LO ranges each of form `[range_min, range_max]` in Hz. + measurement level 2 supports this option. + max_credits: Maximum credits to spend on job. Default: 10 + seed_simulator: Random seed to control sampling, for when backend is a simulator + qubit_lo_freq: List of default qubit LO frequencies in Hz. Will be overridden by + ``schedule_los`` if set. + meas_lo_freq: List of default measurement LO frequencies in Hz. Will be overridden + by ``schedule_los`` if set. + qubit_lo_range: List of drive LO ranges each of form ``[range_min, range_max]`` in Hz. Used to validate the supplied qubit frequencies. - - meas_lo_range (list): - List of measurement LO ranges each of form `[range_min, range_max]` in Hz. + meas_lo_range: List of measurement LO ranges each of form ``[range_min, range_max]`` in Hz. Used to validate the supplied qubit frequencies. - - schedule_los (None or list[Union[Dict[PulseChannel, float], LoConfig]] or \ - Union[Dict[PulseChannel, float], LoConfig]): - Experiment LO configurations, frequencies are given in Hz. - - meas_level (int or MeasLevel): - Set the appropriate level of the measurement output for pulse experiments. - - meas_return (str or MeasReturn): - Level of measurement data for the backend to return. - - For `meas_level` 0 and 1: - * "single" returns information from every shot. - * "avg" returns average measurement output (averaged over number of shots). - - meas_map (list): - List of lists, containing qubits that must be measured together. - - memory_slot_size (int): - Size of each memory slot if the output is Level 0. - - rep_time (float): repetition time of the experiment in s. - The delay between experiments will be rep_time. + schedule_los: Experiment LO configurations, frequencies are given in Hz. + meas_level: Set the appropriate level of the measurement output for pulse experiments. + meas_return: Level of measurement data for the backend to return. + + For ``meas_level`` 0 and 1: + * ``single`` returns information from every shot. + * ``avg`` returns average measurement output (averaged over number of shots). + meas_map: List of lists, containing qubits that must be measured together. + memory_slot_size: Size of each memory slot if the output is Level 0. + rep_time: Repetition time of the experiment in s. + The delay between experiments will be ``rep_time``. Must be from the list provided by the device. - - parameter_binds (list[dict{Parameter: Value}]): - List of Parameter bindings over which the set of experiments will be + parameter_binds: List of Parameter bindings over which the set of experiments will be executed. Each list element (bind) should be of the form {Parameter1: value1, Parameter2: value2, ...}. All binds will be executed across all experiments; e.g., if parameter_binds is a length-n list, and there are m experiments, a total of m x n experiments will be run (one for each experiment/bind pair). + parametric_pulses: A list of pulse shapes which are supported internally on the backend. + Example:: - parametric_pulses (list[str]): - A list of pulse shapes which are supported internally on the backend. - Example: ['gaussian', 'constant'] - - **run_config (dict): - extra arguments used to configure the run (e.g., for Aer configurable + ['gaussian', 'constant'] + **run_config: Extra arguments used to configure the run (e.g., for Aer configurable backends). Refer to the backend documentation for details on these arguments. Returns: - Qobj: a qobj that can be run on a backend. Depending on the type of input, - this will be either a QasmQobj or a PulseQobj. + A ``Qobj`` that can be run on a backend. Depending on the type of input, + this will be either a ``QasmQobj`` or a ``PulseQobj``. Raises: QiskitError: if the input cannot be interpreted as either circuits or schedules diff --git a/qiskit/compiler/schedule.py b/qiskit/compiler/schedule.py index 9716f5193337..01583b7482b4 100644 --- a/qiskit/compiler/schedule.py +++ b/qiskit/compiler/schedule.py @@ -21,34 +21,35 @@ from qiskit.circuit.quantumcircuit import QuantumCircuit from qiskit.exceptions import QiskitError from qiskit.pulse import CmdDef, InstructionScheduleMap, Schedule - +from qiskit.providers import BaseBackend from qiskit.scheduler import schedule_circuit, ScheduleConfig def schedule(circuits: Union[QuantumCircuit, List[QuantumCircuit]], - backend: Optional['BaseBackend'] = None, + backend: Optional[BaseBackend] = None, inst_map: Optional[InstructionScheduleMap] = None, cmd_def: Optional[CmdDef] = None, meas_map: Optional[List[List[int]]] = None, method: Optional[Union[str, List[str]]] = None) -> Union[Schedule, List[Schedule]]: """ - Schedule a circuit to a pulse Schedule, using the backend, according to any specified methods. - Supported methods are documented in - :py:func:`qiskit.pulse.scheduler.schedule_circuit.schedule_circuit`. + Schedule a circuit to a pulse ``Schedule``, using the backend, according to any specified + methods. Supported methods are documented in :py:mod:`qiskit.scheduler.schedule_circuit`. Args: circuits: The quantum circuit or circuits to translate backend: A backend instance, which contains hardware-specific data required for scheduling - inst_map: Mapping of circuit operations to pulse schedules. If None, defaults to the - `backend` `instruction_schedule_map` + inst_map: Mapping of circuit operations to pulse schedules. If ``None``, defaults to the + ``backend``\'s ``instruction_schedule_map`` cmd_def: Deprecated - meas_map: List of sets of qubits that must be measured together. If `None`, defaults to - the `backend` `meas_map` + meas_map: List of sets of qubits that must be measured together. If ``None``, defaults to + the ``backend``\'s ``meas_map`` method: Optionally specify a particular scheduling method + Returns: - A pulse `Schedule` that implements the input circuit + A pulse ``Schedule`` that implements the input circuit + Raises: - QiskitError: If `inst_map` and `meas_map` are not passed and `backend` is not passed + QiskitError: If ``inst_map`` and ``meas_map`` are not passed and ``backend`` is not passed """ if inst_map is None: if cmd_def is not None: diff --git a/qiskit/compiler/transpile.py b/qiskit/compiler/transpile.py index b10cfe331ae0..c7664f46275a 100644 --- a/qiskit/compiler/transpile.py +++ b/qiskit/compiler/transpile.py @@ -13,7 +13,13 @@ # that they have been altered from the originals. """Circuit transpile function""" -from qiskit.transpiler import Layout, CouplingMap +from typing import List, Union, Dict, Callable, Any, Optional, Tuple +from qiskit.circuit.quantumcircuit import QuantumCircuit +from qiskit.providers import BaseBackend +from qiskit.providers.models import BackendProperties +from qiskit.transpiler import Layout, CouplingMap, PropertySet, PassManager +from qiskit.transpiler.basepasses import BasePass +from qiskit.dagcircuit import DAGCircuit from qiskit.tools.parallel import parallel_map from qiskit.transpiler.pass_manager_config import PassManagerConfig from qiskit.pulse import Schedule @@ -28,12 +34,19 @@ level_3_pass_manager) -def transpile(circuits, - backend=None, - basis_gates=None, coupling_map=None, backend_properties=None, - initial_layout=None, seed_transpiler=None, - optimization_level=None, - pass_manager=None, callback=None, output_name=None): +def transpile(circuits: Union[QuantumCircuit, List[QuantumCircuit]], + backend: Optional[BaseBackend] = None, + basis_gates: Optional[List[str]] = None, + coupling_map: Optional[Union[CouplingMap, List[List[int]]]] = None, + backend_properties: Optional[BackendProperties] = None, + initial_layout: Optional[Union[Layout, Dict, List]] = None, + seed_transpiler: Optional[int] = None, + optimization_level: Optional[int] = None, + pass_manager: Optional[PassManager] = None, + callback: Optional[Callable[[BasePass, DAGCircuit, float, + PropertySet, int], Any]] = None, + output_name: Optional[Union[str, List[str]]] = None) -> Union[QuantumCircuit, + List[QuantumCircuit]]: """Transpile one or more circuits, according to some desired transpilation targets. All arguments may be given as either a singleton or list. In case of a list, @@ -42,55 +55,41 @@ def transpile(circuits, Transpilation is done in parallel using multiprocessing. Args: - circuits (QuantumCircuit or list[QuantumCircuit]): - Circuit(s) to transpile - - backend (BaseBackend): - If set, transpiler options are automatically grabbed from - backend.configuration() and backend.properties(). - If any other option is explicitly set (e.g., coupling_map), it + circuits: Circuit(s) to transpile + backend: If set, transpiler options are automatically grabbed from + ``backend.configuration()`` and ``backend.properties()``. + If any other option is explicitly set (e.g., ``coupling_map``), it will override the backend's. - Note: the backend arg is purely for convenience. The resulting - circuit may be run on any backend as long as it is compatible. - - basis_gates (list[str]): - List of basis gate names to unroll to. + .. note:: + The backend arg is purely for convenience. The resulting + circuit may be run on any backend as long as it is compatible. + basis_gates: List of basis gate names to unroll to. e.g:: - ['u1', 'u2', 'u3', 'cx'] - - If None, do not unroll. - - coupling_map (CouplingMap or list): - Coupling map (perhaps custom) to target in mapping. + If ``None``, do not unroll. + coupling_map: Coupling map (perhaps custom) to target in mapping. Multiple formats are supported: - - 1. CouplingMap instance - 2. list + 1. ``CouplingMap`` instance + 2. List Must be given as an adjacency matrix, where each entry specifies all two-qubit interactions supported by backend, e.g:: - [[0, 1], [0, 3], [1, 2], [1, 5], [2, 5], [4, 1], [5, 3]] - - backend_properties (BackendProperties): - properties returned by a backend, including information on gate + backend_properties: properties returned by a backend, including information on gate errors, readout errors, qubit coherence times, etc. Find a backend - that provides this information with: - ``backend.properties()`` - - initial_layout (Layout or dict or list): - Initial position of virtual qubits on physical qubits. + that provides this information with: ``backend.properties()`` + initial_layout: Initial position of virtual qubits on physical qubits. If this layout makes the circuit compatible with the coupling_map constraints, it will be used. The final layout is not guaranteed to be the same, as the transpiler may permute qubits through swaps or other means. - Multiple formats are supported: - 1. Layout instance - 2. dict + 1. ``Layout`` instance + + 2. Dict + * virtual to physical:: {qr[0]: 0, @@ -102,55 +101,41 @@ def transpile(circuits, {0: qr[0], 3: qr[1], 5: qr[2]} + 3. List - 3. list * virtual to physical:: [0, 3, 5] # virtual qubits are ordered (in addition to named) - * physical to virtual:: [qr[0], None, None, qr[1], None, qr[2]] - - seed_transpiler (int): - Sets random seed for the stochastic parts of the transpiler - - optimization_level (int): - How much optimization to perform on the circuits. + seed_transpiler: Sets random seed for the stochastic parts of the transpiler + optimization_level: How much optimization to perform on the circuits. Higher levels generate more optimized circuits, at the expense of longer transpilation time. - * 0: no optimization * 1: light optimization * 2: heavy optimization * 3: even heavier optimization - - If None, level 1 will be chosen as default. - - pass_manager (PassManager): - The pass manager to use for a custom pipeline of transpiler passes. + If ``None``, level 1 will be chosen as default. + pass_manager: The pass manager to use for a custom pipeline of transpiler passes. If this arg is present, all other args will be ignored and the pass manager will be used directly (Qiskit will not attempt to auto-select a pass manager based on transpile options). - - callback (func): - A callback function that will be called after each + callback: A callback function that will be called after each pass execution. The function will be called with 5 keyword - arguments:: - pass_ (Pass): the pass being run. - dag (DAGCircuit): the dag output of the pass. - time (float): the time to execute the pass. - property_set (PropertySet): the property set. - count (int): the index for the pass execution. - + arguments, + | ``pass_``: the pass being run. + | ``dag``: the dag output of the pass. + | ``time``: the time to execute the pass. + | ``property_set``: the property set. + | ``count``: the index for the pass execution. The exact arguments passed expose the internals of the pass manager, and are subject to change as the pass manager internals change. If you intend to reuse a callback function over multiple releases, be sure to check that the arguments being passed are the same. - To use the callback feature, define a function that will take in kwargs dict and access the variables. For example:: - def callback_func(**kwargs): pass_ = kwargs['pass_'] dag = kwargs['dag'] @@ -158,15 +143,12 @@ def callback_func(**kwargs): property_set = kwargs['property_set'] count = kwargs['count'] ... - transpile(circ, callback=callback_func) - - output_name (str or list[str]) : - A list with strings to identify the output circuits. The length of - `list[str]` should be exactly the length of the `circuits` parameter. + output_name: A list with strings to identify the output circuits. The length of + the list should be exactly the length of the ``circuits`` parameter. Returns: - QuantumCircuit or list[QuantumCircuit]: transpiled circuit(s). + The transpiled circuit(s). Raises: TranspilerError: in case of bad inputs to transpiler or errors in passes @@ -213,7 +195,7 @@ def callback_func(**kwargs): return circuits -def _transpile_circuit(circuit_config_tuple): +def _transpile_circuit(circuit_config_tuple: Tuple[QuantumCircuit, Dict]) -> QuantumCircuit: """Select a PassManager and run a single circuit through it. Args: circuit_config_tuple (tuple): @@ -226,7 +208,7 @@ def _transpile_circuit(circuit_config_tuple): 'callback': callable, 'pass_manager_config': PassManagerConfig} Returns: - QuantumCircuit: transpiled circuit + The transpiled circuit Raises: TranspilerError: if transpile_config is not valid or transpilation incurs error """ @@ -277,7 +259,7 @@ def _transpile_circuit(circuit_config_tuple): def _parse_transpile_args(circuits, backend, basis_gates, coupling_map, backend_properties, initial_layout, seed_transpiler, optimization_level, - pass_manager, callback, output_name): + pass_manager, callback, output_name) -> List[Dict]: """Resolve the various types of args allowed to the transpile() function through duck typing, overriding args, etc. Refer to the transpile() docstring for details on what types of inputs are allowed. diff --git a/qiskit/converters/ast_to_dag.py b/qiskit/converters/ast_to_dag.py index 3e07aec68f7c..a8dc17ee96fd 100644 --- a/qiskit/converters/ast_to_dag.py +++ b/qiskit/converters/ast_to_dag.py @@ -25,14 +25,14 @@ from qiskit.circuit.measure import Measure from qiskit.circuit.reset import Reset from qiskit.extensions.standard.barrier import Barrier -from qiskit.extensions.standard.x import ToffoliGate -from qiskit.extensions.standard.swap import FredkinGate -from qiskit.extensions.standard.x import CnotGate -from qiskit.extensions.standard.y import CyGate -from qiskit.extensions.standard.z import CzGate +from qiskit.extensions.standard.x import CCXGate +from qiskit.extensions.standard.swap import CSwapGate +from qiskit.extensions.standard.x import CXGate +from qiskit.extensions.standard.y import CYGate +from qiskit.extensions.standard.z import CZGate from qiskit.extensions.standard.swap import SwapGate from qiskit.extensions.standard.h import HGate -from qiskit.extensions.standard.iden import IdGate +from qiskit.extensions.standard.i import IGate from qiskit.extensions.standard.s import SGate from qiskit.extensions.standard.s import SdgGate from qiskit.extensions.standard.t import TGate @@ -46,14 +46,14 @@ from qiskit.extensions.standard.rx import RXGate from qiskit.extensions.standard.ry import RYGate from qiskit.extensions.standard.rz import RZGate -from qiskit.extensions.standard.u1 import Cu1Gate -from qiskit.extensions.standard.h import CHGate -from qiskit.extensions.standard.rx import CrxGate -from qiskit.extensions.standard.ry import CryGate -from qiskit.extensions.standard.rz import CrzGate -from qiskit.extensions.standard.u3 import Cu3Gate from qiskit.extensions.standard.rxx import RXXGate from qiskit.extensions.standard.rzz import RZZGate +from qiskit.extensions.standard.u1 import CU1Gate +from qiskit.extensions.standard.u3 import CU3Gate +from qiskit.extensions.standard.h import CHGate +from qiskit.extensions.standard.rx import CRXGate +from qiskit.extensions.standard.ry import CRYGate +from qiskit.extensions.standard.rz import CRZGate def ast_to_dag(ast): @@ -113,19 +113,19 @@ class AstInterpreter: "ry": RYGate, "rz": RZGate, "rzz": RZZGate, - "id": IdGate, + "id": IGate, "h": HGate, - "cx": CnotGate, - "cy": CyGate, - "cz": CzGate, + "cx": CXGate, + "cy": CYGate, + "cz": CZGate, "ch": CHGate, - "crx": CrxGate, - "cry": CryGate, - "crz": CrzGate, - "cu1": Cu1Gate, - "cu3": Cu3Gate, - "ccx": ToffoliGate, - "cswap": FredkinGate} + "crx": CRXGate, + "cry": CRYGate, + "crz": CRZGate, + "cu1": CU1Gate, + "cu3": CU3Gate, + "ccx": CCXGate, + "cswap": CSwapGate} def __init__(self, dag): """Initialize interpreter's data.""" @@ -239,11 +239,11 @@ def _process_cnot(self, node): maxidx = max([len(id0), len(id1)]) for idx in range(maxidx): if len(id0) > 1 and len(id1) > 1: - self.dag.apply_operation_back(CnotGate(), [id0[idx], id1[idx]], [], self.condition) + self.dag.apply_operation_back(CXGate(), [id0[idx], id1[idx]], [], self.condition) elif len(id0) > 1: - self.dag.apply_operation_back(CnotGate(), [id0[idx], id1[0]], [], self.condition) + self.dag.apply_operation_back(CXGate(), [id0[idx], id1[0]], [], self.condition) else: - self.dag.apply_operation_back(CnotGate(), [id0[0], id1[idx]], [], self.condition) + self.dag.apply_operation_back(CXGate(), [id0[0], id1[idx]], [], self.condition) def _process_measure(self, node): """Process a measurement node.""" diff --git a/qiskit/converters/circuit_to_gate.py b/qiskit/converters/circuit_to_gate.py index 4ecce7f9cc98..a313b17a8149 100644 --- a/qiskit/converters/circuit_to_gate.py +++ b/qiskit/converters/circuit_to_gate.py @@ -42,6 +42,10 @@ def circuit_to_gate(circuit, parameter_map=None): input circuit. Upon decomposition, this gate will yield the components comprising the original circuit. """ + if circuit.clbits: + raise QiskitError('Circuit with classical bits cannot be converted ' + 'to gate.') + for inst, _, _ in circuit.data: if not isinstance(inst, Gate): raise QiskitError('One or more instructions in this instruction ' diff --git a/qiskit/converters/dag_to_circuit.py b/qiskit/converters/dag_to_circuit.py index 7bd1e161d018..f6cf4e90460c 100644 --- a/qiskit/converters/dag_to_circuit.py +++ b/qiskit/converters/dag_to_circuit.py @@ -31,7 +31,7 @@ def dag_to_circuit(dag): from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit from qiskit.dagcircuit import DAGCircuit from qiskit.converters import circuit_to_dag - from qiskit.extensions.standard import CHGate, U2Gate, CnotGate + from qiskit.extensions.standard import CHGate, U2Gate, CXGate from qiskit.converters import dag_to_circuit %matplotlib inline diff --git a/qiskit/extensions/__init__.py b/qiskit/extensions/__init__.py index 1eedc7b96840..e77596231d0b 100644 --- a/qiskit/extensions/__init__.py +++ b/qiskit/extensions/__init__.py @@ -26,19 +26,19 @@ :toctree: ../stubs/ Barrier - ToffoliGate + CCXGate CHGate - CrxGate - CryGate - CrzGate - FredkinGate - Cu1Gate - Cu3Gate - CnotGate - CyGate - CzGate + CRXGate + CRYGate + CRZGate + CSwapGate + CU1Gate + CU3Gate + CXGate + CYGate + CZGate HGate - IdGate + IGate MSGate RXGate RXXGate diff --git a/qiskit/extensions/quantum_initializer/__init__.py b/qiskit/extensions/quantum_initializer/__init__.py index 5aab84a27772..8b1b3f2b71d6 100644 --- a/qiskit/extensions/quantum_initializer/__init__.py +++ b/qiskit/extensions/quantum_initializer/__init__.py @@ -15,9 +15,9 @@ """Initialize qubit registers to desired arbitrary state.""" from .squ import SingleQubitUnitary -from .ucz import UCZ -from .ucy import UCY -from .ucx import UCX -from .diag import DiagGate -from .ucg import UCG +from .ucrz import UCRZGate +from .ucry import UCRYGate +from .ucrx import UCRXGate +from .diagonal import DiagonalGate +from .uc import UCGate from .isometry import Isometry diff --git a/qiskit/extensions/quantum_initializer/diag.py b/qiskit/extensions/quantum_initializer/diag.py index 296349974673..f8cae74dad7f 100644 --- a/qiskit/extensions/quantum_initializer/diag.py +++ b/qiskit/extensions/quantum_initializer/diag.py @@ -2,7 +2,7 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2019. +# (C) Copyright IBM 2020. # # This code is licensed under the Apache License, Version 2.0. You may # obtain a copy of this license in the LICENSE.txt file in the root directory @@ -12,132 +12,14 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -# The structure of the code is based on Emanuel Malvetti's semester thesis at ETH in 2018, -# which was supervised by Raban Iten and Prof. Renato Renner. +"""The diagonal gate. -# pylint: disable=missing-param-doc -# pylint: disable=missing-type-doc - -""" -Decomposes a diagonal matrix into elementary gates using the method described in Theorem 7 in -"Synthesis of Quantum Logic Circuits" by Shende et al. (https://arxiv.org/pdf/quant-ph/0406176.pdf). +This module is deprecated, see diagonal.py """ -import cmath -import math - -import numpy as np - -from qiskit.circuit import Gate -from qiskit.circuit.quantumcircuit import QuantumCircuit, QuantumRegister -from qiskit.exceptions import QiskitError - -_EPS = 1e-10 # global variable used to chop very small numbers to zero - - -class DiagGate(Gate): - """ - diag = list of the 2^k diagonal entries (for a diagonal gate on k qubits). Must contain at - least two entries. - """ - - def __init__(self, diag): - """Check types""" - # Check if diag has type "list" - if not isinstance(diag, list): - raise QiskitError("The diagonal entries are not provided in a list.") - # Check if the right number of diagonal entries is provided and if the diagonal entries - # have absolute value one. - num_action_qubits = math.log2(len(diag)) - if num_action_qubits < 1 or not num_action_qubits.is_integer(): - raise QiskitError("The number of diagonal entries is not a positive power of 2.") - for z in diag: - try: - complex(z) - except TypeError: - raise QiskitError("Not all of the diagonal entries can be converted to " - "complex numbers.") - if not np.abs(z) - 1 < _EPS: - raise QiskitError("A diagonal entry has not absolute value one.") - # Create new gate. - super().__init__("diag", int(num_action_qubits), diag) - - def _define(self): - diag_circuit = self._dec_diag() - gate = diag_circuit.to_instruction() - q = QuantumRegister(self.num_qubits) - diag_circuit = QuantumCircuit(q) - diag_circuit.append(gate, q[:]) - self.definition = diag_circuit.data - - def _dec_diag(self): - """ - Call to create a circuit implementing the diagonal gate. - """ - q = QuantumRegister(self.num_qubits) - circuit = QuantumCircuit(q) - # Since the diagonal is a unitary, all its entries have absolute value one and the diagonal - # is fully specified by the phases of its entries - diag_phases = [cmath.phase(z) for z in self.params] - n = len(self.params) - while n >= 2: - angles_rz = [] - for i in range(0, n, 2): - diag_phases[i // 2], rz_angle = _extract_rz(diag_phases[i], diag_phases[i + 1]) - angles_rz.append(rz_angle) - num_act_qubits = int(np.log2(n)) - contr_qubits = q[self.num_qubits - num_act_qubits + 1:self.num_qubits] - target_qubit = q[self.num_qubits - num_act_qubits] - circuit.ucz(angles_rz, contr_qubits, target_qubit) - n //= 2 - return circuit - - -# extract a Rz rotation (angle given by first output) such that exp(j*phase)*Rz(z_angle) -# is equal to the diagonal matrix with entires exp(1j*ph1) and exp(1j*ph2) -def _extract_rz(phi1, phi2): - phase = (phi1 + phi2) / 2.0 - z_angle = phi2 - phi1 - return phase, z_angle - - -def diag_gate(self, diag, qubit): - """Attach a diagonal gate to a circuit. - - The decomposition is based on Theorem 7 given in "Synthesis of Quantum Logic Circuits" by - Shende et al. (https://arxiv.org/pdf/quant-ph/0406176.pdf). - - Args: - diag (list): list of the 2^k diagonal entries (for a diagonal gate on k qubits). - Must contain at least two entries - qubit (QuantumRegister|list): list of k qubits the diagonal is - acting on (the order of the qubits specifies the computational basis in which the - diagonal gate is provided: the first element in diag acts on the state where all - the qubits in q are in the state 0, the second entry acts on the state where all - the qubits q[1],...,q[k-1] are in the state zero and q[0] is in the state 1, - and so on) - - Returns: - QuantumCircuit: the diagonal gate which was attached to the circuit. - - Raises: - QiskitError: if the list of the diagonal entries or the qubit list is in bad format; - if the number of diagonal entries is not 2^k, where k denotes the number of qubits - """ - - if isinstance(qubit, QuantumRegister): - qubit = qubit[:] - # Check if q has type "list" - if not isinstance(qubit, list): - raise QiskitError("The qubits must be provided as a list " - "(also if there is only one qubit).") - # Check if diag has type "list" - if not isinstance(diag, list): - raise QiskitError("The diagonal entries are not provided in a list.") - num_action_qubits = math.log2(len(diag)) - if not len(qubit) == num_action_qubits: - raise QiskitError("The number of diagonal entries does not correspond to" - " the number of qubits.") - return self.append(DiagGate(diag), qubit) +import warnings +# pylint: disable=unused-import +from qiskit.extensions.quantum_initializer.diagonal import DiagGate, diag_gate -QuantumCircuit.diag_gate = diag_gate +warnings.warn('This module is deprecated. The DiagonalGate/DiagGate is now in diagonal.py', + category=DeprecationWarning, stacklevel=2) diff --git a/qiskit/extensions/quantum_initializer/diagonal.py b/qiskit/extensions/quantum_initializer/diagonal.py new file mode 100644 index 000000000000..1083ffd36946 --- /dev/null +++ b/qiskit/extensions/quantum_initializer/diagonal.py @@ -0,0 +1,176 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +# The structure of the code is based on Emanuel Malvetti's semester thesis at ETH in 2018, +# which was supervised by Raban Iten and Prof. Renato Renner. + +# pylint: disable=missing-param-doc +# pylint: disable=missing-type-doc + +""" +Decomposes a diagonal matrix into elementary gates using the method described in Theorem 7 in +"Synthesis of Quantum Logic Circuits" by Shende et al. (https://arxiv.org/pdf/quant-ph/0406176.pdf). +""" +import cmath +import math + +import numpy as np + +from qiskit.circuit import Gate +from qiskit.circuit.quantumcircuit import QuantumCircuit, QuantumRegister +from qiskit.exceptions import QiskitError + +_EPS = 1e-10 # global variable used to chop very small numbers to zero + + +class DiagonalMeta(type): + """A metaclass to ensure that DiagonalGate and DiagGate are of the same type. + + Can be removed when DiagGate gets removed. + """ + @classmethod + def __instancecheck__(mcs, inst): + return type(inst) in {DiagonalGate, DiagGate} # pylint: disable=unidiomatic-typecheck + + +class DiagonalGate(Gate, metaclass=DiagonalMeta): + """ + diag = list of the 2^k diagonal entries (for a diagonal gate on k qubits). Must contain at + least two entries. + """ + + def __init__(self, diag): + """Check types""" + # Check if diag has type "list" + if not isinstance(diag, list): + raise QiskitError("The diagonal entries are not provided in a list.") + # Check if the right number of diagonal entries is provided and if the diagonal entries + # have absolute value one. + num_action_qubits = math.log2(len(diag)) + if num_action_qubits < 1 or not num_action_qubits.is_integer(): + raise QiskitError("The number of diagonal entries is not a positive power of 2.") + for z in diag: + try: + complex(z) + except TypeError: + raise QiskitError("Not all of the diagonal entries can be converted to " + "complex numbers.") + if not np.abs(z) - 1 < _EPS: + raise QiskitError("A diagonal entry has not absolute value one.") + # Create new gate. + super().__init__("diagonal", int(num_action_qubits), diag) + + def _define(self): + diag_circuit = self._dec_diag() + gate = diag_circuit.to_instruction() + q = QuantumRegister(self.num_qubits) + diag_circuit = QuantumCircuit(q) + diag_circuit.append(gate, q[:]) + self.definition = diag_circuit.data + + def _dec_diag(self): + """ + Call to create a circuit implementing the diagonal gate. + """ + q = QuantumRegister(self.num_qubits) + circuit = QuantumCircuit(q) + # Since the diagonal is a unitary, all its entries have absolute value one and the diagonal + # is fully specified by the phases of its entries + diag_phases = [cmath.phase(z) for z in self.params] + n = len(self.params) + while n >= 2: + angles_rz = [] + for i in range(0, n, 2): + diag_phases[i // 2], rz_angle = _extract_rz(diag_phases[i], diag_phases[i + 1]) + angles_rz.append(rz_angle) + num_act_qubits = int(np.log2(n)) + contr_qubits = q[self.num_qubits - num_act_qubits + 1:self.num_qubits] + target_qubit = q[self.num_qubits - num_act_qubits] + circuit.ucrz(angles_rz, contr_qubits, target_qubit) + n //= 2 + return circuit + + +# extract a Rz rotation (angle given by first output) such that exp(j*phase)*Rz(z_angle) +# is equal to the diagonal matrix with entires exp(1j*ph1) and exp(1j*ph2) +def _extract_rz(phi1, phi2): + phase = (phi1 + phi2) / 2.0 + z_angle = phi2 - phi1 + return phase, z_angle + + +def diagonal(self, diag, qubit): + """Attach a diagonal gate to a circuit. + + The decomposition is based on Theorem 7 given in "Synthesis of Quantum Logic Circuits" by + Shende et al. (https://arxiv.org/pdf/quant-ph/0406176.pdf). + + Args: + diag (list): list of the 2^k diagonal entries (for a diagonal gate on k qubits). + Must contain at least two entries + qubit (QuantumRegister|list): list of k qubits the diagonal is + acting on (the order of the qubits specifies the computational basis in which the + diagonal gate is provided: the first element in diag acts on the state where all + the qubits in q are in the state 0, the second entry acts on the state where all + the qubits q[1],...,q[k-1] are in the state zero and q[0] is in the state 1, + and so on) + + Returns: + QuantumCircuit: the diagonal gate which was attached to the circuit. + + Raises: + QiskitError: if the list of the diagonal entries or the qubit list is in bad format; + if the number of diagonal entries is not 2^k, where k denotes the number of qubits + """ + + if isinstance(qubit, QuantumRegister): + qubit = qubit[:] + # Check if q has type "list" + if not isinstance(qubit, list): + raise QiskitError("The qubits must be provided as a list " + "(also if there is only one qubit).") + # Check if diag has type "list" + if not isinstance(diag, list): + raise QiskitError("The diagonal entries are not provided in a list.") + num_action_qubits = math.log2(len(diag)) + if not len(qubit) == num_action_qubits: + raise QiskitError("The number of diagonal entries does not correspond to" + " the number of qubits.") + return self.append(DiagonalGate(diag), qubit) + + +class DiagGate(DiagonalGate, metaclass=DiagonalMeta): + """The deprecated DiagonalGate class.""" + + def __init__(self, diag): + import warnings + warnings.warn('The class DiagGate is deprecated as of 0.14.0, and ' + 'will be removed no earlier than 3 months after that release date. ' + 'You should use the class DiagonalGate instead.', + DeprecationWarning, stacklevel=2) + super().__init__(diag) + + +def diag_gate(self, diag, qubit): + """Deprecated version of QuantumCircuit.diagonal.""" + import warnings + warnings.warn('The QuantumCircuit.diag_gate() method is deprecated as of 0.14.0, and ' + 'will be removed no earlier than 3 months after that release date. ' + 'You should use the QuantumCircuit.diagonal() method instead.', + DeprecationWarning, stacklevel=2) + return diagonal(self, diag, qubit) + + +QuantumCircuit.diagonal = diagonal +QuantumCircuit.diag_gate = diag_gate # deprecated diff --git a/qiskit/extensions/quantum_initializer/initializer.py b/qiskit/extensions/quantum_initializer/initializer.py index b728ecd50ce9..6047121457bf 100644 --- a/qiskit/extensions/quantum_initializer/initializer.py +++ b/qiskit/extensions/quantum_initializer/initializer.py @@ -23,7 +23,7 @@ from qiskit.circuit import QuantumCircuit from qiskit.circuit import QuantumRegister from qiskit.circuit import Instruction -from qiskit.extensions.standard.x import CnotGate +from qiskit.extensions.standard.x import CXGate from qiskit.extensions.standard.ry import RYGate from qiskit.extensions.standard.rz import RZGate from qiskit.circuit.reset import Reset @@ -37,6 +37,8 @@ class Initialize(Instruction): Class that implements the (complex amplitude) initialization of some flexible collection of qubit registers (assuming the qubits are in the zero state). + Note that Initialize is an Instruction and not a Gate since it contains a reset instruction, + which is not unitary. """ def __init__(self, params): @@ -221,7 +223,7 @@ def _multiplex(self, target_gate, list_of_angles): circuit.append(multiplex_1.to_instruction(), q[0:-1]) # attach CNOT as follows, thereby flipping the LSB qubit - circuit.append(CnotGate(), [msb, lsb]) + circuit.append(CXGate(), [msb, lsb]) # implement extra efficiency from the paper of cancelling adjacent # CNOTs (by leaving out last CNOT and reversing (NOT inverting) the @@ -233,7 +235,7 @@ def _multiplex(self, target_gate, list_of_angles): circuit.append(multiplex_2.to_instruction(), q[0:-1]) # attach a final CNOT - circuit.append(CnotGate(), [msb, lsb]) + circuit.append(CXGate(), [msb, lsb]) return circuit diff --git a/qiskit/extensions/quantum_initializer/isometry.py b/qiskit/extensions/quantum_initializer/isometry.py index 89336dbf1629..106976cd2100 100644 --- a/qiskit/extensions/quantum_initializer/isometry.py +++ b/qiskit/extensions/quantum_initializer/isometry.py @@ -30,7 +30,7 @@ from qiskit.circuit import QuantumCircuit from qiskit.exceptions import QiskitError from qiskit.quantum_info.operators.predicates import is_isometry -from qiskit.extensions.quantum_initializer.ucg import UCG +from qiskit.extensions.quantum_initializer.uc import UCGate from qiskit.extensions.quantum_initializer.mcg_up_to_diagonal import MCGupDiag _EPS = 1e-10 # global variable used to chop very small numbers to zero @@ -93,25 +93,17 @@ def __init__(self, isometry, num_ancillas_zero, num_ancillas_dirty): num_qubits = int(n) + num_ancillas_zero + num_ancillas_dirty - super().__init__("iso", num_qubits, 0, [isometry]) + super().__init__("isometry", num_qubits, 0, [isometry]) def _define(self): # call to generate the circuit that takes the isometry to the first 2^m columns # of the 2^n identity matrix iso_circuit = self._gates_to_uncompute() - num_gates = len(iso_circuit.data) # invert the circuit to create the circuit implementing the isometry - if not num_gates == 0: - # ToDo: Allow to inverse empty circuits. - gate = iso_circuit.to_instruction().inverse() + gate = iso_circuit.to_instruction().inverse() q = QuantumRegister(self.num_qubits) iso_circuit = QuantumCircuit(q) - if num_gates == 0: - # ToDo: improve handling of empty circuit, such that the following line - # ToDo: is not required. - iso_circuit.iden(q[0]) - else: - iso_circuit.append(gate, q[:]) + iso_circuit.append(gate, q[:]) self.definition = iso_circuit.data def _gates_to_uncompute(self): @@ -139,7 +131,7 @@ def _gates_to_uncompute(self): # remove first column (which is now stored in diag) remaining_isometry = remaining_isometry[:, 1:] if len(diag) > 1 and not _diag_is_identity_up_to_global_phase(diag): - circuit.diag_gate(np.conj(diag).tolist(), q_input) + circuit.diagonal(np.conj(diag).tolist(), q_input) return circuit def _decompose_column(self, circuit, q, diag, remaining_isometry, column_index): @@ -163,7 +155,7 @@ def _disentangle(self, circuit, q, diag, remaining_isometry, column_index, s): v = remaining_isometry n = int(np.log2(self.params[0].shape[0])) - # MCG to set one entry to zero (preparation for disentangling with UCG): + # MCG to set one entry to zero (preparation for disentangling with UCGate): index1 = 2 * _a(k, s + 1) * 2 ** s + _b(k, s + 1) index2 = (2 * _a(k, s + 1) + 1) * 2 ** s + _b(k, s + 1) target_label = n - s - 1 @@ -183,17 +175,17 @@ def _disentangle(self, circuit, q, diag, remaining_isometry, column_index, s): # update the diag according to the applied diagonal gate _apply_diagonal_gate_to_diag(diag, control_labels + [target_label], diag_mcg_inverse, n) - # UCG to disentangle a qubit: - # Find the UCG, decompose it and apply it to the remaining isometry + # UCGate to disentangle a qubit: + # Find the UCGate, decompose it and apply it to the remaining isometry single_qubit_gates = self._find_squs_for_disentangling(v, k, s) if not _ucg_is_identity_up_to_global_phase(single_qubit_gates): control_labels = list(range(target_label)) diagonal_ucg = self._append_ucg_up_to_diagonal(circuit, q, single_qubit_gates, control_labels, target_label) - # merge the diagonal into the UCG for efficient application of both together + # merge the diagonal into the UCGate for efficient application of both together diagonal_ucg_inverse = np.conj(diagonal_ucg).tolist() - single_qubit_gates = _merge_UCG_and_diag(single_qubit_gates, diagonal_ucg_inverse) - # apply the UCG (with the merged diagonal gate) to the remaining isometry + single_qubit_gates = _merge_UCGate_and_diag(single_qubit_gates, diagonal_ucg_inverse) + # apply the UCGate (with the merged diagonal gate) to the remaining isometry _apply_ucg(v, len(control_labels), single_qubit_gates) # update the diag according to the applied diagonal gate _apply_diagonal_gate_to_diag(diag, control_labels + [target_label], @@ -202,7 +194,7 @@ def _disentangle(self, circuit, q, diag, remaining_isometry, column_index, s): # diag_inv = np.conj(diag).tolist() # _apply_diagonal_gate(v, control_labels + [target_label], diag_inv) - # This method finds the single-qubit gates for a UCG to disentangle a qubit: + # This method finds the single-qubit gates for a UCGate to disentangle a qubit: # we consider the n-qubit state v[:,0] starting with k zeros (in the computational basis). # The qubit with label n-s-1 is disentangled into the basis state k_s(k,s). def _find_squs_for_disentangling(self, v, k, s): @@ -218,17 +210,17 @@ def _find_squs_for_disentangling(self, v, k, s): for l in range(i_start, 2 ** (n - s - 1))] return id_list + squs - # Append a UCG up to diagonal to the circuit circ. + # Append a UCGate up to diagonal to the circuit circ. def _append_ucg_up_to_diagonal(self, circ, q, single_qubit_gates, control_labels, target_label): (q_input, q_ancillas_for_output, q_ancillas_zero, q_ancillas_dirty) = \ self._define_qubit_role(q) n = int(np.log2(self.params[0].shape[0])) qubits = q_input + q_ancillas_for_output # Note that we have to reverse the control labels, since controls are provided by - # increasing qubit number toa UCG by convention + # increasing qubit number toa UCGate by convention control_qubits = _reverse_qubit_oder(_get_qubits_by_label(control_labels, qubits, n)) target_qubit = _get_qubits_by_label([target_label], qubits, n)[0] - ucg = UCG(single_qubit_gates, up_to_diagonal=True) + ucg = UCGate(single_qubit_gates, up_to_diagonal=True) circ.append(ucg, [target_qubit] + control_qubits) return ucg._get_diagonal() @@ -283,8 +275,8 @@ def _reverse_qubit_state(state, basis_state): # Methods for applying gates to matrices (should be moved to Qiskit AER) # Input: matrix m with 2^n rows (and arbitrary many columns). Think of the columns as states -# on n qubits. The method applies a uniformly controlled gate (UCG) to all the columns, where -# the UCG is specified by the inputs k and single_qubit_gates: +# on n qubits. The method applies a uniformly controlled gate (UCGate) to all the columns, where +# the UCGate is specified by the inputs k and single_qubit_gates: # k = number of controls. We assume that the controls are on the k most significant qubits # (and the target is on the (k+1)th significant qubit) @@ -440,10 +432,10 @@ def _get_binary_rep_as_list(n, num_digits): return binary[-num_digits:] -# absorb a diagonal gate into a UCG +# absorb a diagonal gate into a UCGate -def _merge_UCG_and_diag(single_qubit_gates, diag): +def _merge_UCGate_and_diag(single_qubit_gates, diag): for (i, gate) in enumerate(single_qubit_gates): single_qubit_gates[i] = \ np.array([[diag[2 * i], 0.], [0., diag[2 * i + 1]]]).dot(gate) @@ -553,4 +545,6 @@ def iso(self, isometry, q_input, q_ancillas_for_output, q_ancillas_zero=None, q_input + q_ancillas_for_output + q_ancillas_zero + q_ancillas_dirty) +# support both QuantumCircuit.iso and QuantumCircuit.isometry QuantumCircuit.iso = iso +QuantumCircuit.isometry = iso diff --git a/qiskit/extensions/quantum_initializer/mcg_up_to_diagonal.py b/qiskit/extensions/quantum_initializer/mcg_up_to_diagonal.py index 46524d2414f4..01f71829eacf 100644 --- a/qiskit/extensions/quantum_initializer/mcg_up_to_diagonal.py +++ b/qiskit/extensions/quantum_initializer/mcg_up_to_diagonal.py @@ -29,7 +29,7 @@ from qiskit.circuit.quantumcircuit import QuantumRegister, QuantumCircuit from qiskit.quantum_info.operators.predicates import is_isometry from qiskit.exceptions import QiskitError -from qiskit.extensions.quantum_initializer.ucg import UCG +from qiskit.extensions.quantum_initializer.uc import UCGate _EPS = 1e-10 # global variable used to chop very small numbers to zero @@ -94,14 +94,14 @@ def _dec_mcg_up_diag(self): circuit = QuantumCircuit(q) (q_target, q_controls, q_ancillas_zero, q_ancillas_dirty) = self._define_qubit_role(q) # ToDo: Keep this threshold updated such that the lowest gate count is achieved: - # ToDo: we implement the MCG with a UCG up to diagonal if the number of controls is + # ToDo: we implement the MCG with a UCGate up to diagonal if the number of controls is # ToDo: smaller than the threshold. threshold = float("inf") if self.num_controls < threshold: - # Implement the MCG as a UCG (up to diagonal) + # Implement the MCG as a UCGate (up to diagonal) gate_list = [np.eye(2, 2) for i in range(2 ** self.num_controls)] gate_list[-1] = self.params[0] - ucg = UCG(gate_list, up_to_diagonal=True) + ucg = UCGate(gate_list, up_to_diagonal=True) circuit.append(ucg, [q_target] + q_controls) diag = ucg._get_diagonal() # else: diff --git a/qiskit/extensions/quantum_initializer/squ.py b/qiskit/extensions/quantum_initializer/squ.py index 820e34192d4e..0cb3cf76c576 100644 --- a/qiskit/extensions/quantum_initializer/squ.py +++ b/qiskit/extensions/quantum_initializer/squ.py @@ -84,22 +84,16 @@ def _dec_single_qubit_unitary(self): circuit = QuantumCircuit(q) # First, we find the rotation angles (where we can ignore the global phase) (a, b, c, _) = self._zyz_dec() - # Add the gates to o the circuit - is_identity = True + # Add the gates to the circuit if abs(a) > _EPS: circuit.rz(a, q[0]) - is_identity = False if abs(b) > _EPS: circuit.ry(b, q[0]) - is_identity = False if abs(c) > _EPS: if self.up_to_diagonal: diag = [np.exp(-1j * c / 2.), np.exp(1j * c / 2.)] else: circuit.rz(c, q[0]) - is_identity = False - if is_identity: - circuit.iden(q[0]) return circuit, diag def _zyz_dec(self): diff --git a/qiskit/extensions/quantum_initializer/uc.py b/qiskit/extensions/quantum_initializer/uc.py new file mode 100644 index 000000000000..f58afc3643be --- /dev/null +++ b/qiskit/extensions/quantum_initializer/uc.py @@ -0,0 +1,348 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +# The structure of the code is based on Emanuel Malvetti's semester thesis at +# ETH in 2018, which was supervised by Raban Iten and Prof. Renato Renner. + +# pylint: disable=invalid-name +# pylint: disable=missing-param-doc +# pylint: disable=missing-type-doc + +""" +Uniformly controlled gates (also called multiplexed gates). + +These gates can have several control qubits and a single target qubit. +If the k control qubits are in the state |i> (in the computational basis), +a single-qubit unitary U_i is applied to the target qubit. + +This gate is represented by a block-diagonal matrix, where each block is a +2x2 unitary: + + [[U_0, 0, ...., 0], + [0, U_1, ...., 0], + . + . + [0, 0, ...., U_(2^k-1)]] +""" + +import cmath +import math + +import numpy as np + +from qiskit.circuit.gate import Gate +from qiskit.extensions.standard.h import HGate +from qiskit.quantum_info.operators.predicates import is_unitary_matrix +from qiskit.circuit.quantumregister import QuantumRegister +from qiskit.circuit.quantumcircuit import QuantumCircuit +from qiskit.exceptions import QiskitError +from qiskit.quantum_info.synthesis import OneQubitEulerDecomposer + +_EPS = 1e-10 # global variable used to chop very small numbers to zero +_DECOMPOSER1Q = OneQubitEulerDecomposer('U3') + + +class UCMeta(type): + """A metaclass to ensure that UCGate and UCG are of the same type. + + Can be removed when UCGG gets removed. + """ + @classmethod + def __instancecheck__(mcs, inst): + return type(inst) in {UCGate, UCG} # pylint: disable=unidiomatic-typecheck + + +class UCGate(Gate, metaclass=UCMeta): + """Uniformly controlled gate (also called multiplexed gate). + The decomposition is based on: https://arxiv.org/pdf/quant-ph/0410066.pdf. + """ + + def __init__(self, gate_list, up_to_diagonal=False): + """UCGate Gate initializer. + + Args: + gate_list (list[ndarray]): list of two qubit unitaries [U_0,...,U_{2^k-1}], + where each single-qubit unitary U_i is given as a 2*2 numpy array. + + up_to_diagonal (bool): determines if the gate is implemented up to a diagonal. + or if it is decomposed completely (default: False). + If the UCGate u is decomposed up to a diagonal d, this means that the circuit + implements a unitary u' such that d.u'=u. + + Raises: + QiskitError: in case of bad input to the constructor + """ + # check input format + if not isinstance(gate_list, list): + raise QiskitError("The single-qubit unitaries are not provided in a list.") + for gate in gate_list: + if not gate.shape == (2, 2): + raise QiskitError("The dimension of a controlled gate is not equal to (2,2).") + if not gate_list: + raise QiskitError("The gate list cannot be empty.") + + # Check if number of gates in gate_list is a positive power of two + num_contr = math.log2(len(gate_list)) + if num_contr < 0 or not num_contr.is_integer(): + raise QiskitError("The number of controlled single-qubit gates is not a " + "non-negative power of 2.") + + # Check if the single-qubit gates are unitaries + for gate in gate_list: + if not is_unitary_matrix(gate, _EPS): + raise QiskitError("A controlled gate is not unitary.") + + # Create new gate. + super().__init__("multiplexer", int(num_contr) + 1, gate_list) + self.up_to_diagonal = up_to_diagonal + + def _get_diagonal(self): + # Important: for a control list q_controls = [q[0],...,q_[k-1]] the + # diagonal gate is provided in the computational basis of the qubits + # q[k-1],...,q[0],q_target, decreasingly ordered with respect to the + # significance of the qubit in the computational basis + _, diag = self._dec_ucg() + return diag + + def _define(self): + ucg_circuit, _ = self._dec_ucg() + self.definition = ucg_circuit.data + + def _dec_ucg(self): + """ + Call to create a circuit that implements the uniformly controlled gate. If + up_to_diagonal=True, the circuit implements the gate up to a diagonal gate and + the diagonal gate is also returned. + """ + diag = np.ones(2 ** self.num_qubits).tolist() + q = QuantumRegister(self.num_qubits) + q_controls = q[1:] + q_target = q[0] + circuit = QuantumCircuit(q) + # If there is no control, we use the ZYZ decomposition + if not q_controls: + theta, phi, lamb = _DECOMPOSER1Q.angles(self.params[0]) + circuit.u3(theta, phi, lamb, q) + return circuit, diag + # If there is at least one control, first, + # we find the single qubit gates of the decomposition. + (single_qubit_gates, diag) = self._dec_ucg_help() + # Now, it is easy to place the C-NOT gates and some Hadamards and Rz(pi/2) gates + # (which are absorbed into the single-qubit unitaries) to get back the full decomposition. + for i, gate in enumerate(single_qubit_gates): + # Absorb Hadamards and Rz(pi/2) gates + if i == 0: + squ = HGate().to_matrix().dot(gate) + elif i == len(single_qubit_gates) - 1: + squ = gate.dot(UCGate._rz(np.pi / 2)).dot(HGate().to_matrix()) + else: + squ = HGate().to_matrix().dot(gate.dot(UCGate._rz(np.pi / 2))).dot( + HGate().to_matrix()) + # Add single-qubit gate + circuit.squ(squ, q_target) + # The number of the control qubit is given by the number of zeros at the end + # of the binary representation of (i+1) + binary_rep = np.binary_repr(i + 1) + num_trailing_zeros = len(binary_rep) - len(binary_rep.rstrip('0')) + q_contr_index = num_trailing_zeros + # Add C-NOT gate + if not i == len(single_qubit_gates) - 1: + circuit.cx(q_controls[q_contr_index], q_target) + if not self.up_to_diagonal: + # Important: the diagonal gate is given in the computational basis of the qubits + # q[k-1],...,q[0],q_target (ordered with decreasing significance), + # where q[i] are the control qubits and t denotes the target qubit. + circuit.diagonal(diag.tolist(), q) + return circuit, diag + + def _dec_ucg_help(self): + """ + This method finds the single qubit gate arising in the decomposition of UCGates given in + https://arxiv.org/pdf/quant-ph/0410066.pdf. + """ + single_qubit_gates = [gate.astype(complex) for gate in self.params] + diag = np.ones(2 ** self.num_qubits, dtype=complex) + num_contr = self.num_qubits - 1 + for dec_step in range(num_contr): + num_ucgs = 2 ** dec_step + # The decomposition works recursively and the following loop goes over the different + # UCGates that arise in the decomposition + for ucg_index in range(num_ucgs): + len_ucg = 2 ** (num_contr - dec_step) + for i in range(int(len_ucg / 2)): + shift = ucg_index * len_ucg + a = single_qubit_gates[shift + i] + b = single_qubit_gates[shift + len_ucg // 2 + i] + # Apply the decomposition for UCGates given in equation (3) in + # https://arxiv.org/pdf/quant-ph/0410066.pdf + # to demultiplex one control of all the num_ucgs uniformly-controlled gates + # with log2(len_ucg) uniform controls + v, u, r = self._demultiplex_single_uc(a, b) + # replace the single-qubit gates with v,u (the already existing ones + # are not needed any more) + single_qubit_gates[shift + i] = v + single_qubit_gates[shift + len_ucg // 2 + i] = u + # Now we decompose the gates D as described in Figure 4 in + # https://arxiv.org/pdf/quant-ph/0410066.pdf and merge some of the gates + # into the UCGates and the diagonal at the end of the circuit + + # Remark: The Rz(pi/2) rotation acting on the target qubit and the Hadamard + # gates arising in the decomposition of D are ignored for the moment (they will + # be added together with the C-NOT gates at the end of the decomposition + # (in the method dec_ucg())) + if ucg_index < num_ucgs - 1: + # Absorb the Rz(pi/2) rotation on the control into the UC-Rz gate and + # merge the UC-Rz rotation with the following UCGate, + # which hasn't been decomposed yet. + k = shift + len_ucg + i + single_qubit_gates[k] = \ + single_qubit_gates[k].dot(UCGate._ct(r)) * \ + UCGate._rz(np.pi / 2).item((0, 0)) + k = k + len_ucg // 2 + single_qubit_gates[k] = \ + single_qubit_gates[k].dot(r) * UCGate._rz(np.pi / 2).item((1, 1)) + else: + # Absorb the Rz(pi/2) rotation on the control into the UC-Rz gate and merge + # the trailing UC-Rz rotation into a diagonal gate at the end of the circuit + for ucg_index_2 in range(num_ucgs): + shift_2 = ucg_index_2 * len_ucg + k = 2 * (i + shift_2) + diag[k] = diag[k] * UCGate._ct(r).item((0, 0)) * \ + UCGate._rz(np.pi / 2).item((0, 0)) + diag[k + 1] = diag[k + 1] * UCGate._ct(r).item((1, 1)) * UCGate._rz( + np.pi / 2).item((0, 0)) + k = len_ucg + k + diag[k] *= r.item((0, 0)) * UCGate._rz(np.pi / 2).item((1, 1)) + diag[k + 1] *= r.item((1, 1)) * UCGate._rz(np.pi / 2).item((1, 1)) + return single_qubit_gates, diag + + def _demultiplex_single_uc(self, a, b): + """ + This method implements the decomposition given in equation (3) in + https://arxiv.org/pdf/quant-ph/0410066.pdf. + The decomposition is used recursively to decompose uniformly controlled gates. + a,b = single qubit unitaries + v,u,r = outcome of the decomposition given in the reference mentioned above + (see there for the details). + """ + # The notation is chosen as in https://arxiv.org/pdf/quant-ph/0410066.pdf. + x = a.dot(UCGate._ct(b)) + det_x = np.linalg.det(x) + x11 = x.item((0, 0)) / cmath.sqrt(det_x) + phi = cmath.phase(det_x) + r1 = cmath.exp(1j / 2 * (np.pi / 2 - phi / 2 - cmath.phase(x11))) + r2 = cmath.exp(1j / 2 * (np.pi / 2 - phi / 2 + cmath.phase(x11) + np.pi)) + r = np.array([[r1, 0], [0, r2]], dtype=complex) + d, u = np.linalg.eig(r.dot(x).dot(r)) + # If d is not equal to diag(i,-i), then we put it into this "standard" form + # (see eq. (13) in https://arxiv.org/pdf/quant-ph/0410066.pdf) by interchanging + # the eigenvalues and eigenvectors. + if abs(d[0] + 1j) < _EPS: + d = np.flip(d, 0) + u = np.flip(u, 1) + d = np.diag(np.sqrt(d)) + v = d.dot(UCGate._ct(u)).dot(UCGate._ct(r)).dot(b) + return v, u, r + + @staticmethod + def _ct(m): + return np.transpose(np.conjugate(m)) + + @staticmethod + def _rz(alpha): + return np.array([[np.exp(1j * alpha / 2), 0], [0, np.exp(-1j * alpha / 2)]]) + + +def uc(self, gate_list, q_controls, q_target, up_to_diagonal=False): + """Attach a uniformly controlled gates (also called multiplexed gates) to a circuit. + + The decomposition was introduced by Bergholm et al. in + https://arxiv.org/pdf/quant-ph/0410066.pdf. + + Args: + gate_list (list[ndarray]): list of two qubit unitaries [U_0,...,U_{2^k-1}], + where each single-qubit unitary U_i is a given as a 2*2 array + q_controls (QuantumRegister|list[(QuantumRegister,int)]): list of k control qubits. + The qubits are ordered according to their significance in the computational basis. + For example if q_controls=[q[1],q[2]] (with q = QuantumRegister(2)), + the unitary U_0 is performed if q[1] and q[2] are in the state zero, U_1 is + performed if q[2] is in the state zero and q[1] is in the state one, and so on + q_target (QuantumRegister|(QuantumRegister,int)): target qubit, where we act on with + the single-qubit gates. + up_to_diagonal (bool): If set to True, the uniformly controlled gate is decomposed up + to a diagonal gate, i.e. a unitary u' is implemented such that there exists a + diagonal gate d with u = d.dot(u'), where the unitary u describes the uniformly + controlled gate + + Returns: + QuantumCircuit: the uniformly controlled gate is attached to the circuit. + + Raises: + QiskitError: if the list number of control qubits does not correspond to the provided + number of single-qubit unitaries; if an input is of the wrong type + """ + + if isinstance(q_controls, QuantumRegister): + q_controls = q_controls[:] + if isinstance(q_target, QuantumRegister): + q_target = q_target[:] + if len(q_target) == 1: + q_target = q_target[0] + else: + raise QiskitError("The target qubit is a QuantumRegister containing more than" + " one qubit.") + # Check if q_controls has type "list" + if not isinstance(q_controls, list): + raise QiskitError("The control qubits must be provided as a list" + " (also if there is only one control qubit).") + # Check if gate_list has type "list" + if not isinstance(gate_list, list): + raise QiskitError("The single-qubit unitaries are not provided in a list.") + # Check if number of gates in gate_list is a positive power of two + num_contr = math.log2(len(gate_list)) + if num_contr < 0 or not num_contr.is_integer(): + raise QiskitError("The number of controlled single-qubit gates is not a non negative" + " power of 2.") + # Check if number of control qubits does correspond to the number of single-qubit rotations + if num_contr != len(q_controls): + raise QiskitError("Number of controlled gates does not correspond to the number of" + " control qubits.") + return self.append(UCGate(gate_list, up_to_diagonal), [q_target] + q_controls) + + +class UCG(UCGate, metaclass=UCMeta): + """The deprecated UCGate class.""" + + def __init__(self, gate_list, up_to_diagonal=False): + import warnings + warnings.warn('The class UCG is deprecated as of 0.14.0, and ' + 'will be removed no earlier than 3 months after that release date. ' + 'You should use the class UCGate instead.', + DeprecationWarning, stacklevel=2) + super().__init__(gate_list, up_to_diagonal) + + +def ucg(self, angle_list, q_controls, q_target, up_to_diagonal=False): + """Deprecated version of uc.""" + + import warnings + warnings.warn('The QuantumCircuit.ucg() method is deprecated as of 0.14.0, and ' + 'will be removed no earlier than 3 months after that release date. ' + 'You should use the QuantumCircuit.uc() method instead.', + DeprecationWarning, stacklevel=2) + return uc(self, angle_list, q_controls, q_target, up_to_diagonal) + + +QuantumCircuit.uc = uc +QuantumCircuit.ucg = ucg # deprecated, but still supported diff --git a/qiskit/extensions/quantum_initializer/uc_pauli_rot.py b/qiskit/extensions/quantum_initializer/uc_pauli_rot.py new file mode 100644 index 000000000000..d2487f84e0bb --- /dev/null +++ b/qiskit/extensions/quantum_initializer/uc_pauli_rot.py @@ -0,0 +1,188 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +# The structure of the code is based on Emanuel Malvetti's semester thesis at ETH in 2018, +# which was supervised by Raban Iten and Prof. Renato Renner. + +""" +(Abstract) base class for uniformly controlled (also called multiplexed) single-qubit rotations R_t. +This class provides a basis for the decomposition of uniformly controlled R_x,R_y and R_z gates +(i.e., for t=x,y,z). These gates can have several control qubits and a single target qubit. +If the k control qubits are in the state ket(i) (in the computational bases), +a single-qubit rotation R_t(a_i) is applied to the target qubit for a (real) angle a_i. +""" + +import math + +import numpy as np + +from qiskit.circuit import Gate, QuantumCircuit +from qiskit.circuit.quantumcircuit import QuantumRegister +from qiskit.exceptions import QiskitError + +_EPS = 1e-10 # global variable used to chop very small numbers to zero + + +class UCPauliRotMeta(type): + """A metaclass to ensure that UCPauliRotGate and UCRot are of the same type. + + Can be removed when UCRot gets removed. + """ + @classmethod + def __instancecheck__(mcs, inst): + return type(inst) in {UCPauliRotGate, UCRot} # pylint: disable=unidiomatic-typecheck + + +class UCPauliRotGate(Gate, metaclass=UCPauliRotMeta): + """ + Uniformly controlled rotations (also called multiplexed rotations). + The decomposition is based on 'Synthesis of Quantum Logic Circuits' + by Shende et al. (https://arxiv.org/pdf/quant-ph/0406176.pdf) + + Input: + angle_list = list of (real) rotation angles [a_0,...,a_{2^k-1}]. Must have at least one entry. + + rot_axis = rotation axis for the single qubit rotations + (currently, 'X', 'Y' and 'Z' are supported) + """ + + def __init__(self, angle_list, rot_axis): + self.rot_axes = rot_axis + # Check if angle_list has type "list" + if not isinstance(angle_list, list): + raise QiskitError('The angles are not provided in a list.') + # Check if the angles in angle_list are real numbers + for angle in angle_list: + try: + float(angle) + except TypeError: + raise QiskitError( + 'An angle cannot be converted to type float (real angles are expected).') + num_contr = math.log2(len(angle_list)) + if num_contr < 0 or not num_contr.is_integer(): + raise QiskitError( + 'The number of controlled rotation gates is not a non-negative power of 2.') + if rot_axis not in ('X', 'Y', 'Z'): + raise QiskitError('Rotation axis is not supported.') + # Create new gate. + num_qubits = int(num_contr) + 1 + super().__init__('ucr' + rot_axis.lower(), num_qubits, angle_list) + + def _define(self): + ucr_circuit = self._dec_ucrot() + gate = ucr_circuit.to_instruction() + q = QuantumRegister(self.num_qubits) + ucr_circuit = QuantumCircuit(q) + ucr_circuit.append(gate, q[:]) + self.definition = ucr_circuit.data + + def _dec_ucrot(self): + """ + Finds a decomposition of a UC rotation gate into elementary gates + (C-NOTs and single-qubit rotations). + """ + q = QuantumRegister(self.num_qubits) + circuit = QuantumCircuit(q) + q_target = q[0] + q_controls = q[1:] + if not q_controls: # equivalent to: if len(q_controls) == 0 + if self.rot_axes == 'X': + if np.abs(self.params[0]) > _EPS: + circuit.rx(self.params[0], q_target) + if self.rot_axes == 'Y': + if np.abs(self.params[0]) > _EPS: + circuit.ry(self.params[0], q_target) + if self.rot_axes == 'Z': + if np.abs(self.params[0]) > _EPS: + circuit.rz(self.params[0], q_target) + else: + # First, we find the rotation angles of the single-qubit rotations acting + # on the target qubit + angles = self.params.copy() + UCPauliRotGate._dec_uc_rotations(angles, 0, len(angles), False) + # Now, it is easy to place the C-NOT gates to get back the full decomposition. + for (i, angle) in enumerate(angles): + if self.rot_axes == 'X': + if np.abs(angle) > _EPS: + circuit.rx(angle, q_target) + if self.rot_axes == 'Y': + if np.abs(angle) > _EPS: + circuit.ry(angle, q_target) + if self.rot_axes == 'Z': + if np.abs(angle) > _EPS: + circuit.rz(angle, q_target) + # Determine the index of the qubit we want to control the C-NOT gate. + # Note that it corresponds + # to the number of trailing zeros in the binary representation of i+1 + if not i == len(angles) - 1: + binary_rep = np.binary_repr(i + 1) + q_contr_index = len(binary_rep) - \ + len(binary_rep.rstrip('0')) + else: + # Handle special case: + q_contr_index = len(q_controls) - 1 + # For X rotations, we have to additionally place some Ry gates around the + # C-NOT gates. They change the basis of the NOT operation, such that the + # decomposition of for uniformly controlled X rotations works correctly by symmetry + # with the decomposition of uniformly controlled Z or Y rotations + if self.rot_axes == 'X': + circuit.ry(np.pi / 2, q_target) + circuit.cx(q_controls[q_contr_index], q_target) + if self.rot_axes == 'X': + circuit.ry(-np.pi / 2, q_target) + return circuit + + @staticmethod + def _dec_uc_rotations(angles, start_index, end_index, reversed_dec): + """ + Calculates rotation angles for a uniformly controlled R_t gate with a C-NOT gate at + the end of the circuit. The rotation angles of the gate R_t are stored in + angles[start_index:end_index]. If reversed_dec == True, it decomposes the gate such that + there is a C-NOT gate at the start of the circuit (in fact, the circuit topology for + the reversed decomposition is the reversed one of the original decomposition) + """ + interval_len_half = (end_index - start_index) // 2 + for i in range(start_index, start_index + interval_len_half): + if not reversed_dec: + angles[i], angles[i + interval_len_half] = \ + UCPauliRotGate._update_angles( + angles[i], angles[i + interval_len_half]) + else: + angles[i + interval_len_half], angles[i] = \ + UCPauliRotGate._update_angles( + angles[i], angles[i + interval_len_half]) + if interval_len_half <= 1: + return + else: + UCPauliRotGate._dec_uc_rotations(angles, start_index, start_index + interval_len_half, + False) + UCPauliRotGate._dec_uc_rotations(angles, start_index + interval_len_half, end_index, + True) + + @staticmethod + def _update_angles(angle1, angle2): + """Calculate the new rotation angles according to Shende's decomposition.""" + return (angle1 + angle2) / 2.0, (angle1 - angle2) / 2.0 + + +class UCRot(UCPauliRotGate, metaclass=UCPauliRotMeta): + """The deprecated DiagonalGate class.""" + + def __init__(self, angle_list, rot_axis): + import warnings + warnings.warn('The class UCRot is deprecated as of 0.14.0, and ' + 'will be removed no earlier than 3 months after that release date. ' + 'You should use the class UCPauliRotGate instead.', + DeprecationWarning, stacklevel=2) + super().__init__(angle_list, rot_axis) diff --git a/qiskit/extensions/quantum_initializer/ucg.py b/qiskit/extensions/quantum_initializer/ucg.py index 80f0737aa227..980d54013b5e 100644 --- a/qiskit/extensions/quantum_initializer/ucg.py +++ b/qiskit/extensions/quantum_initializer/ucg.py @@ -2,7 +2,7 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2019. +# (C) Copyright IBM 2020. # # This code is licensed under the Apache License, Version 2.0. You may # obtain a copy of this license in the LICENSE.txt file in the root directory @@ -12,300 +12,14 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -# The structure of the code is based on Emanuel Malvetti's semester thesis at -# ETH in 2018, which was supervised by Raban Iten and Prof. Renato Renner. +"""The uniformly controlled gate. -# pylint: disable=invalid-name -# pylint: disable=missing-param-doc -# pylint: disable=missing-type-doc - -""" -Uniformly controlled gates (also called multiplexed gates). - -These gates can have several control qubits and a single target qubit. -If the k control qubits are in the state |i> (in the computational basis), -a single-qubit unitary U_i is applied to the target qubit. - -This gate is represented by a block-diagonal matrix, where each block is a -2x2 unitary: - - [[U_0, 0, ...., 0], - [0, U_1, ...., 0], - . - . - [0, 0, ...., U_(2^k-1)]] +This module is deprecated, see uc.py. """ -import cmath -import math - -import numpy as np - -from qiskit.circuit.gate import Gate -from qiskit.extensions.standard.h import HGate -from qiskit.quantum_info.operators.predicates import is_unitary_matrix -from qiskit.circuit.quantumregister import QuantumRegister -from qiskit.circuit.quantumcircuit import QuantumCircuit -from qiskit.exceptions import QiskitError -from qiskit.quantum_info.synthesis import euler_angles_1q - -_EPS = 1e-10 # global variable used to chop very small numbers to zero - - -class UCG(Gate): - """Uniformly controlled gate (also called multiplexed gate). - The decomposition is based on: https://arxiv.org/pdf/quant-ph/0410066.pdf. - """ - - def __init__(self, gate_list, up_to_diagonal=False): - """UCG Gate initializer. - - Args: - gate_list (list[ndarray]): list of two qubit unitaries [U_0,...,U_{2^k-1}], - where each single-qubit unitary U_i is given as a 2*2 numpy array. - - up_to_diagonal (bool): determines if the gate is implemented up to a diagonal. - or if it is decomposed completely (default: False). - If the UCG u is decomposed up to a diagonal d, this means that the circuit - implements a unitary u' such that d.u'=u. - - Raises: - QiskitError: in case of bad input to the constructor - """ - # check input format - if not isinstance(gate_list, list): - raise QiskitError("The single-qubit unitaries are not provided in a list.") - for gate in gate_list: - if not gate.shape == (2, 2): - raise QiskitError("The dimension of a controlled gate is not equal to (2,2).") - if not gate_list: - raise QiskitError("The gate list cannot be empty.") - - # Check if number of gates in gate_list is a positive power of two - num_contr = math.log2(len(gate_list)) - if num_contr < 0 or not num_contr.is_integer(): - raise QiskitError("The number of controlled single-qubit gates is not a " - "non-negative power of 2.") - - # Check if the single-qubit gates are unitaries - for gate in gate_list: - if not is_unitary_matrix(gate, _EPS): - raise QiskitError("A controlled gate is not unitary.") - - # Create new gate. - super().__init__("multiplexer", int(num_contr) + 1, gate_list) - self.up_to_diagonal = up_to_diagonal - - def _get_diagonal(self): - # Important: for a control list q_controls = [q[0],...,q_[k-1]] the - # diagonal gate is provided in the computational basis of the qubits - # q[k-1],...,q[0],q_target, decreasingly ordered with respect to the - # significance of the qubit in the computational basis - _, diag = self._dec_ucg() - return diag - - def _define(self): - ucg_circuit, _ = self._dec_ucg() - self.definition = ucg_circuit.data - - def _dec_ucg(self): - """ - Call to create a circuit that implements the uniformly controlled gate. If - up_to_diagonal=True, the circuit implements the gate up to a diagonal gate and - the diagonal gate is also returned. - """ - diag = np.ones(2 ** self.num_qubits).tolist() - q = QuantumRegister(self.num_qubits) - q_controls = q[1:] - q_target = q[0] - circuit = QuantumCircuit(q) - # If there is no control, we use the ZYZ decomposition - if not q_controls: - theta, phi, lamb = euler_angles_1q(self.params[0]) - circuit.u3(theta, phi, lamb, q) - return circuit, diag - # If there is at least one control, first, - # we find the single qubit gates of the decomposition. - (single_qubit_gates, diag) = self._dec_ucg_help() - # Now, it is easy to place the C-NOT gates and some Hadamards and Rz(pi/2) gates - # (which are absorbed into the single-qubit unitaries) to get back the full decomposition. - for i, gate in enumerate(single_qubit_gates): - # Absorb Hadamards and Rz(pi/2) gates - if i == 0: - squ = HGate().to_matrix().dot(gate) - elif i == len(single_qubit_gates) - 1: - squ = gate.dot(UCG._rz(np.pi / 2)).dot(HGate().to_matrix()) - else: - squ = HGate().to_matrix().dot(gate.dot(UCG._rz(np.pi / 2))).dot(HGate().to_matrix()) - # Add single-qubit gate - circuit.squ(squ, q_target) - # The number of the control qubit is given by the number of zeros at the end - # of the binary representation of (i+1) - binary_rep = np.binary_repr(i + 1) - num_trailing_zeros = len(binary_rep) - len(binary_rep.rstrip('0')) - q_contr_index = num_trailing_zeros - # Add C-NOT gate - if not i == len(single_qubit_gates) - 1: - circuit.cx(q_controls[q_contr_index], q_target) - if not self.up_to_diagonal: - # Important: the diagonal gate is given in the computational basis of the qubits - # q[k-1],...,q[0],q_target (ordered with decreasing significance), - # where q[i] are the control qubits and t denotes the target qubit. - circuit.diag_gate(diag.tolist(), q) - return circuit, diag - - def _dec_ucg_help(self): - """ - This method finds the single qubit gate arising in the decomposition of UCGs given in - https://arxiv.org/pdf/quant-ph/0410066.pdf. - """ - single_qubit_gates = [gate.astype(complex) for gate in self.params] - diag = np.ones(2 ** self.num_qubits, dtype=complex) - num_contr = self.num_qubits - 1 - for dec_step in range(num_contr): - num_ucgs = 2 ** dec_step - # The decomposition works recursively and the following loop goes over the different - # UCGs that arise in the decomposition - for ucg_index in range(num_ucgs): - len_ucg = 2 ** (num_contr - dec_step) - for i in range(int(len_ucg / 2)): - shift = ucg_index * len_ucg - a = single_qubit_gates[shift + i] - b = single_qubit_gates[shift + len_ucg // 2 + i] - # Apply the decomposition for UCGs given in equation (3) in - # https://arxiv.org/pdf/quant-ph/0410066.pdf - # to demultiplex one control of all the num_ucgs uniformly-controlled gates - # with log2(len_ucg) uniform controls - v, u, r = self._demultiplex_single_uc(a, b) - # replace the single-qubit gates with v,u (the already existing ones - # are not needed any more) - single_qubit_gates[shift + i] = v - single_qubit_gates[shift + len_ucg // 2 + i] = u - # Now we decompose the gates D as described in Figure 4 in - # https://arxiv.org/pdf/quant-ph/0410066.pdf and merge some of the gates - # into the UCGs and the diagonal at the end of the circuit - - # Remark: The Rz(pi/2) rotation acting on the target qubit and the Hadamard - # gates arising in the decomposition of D are ignored for the moment (they will - # be added together with the C-NOT gates at the end of the decomposition - # (in the method dec_ucg())) - if ucg_index < num_ucgs - 1: - # Absorb the Rz(pi/2) rotation on the control into the UC-Rz gate and - # merge the UC-Rz rotation with the following UCG, - # which hasn't been decomposed yet. - k = shift + len_ucg + i - single_qubit_gates[k] = \ - single_qubit_gates[k].dot(UCG._ct(r)) * UCG._rz(np.pi / 2).item((0, 0)) - k = k + len_ucg // 2 - single_qubit_gates[k] = \ - single_qubit_gates[k].dot(r) * UCG._rz(np.pi / 2).item((1, 1)) - else: - # Absorb the Rz(pi/2) rotation on the control into the UC-Rz gate and merge - # the trailing UC-Rz rotation into a diagonal gate at the end of the circuit - for ucg_index_2 in range(num_ucgs): - shift_2 = ucg_index_2 * len_ucg - k = 2 * (i + shift_2) - diag[k] = diag[k] * UCG._ct(r).item((0, 0)) * UCG._rz(np.pi / 2).item( - (0, 0)) - diag[k + 1] = diag[k + 1] * UCG._ct(r).item((1, 1)) * UCG._rz( - np.pi / 2).item((0, 0)) - k = len_ucg + k - diag[k] *= r.item((0, 0)) * UCG._rz(np.pi / 2).item((1, 1)) - diag[k + 1] *= r.item((1, 1)) * UCG._rz(np.pi / 2).item((1, 1)) - return single_qubit_gates, diag - - def _demultiplex_single_uc(self, a, b): - """ - This method implements the decomposition given in equation (3) in - https://arxiv.org/pdf/quant-ph/0410066.pdf. - The decomposition is used recursively to decompose uniformly controlled gates. - a,b = single qubit unitaries - v,u,r = outcome of the decomposition given in the reference mentioned above - (see there for the details). - """ - # The notation is chosen as in https://arxiv.org/pdf/quant-ph/0410066.pdf. - x = a.dot(UCG._ct(b)) - det_x = np.linalg.det(x) - x11 = x.item((0, 0)) / cmath.sqrt(det_x) - phi = cmath.phase(det_x) - r1 = cmath.exp(1j / 2 * (np.pi / 2 - phi / 2 - cmath.phase(x11))) - r2 = cmath.exp(1j / 2 * (np.pi / 2 - phi / 2 + cmath.phase(x11) + np.pi)) - r = np.array([[r1, 0], [0, r2]], dtype=complex) - d, u = np.linalg.eig(r.dot(x).dot(r)) - # If d is not equal to diag(i,-i), then we put it into this "standard" form - # (see eq. (13) in https://arxiv.org/pdf/quant-ph/0410066.pdf) by interchanging - # the eigenvalues and eigenvectors. - if abs(d[0] + 1j) < _EPS: - d = np.flip(d, 0) - u = np.flip(u, 1) - d = np.diag(np.sqrt(d)) - v = d.dot(UCG._ct(u)).dot(UCG._ct(r)).dot(b) - return v, u, r - - @staticmethod - def _ct(m): - return np.transpose(np.conjugate(m)) - - @staticmethod - def _rz(alpha): - return np.array([[np.exp(1j * alpha / 2), 0], [0, np.exp(-1j * alpha / 2)]]) - - -def ucg(self, gate_list, q_controls, q_target, up_to_diagonal=False): - """Attach a uniformly controlled gates (also called multiplexed gates) to a circuit. - - The decomposition was introduced by Bergholm et al. in - https://arxiv.org/pdf/quant-ph/0410066.pdf. - - Args: - gate_list (list[ndarray]): list of two qubit unitaries [U_0,...,U_{2^k-1}], - where each single-qubit unitary U_i is a given as a 2*2 array - q_controls (QuantumRegister|list[(QuantumRegister,int)]): list of k control qubits. - The qubits are ordered according to their significance in the computational basis. - For example if q_controls=[q[1],q[2]] (with q = QuantumRegister(2)), - the unitary U_0 is performed if q[1] and q[2] are in the state zero, U_1 is - performed if q[2] is in the state zero and q[1] is in the state one, and so on - q_target (QuantumRegister|(QuantumRegister,int)): target qubit, where we act on with - the single-qubit gates. - up_to_diagonal (bool): If set to True, the uniformly controlled gate is decomposed up - to a diagonal gate, i.e. a unitary u' is implemented such that there exists a - diagonal gate d with u = d.dot(u'), where the unitary u describes the uniformly - controlled gate - - Returns: - QuantumCircuit: the uniformly controlled gate is attached to the circuit. - - Raises: - QiskitError: if the list number of control qubits does not correspond to the provided - number of single-qubit unitaries; if an input is of the wrong type - """ - - if isinstance(q_controls, QuantumRegister): - q_controls = q_controls[:] - if isinstance(q_target, QuantumRegister): - q_target = q_target[:] - if len(q_target) == 1: - q_target = q_target[0] - else: - raise QiskitError("The target qubit is a QuantumRegister containing more than" - " one qubit.") - # Check if q_controls has type "list" - if not isinstance(q_controls, list): - raise QiskitError("The control qubits must be provided as a list" - " (also if there is only one control qubit).") - # Check if gate_list has type "list" - if not isinstance(gate_list, list): - raise QiskitError("The single-qubit unitaries are not provided in a list.") - # Check if number of gates in gate_list is a positive power of two - num_contr = math.log2(len(gate_list)) - if num_contr < 0 or not num_contr.is_integer(): - raise QiskitError("The number of controlled single-qubit gates is not a non negative" - " power of 2.") - # Check if number of control qubits does correspond to the number of single-qubit rotations - if num_contr != len(q_controls): - raise QiskitError("Number of controlled gates does not correspond to the number of" - " control qubits.") - return self.append(UCG(gate_list, up_to_diagonal), [q_target] + q_controls) - +import warnings +# pylint: disable=unused-import +from qiskit.extensions.quantum_initializer.uc import UCG, ucg -QuantumCircuit.ucg = ucg +warnings.warn('This module is deprecated. The UCGate/UCG can now be found in uc.py', + category=DeprecationWarning, stacklevel=2) diff --git a/qiskit/extensions/quantum_initializer/ucrot.py b/qiskit/extensions/quantum_initializer/ucrot.py index 81cff4c4bfe9..514a86f7d471 100644 --- a/qiskit/extensions/quantum_initializer/ucrot.py +++ b/qiskit/extensions/quantum_initializer/ucrot.py @@ -2,7 +2,7 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2019. +# (C) Copyright IBM 2020. # # This code is licensed under the Apache License, Version 2.0. You may # obtain a copy of this license in the LICENSE.txt file in the root directory @@ -12,157 +12,14 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -# The structure of the code is based on Emanuel Malvetti's semester thesis at ETH in 2018, -# which was supervised by Raban Iten and Prof. Renato Renner. +"""The uniformly controlled Pauli rotation gate. +This module is deprecated, see uc_pauli_rot.py """ -(Abstract) base class for uniformly controlled (also called multiplexed) single-qubit rotations R_t. -This class provides a basis for the decomposition of uniformly controlled R_x,R_y and R_z gates -(i.e., for t=x,y,z). These gates can have several control qubits and a single target qubit. -If the k control qubits are in the state ket(i) (in the computational bases), -a single-qubit rotation R_t(a_i) is applied to the target qubit for a (real) angle a_i. -""" - -import math - -import numpy as np - -from qiskit.circuit import Gate, QuantumCircuit -from qiskit.circuit.quantumcircuit import QuantumRegister -from qiskit.exceptions import QiskitError - -_EPS = 1e-10 # global variable used to chop very small numbers to zero - - -class UCRot(Gate): - """ - Uniformly controlled rotations (also called multiplexed rotations). - The decomposition is based on 'Synthesis of Quantum Logic Circuits' - by Shende et al. (https://arxiv.org/pdf/quant-ph/0406176.pdf) - - Input: - angle_list = list of (real) rotation angles [a_0,...,a_{2^k-1}]. Must have at least one entry. - - rot_axis = rotation axis for the single qubit rotations - (currently, "X","Y" and "Z" are supported) - """ - - def __init__(self, angle_list, rot_axis): - self.rot_axes = rot_axis - # Check if angle_list has type "list" - if not isinstance(angle_list, list): - raise QiskitError("The angles are not provided in a list.") - # Check if the angles in angle_list are real numbers - for angle in angle_list: - try: - float(angle) - except TypeError: - raise QiskitError( - "An angle cannot be converted to type float (real angles are expected).") - num_contr = math.log2(len(angle_list)) - if num_contr < 0 or not num_contr.is_integer(): - raise QiskitError( - "The number of controlled rotation gates is not a non-negative power of 2.") - if rot_axis not in ("X", "Y", "Z"): - raise QiskitError("Rotation axis is not supported.") - # Create new gate. - num_qubits = int(num_contr) + 1 - super().__init__("ucrot" + rot_axis, num_qubits, angle_list) - - def _define(self): - ucr_circuit = self._dec_ucrot() - gate_num = len(ucr_circuit.data) - gate = ucr_circuit.to_instruction() - q = QuantumRegister(self.num_qubits) - ucr_circuit = QuantumCircuit(q) - if gate_num == 0: - # ToDo: if we would not add the identity here, this would lead to troubles - # ToDo: simulating the circuit afterwards. - # this should probably be fixed in the behaviour of QuantumCircuit. - ucr_circuit.iden(q[0]) - else: - ucr_circuit.append(gate, q[:]) - self.definition = ucr_circuit.data - - def _dec_ucrot(self): - """ - finds a decomposition of a UC rotation gate into elementary gates - (C-NOTs and single-qubit rotations). - """ - q = QuantumRegister(self.num_qubits) - circuit = QuantumCircuit(q) - q_target = q[0] - q_controls = q[1:] - if not q_controls: # equivalent to: if len(q_controls) == 0 - if self.rot_axes == "X": - if np.abs(self.params[0]) > _EPS: - circuit.rx(self.params[0], q_target) - if self.rot_axes == "Y": - if np.abs(self.params[0]) > _EPS: - circuit.ry(self.params[0], q_target) - if self.rot_axes == "Z": - if np.abs(self.params[0]) > _EPS: - circuit.rz(self.params[0], q_target) - else: - # First, we find the rotation angles of the single-qubit rotations acting - # on the target qubit - angles = self.params.copy() - UCRot._dec_uc_rotations(angles, 0, len(angles), False) - # Now, it is easy to place the C-NOT gates to get back the full decomposition.s - for (i, angle) in enumerate(angles): - if self.rot_axes == "X": - if np.abs(angle) > _EPS: - circuit.rx(angle, q_target) - if self.rot_axes == "Y": - if np.abs(angle) > _EPS: - circuit.ry(angle, q_target) - if self.rot_axes == "Z": - if np.abs(angle) > _EPS: - circuit.rz(angle, q_target) - # Determine the index of the qubit we want to control the C-NOT gate. - # Note that it corresponds - # to the number of trailing zeros in the binary representation of i+1 - if not i == len(angles) - 1: - binary_rep = np.binary_repr(i + 1) - q_contr_index = len(binary_rep) - len(binary_rep.rstrip('0')) - else: - # Handle special case: - q_contr_index = len(q_controls) - 1 - # For X rotations, we have to additionally place some Ry gates around the - # C-NOT gates. They change the basis of the NOT operation, such that the - # decomposition of for uniformly controlled X rotations works correctly by symmetry - # with the decomposition of uniformly controlled Z or Y rotations - if self.rot_axes == "X": - circuit.ry(np.pi / 2, q_target) - circuit.cx(q_controls[q_contr_index], q_target) - if self.rot_axes == "X": - circuit.ry(-np.pi / 2, q_target) - return circuit - @staticmethod - def _dec_uc_rotations(angles, start_index, end_index, reversedDec): - """ - Calculates rotation angles for a uniformly controlled R_t gate with a C-NOT gate at - the end of the circuit. The rotation angles of the gate R_t are stored in - angles[start_index:end_index]. If reversed == True, it decomposes the gate such that - there is a C-NOT gate at the start of the circuit (in fact, the circuit topology for - the reversed decomposition is the reversed one of the original decomposition) - """ - interval_len_half = (end_index - start_index) // 2 - for i in range(start_index, start_index + interval_len_half): - if not reversedDec: - angles[i], angles[i + interval_len_half] = UCRot._update_angles(angles[i], angles[ - i + interval_len_half]) - else: - angles[i + interval_len_half], angles[i] = UCRot._update_angles(angles[i], angles[ - i + interval_len_half]) - if interval_len_half <= 1: - return - else: - UCRot._dec_uc_rotations(angles, start_index, start_index + interval_len_half, False) - UCRot._dec_uc_rotations(angles, start_index + interval_len_half, end_index, True) +import warnings +# pylint: disable=unused-import +from qiskit.extensions.quantum_initializer.uc_pauli_rot import UCRot - @staticmethod - def _update_angles(angle1, angle2): - """Calculate the new rotation angles according to Shende's decomposition""" - return (angle1 + angle2) / 2.0, (angle1 - angle2) / 2.0 +warnings.warn('This module is deprecated. The UCPauliRotGate/UCRot is now in uc_pauli_rot.py', + category=DeprecationWarning, stacklevel=2) diff --git a/qiskit/extensions/quantum_initializer/ucrx.py b/qiskit/extensions/quantum_initializer/ucrx.py new file mode 100644 index 000000000000..f6893db4d2fe --- /dev/null +++ b/qiskit/extensions/quantum_initializer/ucrx.py @@ -0,0 +1,128 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +# pylint: disable=missing-param-doc +# pylint: disable=missing-type-doc + +""" +Implementation of the abstract class UCPauliRotGate for uniformly controlled +(also called multiplexed) single-qubit rotations around the X-axes +(i.e., uniformly controlled R_x rotations). +These gates can have several control qubits and a single target qubit. +If the k control qubits are in the state ket(i) (in the computational bases), +a single-qubit rotation R_x(a_i) is applied to the target qubit. +""" +import math + +from qiskit import QuantumRegister, QiskitError +from qiskit.circuit.quantumcircuit import QuantumCircuit +from qiskit.extensions.quantum_initializer.uc_pauli_rot import UCPauliRotGate, UCPauliRotMeta + + +class UCRXMeta(UCPauliRotMeta): + """A metaclass to ensure that UCRXGate and UCX are of the same type. + + Can be removed when UCX gets removed. + """ + @classmethod + def __instancecheck__(mcs, inst): + return type(inst) in {UCRXGate, UCX} # pylint: disable=unidiomatic-typecheck + + +class UCRXGate(UCPauliRotGate, metaclass=UCRXMeta): + """ + Uniformly controlled rotations (also called multiplexed rotations). + The decomposition is based on + 'Synthesis of Quantum Logic Circuits' by V. Shende et al. + (https://arxiv.org/pdf/quant-ph/0406176.pdf) + + Input: + angle_list = list of (real) rotation angles [a_0,...,a_{2^k-1}] + """ + + def __init__(self, angle_list): + super().__init__(angle_list, "X") + + +def ucrx(self, angle_list, q_controls, q_target): + """Attach a uniformly controlled (also called multiplexed) Rx rotation gate to a circuit. + + The decomposition is base on https://arxiv.org/pdf/quant-ph/0406176.pdf by Shende et al. + + Args: + angle_list (list): list of (real) rotation angles [a_0,...,a_{2^k-1}] + q_controls (QuantumRegister|list): list of k control qubits + (or empty list if no controls). The control qubits are ordered according to their + significance in increasing order: For example if q_controls=[q[1],q[2]] + (with q = QuantumRegister(2)), the rotation Rx(a_0)is performed if q[1] and q[2] + are in the state zero, the rotation Rx(a_1) is performed if q[1] is in the state + one and q[2] is in the state zero, and so on + q_target (QuantumRegister|Qubit): target qubit, where we act on with + the single-qubit rotation gates + + Returns: + QuantumCircuit: the uniformly controlled rotation gate is attached to the circuit. + + Raises: + QiskitError: if the list number of control qubits does not correspond to the provided + number of single-qubit unitaries; if an input is of the wrong type + """ + + if isinstance(q_controls, QuantumRegister): + q_controls = q_controls[:] + if isinstance(q_target, QuantumRegister): + q_target = q_target[:] + if len(q_target) == 1: + q_target = q_target[0] + else: + raise QiskitError("The target qubit is a QuantumRegister containing more" + " than one qubits.") + # Check if q_controls has type "list" + if not isinstance(angle_list, list): + raise QiskitError("The angles must be provided as a list.") + num_contr = math.log2(len(angle_list)) + if num_contr < 0 or not num_contr.is_integer(): + raise QiskitError("The number of controlled rotation gates is not a non-negative" + " power of 2.") + # Check if number of control qubits does correspond to the number of rotations + if num_contr != len(q_controls): + raise QiskitError("Number of controlled rotations does not correspond to the number of" + " control-qubits.") + return self.append(UCRXGate(angle_list), [q_target] + q_controls, []) + + +class UCX(UCRXGate, metaclass=UCRXMeta): + """The deprecated UCRXGate class.""" + + def __init__(self, angle_list): + import warnings + warnings.warn('The class UCX is deprecated as of 0.14.0, and ' + 'will be removed no earlier than 3 months after that release date. ' + 'You should use the class UCRXGate instead.', + DeprecationWarning, stacklevel=2) + super().__init__(angle_list) + + +def ucx(self, angle_list, q_controls, q_target): + """Deprecated version of ucrx.""" + import warnings + warnings.warn('The QuantumCircuit. ucx() method is deprecated as of 0.14.0, and ' + 'will be removed no earlier than 3 months after that release date. ' + 'You should use the QuantumCircuit. ucrx() method instead.', + DeprecationWarning, stacklevel=2) + return ucrx(self, angle_list, q_controls, q_target) + + +QuantumCircuit.ucrx = ucrx +QuantumCircuit.ucx = ucx # deprecated, but still supported diff --git a/qiskit/extensions/quantum_initializer/ucry.py b/qiskit/extensions/quantum_initializer/ucry.py new file mode 100644 index 000000000000..280bbad20dd6 --- /dev/null +++ b/qiskit/extensions/quantum_initializer/ucry.py @@ -0,0 +1,125 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" +Implementation of the abstract class UCPauliRotGate for uniformly controlled +(also called multiplexed) single-qubit rotations +around the Y-axes (i.e., uniformly controlled R_y rotations). +These gates can have several control qubits and a single target qubit. +If the k control qubits are in the state ket(i) (in the computational bases), +a single-qubit rotation R_y(a_i) is applied to the target qubit. +""" +import math + +from qiskit import QuantumRegister, QiskitError +from qiskit.circuit.quantumcircuit import QuantumCircuit +from qiskit.extensions.quantum_initializer.uc_pauli_rot import UCPauliRotGate, UCPauliRotMeta + + +class UCRYMeta(UCPauliRotMeta): + """A metaclass to ensure that UCRYGate and UCY are of the same type. + + Can be removed when UCY gets removed. + """ + @classmethod + def __instancecheck__(mcs, inst): + return type(inst) in {UCRYGate, UCY} # pylint: disable=unidiomatic-typecheck + + +class UCRYGate(UCPauliRotGate, metaclass=UCRYMeta): + """ + Uniformly controlled rotations (also called multiplexed rotations). + The decomposition is based on + 'Synthesis of Quantum Logic Circuits' by V. Shende et al. + (https://arxiv.org/pdf/quant-ph/0406176.pdf) + + Input: + angle_list = list of (real) rotation angles [a_0,...,a_{2^k-1}] + """ + + def __init__(self, angle_list): + super().__init__(angle_list, "Y") + + +def ucry(self, angle_list, q_controls, q_target): + """Attach a uniformly controlled (also called multiplexed) Ry rotation gate to a circuit. + + The decomposition is base on https://arxiv.org/pdf/quant-ph/0406176.pdf by Shende et al. + + Args: + angle_list (list[numbers): list of (real) rotation angles [a_0,...,a_{2^k-1}] + q_controls (QuantumRegister|list[Qubit]): list of k control qubits + (or empty list if no controls). The control qubits are ordered according to their + significance in increasing order: For example if q_controls=[q[1],q[2]] + (with q = QuantumRegister(2)), the rotation Ry(a_0)is performed if q[1] and q[2] + are in the state zero, the rotation Ry(a_1) is performed if q[1] is in the state + one and q[2] is in the state zero, and so on + q_target (QuantumRegister|Qubit): target qubit, where we act on with + the single-qubit rotation gates + + Returns: + QuantumCircuit: the uniformly controlled rotation gate is attached to the circuit. + + Raises: + QiskitError: if the list number of control qubits does not correspond to the provided + number of single-qubit unitaries; if an input is of the wrong type + """ + + if isinstance(q_controls, QuantumRegister): + q_controls = q_controls[:] + if isinstance(q_target, QuantumRegister): + q_target = q_target[:] + if len(q_target) == 1: + q_target = q_target[0] + else: + raise QiskitError("The target qubit is a QuantumRegister containing" + " more than one qubits.") + # Check if q_controls has type "list" + if not isinstance(angle_list, list): + raise QiskitError("The angles must be provided as a list.") + num_contr = math.log2(len(angle_list)) + if num_contr < 0 or not num_contr.is_integer(): + raise QiskitError("The number of controlled rotation gates is not" + " a non-negative power of 2.") + # Check if number of control qubits does correspond to the number of rotations + if num_contr != len(q_controls): + raise QiskitError("Number of controlled rotations does not correspond to" + " the number of control-qubits.") + return self.append(UCRYGate(angle_list), [q_target] + q_controls, []) + + +class UCY(UCRYGate, metaclass=UCRYMeta): + """The deprecated UCRYGate class.""" + + def __init__(self, angle_list): + import warnings + warnings.warn('The class UCY is deprecated as of 0.14.0, and ' + 'will be removed no earlier than 3 months after that release date. ' + 'You should use the class UCRYGate instead.', + DeprecationWarning, stacklevel=2) + super().__init__(angle_list) + + +def ucy(self, angle_list, q_controls, q_target): + """Deprecated version of ucry.""" + import warnings + warnings.warn('The QuantumCircuit ucy() method is deprecated as of 0.14.0, and ' + 'will be removed no earlier than 3 months after that release date. ' + 'You should use the QuantumCircuit ucry() method instead.', + DeprecationWarning, stacklevel=2) + return ucry(self, angle_list, q_controls, q_target) + + +QuantumCircuit.ucry = ucry +QuantumCircuit.ucy = ucy # deprecated, but still supported diff --git a/qiskit/extensions/quantum_initializer/ucrz.py b/qiskit/extensions/quantum_initializer/ucrz.py new file mode 100644 index 000000000000..cfdd7e684629 --- /dev/null +++ b/qiskit/extensions/quantum_initializer/ucrz.py @@ -0,0 +1,126 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" +Implementation of the abstract class UCPauliRotGate for uniformly controlled +(also called multiplexed) single-qubit rotations +around the Z-axes (i.e., uniformly controlled R_z rotations). +These gates can have several control qubits and a single target qubit. +If the k control qubits are in the state ket(i) (in the computational bases), +a single-qubit rotation R_z(a_i) is applied to the target qubit. +""" +import math + +from qiskit import QuantumRegister, QiskitError +from qiskit.circuit.quantumcircuit import QuantumCircuit +from qiskit.extensions.quantum_initializer.uc_pauli_rot import UCPauliRotGate, UCPauliRotMeta + + +class UCRZMeta(UCPauliRotMeta): + """A metaclass to ensure that UCRZGate and UCZ are of the same type. + + Can be removed when UCZ gets removed. + """ + @classmethod + def __instancecheck__(mcs, inst): + return type(inst) in {UCRZGate, UCZ} # pylint: disable=unidiomatic-typecheck + + +class UCRZGate(UCPauliRotGate, metaclass=UCRZMeta): + """ + Uniformly controlled rotations (also called multiplexed rotations). + The decomposition is based on + 'Synthesis of Quantum Logic Circuits' by V. Shende et al. + (https://arxiv.org/pdf/quant-ph/0406176.pdf) + + Input: + angle_list = list of (real) rotation angles [a_0,...,a_{2^k-1}] + """ + + def __init__(self, angle_list): + super().__init__(angle_list, "Z") + + +def ucrz(self, angle_list, q_controls, q_target): + """Attach a uniformly controlled (also called multiplexed gates) Rz rotation gate to a circuit. + + The decomposition is base on https://arxiv.org/pdf/quant-ph/0406176.pdf by Shende et al. + + Args: + angle_list (list[numbers): list of (real) rotation angles [a_0,...,a_{2^k-1}] + q_controls (QuantumRegister|list[Qubit]): list of k control qubits + (or empty list if no controls). The control qubits are ordered according to their + significance in increasing order: For example if q_controls=[q[1],q[2]] + (with q = QuantumRegister(2)), the rotation Rz(a_0)is performed if q[1] and q[2] + are in the state zero, the rotation Rz(a_1) is performed if q[1] is in + the state one and q[2] is in the state zero, and so on + q_target (QuantumRegister|Qubit): target qubit, where we act on with + the single-qubit rotation gates + + Returns: + QuantumCircuit: the uniformly controlled rotation gate is attached to the circuit. + + Raises: + QiskitError: if the list number of control qubits does not correspond to + the provided number of single-qubit unitaries; if an input is of + the wrong type + """ + + if isinstance(q_controls, QuantumRegister): + q_controls = q_controls[:] + if isinstance(q_target, QuantumRegister): + q_target = q_target[:] + if len(q_target) == 1: + q_target = q_target[0] + else: + raise QiskitError("The target qubit is a QuantumRegister containing" + " more than one qubits.") + # Check if q_controls has type "list" + if not isinstance(angle_list, list): + raise QiskitError("The angles must be provided as a list.") + num_contr = math.log2(len(angle_list)) + if num_contr < 0 or not num_contr.is_integer(): + raise QiskitError("The number of controlled rotation gates is not a non-negative" + " power of 2.") + # Check if number of control qubits does correspond to the number of rotations + if num_contr != len(q_controls): + raise QiskitError("Number of controlled rotations does not correspond to the number" + " of control-qubits.") + return self.append(UCRZGate(angle_list), [q_target] + q_controls, []) + + +class UCZ(UCRZGate, metaclass=UCRZMeta): + """The deprecated UCRZGate class.""" + + def __init__(self, angle_list): + import warnings + warnings.warn('The class UCZ is deprecated as of 0.14.0, and ' + 'will be removed no earlier than 3 months after that release date. ' + 'You should use the class UCRZGate instead.', + DeprecationWarning, stacklevel=2) + super().__init__(angle_list) + + +def ucz(self, angle_list, q_controls, q_target): + """Deprecated version of ucrz.""" + import warnings + warnings.warn('The QuantumCircuit.ucz() method is deprecated as of 0.14.0, and ' + 'will be removed no earlier than 3 months after that release date. ' + 'You should use the QuantumCircuit.ucrz() method instead.', + DeprecationWarning, stacklevel=2) + return ucrz(self, angle_list, q_controls, q_target) + + +QuantumCircuit.ucrz = ucrz +QuantumCircuit.ucz = ucz # deprecated, but still supported diff --git a/qiskit/extensions/quantum_initializer/ucx.py b/qiskit/extensions/quantum_initializer/ucx.py index 3dbc3a12eae3..9da13eda5d28 100644 --- a/qiskit/extensions/quantum_initializer/ucx.py +++ b/qiskit/extensions/quantum_initializer/ucx.py @@ -2,7 +2,7 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2019. +# (C) Copyright IBM 2020. # # This code is licensed under the Apache License, Version 2.0. You may # obtain a copy of this license in the LICENSE.txt file in the root directory @@ -12,84 +12,14 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -# pylint: disable=missing-param-doc -# pylint: disable=missing-type-doc +"""The uniformly controlled RX gate. +This module is deprecated, see ucrx.py """ -Implementation of the abstract class UCRot for uniformly controlled -(also called multiplexed) single-qubit rotations around the X-axes -(i.e., uniformly controlled R_x rotations). -These gates can have several control qubits and a single target qubit. -If the k control qubits are in the state ket(i) (in the computational bases), -a single-qubit rotation R_x(a_i) is applied to the target qubit. -""" -import math - -from qiskit import QuantumRegister, QiskitError -from qiskit.circuit.quantumcircuit import QuantumCircuit -from qiskit.extensions.quantum_initializer.ucrot import UCRot - - -class UCX(UCRot): - """ - Uniformly controlled rotations (also called multiplexed rotations). - The decomposition is based on - 'Synthesis of Quantum Logic Circuits' by V. Shende et al. - (https://arxiv.org/pdf/quant-ph/0406176.pdf) - - Input: - angle_list = list of (real) rotation angles [a_0,...,a_{2^k-1}] - """ - - def __init__(self, angle_list): - super().__init__(angle_list, "X") - - -def ucx(self, angle_list, q_controls, q_target): - """Attach a uniformly controlled (also called multiplexed) Rx rotation gate to a circuit. - - The decomposition is base on https://arxiv.org/pdf/quant-ph/0406176.pdf by Shende et al. - - Args: - angle_list (list): list of (real) rotation angles [a_0,...,a_{2^k-1}] - q_controls (QuantumRegister|list): list of k control qubits - (or empty list if no controls). The control qubits are ordered according to their - significance in increasing order: For example if q_controls=[q[1],q[2]] - (with q = QuantumRegister(2)), the rotation Rx(a_0)is performed if q[1] and q[2] - are in the state zero, the rotation Rx(a_1) is performed if q[1] is in the state - one and q[2] is in the state zero, and so on - q_target (QuantumRegister|Qubit): target qubit, where we act on with - the single-qubit rotation gates - - Returns: - QuantumCircuit: the uniformly controlled rotation gate is attached to the circuit. - - Raises: - QiskitError: if the list number of control qubits does not correspond to the provided - number of single-qubit unitaries; if an input is of the wrong type - """ - - if isinstance(q_controls, QuantumRegister): - q_controls = q_controls[:] - if isinstance(q_target, QuantumRegister): - q_target = q_target[:] - if len(q_target) == 1: - q_target = q_target[0] - else: - raise QiskitError("The target qubit is a QuantumRegister containing more" - " than one qubits.") - # Check if q_controls has type "list" - if not isinstance(angle_list, list): - raise QiskitError("The angles must be provided as a list.") - num_contr = math.log2(len(angle_list)) - if num_contr < 0 or not num_contr.is_integer(): - raise QiskitError("The number of controlled rotation gates is not a non-negative" - " power of 2.") - # Check if number of control qubits does correspond to the number of rotations - if num_contr != len(q_controls): - raise QiskitError("Number of controlled rotations does not correspond to the number of" - " control-qubits.") - return self.append(UCX(angle_list), [q_target] + q_controls, []) +import warnings +# pylint: disable=unused-import +from qiskit.extensions.quantum_initializer.ucrx import UCX, ucx -QuantumCircuit.ucx = ucx +warnings.warn('This module is deprecated. The UCRXGate/UCX can now be found in ucrx.py', + category=DeprecationWarning, stacklevel=2) diff --git a/qiskit/extensions/quantum_initializer/ucy.py b/qiskit/extensions/quantum_initializer/ucy.py index 1c3546cd3675..f993508bcd46 100644 --- a/qiskit/extensions/quantum_initializer/ucy.py +++ b/qiskit/extensions/quantum_initializer/ucy.py @@ -2,7 +2,7 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2019. +# (C) Copyright IBM 2020. # # This code is licensed under the Apache License, Version 2.0. You may # obtain a copy of this license in the LICENSE.txt file in the root directory @@ -12,81 +12,14 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Implementation of the abstract class UCRot for uniformly controlled -(also called multiplexed) single-qubit rotations -around the Y-axes (i.e., uniformly controlled R_y rotations). -These gates can have several control qubits and a single target qubit. -If the k control qubits are in the state ket(i) (in the computational bases), -a single-qubit rotation R_y(a_i) is applied to the target qubit. -""" -import math - -from qiskit import QuantumRegister, QiskitError -from qiskit.circuit.quantumcircuit import QuantumCircuit -from qiskit.extensions.quantum_initializer.ucrot import UCRot - - -class UCY(UCRot): - """ - Uniformly controlled rotations (also called multiplexed rotations). - The decomposition is based on - 'Synthesis of Quantum Logic Circuits' by V. Shende et al. - (https://arxiv.org/pdf/quant-ph/0406176.pdf) - - Input: - angle_list = list of (real) rotation angles [a_0,...,a_{2^k-1}] - """ - - def __init__(self, angle_list): - super().__init__(angle_list, "Y") +"""The uniformly controlled RY gate. +This module is deprecated, see ucry.py +""" -def ucy(self, angle_list, q_controls, q_target): - """Attach a uniformly controlled (also called multiplexed) Ry rotation gate to a circuit. - - The decomposition is base on https://arxiv.org/pdf/quant-ph/0406176.pdf by Shende et al. - - Args: - angle_list (list[numbers): list of (real) rotation angles [a_0,...,a_{2^k-1}] - q_controls (QuantumRegister|list[Qubit]): list of k control qubits - (or empty list if no controls). The control qubits are ordered according to their - significance in increasing order: For example if q_controls=[q[1],q[2]] - (with q = QuantumRegister(2)), the rotation Ry(a_0)is performed if q[1] and q[2] - are in the state zero, the rotation Ry(a_1) is performed if q[1] is in the state - one and q[2] is in the state zero, and so on - q_target (QuantumRegister|Qubit): target qubit, where we act on with - the single-qubit rotation gates - - Returns: - QuantumCircuit: the uniformly controlled rotation gate is attached to the circuit. - - Raises: - QiskitError: if the list number of control qubits does not correspond to the provided - number of single-qubit unitaries; if an input is of the wrong type - """ - - if isinstance(q_controls, QuantumRegister): - q_controls = q_controls[:] - if isinstance(q_target, QuantumRegister): - q_target = q_target[:] - if len(q_target) == 1: - q_target = q_target[0] - else: - raise QiskitError("The target qubit is a QuantumRegister containing" - " more than one qubits.") - # Check if q_controls has type "list" - if not isinstance(angle_list, list): - raise QiskitError("The angles must be provided as a list.") - num_contr = math.log2(len(angle_list)) - if num_contr < 0 or not num_contr.is_integer(): - raise QiskitError("The number of controlled rotation gates is not" - " a non-negative power of 2.") - # Check if number of control qubits does correspond to the number of rotations - if num_contr != len(q_controls): - raise QiskitError("Number of controlled rotations does not correspond to" - " the number of control-qubits.") - return self.append(UCY(angle_list), [q_target] + q_controls, []) - +import warnings +# pylint: disable=unused-import +from qiskit.extensions.quantum_initializer.ucry import UCY, ucy -QuantumCircuit.ucy = ucy +warnings.warn('This module is deprecated. The UCRYGate/UCY can now be found in ucry.py', + category=DeprecationWarning, stacklevel=2) diff --git a/qiskit/extensions/quantum_initializer/ucz.py b/qiskit/extensions/quantum_initializer/ucz.py index 132ef2967f43..b3ca60da5ee1 100644 --- a/qiskit/extensions/quantum_initializer/ucz.py +++ b/qiskit/extensions/quantum_initializer/ucz.py @@ -2,7 +2,7 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2019. +# (C) Copyright IBM 2020. # # This code is licensed under the Apache License, Version 2.0. You may # obtain a copy of this license in the LICENSE.txt file in the root directory @@ -12,82 +12,14 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Implementation of the abstract class UCRot for uniformly controlled -(also called multiplexed) single-qubit rotations -around the Z-axes (i.e., uniformly controlled R_z rotations). -These gates can have several control qubits and a single target qubit. -If the k control qubits are in the state ket(i) (in the computational bases), -a single-qubit rotation R_z(a_i) is applied to the target qubit. -""" -import math - -from qiskit import QuantumRegister, QiskitError -from qiskit.circuit.quantumcircuit import QuantumCircuit -from qiskit.extensions.quantum_initializer.ucrot import UCRot - - -class UCZ(UCRot): - """ - Uniformly controlled rotations (also called multiplexed rotations). - The decomposition is based on - 'Synthesis of Quantum Logic Circuits' by V. Shende et al. - (https://arxiv.org/pdf/quant-ph/0406176.pdf) - - Input: - angle_list = list of (real) rotation angles [a_0,...,a_{2^k-1}] - """ - - def __init__(self, angle_list): - super().__init__(angle_list, "Z") +"""The uniformly controlled RZ gate. +This module is deprecated, see ucrz.py +""" -def ucz(self, angle_list, q_controls, q_target): - """Attach a uniformly controlled (also called multiplexed gates) Rz rotation gate to a circuit. - - The decomposition is base on https://arxiv.org/pdf/quant-ph/0406176.pdf by Shende et al. - - Args: - angle_list (list[numbers): list of (real) rotation angles [a_0,...,a_{2^k-1}] - q_controls (QuantumRegister|list[Qubit]): list of k control qubits - (or empty list if no controls). The control qubits are ordered according to their - significance in increasing order: For example if q_controls=[q[1],q[2]] - (with q = QuantumRegister(2)), the rotation Rz(a_0)is performed if q[1] and q[2] - are in the state zero, the rotation Rz(a_1) is performed if q[1] is in - the state one and q[2] is in the state zero, and so on - q_target (QuantumRegister|Qubit): target qubit, where we act on with - the single-qubit rotation gates - - Returns: - QuantumCircuit: the uniformly controlled rotation gate is attached to the circuit. - - Raises: - QiskitError: if the list number of control qubits does not correspond to - the provided number of single-qubit unitaries; if an input is of - the wrong type - """ - - if isinstance(q_controls, QuantumRegister): - q_controls = q_controls[:] - if isinstance(q_target, QuantumRegister): - q_target = q_target[:] - if len(q_target) == 1: - q_target = q_target[0] - else: - raise QiskitError("The target qubit is a QuantumRegister containing" - " more than one qubits.") - # Check if q_controls has type "list" - if not isinstance(angle_list, list): - raise QiskitError("The angles must be provided as a list.") - num_contr = math.log2(len(angle_list)) - if num_contr < 0 or not num_contr.is_integer(): - raise QiskitError("The number of controlled rotation gates is not a non-negative" - " power of 2.") - # Check if number of control qubits does correspond to the number of rotations - if num_contr != len(q_controls): - raise QiskitError("Number of controlled rotations does not correspond to the number" - " of control-qubits.") - return self.append(UCZ(angle_list), [q_target] + q_controls, []) - +import warnings +# pylint: disable=unused-import +from qiskit.extensions.quantum_initializer.ucrz import UCZ, ucz -QuantumCircuit.ucz = ucz +warnings.warn('This module is deprecated. The UCRZGate/UCZ can now be found in ucrz.py', + category=DeprecationWarning, stacklevel=2) diff --git a/qiskit/extensions/standard/__init__.py b/qiskit/extensions/standard/__init__.py index 863d3821ad04..bbed57f36a31 100644 --- a/qiskit/extensions/standard/__init__.py +++ b/qiskit/extensions/standard/__init__.py @@ -15,22 +15,40 @@ """Standard gates.""" from .barrier import Barrier from .h import HGate, CHGate -from .iden import IdGate +from .i import IGate from .ms import MSGate from .r import RGate from .rccx import RCCXGate from .rcccx import RCCCXGate -from .rx import RXGate, CrxGate +from .rx import RXGate, CRXGate from .rxx import RXXGate -from .ry import RYGate, CryGate -from .rz import RZGate, CrzGate +from .ry import RYGate, CRYGate +from .rz import RZGate, CRZGate from .rzz import RZZGate from .s import SGate, SdgGate -from .swap import SwapGate, FredkinGate +from .swap import SwapGate, CSwapGate from .t import TGate, TdgGate -from .u1 import U1Gate, Cu1Gate +from .u1 import U1Gate, CU1Gate from .u2 import U2Gate -from .u3 import U3Gate, Cu3Gate -from .x import XGate, CnotGate, ToffoliGate -from .y import YGate, CyGate -from .z import ZGate, CzGate +from .u3 import U3Gate, CU3Gate +from .x import XGate, CXGate, CCXGate +from .y import YGate, CYGate +from .z import ZGate, CZGate + +# to be converted to gates +from .multi_control_u1_gate import mcu1 +from .multi_control_toffoli_gate import mct +from .multi_control_rotation_gates import mcrx, mcry, mcrz + +# deprecated gates, to be removed +from .i import IdGate +from .x import ToffoliGate +from .swap import FredkinGate +from .x import CnotGate +from .y import CyGate +from .z import CzGate +from .u1 import Cu1Gate +from .u3 import Cu3Gate +from .rx import CrxGate +from .ry import CryGate +from .rz import CrzGate diff --git a/qiskit/extensions/standard/ccx.py b/qiskit/extensions/standard/ccx.py index 685d996265af..816c11f36339 100644 --- a/qiskit/extensions/standard/ccx.py +++ b/qiskit/extensions/standard/ccx.py @@ -13,7 +13,7 @@ # that they have been altered from the originals. """ -Toffoli gate. Controlled-Controlled-X. +Controlled-Controlled-X (or Toffoli) Gate. """ import warnings diff --git a/qiskit/extensions/standard/ch.py b/qiskit/extensions/standard/ch.py index c6223843cac0..19bd446ef99e 100644 --- a/qiskit/extensions/standard/ch.py +++ b/qiskit/extensions/standard/ch.py @@ -13,11 +13,11 @@ # that they have been altered from the originals. """ -controlled-H gate. +Controlled-H gate. """ import warnings # pylint: disable=unused-import -from qiskit.extensions.standard.h import HGate, ch +from qiskit.extensions.standard.h import CHGate, ch warnings.warn('This module is deprecated. The CHGate can now be found in h.py', category=DeprecationWarning, stacklevel=2) diff --git a/qiskit/extensions/standard/crx.py b/qiskit/extensions/standard/crx.py index cc8b4f373e87..5155fa62e524 100644 --- a/qiskit/extensions/standard/crx.py +++ b/qiskit/extensions/standard/crx.py @@ -13,7 +13,7 @@ # that they have been altered from the originals. """ -controlled-rx gate. +Controlled-rx gate. """ import warnings # pylint: disable=unused-import diff --git a/qiskit/extensions/standard/cry.py b/qiskit/extensions/standard/cry.py index 91d0ad400934..610fdee92010 100644 --- a/qiskit/extensions/standard/cry.py +++ b/qiskit/extensions/standard/cry.py @@ -13,7 +13,7 @@ # that they have been altered from the originals. """ -controlled-ry gate. +Controlled-ry gate. """ import warnings # pylint: disable=unused-import diff --git a/qiskit/extensions/standard/crz.py b/qiskit/extensions/standard/crz.py index 4b582257ffc5..001304f21802 100644 --- a/qiskit/extensions/standard/crz.py +++ b/qiskit/extensions/standard/crz.py @@ -13,7 +13,7 @@ # that they have been altered from the originals. """ -controlled-rz gate. +Controlled-rz gate. """ import warnings # pylint: disable=unused-import diff --git a/qiskit/extensions/standard/cswap.py b/qiskit/extensions/standard/cswap.py index 48e4d9ee7cd4..9a39550ee305 100644 --- a/qiskit/extensions/standard/cswap.py +++ b/qiskit/extensions/standard/cswap.py @@ -13,7 +13,7 @@ # that they have been altered from the originals. """ -Fredkin gate. Controlled-SWAP. +Controlled-Swap gate or Fredkin gate. """ import warnings # pylint: disable=unused-import diff --git a/qiskit/extensions/standard/cu1.py b/qiskit/extensions/standard/cu1.py index 0f671409ee82..68acbec0eb01 100644 --- a/qiskit/extensions/standard/cu1.py +++ b/qiskit/extensions/standard/cu1.py @@ -13,7 +13,7 @@ # that they have been altered from the originals. """ -controlled-u1 gate. +Controlled-u1 gate. """ import warnings # pylint: disable=unused-import diff --git a/qiskit/extensions/standard/cu3.py b/qiskit/extensions/standard/cu3.py index ecc5504b18a3..4b7d9fd1a08f 100644 --- a/qiskit/extensions/standard/cu3.py +++ b/qiskit/extensions/standard/cu3.py @@ -13,7 +13,7 @@ # that they have been altered from the originals. """ -controlled-u3 gate. +Controlled-u3 gate. """ import warnings # pylint: disable=unused-import diff --git a/qiskit/extensions/standard/cx.py b/qiskit/extensions/standard/cx.py index 0562ad745fb6..121647049828 100644 --- a/qiskit/extensions/standard/cx.py +++ b/qiskit/extensions/standard/cx.py @@ -13,7 +13,7 @@ # that they have been altered from the originals. """ -controlled-NOT gate. +Controlled-not gate. """ import warnings # pylint: disable=unused-import diff --git a/qiskit/extensions/standard/cy.py b/qiskit/extensions/standard/cy.py index 98157d4b7032..c2c2574990db 100644 --- a/qiskit/extensions/standard/cy.py +++ b/qiskit/extensions/standard/cy.py @@ -13,7 +13,7 @@ # that they have been altered from the originals. """ -controlled-Y gate. +Controlled-Y gate. """ import warnings # pylint: disable=unused-import diff --git a/qiskit/extensions/standard/cz.py b/qiskit/extensions/standard/cz.py index 37be8f528c2f..12ce11bbaee1 100644 --- a/qiskit/extensions/standard/cz.py +++ b/qiskit/extensions/standard/cz.py @@ -13,7 +13,7 @@ # that they have been altered from the originals. """ -controlled-Phase gate. +Controlled-Phase gate. """ import warnings # pylint: disable=unused-import diff --git a/qiskit/extensions/standard/h.py b/qiskit/extensions/standard/h.py index 002bca37b813..b44b9591359b 100644 --- a/qiskit/extensions/standard/h.py +++ b/qiskit/extensions/standard/h.py @@ -20,17 +20,32 @@ from qiskit.circuit import QuantumCircuit from qiskit.circuit import QuantumRegister from qiskit.circuit.controlledgate import ControlledGate +from qiskit.extensions.standard.t import TGate, TdgGate +from qiskit.extensions.standard.s import SGate, SdgGate from qiskit.qasm import pi from qiskit.util import deprecate_arguments # pylint: disable=cyclic-import class HGate(Gate): - """Hadamard gate.""" + r"""Hadamard gate. - def __init__(self, label=None): + **Matrix Definition** + + The matrix for this gate is given by: + + .. math:: + + U_{\text{H}} = \frac{1}{\sqrt{2}} + \begin{bmatrix} + 1 & 1 \\ + 1 & -1 + \end{bmatrix} + """ + + def __init__(self, phase=0, label=None): """Create new Hadamard gate.""" - super().__init__("h", 1, [], label=label) + super().__init__('h', 1, [], phase=phase, label=label) def _define(self): """ @@ -38,33 +53,34 @@ def _define(self): """ from qiskit.extensions.standard.u2 import U2Gate definition = [] - q = QuantumRegister(1, "q") - rule = [ - (U2Gate(0, pi), [q[0]], []) + q = QuantumRegister(1, 'q') + self.definition = [ + (U2Gate(0, pi, phase=self.phase), [q[0]], []) ] - for inst in rule: - definition.append(inst) - self.definition = definition - def control(self, num_ctrl_qubits=1, label=None): + def control(self, num_ctrl_qubits=1, label=None, ctrl_state=None): """Controlled version of this gate. Args: num_ctrl_qubits (int): number of control qubits. label (str or None): An optional label for the gate [Default: None] + ctrl_state (int or str or None): control state expressed as integer, + string (e.g. '110'), or None. If None, use all 1s. Returns: ControlledGate: controlled version of this gate. """ - if num_ctrl_qubits == 1: - return CHGate() - return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label) + if ctrl_state is None: + if num_ctrl_qubits == 1: + return CHGate() + return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label, + ctrl_state=ctrl_state) def inverse(self): """Invert this gate.""" - return HGate() # self-inverse + return HGate(phase=-self.phase) # self-inverse - def to_matrix(self): + def _matrix_definition(self): """Return a Numpy.array for the H gate.""" return numpy.array([[1, 1], [1, -1]], dtype=complex) / numpy.sqrt(2) @@ -102,11 +118,30 @@ def h(self, qubit, *, q=None): # pylint: disable=invalid-name,unused-argument class CHGate(ControlledGate): - """controlled-H gate.""" + r"""The controlled-H gate. + + **Matrix Definition** - def __init__(self): + The matrix for this gate is given by: + + .. math:: + + U_{\text{CH}} = + I \otimes |0 \rangle\!\langle 0| + + U_{\text{H}} \otimes |1 \rangle\!\langle 1| + = \begin{bmatrix} + 1 & 0 & 0 & 0 \\ + 0 & \frac{1}{\sqrt{2}} & 0 & \frac{1}{\sqrt{2}} \\ + 0 & 0 & 1 & 0 \\ + 0 & \frac{1}{\sqrt{2}} & 0 & -\frac{1}{\sqrt{2}} + \end{bmatrix} + """ + + + def __init__(self, phase=0, label=None): """Create new CH gate.""" - super().__init__("ch", 2, [], num_ctrl_qubits=1) + super().__init__('ch', 2, [], phase=0, label=None, + num_ctrl_qubits=1) self.base_gate = HGate() def _define(self): @@ -121,34 +156,29 @@ def _define(self): sdg b; } """ - from qiskit.extensions.standard.s import SGate, SdgGate - from qiskit.extensions.standard.t import TGate, TdgGate - from qiskit.extensions.standard.x import CnotGate - definition = [] - q = QuantumRegister(2, "q") - rule = [ - (SGate(), [q[1]], []), + from qiskit.extensions.standard.x import CXGate + q = QuantumRegister(2, 'q') + self.definition = [ + (SGate(phase=self.phase), [q[1]], []), (HGate(), [q[1]], []), (TGate(), [q[1]], []), - (CnotGate(), [q[0], q[1]], []), + (CXGate(), [q[0], q[1]], []), (TdgGate(), [q[1]], []), (HGate(), [q[1]], []), (SdgGate(), [q[1]], []) ] - for inst in rule: - definition.append(inst) - self.definition = definition def inverse(self): """Invert this gate.""" - return CHGate() # self-inverse + return CHGate(phase=-self.phase) # self-inverse - def to_matrix(self): - """Return a Numpy.array for the Ch gate.""" + def _matrix_definition(self): + """Return a numpy.array for the CH gate.""" return numpy.array([[1, 0, 0, 0], - [0, 1/numpy.sqrt(2), 0, 1/numpy.sqrt(2)], + [0, 1 / numpy.sqrt(2), 0, 1 / numpy.sqrt(2)], [0, 0, 1, 0], - [0, 1/numpy.sqrt(2), 0, -1/numpy.sqrt(2)]], dtype=complex) + [0, 1 / numpy.sqrt(2), 0, -1 / numpy.sqrt(2)]], + dtype=complex) @deprecate_arguments({'ctl': 'control_qubit', 'tgt': 'target_qubit'}) diff --git a/qiskit/extensions/standard/i.py b/qiskit/extensions/standard/i.py new file mode 100644 index 000000000000..41069bec4da0 --- /dev/null +++ b/qiskit/extensions/standard/i.py @@ -0,0 +1,123 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" +Identity gate. +""" +import warnings +import numpy +from qiskit.circuit import Gate +from qiskit.circuit import QuantumCircuit +from qiskit.util import deprecate_arguments + + +class IMeta(type): + """A metaclass to ensure that Id and I are of the same type. + + Can be removed when IdGate gets removed. + """ + @classmethod + def __instancecheck__(mcs, inst): + return type(inst) in {IGate, IdGate} # pylint: disable=unidiomatic-typecheck + + +class IGate(Gate, metaclass=IMeta): + r"""Identity gate. + + Identity gate corresponds to a single-qubit gate wait cycle, + and should not be optimized or unrolled (it is an opaque gate). + + **Matrix Definition** + + The matrix for this gate is given by: + + .. math:: + + U_{\text{I}} = + \begin{bmatrix} + 1 & 0 \\ + 0 & 1 + \end{bmatrix} + """ + + def __init__(self, label=None): + """Create new Identity gate.""" + super().__init__('id', 1, [], label=label) + + def inverse(self): + """Invert this gate.""" + return IGate() # self-inverse + + def to_matrix(self): + """Return a numpy.array for the identity gate.""" + return numpy.array([[1, 0], + [0, 1]], dtype=complex) + + +class IdGate(IGate, metaclass=IMeta): + """The deprecated IGate class.""" + + def __init__(self): + warnings.warn('The class IdGate is deprecated as of 0.14.0, and ' + 'will be removed no earlier than 3 months after that release date. ' + 'You should use the class IGate instead.', + DeprecationWarning, stacklevel=2) + super().__init__() + + +@deprecate_arguments({'q': 'qubit'}) +def i(self, qubit, *, q=None): # pylint: disable=unused-argument + """Apply Identity to to a specified qubit (qubit). + + The Identity gate ensures that nothing is applied to a qubit for one unit + of gate time. It leaves the quantum states |0> and |1> unchanged. + The Identity gate should not be optimized or unrolled (it is an opaque gate). + + Examples: + + Circuit Representation: + + .. jupyter-execute:: + + from qiskit import QuantumCircuit + + circuit = QuantumCircuit(1) + circuit.id(0) # or circuit.i(0) + circuit.draw() + + Matrix Representation: + + .. jupyter-execute:: + + from qiskit.extensions.standard.i import IGate + IGate().to_matrix() + """ + return self.append(IGate(), [qubit], []) + + +@deprecate_arguments({'q': 'qubit'}) +def iden(self, qubit, *, q=None): # pylint: disable=unused-argument + """Deprecated identity gate.""" + warnings.warn('The QuantumCircuit.iden() method is deprecated as of 0.14.0, and ' + 'will be removed no earlier than 3 months after that release date. ' + 'You should use the QuantumCircuit.i() method instead.', + DeprecationWarning, stacklevel=2) + return self.append(IGate(), [qubit], []) + + +# support both i and id as methods of QuantumCircuit +QuantumCircuit.i = i +QuantumCircuit.id = i + +QuantumCircuit.iden = iden # deprecated, remove once IdGate is removed diff --git a/qiskit/extensions/standard/iden.py b/qiskit/extensions/standard/iden.py index bfcafe17d473..d40485931bd6 100644 --- a/qiskit/extensions/standard/iden.py +++ b/qiskit/extensions/standard/iden.py @@ -15,61 +15,9 @@ """ Identity gate. """ -import numpy -from qiskit.circuit import Gate -from qiskit.circuit import QuantumCircuit -from qiskit.util import deprecate_arguments +import warnings +# pylint: disable=unused-import +from qiskit.extensions.standard.i import IdGate, iden - -class IdGate(Gate): - """Identity gate. - - Identity gate corresponds to a single-qubit gate wait cycle, - and should not be optimized or unrolled (it is an opaque gate). - """ - - def __init__(self, label=None): - """Create new Identity gate.""" - super().__init__("id", 1, [], label=label) - - def inverse(self): - """Invert this gate.""" - return IdGate() # self-inverse - - def to_matrix(self): - """Return a Numpy.array for the Id gate.""" - return numpy.array([[1, 0], - [0, 1]], dtype=complex) - - -@deprecate_arguments({'q': 'qubit'}) -def iden(self, qubit, *, q=None): # pylint: disable=unused-argument - """Apply Identity to to a specified qubit (qubit). - - The Identity gate ensures that nothing is applied to a qubit for one unit - of gate time. It leaves the quantum states |0> and |1> unchanged. - The Identity gate should not be optimized or unrolled (it is an opaque gate). - - Examples: - - Circuit Representation: - - .. jupyter-execute:: - - from qiskit import QuantumCircuit - - circuit = QuantumCircuit(1) - circuit.iden(0) - circuit.draw() - - Matrix Representation: - - .. jupyter-execute:: - - from qiskit.extensions.standard.iden import IdGate - IdGate().to_matrix() - """ - return self.append(IdGate(), [qubit], []) - - -QuantumCircuit.iden = iden +warnings.warn('This module is deprecated. The IdGate can now be found in i.py', + category=DeprecationWarning, stacklevel=2) diff --git a/qiskit/extensions/standard/ms.py b/qiskit/extensions/standard/ms.py old mode 100755 new mode 100644 index 161704ce1f45..f9ab0e0bb3ac --- a/qiskit/extensions/standard/ms.py +++ b/qiskit/extensions/standard/ms.py @@ -33,23 +33,26 @@ class MSGate(Gate): """Global Molmer-Sorensen gate.""" - def __init__(self, n_qubits, theta): + def __init__(self, n_qubits, theta, phase=0, label=None): """Create new MS gate.""" - super().__init__("ms", n_qubits, [theta]) + super().__init__('ms', n_qubits, [theta], + phase=phase, label=label) def _define(self): from qiskit.extensions.standard.rxx import RXXGate + q = QuantumRegister(self.num_qubits, 'q') definition = [] - q = QuantumRegister(self.num_qubits, "q") - rule = [] for i in range(self.num_qubits): - for j in range(i+1, self.num_qubits): - rule += [(RXXGate(self.params[0]), [q[i], q[j]], [])] - - for inst in rule: - definition.append(inst) + phase = self.phase if i == 0 else 0 + for j in range(i + 1, self.num_qubits): + definition += [(RXXGate(self.params[0], + phase=phase), [q[i], q[j]], [])] self.definition = definition + def inverse(self): + """Invert this gate.""" + return MSGate(self.num_qubits, -self.params[0], phase=self.phase) + def ms(self, theta, qubits): """Apply MS to q1 and q2.""" diff --git a/qiskit/extensions/standard/multi_control_rotation_gates.py b/qiskit/extensions/standard/multi_control_rotation_gates.py index 14549ee4e555..4fa1ba51c396 100644 --- a/qiskit/extensions/standard/multi_control_rotation_gates.py +++ b/qiskit/extensions/standard/multi_control_rotation_gates.py @@ -18,7 +18,7 @@ import logging from math import pi from qiskit.circuit import QuantumCircuit, QuantumRegister, Qubit -from qiskit import QiskitError +from qiskit.exceptions import QiskitError logger = logging.getLogger(__name__) diff --git a/qiskit/extensions/standard/multi_control_toffoli_gate.py b/qiskit/extensions/standard/multi_control_toffoli_gate.py index 558835a5c18e..1cba4f22e41f 100644 --- a/qiskit/extensions/standard/multi_control_toffoli_gate.py +++ b/qiskit/extensions/standard/multi_control_toffoli_gate.py @@ -20,8 +20,7 @@ from math import pi, ceil from qiskit.circuit import QuantumCircuit, QuantumRegister, Qubit - -from qiskit import QiskitError +from qiskit.exceptions import QiskitError logger = logging.getLogger(__name__) diff --git a/qiskit/extensions/standard/r.py b/qiskit/extensions/standard/r.py index 4e189d23b43b..a739a152ed18 100644 --- a/qiskit/extensions/standard/r.py +++ b/qiskit/extensions/standard/r.py @@ -25,37 +25,50 @@ class RGate(Gate): - """Rotation θ around the cos(φ)x + sin(φ)y axis.""" + r"""Rotation θ around the cos(φ)x + sin(φ)y axis. - def __init__(self, theta, phi): - """Create new r single-qubit gate.""" - super().__init__("r", 1, [theta, phi]) + **Matrix Definition** + + The matrix for this gate is given by: + + .. math:: + + U_{\text{R}}(\theta, \phi) + = \exp\left(-i (\cos(\phi)\sigma_X + \sin(\phi)\sigma_Y) \right) + = \begin{bmatrix} + \cos(\theta/2) & -i e^{-i\phi}\sin(\theta/2) \\ + -i e^{i\phi}\sin(\theta/2) & \cos(\theta/2) + \end{bmatrix} + """ + + def __init__(self, theta, phi, phase=0, label=None): + """Create new r single qubit gate.""" + super().__init__('r', 1, [theta, phi], + phase=phase, label=label) def _define(self): """ gate r(θ, φ) a {u3(θ, φ - π/2, -φ + π/2) a;} """ from qiskit.extensions.standard.u3 import U3Gate - definition = [] - q = QuantumRegister(1, "q") + q = QuantumRegister(1, 'q') theta = self.params[0] phi = self.params[1] - rule = [ - (U3Gate(theta, phi - pi / 2, -phi + pi / 2), [q[0]], []) + self.definition = [ + (U3Gate(theta, phi - pi / 2, -phi + pi / 2, + phase=self.phase), [q[0]], []) ] - for inst in rule: - definition.append(inst) - self.definition = definition def inverse(self): """Invert this gate. r(θ, φ)^dagger = r(-θ, φ) """ - return RGate(-self.params[0], self.params[1]) + return RGate(-self.params[0], self.params[1], + phase=-self.phase) - def to_matrix(self): - """Return a Numpy.array for the R gate.""" + def _matrix_definition(self): + """Return a numpy.array for the R gate.""" cos = math.cos(self.params[0] / 2) sin = math.sin(self.params[0] / 2) exp_m = numpy.exp(-1j * self.params[1]) diff --git a/qiskit/extensions/standard/rcccx.py b/qiskit/extensions/standard/rcccx.py index b91917a86639..1fb481e07167 100644 --- a/qiskit/extensions/standard/rcccx.py +++ b/qiskit/extensions/standard/rcccx.py @@ -19,7 +19,7 @@ from qiskit.circuit import QuantumCircuit, Gate, QuantumRegister from qiskit.extensions.standard.u1 import U1Gate from qiskit.extensions.standard.u2 import U2Gate -from qiskit.extensions.standard.x import CnotGate +from qiskit.extensions.standard.x import CXGate from qiskit.qasm import pi @@ -67,20 +67,20 @@ def _define(self): rule = [ (U2Gate(0, pi), [q[3]], []), # H gate (U1Gate(pi / 4), [q[3]], []), # T gate - (CnotGate(), [q[2], q[3]], []), + (CXGate(), [q[2], q[3]], []), (U1Gate(-pi / 4), [q[3]], []), # inverse T gate (U2Gate(0, pi), [q[3]], []), - (CnotGate(), [q[0], q[3]], []), + (CXGate(), [q[0], q[3]], []), (U1Gate(pi / 4), [q[3]], []), - (CnotGate(), [q[1], q[3]], []), + (CXGate(), [q[1], q[3]], []), (U1Gate(-pi / 4), [q[3]], []), - (CnotGate(), [q[0], q[3]], []), + (CXGate(), [q[0], q[3]], []), (U1Gate(pi / 4), [q[3]], []), - (CnotGate(), [q[1], q[3]], []), + (CXGate(), [q[1], q[3]], []), (U1Gate(-pi / 4), [q[3]], []), (U2Gate(0, pi), [q[3]], []), (U1Gate(pi / 4), [q[3]], []), - (CnotGate(), [q[2], q[3]], []), + (CXGate(), [q[2], q[3]], []), (U1Gate(-pi / 4), [q[3]], []), (U2Gate(0, pi), [q[3]], []), ] diff --git a/qiskit/extensions/standard/rccx.py b/qiskit/extensions/standard/rccx.py index 1ee5f58c7364..7835b80c138a 100644 --- a/qiskit/extensions/standard/rccx.py +++ b/qiskit/extensions/standard/rccx.py @@ -19,7 +19,7 @@ from qiskit.circuit import QuantumCircuit, Gate, QuantumRegister from qiskit.extensions.standard.u1 import U1Gate from qiskit.extensions.standard.u2 import U2Gate -from qiskit.extensions.standard.x import CnotGate +from qiskit.extensions.standard.x import CXGate from qiskit.qasm import pi @@ -59,11 +59,11 @@ def _define(self): rule = [ (U2Gate(0, pi), [q[2]], []), # H gate (U1Gate(pi / 4), [q[2]], []), # T gate - (CnotGate(), [q[1], q[2]], []), + (CXGate(), [q[1], q[2]], []), (U1Gate(-pi / 4), [q[2]], []), # inverse T gate - (CnotGate(), [q[0], q[2]], []), + (CXGate(), [q[0], q[2]], []), (U1Gate(pi / 4), [q[2]], []), - (CnotGate(), [q[1], q[2]], []), + (CXGate(), [q[1], q[2]], []), (U1Gate(-pi / 4), [q[2]], []), # inverse T gate (U2Gate(0, pi), [q[2]], []), # H gate ] diff --git a/qiskit/extensions/standard/rx.py b/qiskit/extensions/standard/rx.py index c14335e654a8..eec3f6d7e81a 100644 --- a/qiskit/extensions/standard/rx.py +++ b/qiskit/extensions/standard/rx.py @@ -17,8 +17,8 @@ """ import math import numpy -from qiskit.circuit import ControlledGate from qiskit.circuit import Gate +from qiskit.circuit import ControlledGate from qiskit.circuit import QuantumCircuit from qiskit.circuit import QuantumRegister from qiskit.qasm import pi @@ -26,49 +26,65 @@ class RXGate(Gate): - """rotation around the x-axis.""" + r"""The rotation around the x-axis. - def __init__(self, theta): + **Matrix Definition** + + The matrix for this gate is given by: + + .. math:: + + U_{\text{RX}}(\theta) + = \exp\left(-i \frac{\theta}{2} \sigma_X \right) + = \begin{bmatrix} + \cos(\theta / 2) & -i \sin(\theta / 2) \\ + -i \sin(\theta / 2) & \cos(\theta / 2) + \end{bmatrix} + """ + + def __init__(self, theta, phase=0, label=None): """Create new rx single qubit gate.""" - super().__init__("rx", 1, [theta]) + super().__init__('rx', 1, [theta], + phase=phase, label=label) def _define(self): """ gate rx(theta) a {r(theta, 0) a;} """ from qiskit.extensions.standard.r import RGate - definition = [] - q = QuantumRegister(1, "q") - rule = [ - (RGate(self.params[0], 0), [q[0]], []) + q = QuantumRegister(1, 'q') + self.definition = [ + (RGate(self.params[0], 0, phase=self.phase), + [q[0]], []) ] - for inst in rule: - definition.append(inst) - self.definition = definition - def control(self, num_ctrl_qubits=1, label=None): + def control(self, num_ctrl_qubits=1, label=None, ctrl_state=None): """Controlled version of this gate. Args: num_ctrl_qubits (int): number of control qubits. label (str or None): An optional label for the gate [Default: None] + ctrl_state (int or str or None): control state expressed as integer, + string (e.g. '110'), or None. If None, use all 1s. Returns: ControlledGate: controlled version of this gate. """ - if num_ctrl_qubits == 1: - return CrxGate(self.params[0]) - return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label) + if ctrl_state is None: + if num_ctrl_qubits == 1: + return CRXGate(self.params[0]) + return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label, + ctrl_state=ctrl_state) def inverse(self): """Invert this gate. rx(theta)^dagger = rx(-theta) """ - return RXGate(-self.params[0]) + return RXGate(-self.params[0], phase=-self.phase) - def to_matrix(self): - """Return a Numpy.array for the RX gate.""" + def _matrix_definition(self): + """Return a numpy.array for the RX gate.""" cos = math.cos(self.params[0] / 2) sin = math.sin(self.params[0] / 2) return numpy.array([[cos, -1j * sin], @@ -108,12 +124,23 @@ def rx(self, theta, qubit, *, q=None): # pylint: disable=invalid-name,unused-ar QuantumCircuit.rx = rx -class CrxGate(ControlledGate): - """controlled-rx gate.""" +class CRXMeta(type): + """A metaclass to ensure that CrxGate and CRXGate are of the same type. - def __init__(self, theta): + Can be removed when CrxGate gets removed. + """ + @classmethod + def __instancecheck__(mcs, inst): + return type(inst) in {CRXGate, CrxGate} # pylint: disable=unidiomatic-typecheck + + +class CRXGate(ControlledGate, metaclass=CRXMeta): + """The controlled-rx gate.""" + + def __init__(self, theta, phase=0, label=None): """Create new crx gate.""" - super().__init__('crx', 2, [theta], num_ctrl_qubits=1) + super().__init__('crx', 2, [theta], phase=0, label=None, + num_ctrl_qubits=1) self.base_gate = RXGate(theta) def _define(self): @@ -128,14 +155,14 @@ def _define(self): """ from qiskit.extensions.standard.u1 import U1Gate from qiskit.extensions.standard.u3 import U3Gate - from qiskit.extensions.standard.x import CnotGate + from qiskit.extensions.standard.x import CXGate definition = [] q = QuantumRegister(2, 'q') rule = [ - (U1Gate(pi / 2), [q[1]], []), - (CnotGate(), [q[0], q[1]], []), + (U1Gate(pi / 2, phase=self.phase), [q[1]], []), + (CXGate(), [q[0], q[1]], []), (U3Gate(-self.params[0] / 2, 0, 0), [q[1]], []), - (CnotGate(), [q[0], q[1]], []), + (CXGate(), [q[0], q[1]], []), (U3Gate(self.params[0] / 2, -pi / 2, 0), [q[1]], []) ] for inst in rule: @@ -144,7 +171,19 @@ def _define(self): def inverse(self): """Invert this gate.""" - return CrxGate(-self.params[0]) + return CRXGate(-self.params[0]) + + +class CrxGate(CRXGate, metaclass=CRXMeta): + """The deprecated CRXGate class.""" + + def __init__(self, theta): + import warnings + warnings.warn('The class CrxGate is deprecated as of 0.14.0, and ' + 'will be removed no earlier than 3 months after that release date. ' + 'You should use the class CRXGate instead.', + DeprecationWarning, stacklevel=2) + super().__init__(theta) @deprecate_arguments({'ctl': 'control_qubit', @@ -152,7 +191,7 @@ def inverse(self): def crx(self, theta, control_qubit, target_qubit, *, ctl=None, tgt=None): # pylint: disable=unused-argument """Apply crx from ctl to tgt with angle theta.""" - return self.append(CrxGate(theta), [control_qubit, target_qubit], []) + return self.append(CRXGate(theta), [control_qubit, target_qubit], []) QuantumCircuit.crx = crx diff --git a/qiskit/extensions/standard/rxx.py b/qiskit/extensions/standard/rxx.py index 5c5dcf726956..e2f234f09be0 100644 --- a/qiskit/extensions/standard/rxx.py +++ b/qiskit/extensions/standard/rxx.py @@ -16,59 +16,65 @@ Two-qubit XX-rotation gate. """ import numpy as np + from qiskit.circuit import Gate from qiskit.circuit import QuantumCircuit from qiskit.circuit import QuantumRegister class RXXGate(Gate): - """Two-qubit XX-rotation gate. + r"""Two-qubit XX-rotation gate. + + This gate corresponds to the rotation U(θ) = exp(-1j * θ * X⊗X / 2). + + ** Matrix Definition** + + The matrix for this gate is given by: + + .. math:: - This gate corresponds to the rotation U(θ) = exp(-1j * θ * X⊗X / 2) + U_{\text{RZ}}(\theta) + = \exp\left(-i \frac{\theta}{2} + (\sigma_X\otimes\sigma_X) \right) + = \begin{bmatrix} + \cos(\theta / 2) & 0 & 0 & -i \sin(\theta / 2) \\ + 0 & \cos(\theta / 2) & -i \sin(\theta / 2) & 0 \\ + 0 & -i \sin(\theta / 2) & \cos(\theta / 2) & 0 \\ + -i \sin(\theta / 2) & 0 & 0 & \cos(\theta / 2) + \end{bmatrix} """ - def __init__(self, theta): + def __init__(self, theta, phase=0, label=None): """Create new rxx gate.""" - super().__init__("rxx", 2, [theta]) + super().__init__('rxx', 2, [theta], + phase=phase, label=label) def _define(self): - """Calculate a subcircuit that implements this unitary.""" - from qiskit.extensions.standard.x import CnotGate - from qiskit.extensions.standard.u1 import U1Gate - from qiskit.extensions.standard.u2 import U2Gate - from qiskit.extensions.standard.u3 import U3Gate - from qiskit.extensions.standard.h import HGate - definition = [] - q = QuantumRegister(2, "q") - theta = self.params[0] - rule = [ - (U3Gate(np.pi / 2, theta, 0), [q[0]], []), - (HGate(), [q[1]], []), - (CnotGate(), [q[0], q[1]], []), - (U1Gate(-theta), [q[1]], []), - (CnotGate(), [q[0], q[1]], []), - (HGate(), [q[1]], []), - (U2Gate(-np.pi, np.pi - theta), [q[0]], []), + """ + gate rzz(theta) a, b { cx a, b; rz(theta) b; cx a, b; } + """ + from qiskit.extensions.standard.x import CXGate + from qiskit.extensions.standard.rz import RZGate + q = QuantumRegister(2, 'q') + self.definition = [ + (CXGate(), [q[0], q[1]], []), + (RZGate(self.params[0], phase=self.phase), + [q[1]], []), + (CXGate(), [q[0], q[1]], []) ] - for inst in rule: - definition.append(inst) - self.definition = definition def inverse(self): """Invert this gate.""" - return RXXGate(-self.params[0]) + return RXXGate(-self.params[0], phase=-self.phase) - # NOTE: we should use the following as the canonical matrix - # definition but we don't include it yet since it differs from - # the circuit decomposition matrix by a global phase - # def to_matrix(self): - # """Return a Numpy.array for the RXX gate.""" - # theta = float(self.params[0]) - # return np.array([ - # [np.cos(theta / 2), 0, 0, -1j * np.sin(theta / 2)], - # [0, np.cos(theta / 2), -1j * np.sin(theta / 2), 0], - # [0, -1j * np.sin(theta / 2), np.cos(theta / 2), 0], - # [-1j * np.sin(theta / 2), 0, 0, np.cos(theta / 2)]], dtype=complex) + def _matrix_definition(self): + """Return a Numpy.array for the RXX gate.""" + theta = float(self.params[0]) + return np.array([ + [np.cos(theta / 2), 0, 0, -1j * np.sin(theta / 2)], + [0, np.cos(theta / 2), -1j * np.sin(theta / 2), 0], + [0, -1j * np.sin(theta / 2), np.cos(theta / 2), 0], + [-1j * np.sin(theta / 2), 0, 0, np.cos(theta / 2)]], dtype=complex) def rxx(self, theta, qubit1, qubit2): @@ -76,5 +82,4 @@ def rxx(self, theta, qubit1, qubit2): return self.append(RXXGate(theta), [qubit1, qubit2], []) -# Add to QuantumCircuit class QuantumCircuit.rxx = rxx diff --git a/qiskit/extensions/standard/ry.py b/qiskit/extensions/standard/ry.py index f9d86892e47c..c3fbe95bef96 100644 --- a/qiskit/extensions/standard/ry.py +++ b/qiskit/extensions/standard/ry.py @@ -26,49 +26,64 @@ class RYGate(Gate): - """rotation around the y-axis.""" + r"""The rotation around the y-axis. - def __init__(self, theta): - """Create new ry single qubit gate.""" - super().__init__("ry", 1, [theta]) + **Matrix Definition** + + The matrix for this gate is given by: + + .. math:: + + U_{\text{RY}}(\theta) + = \exp\left(-i \frac{\theta}{2} \sigma_Y \right) + = \begin{bmatrix} + \cos(\theta / 2) & -\sin(\theta / 2) \\ + \sin(\theta / 2) & \cos(\theta / 2) + \end{bmatrix} + """ + + def __init__(self, theta, phase=0, label=None): + super().__init__('ry', 1, [theta], + phase=phase, label=label) def _define(self): """ gate ry(theta) a { r(theta, pi/2) a; } """ from qiskit.extensions.standard.r import RGate - definition = [] - q = QuantumRegister(1, "q") - rule = [ - (RGate(self.params[0], pi/2), [q[0]], []) + q = QuantumRegister(1, 'q') + self.definition = [ + (RGate(self.params[0], pi/2, phase=self.phase), + [q[0]], []) ] - for inst in rule: - definition.append(inst) - self.definition = definition - def control(self, num_ctrl_qubits=1, label=None): + def control(self, num_ctrl_qubits=1, label=None, ctrl_state=None): """Controlled version of this gate. Args: num_ctrl_qubits (int): number of control qubits. label (str or None): An optional label for the gate [Default: None] + ctrl_state (int or str or None): control state expressed as integer, + string (e.g. '110'), or None. If None, use all 1s. Returns: ControlledGate: controlled version of this gate. """ - if num_ctrl_qubits == 1: - return CryGate(self.params[0]) - return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label) + if ctrl_state is None: + if num_ctrl_qubits == 1: + return CRYGate(self.params[0]) + return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label, + ctrl_state=ctrl_state) def inverse(self): """Invert this gate. ry(theta)^dagger = ry(-theta) """ - return RYGate(-self.params[0]) + return RYGate(-self.params[0], phase=-self.phase) - def to_matrix(self): - """Return a Numpy.array for the RY gate.""" + def _matrix_definition(self): + """Return a numpy.array for the RY gate.""" cos = math.cos(self.params[0] / 2) sin = math.sin(self.params[0] / 2) return numpy.array([[cos, -sin], @@ -108,12 +123,22 @@ def ry(self, theta, qubit, *, q=None): # pylint: disable=invalid-name,unused-ar QuantumCircuit.ry = ry -class CryGate(ControlledGate): - """controlled-ry gate.""" +class CRYMeta(type): + """A metaclass to ensure that CryGate and CRYGate are of the same type. + + Can be removed when CryGate gets removed. + """ + @classmethod + def __instancecheck__(mcs, inst): + return type(inst) in {CRYGate, CryGate} # pylint: disable=unidiomatic-typecheck + + +class CRYGate(ControlledGate, metaclass=CRYMeta): + """The controlled-ry gate.""" def __init__(self, theta): """Create new cry gate.""" - super().__init__("cry", 2, [theta], num_ctrl_qubits=1) + super().__init__('cry', 2, [theta], num_ctrl_qubits=1) self.base_gate = RYGate(theta) def _define(self): @@ -124,15 +149,15 @@ def _define(self): } """ - from qiskit.extensions.standard.x import CnotGate from qiskit.extensions.standard.u3 import U3Gate + from qiskit.extensions.standard.x import CXGate definition = [] - q = QuantumRegister(2, "q") + q = QuantumRegister(2, 'q') rule = [ (U3Gate(self.params[0] / 2, 0, 0), [q[1]], []), - (CnotGate(), [q[0], q[1]], []), + (CXGate(), [q[0], q[1]], []), (U3Gate(-self.params[0] / 2, 0, 0), [q[1]], []), - (CnotGate(), [q[0], q[1]], []) + (CXGate(), [q[0], q[1]], []) ] for inst in rule: definition.append(inst) @@ -140,7 +165,19 @@ def _define(self): def inverse(self): """Invert this gate.""" - return CryGate(-self.params[0]) + return CRYGate(-self.params[0]) + + +class CryGate(CRYGate, metaclass=CRYMeta): + """The deprecated CRYGate class.""" + + def __init__(self, theta): + import warnings + warnings.warn('The class CryGate is deprecated as of 0.14.0, and ' + 'will be removed no earlier than 3 months after that release date. ' + 'You should use the class CRYGate instead.', + DeprecationWarning, stacklevel=2) + super().__init__(theta) @deprecate_arguments({'ctl': 'control_qubit', @@ -148,7 +185,7 @@ def inverse(self): def cry(self, theta, control_qubit, target_qubit, *, ctl=None, tgt=None): # pylint: disable=unused-argument """Apply cry from ctl to tgt with angle theta.""" - return self.append(CryGate(theta), [control_qubit, target_qubit], []) + return self.append(CRYGate(theta), [control_qubit, target_qubit], []) QuantumCircuit.cry = cry diff --git a/qiskit/extensions/standard/rz.py b/qiskit/extensions/standard/rz.py index f215c276a9a5..2d2d73dc36fa 100644 --- a/qiskit/extensions/standard/rz.py +++ b/qiskit/extensions/standard/rz.py @@ -15,6 +15,7 @@ """ Rotation around the z-axis. """ +import numpy from qiskit.circuit import Gate from qiskit.circuit import ControlledGate from qiskit.circuit import QuantumCircuit @@ -23,11 +24,26 @@ class RZGate(Gate): - """rotation around the z-axis.""" + r"""The rotation around the z-axis. - def __init__(self, phi): - """Create new rz single qubit gate.""" - super().__init__("rz", 1, [phi]) + **Matrix Definition** + + The matrix for this gate is given by: + + .. math:: + + U_{\text{RZ}}(\theta) + = \exp\left(-i \frac{\theta}{2} \sigma_Z \right) + = \begin{bmatrix} + e^{-i \theta/2} & 0 \\ + 0 & e^{i \theta/2} + \end{bmatrix} + """ + + def __init__(self, phi, phase=0, label=None): + """Create new RZ single qubit gate.""" + super().__init__('rz', 1, [phi], + phase=phase, label=label) def _define(self): """ @@ -35,34 +51,41 @@ def _define(self): """ from qiskit.extensions.standard.u1 import U1Gate definition = [] - q = QuantumRegister(1, "q") - rule = [ - (U1Gate(self.params[0]), [q[0]], []) + q = QuantumRegister(1, 'q') + self.definition = [ + (U1Gate(self.params[0], phase=self.phase), [q[0]], []) ] - for inst in rule: - definition.append(inst) - self.definition = definition - def control(self, num_ctrl_qubits=1, label=None): + def control(self, num_ctrl_qubits=1, label=None, ctrl_state=None): """Controlled version of this gate. Args: num_ctrl_qubits (int): number of control qubits. label (str or None): An optional label for the gate [Default: None] + ctrl_state (int or str or None): control state expressed as integer, + string (e.g. '110'), or None. If None, use all 1s. Returns: ControlledGate: controlled version of this gate. """ - if num_ctrl_qubits == 1: - return CrzGate(self.params[0]) - return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label) + if ctrl_state is None: + if num_ctrl_qubits == 1: + return CRZGate(self.params[0]) + return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label, + ctrl_state=ctrl_state) def inverse(self): """Invert this gate. rz(phi)^dagger = rz(-phi) """ - return RZGate(-self.params[0]) + return RZGate(-self.params[0], phase=-self.phase) + + def _matrix_definition(self): + """Return a Numpy.array for the RZ gate.""" + return numpy.array([[numpy.exp(-1j * self.params[0] / 2), 0], + [0, numpy.exp(1j * self.params[0] / 2)]], + dtype=complex) @deprecate_arguments({'q': 'qubit'}) @@ -90,12 +113,40 @@ def rz(self, phi, qubit, *, q=None): # pylint: disable=invalid-name,unused-argu QuantumCircuit.rz = rz -class CrzGate(ControlledGate): - """controlled-rz gate.""" +class CRZMeta(type): + """A metaclass to ensure that CrzGate and CRZGate are of the same type. - def __init__(self, theta): + Can be removed when CrzGate gets removed. + """ + @classmethod + def __instancecheck__(mcs, inst): + return type(inst) in {CRZGate, CrzGate} # pylint: disable=unidiomatic-typecheck + + +class CRZGate(ControlledGate, metaclass=CRZMeta): + r"""The controlled-rz gate. + + **Matrix Definition** + + The matrix for this gate is given by: + + .. math:: + + U_{\text{CRZ}}(\theta) = + I \otimes |0 \rangle\!\langle 0| + + U_{\text{RZ}}(\theta) \otimes |1 \rangle\!\langle 1| + = \begin{bmatrix} + 1 & 0 & 0 & 0 \\ + 0 & e^{-i \theta/2} & 0 & 0 \\ + 0 & 0 & 1 & 0 \\ + 0 & 0 & 0 & e^{i \theta/2} + \end{bmatrix} + """ + + def __init__(self, theta, phase=0, label=None): """Create new crz gate.""" - super().__init__("crz", 2, [theta], num_ctrl_qubits=1) + super().__init__('crz', 2, [theta], phase=0, label=None, + num_ctrl_qubits=1) self.base_gate = RZGate(theta) def _define(self): @@ -105,23 +156,31 @@ def _define(self): u1(-lambda/2) b; cx a,b; } """ - from qiskit.extensions.standard.x import CnotGate from qiskit.extensions.standard.u1 import U1Gate - definition = [] - q = QuantumRegister(2, "q") - rule = [ - (U1Gate(self.params[0] / 2), [q[1]], []), - (CnotGate(), [q[0], q[1]], []), + from qiskit.extensions.standard.x import CXGate + q = QuantumRegister(2, 'q') + self.definition = [ + (U1Gate(self.params[0] / 2, phase=self.phase), [q[1]], []), + (CXGate(), [q[0], q[1]], []), (U1Gate(-self.params[0] / 2), [q[1]], []), - (CnotGate(), [q[0], q[1]], []) + (CXGate(), [q[0], q[1]], []) ] - for inst in rule: - definition.append(inst) - self.definition = definition def inverse(self): """Invert this gate.""" - return CrzGate(-self.params[0]) + return CRZGate(-self.params[0]) + + +class CrzGate(CRZGate, metaclass=CRZMeta): + """The deprecated CRZGate class.""" + + def __init__(self, theta): + import warnings + warnings.warn('The class CrzGate is deprecated as of 0.14.0, and ' + 'will be removed no earlier than 3 months after that release date. ' + 'You should use the class CRZGate instead.', + DeprecationWarning, stacklevel=2) + super().__init__(theta) @deprecate_arguments({'ctl': 'control_qubit', 'tgt': 'target_qubit'}) @@ -144,7 +203,7 @@ def crz(self, theta, control_qubit, target_qubit, circuit.crz(theta,0,1) circuit.draw() """ - return self.append(CrzGate(theta), [control_qubit, target_qubit], []) + return self.append(CRZGate(theta), [control_qubit, target_qubit], []) QuantumCircuit.crz = crz diff --git a/qiskit/extensions/standard/rzz.py b/qiskit/extensions/standard/rzz.py index 717fb264dc96..7a1d94991f0f 100644 --- a/qiskit/extensions/standard/rzz.py +++ b/qiskit/extensions/standard/rzz.py @@ -13,40 +13,62 @@ # that they have been altered from the originals. """ -two-qubit ZZ-rotation gate. +Two-qubit ZZ-rotation gate. """ +import numpy from qiskit.circuit import Gate from qiskit.circuit import QuantumCircuit from qiskit.circuit import QuantumRegister class RZZGate(Gate): - """Two-qubit ZZ-rotation gate.""" + r"""The two-qubit ZZ-rotation gate. - def __init__(self, theta): + **Matrix Definition** + + The matrix for this gate is given by: + + .. math:: + + U_{\text{RZ}}(\theta) + = \exp\left(-i \frac{\theta}{2} + (\sigma_Z\otimes\sigma_Z) \right) + = \begin{bmatrix} + e^{-i\theta/2} & 0 & 0 & 0 \\ + 0 & 0& e^{\theta/2} & 0 \\ + 0 & 0 & e^{i\theta/2} & 0 \\ + 0 & 0 & 0 & e^{-i\theta/2} + \end{bmatrix} + """ + + def __init__(self, theta, phase=0, label=None): """Create new rzz gate.""" - super().__init__("rzz", 2, [theta]) + super().__init__('rzz', 2, [theta], + phase=phase, label=label) def _define(self): """ - gate rzz(theta) a, b { cx a, b; u1(theta) b; cx a, b; } + gate rzz(theta) a, b { cx a, b; rz(theta) b; cx a, b; } """ - from qiskit.extensions.standard.u1 import U1Gate - from qiskit.extensions.standard.x import CnotGate - definition = [] - q = QuantumRegister(2, "q") - rule = [ - (CnotGate(), [q[0], q[1]], []), - (U1Gate(self.params[0]), [q[1]], []), - (CnotGate(), [q[0], q[1]], []) + from qiskit.extensions.standard.rz import RZGate + from qiskit.extensions.standard.x import CXGate + q = QuantumRegister(2, 'q') + self.definition = [ + (CXGate(), [q[0], q[1]], []), + (RZGate(self.params[0], phase=self.phase), + [q[1]], []), + (CXGate(), [q[0], q[1]], []) ] - for inst in rule: - definition.append(inst) - self.definition = definition def inverse(self): """Invert this gate.""" - return RZZGate(-self.params[0]) + return RZZGate(-self.params[0], phase=-self.phase) + + def _matrix_definition(self): + """Return a Numpy.array for the RZZ gate.""" + exp_p = numpy.exp(1j * self.params[0] / 2) + exp_m = numpy.exp(-1j * self.params[0] / 2) + return numpy.diag([exp_m, exp_p, exp_p, exp_m]) def rzz(self, theta, qubit1, qubit2): diff --git a/qiskit/extensions/standard/s.py b/qiskit/extensions/standard/s.py index cea199c41a22..3cb5bea7e9c8 100644 --- a/qiskit/extensions/standard/s.py +++ b/qiskit/extensions/standard/s.py @@ -13,7 +13,7 @@ # that they have been altered from the originals. """ -S=diag(1,i) Clifford phase gate or its inverse. +The S gate (Clifford phase gate) and its inverse. """ import numpy from qiskit.circuit import Gate @@ -24,63 +24,81 @@ class SGate(Gate): - """S=diag(1,i) Clifford phase gate.""" + r"""The S gate, also called Clifford phase gate. - def __init__(self, label=None): + **Matrix Definition** + + The matrix for this gate is given by: + + .. math:: + + U_{\text{S}}(\theta, \phi) = + \begin{bmatrix} + 1 & 0 \\ + 0 & i + \end{bmatrix} + """ + + def __init__(self, phase=0, label=None): """Create new S gate.""" - super().__init__("s", 1, [], label=label) + super().__init__('s', 1, [], phase=phase, label=label) def _define(self): """ gate s a { u1(pi/2) a; } """ from qiskit.extensions.standard.u1 import U1Gate - definition = [] - q = QuantumRegister(1, "q") - rule = [ - (U1Gate(pi / 2), [q[0]], []) + q = QuantumRegister(1, 'q') + self.definition = [ + (U1Gate(pi / 2, phase=self.phase), [q[0]], []) ] - for inst in rule: - definition.append(inst) - self.definition = definition def inverse(self): """Invert this gate.""" - return SdgGate() + return SdgGate(phase=-self.phase) - def to_matrix(self): - """Return a Numpy.array for the S gate.""" + def _matrix_definition(self): + """Return a numpy.array for the S gate.""" return numpy.array([[1, 0], [0, 1j]], dtype=complex) class SdgGate(Gate): - """Sdg=diag(1,-i) Clifford adjoint phase gate.""" + r"""Sdg Clifford adjoint phase gate. + + **Matrix Definition** + + The matrix for this gate is given by: + + .. math:: + + U_{\text{S}^\dagger} = + \begin{bmatrix} + 1 & 0 \\ + 0 & -i + \end{bmatrix} + """ - def __init__(self, label=None): + def __init__(self, phase=0, label=None): """Create new Sdg gate.""" - super().__init__("sdg", 1, [], label=label) + super().__init__("sdg", 1, [], phase=phase, label=label) def _define(self): """ gate sdg a { u1(-pi/2) a; } """ from qiskit.extensions.standard.u1 import U1Gate - definition = [] - q = QuantumRegister(1, "q") - rule = [ - (U1Gate(-pi / 2), [q[0]], []) + q = QuantumRegister(1, 'q') + self.definition = [ + (U1Gate(-pi / 2, phase=self.phase), [q[0]], []) ] - for inst in rule: - definition.append(inst) - self.definition = definition def inverse(self): """Invert this gate.""" - return SGate() + return SGate(phase=-self.phase) - def to_matrix(self): - """Return a Numpy.array for the Sdg gate.""" + def _matrix_definition(self): + """Return a numpy.array for the Sdg gate.""" return numpy.array([[1, 0], [0, -1j]], dtype=complex) diff --git a/qiskit/extensions/standard/swap.py b/qiskit/extensions/standard/swap.py index 1cfce87a4840..c73c12634232 100644 --- a/qiskit/extensions/standard/swap.py +++ b/qiskit/extensions/standard/swap.py @@ -13,7 +13,7 @@ # that they have been altered from the originals. """ -SWAP gate. +Swap gate. """ import numpy from qiskit.circuit import ControlledGate @@ -24,48 +24,64 @@ class SwapGate(Gate): - """SWAP gate.""" + r"""Swap gate. - def __init__(self): - """Create new SWAP gate.""" - super().__init__("swap", 2, []) + **Matrix Definition** + + The matrix for this gate is given by: + + .. math:: + + U_{\text{Swap}} + = \begin{bmatrix} + 1 & 0 & 0 & 0 \\ + 0 & 0 & 1 & 0 \\ + 0 & 1 & 0 & 0 \\ + 0 & 0 & 0 & 1 + \end{bmatrix} + """ + + def __init__(self, phase=0, label=None): + """Create new Swap gate.""" + super().__init__("swap", 2, [], + phase=phase, label=label) def _define(self): """ gate swap a,b { cx a,b; cx b,a; cx a,b; } """ - from qiskit.extensions.standard.x import CnotGate - definition = [] - q = QuantumRegister(2, "q") - rule = [ - (CnotGate(), [q[0], q[1]], []), - (CnotGate(), [q[1], q[0]], []), - (CnotGate(), [q[0], q[1]], []) + from qiskit.extensions.standard.x import CXGate + q = QuantumRegister(2, 'q') + self.definition = [ + (CXGate(phase=self.phase), [q[0], q[1]], []), + (CXGate(), [q[1], q[0]], []), + (CXGate(), [q[0], q[1]], []) ] - for inst in rule: - definition.append(inst) - self.definition = definition - def control(self, num_ctrl_qubits=1, label=None): + def control(self, num_ctrl_qubits=1, label=None, ctrl_state=None): """Controlled version of this gate. Args: num_ctrl_qubits (int): number of control qubits. label (str or None): An optional label for the gate [Default: None] + ctrl_state (int or str or None): control state expressed as integer, + string (e.g. '110'), or None. If None, use all 1s. Returns: ControlledGate: controlled version of this gate. """ - if num_ctrl_qubits == 1: - return FredkinGate() - return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label) + if ctrl_state is None: + if num_ctrl_qubits == 1: + return CSwapGate() + return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label, + ctrl_state=ctrl_state) def inverse(self): """Invert this gate.""" - return SwapGate() # self-inverse + return SwapGate(phase=-self.phase) # self-inverse - def to_matrix(self): - """Return a Numpy.array for the Swap gate.""" + def _matrix_definition(self): + """Return a numpy.array for the SWAP gate.""" return numpy.array([[1, 0, 0, 0], [0, 0, 1, 0], [0, 1, 0, 0], @@ -101,12 +117,44 @@ def swap(self, qubit1, qubit2): QuantumCircuit.swap = swap -class FredkinGate(ControlledGate): - """Fredkin gate.""" +class CSwapMeta(type): + """A Metaclass to ensure that CSwapGate and FredkinGate are of the same type. - def __init__(self): - """Create new Fredkin gate.""" - super().__init__("cswap", 3, [], num_ctrl_qubits=1) + Can be removed when FredkinGate gets removed. + """ + @classmethod + def __instancecheck__(mcs, inst): + return type(inst) in {CSwapGate, FredkinGate} # pylint: disable=unidiomatic-typecheck + + +class CSwapGate(ControlledGate, metaclass=CSwapMeta): + r"""The controlled-swap gate, also called Fredkin gate. + + **Matrix Definition** + + The matrix for this gate is given by: + + .. math:: + + U_{\text{CSwap}} =& + I \otimes |0 \rangle\!\langle 0| + + U_{\text{Swap}} \otimes |1 \rangle\!\langle 1| \\ + =& + \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 & 0 & 0 & 1 & 0 & 0 \\ + 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 \\ + 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 \\ + 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 \\ + 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 + \end{bmatrix} + """ + def __init__(self, phase=0, label=None): + """Create new CSwap gate.""" + super().__init__("cswap", 3, [], phase=0, label=None, + num_ctrl_qubits=1) self.base_gate = SwapGate() def _define(self): @@ -117,24 +165,21 @@ def _define(self): cx c,b; } """ - from qiskit.extensions.standard.x import CnotGate, ToffoliGate - definition = [] - q = QuantumRegister(3, "q") - rule = [ - (CnotGate(), [q[2], q[1]], []), - (ToffoliGate(), [q[0], q[1], q[2]], []), - (CnotGate(), [q[2], q[1]], []) + from qiskit.extensions.standard.x import CXGate + from qiskit.extensions.standard.x import CCXGate + q = QuantumRegister(3, 'q') + self.definition = [ + (CXGate(phase=self.phase), [q[2], q[1]], []), + (CCXGate(), [q[0], q[1], q[2]], []), + (CXGate(), [q[2], q[1]], []) ] - for inst in rule: - definition.append(inst) - self.definition = definition def inverse(self): """Invert this gate.""" - return FredkinGate() # self-inverse + return CSwapGate(phase=-self.phase) # self-inverse - def to_matrix(self): - """Return a Numpy.array for the Fredkin (CSWAP) gate.""" + def _matrix_definition(self): + """Return a numpy.array for the Fredkin (CSWAP) gate.""" return numpy.array([[1, 0, 0, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0, 0], [0, 0, 1, 0, 0, 0, 0, 0], @@ -145,6 +190,19 @@ def to_matrix(self): [0, 0, 0, 0, 0, 0, 0, 1]], dtype=complex) + +class FredkinGate(CSwapGate, metaclass=CSwapMeta): + """The deprecated CSwapGate class.""" + + def __init__(self): + import warnings + warnings.warn('The class FredkinGate is deprecated as of 0.14.0, and ' + 'will be removed no earlier than 3 months after that release date. ' + 'You should use the class CSwapGate instead.', + DeprecationWarning, stacklevel=2) + super().__init__() + + @deprecate_arguments({'ctl': 'control_qubit', 'tgt1': 'target_qubit1', 'tgt2': 'target_qubit2'}) @@ -171,13 +229,12 @@ def cswap(self, control_qubit, target_qubit1, target_qubit2, .. jupyter-execute:: - from qiskit.extensions.standard.swap import FredkinGate - FredkinGate().to_matrix() + from qiskit.extensions.standard.swap import CSwapGate + CSwapGate().to_matrix() """ - return self.append(FredkinGate(), - [control_qubit, target_qubit1, target_qubit2], - []) + return self.append(CSwapGate(), [control_qubit, target_qubit1, target_qubit2], []) +# support both cswap and fredkin as methods of QuantumCircuit QuantumCircuit.cswap = cswap QuantumCircuit.fredkin = cswap diff --git a/qiskit/extensions/standard/t.py b/qiskit/extensions/standard/t.py index dd05b7ca5d92..6a3b3cd5f069 100644 --- a/qiskit/extensions/standard/t.py +++ b/qiskit/extensions/standard/t.py @@ -24,65 +24,83 @@ class TGate(Gate): - """T Gate: pi/4 rotation around Z axis.""" + r"""T Gate: pi/4 rotation around Z axis. - def __init__(self, label=None): + **Matrix Definition** + + The matrix for this gate is given by: + + .. math:: + + U_{\text{T}} = + \begin{bmatrix} + 1 & 0 \\ + 0 & e^{i \pi / 4} + \end{bmatrix} + """ + + def __init__(self, phase=0, label=None): """Create new T gate.""" - super().__init__("t", 1, [], label=label) + super().__init__('t', 1, [], phase=phase, label=label) def _define(self): """ gate t a { u1(pi/4) a; } """ from qiskit.extensions.standard.u1 import U1Gate - definition = [] - q = QuantumRegister(1, "q") - rule = [ - (U1Gate(pi/4), [q[0]], []) + q = QuantumRegister(1, 'q') + self.definition = [ + (U1Gate(pi/4, phase=self.phase), [q[0]], []) ] - for inst in rule: - definition.append(inst) - self.definition = definition def inverse(self): """Invert this gate.""" - return TdgGate() + return TdgGate(phase=-self.phase) - def to_matrix(self): - """Return a Numpy.array for the S gate.""" + def _matrix_definition(self): + """Return a numpy.array for the T gate.""" return numpy.array([[1, 0], - [0, (1+1j) / numpy.sqrt(2)]], dtype=complex) + [0, (1 + 1j) / numpy.sqrt(2)]], dtype=complex) class TdgGate(Gate): - """T Gate: -pi/4 rotation around Z axis.""" + r"""T dagger gate: -pi/4 rotation around Z axis + + **Matrix Definition** - def __init__(self, label=None): + The matrix for this gate is given by: + + .. math:: + + U_{\text{T}^\dagger} = + \begin{bmatrix} + 1 & 0 \\ + 0 & e^{-i \pi / 4} + \end{bmatrix} + """ + + def __init__(self, phase=0, label=None): """Create new Tdg gate.""" - super().__init__("tdg", 1, [], label=label) + super().__init__("tdg", 1, [], phase=phase, label=label) def _define(self): """ - gate t a { u1(pi/4) a; } + gate tdg a { u1(pi/4) a; } """ from qiskit.extensions.standard.u1 import U1Gate - definition = [] - q = QuantumRegister(1, "q") - rule = [ - (U1Gate(-pi/4), [q[0]], []) + q = QuantumRegister(1, 'q') + self.definition = [ + (U1Gate(-pi / 4, phase=self.phase), [q[0]], []) ] - for inst in rule: - definition.append(inst) - self.definition = definition def inverse(self): """Invert this gate.""" - return TGate() + return TGate(phase=-self.phase) - def to_matrix(self): - """Return a Numpy.array for the S gate.""" + def _matrix_definition(self): + """Return a numpy.array for the inverse T gate.""" return numpy.array([[1, 0], - [0, (1-1j) / numpy.sqrt(2)]], dtype=complex) + [0, (1 - 1j) / numpy.sqrt(2)]], dtype=complex) @deprecate_arguments({'q': 'qubit'}) diff --git a/qiskit/extensions/standard/u1.py b/qiskit/extensions/standard/u1.py index 014d24d90718..6dbce9b55947 100644 --- a/qiskit/extensions/standard/u1.py +++ b/qiskit/extensions/standard/u1.py @@ -25,45 +25,59 @@ # pylint: disable=cyclic-import class U1Gate(Gate): - """Diagonal single-qubit gate.""" + r"""Diagonal single-qubit gate. - def __init__(self, theta, label=None): + **Matrix Definition** + + The matrix for this gate is given by: + + .. math:: + + U_1(\lambda) = \begin{bmatrix} + 1 & 0 \\ + 0 & e^{i \lambda} + \end{bmatrix} + """ + + def __init__(self, theta, phase=0, label=None): """Create new diagonal single-qubit gate.""" - super().__init__("u1", 1, [theta], label=label) + super().__init__('u1', 1, [theta], + phase=phase, label=label) def _define(self): from qiskit.extensions.standard.u3 import U3Gate definition = [] - q = QuantumRegister(1, "q") - rule = [ - (U3Gate(0, 0, self.params[0]), [q[0]], []) + q = QuantumRegister(1, 'q') + self.definition = [ + (U3Gate(0, 0, self.params[0], phase=self.phase), + [q[0]], []) ] - for inst in rule: - definition.append(inst) - self.definition = definition - def control(self, num_ctrl_qubits=1, label=None): + def control(self, num_ctrl_qubits=1, label=None, ctrl_state=None): """Controlled version of this gate. Args: num_ctrl_qubits (int): number of control qubits. label (str or None): An optional label for the gate [Default: None] + ctrl_state (int or str or None): control state expressed as integer, + string (e.g. '110'), or None. If None, use all 1s. Returns: ControlledGate: controlled version of this gate. """ - if num_ctrl_qubits == 1: - return Cu1Gate(*self.params) - return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label) + if ctrl_state is None: + if num_ctrl_qubits == 1: + return CU1Gate(*self.params) + return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label, + ctrl_state=ctrl_state) def inverse(self): """Invert this gate.""" - return U1Gate(-self.params[0]) + return U1Gate(-self.params[0], phase=-self.phase) - def to_matrix(self): - """Return a Numpy.array for the U1 gate.""" - lam = self.params[0] - lam = float(lam) + def _matrix_definition(self): + """Return a numpy.array for the U1 gate.""" + lam = float(self.params[0]) return numpy.array([[1, 0], [0, numpy.exp(1j * lam)]], dtype=complex) @@ -99,12 +113,41 @@ def u1(self, theta, qubit, *, q=None): # pylint: disable=invalid-name,unused-ar QuantumCircuit.u1 = u1 -class Cu1Gate(ControlledGate): - """controlled-u1 gate.""" +class CU1Meta(type): + """A metaclass to ensure that Cu1Gate and CU1Gate are of the same type. - def __init__(self, theta): + Can be removed when Cu1Gate gets removed. + """ + @classmethod + def __instancecheck__(mcs, inst): + return type(inst) in {CU1Gate, Cu1Gate} # pylint: disable=unidiomatic-typecheck + + +class CU1Gate(ControlledGate, metaclass=CU1Meta): + r"""The controlled-u1 gate. + + **Matrix Definition** + + The matrix for this gate is given by: + + .. math:: + + U_{\text{Cu1}}(\lambda) = + I \otimes |0 \rangle\!\langle 0| + + U_{1}(\lambda) \otimes |1 \rangle\!\langle 1| + = + \begin{bmatrix} + 1 & 0 & 0 & 0 \\ + 0 & 1 & 0 & 0 \\ + 0 & 0 & 1 & 0 \\ + 0 & 0 & 0 & e^{i \lambda} + \end{bmatrix} + """ + + def __init__(self, theta, phase=0, label=None): """Create new cu1 gate.""" - super().__init__("cu1", 2, [theta], num_ctrl_qubits=1) + super().__init__('cu1', 2, [theta], phase=0, label=None, + num_ctrl_qubits=1) self.base_gate = U1Gate(theta) def _define(self): @@ -115,23 +158,38 @@ def _define(self): u1(lambda/2) b; } """ - from qiskit.extensions.standard.x import CnotGate - definition = [] - q = QuantumRegister(2, "q") - rule = [ - (U1Gate(self.params[0] / 2), [q[0]], []), - (CnotGate(), [q[0], q[1]], []), + from qiskit.extensions.standard.x import CXGate + q = QuantumRegister(2, 'q') + self.definition = [ + (U1Gate(self.params[0] / 2, phase=self.phase), [q[0]], []), + (CXGate(), [q[0], q[1]], []), (U1Gate(-self.params[0] / 2), [q[1]], []), - (CnotGate(), [q[0], q[1]], []), + (CXGate(), [q[0], q[1]], []), (U1Gate(self.params[0] / 2), [q[1]], []) ] - for inst in rule: - definition.append(inst) - self.definition = definition def inverse(self): """Invert this gate.""" - return Cu1Gate(-self.params[0]) + return CU1Gate(-self.params[0], phase=-self.phase) + + def _matrix_definition(self): + """Return a Numpy.array for the Cu1 gate.""" + lam = float(self.params[0]) + return numpy.array([[1, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, numpy.exp(1j * lam)]], dtype=complex) + +class Cu1Gate(CU1Gate, metaclass=CU1Meta): + """The deprecated CU1Gate class.""" + + def __init__(self, theta): + import warnings + warnings.warn('The class Cu1Gate is deprecated as of 0.14.0, and ' + 'will be removed no earlier than 3 months after that release date. ' + 'You should use the class CU1Gate instead.', + DeprecationWarning, stacklevel=2) + super().__init__(theta) @deprecate_arguments({'ctl': 'control_qubit', @@ -155,7 +213,7 @@ def cu1(self, theta, control_qubit, target_qubit, circuit.cu1(theta,0,1) circuit.draw() """ - return self.append(Cu1Gate(theta), [control_qubit, target_qubit], []) + return self.append(CU1Gate(theta), [control_qubit, target_qubit], []) QuantumCircuit.cu1 = cu1 diff --git a/qiskit/extensions/standard/u2.py b/qiskit/extensions/standard/u2.py index 50fcf4b35c13..9d9308d9fc30 100644 --- a/qiskit/extensions/standard/u2.py +++ b/qiskit/extensions/standard/u2.py @@ -24,39 +24,56 @@ class U2Gate(Gate): - """One-pulse single-qubit gate.""" + r"""One-pulse single-qubit gate. - def __init__(self, phi, lam, label=None): + **Matrix Definition** + + The matrix for this gate is given by: + + .. math:: + + U_2(\phi, \lambda) = \frac{1}{\sqrt{2}}\begin{bmatrix} + 1 & -e^{i \lambda} \\ + e^{i \phi} & e^{i (\phi+\lambda)} + \end{bmatrix} + """ + + def __init__(self, phi, lam, phase=0, label=None): """Create new one-pulse single-qubit gate.""" - super().__init__("u2", 1, [phi, lam], label=label) + super().__init__('u2', 1, [phi, lam], + phase=phase, label=label) def _define(self): from qiskit.extensions.standard.u3 import U3Gate - definition = [] - q = QuantumRegister(1, "q") - rule = [(U3Gate(pi / 2, self.params[0], self.params[1]), [q[0]], [])] - for inst in rule: - definition.append(inst) - self.definition = definition + q = QuantumRegister(1, 'q') + self.definition = [ + (U3Gate(pi / 2, self.params[0], self.params[1], + phase=self.phase), [q[0]], []) + ] def inverse(self): """Invert this gate. u2(phi,lamb)^dagger = u2(-lamb-pi,-phi+pi) """ - return U2Gate(-self.params[1] - pi, -self.params[0] + pi) + return U2Gate(-self.params[1] - pi, -self.params[0] + pi, + phase=-self.phase) - def to_matrix(self): + def _matrix_definition(self): """Return a Numpy.array for the U2 gate.""" isqrt2 = 1 / numpy.sqrt(2) phi, lam = self.params phi, lam = float(phi), float(lam) - return numpy.array([[isqrt2, -numpy.exp(1j * lam) * isqrt2], - [ - numpy.exp(1j * phi) * isqrt2, - numpy.exp(1j * (phi + lam)) * isqrt2 - ]], - dtype=complex) + return numpy.array([ + [ + isqrt2, + -numpy.exp(1j * lam) * isqrt2 + ], + [ + numpy.exp(1j * phi) * isqrt2, + numpy.exp(1j * (phi + lam)) * isqrt2 + ] + ], dtype=complex) @deprecate_arguments({'q': 'qubit'}) diff --git a/qiskit/extensions/standard/u3.py b/qiskit/extensions/standard/u3.py index 3a4449a69b28..9c9a803e6ba5 100644 --- a/qiskit/extensions/standard/u3.py +++ b/qiskit/extensions/standard/u3.py @@ -26,47 +26,65 @@ # pylint: disable=cyclic-import class U3Gate(Gate): - """Two-pulse single-qubit gate.""" + r"""Two-pulse single-qubit gate. - def __init__(self, theta, phi, lam, label=None): + **Matrix Definition** + + The matrix for this gate is given by: + + .. math:: + + U_3(\theta, \phi, \lambda) = \begin{bmatrix} + \cos(\theta / 2) & -e^{i\lambda}\sin(\theta / 2) \\ + e^{i\phi}\sin(\theta / 2) & e^{i(\phi+\lambda)}\cos(\theta / 2) + \end{bmatrix} + """ + + def __init__(self, theta, phi, lam, phase=0, label=None): """Create new two-pulse single qubit gate.""" - super().__init__("u3", 1, [theta, phi, lam], label=label) + super().__init__('u3', 1, [theta, phi, lam], + phase=phase, label=label) def inverse(self): """Invert this gate. u3(theta, phi, lamb)^dagger = u3(-theta, -lam, -phi) """ - return U3Gate(-self.params[0], -self.params[2], -self.params[1]) + return U3Gate(-self.params[0], -self.params[2], -self.params[1], + phase=-self.phase) - def control(self, num_ctrl_qubits=1, label=None): + def control(self, num_ctrl_qubits=1, label=None, ctrl_state=None): """Controlled version of this gate. Args: num_ctrl_qubits (int): number of control qubits. label (str or None): An optional label for the gate [Default: None] + ctrl_state (int or str or None): control state expressed as integer, + string (e.g. '110'), or None. If None, use all 1s. Returns: ControlledGate: controlled version of this gate. """ - if num_ctrl_qubits == 1: - return Cu3Gate(*self.params) - return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label) + if ctrl_state is None: + if num_ctrl_qubits == 1: + return CU3Gate(*self.params) + return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label, + ctrl_state=ctrl_state) - def to_matrix(self): + def _matrix_definition(self): """Return a Numpy.array for the U3 gate.""" theta, phi, lam = self.params theta, phi, lam = float(theta), float(phi), float(lam) - return numpy.array( - [[ + return numpy.array([ + [ numpy.cos(theta / 2), -numpy.exp(1j * lam) * numpy.sin(theta / 2) ], - [ - numpy.exp(1j * phi) * numpy.sin(theta / 2), - numpy.exp(1j * (phi + lam)) * numpy.cos(theta / 2) - ]], - dtype=complex) + [ + numpy.exp(1j * phi) * numpy.sin(theta / 2), + numpy.exp(1j * (phi + lam)) * numpy.cos(theta / 2) + ] + ], dtype=complex) @deprecate_arguments({'q': 'qubit'}) @@ -103,13 +121,24 @@ def u3(self, theta, phi, lam, qubit, *, q=None): # pylint: disable=invalid-name QuantumCircuit.u3 = u3 -class Cu3Gate(ControlledGate): - """controlled-u3 gate.""" +class CU3Meta(type): + """A metaclass to ensure that Cu3Gate and CU3Gate are of the same type. - def __init__(self, theta, phi, lam): + Can be removed when Cu3Gate gets removed. + """ + @classmethod + def __instancecheck__(mcs, inst): + return type(inst) in {CU3Gate, Cu3Gate} # pylint: disable=unidiomatic-typecheck + + +class CU3Gate(ControlledGate, metaclass=CU3Meta): + """The controlled-u3 gate.""" + + def __init__(self, theta, phi, lam, phase=0, label=None): """Create new cu3 gate.""" - super().__init__("cu3", 2, [theta, phi, lam], num_ctrl_qubits=1) - self.base_gate = U3Gate(theta, phi, lam) + super().__init__('cu3', 2, [theta, phi, lam], phase=0, label=None, + num_ctrl_qubits=1) + self.base_gate = U3Gate(theta, phi, lam, phase=phase) def _define(self): """ @@ -123,24 +152,46 @@ def _define(self): } """ from qiskit.extensions.standard.u1 import U1Gate - from qiskit.extensions.standard.x import CnotGate - definition = [] - q = QuantumRegister(2, "q") - rule = [ - (U1Gate((self.params[2] + self.params[1]) / 2), [q[0]], []), + from qiskit.extensions.standard.x import CXGate + q = QuantumRegister(2, 'q') + self.definition = [ + (U1Gate((self.params[2] + self.params[1]) / 2, phase=self.phase), [q[0]], []), (U1Gate((self.params[2] - self.params[1]) / 2), [q[1]], []), - (CnotGate(), [q[0], q[1]], []), + (CXGate(), [q[0], q[1]], []), (U3Gate(-self.params[0] / 2, 0, -(self.params[1] + self.params[2]) / 2), [q[1]], []), - (CnotGate(), [q[0], q[1]], []), + (CXGate(), [q[0], q[1]], []), (U3Gate(self.params[0] / 2, self.params[1], 0), [q[1]], []) ] - for inst in rule: - definition.append(inst) - self.definition = definition def inverse(self): """Invert this gate.""" - return Cu3Gate(-self.params[0], -self.params[2], -self.params[1]) + return CU3Gate(-self.params[0], -self.params[2], -self.params[1], + phase=-self.phase) + + def _matrix_definition(self): + """Return a Numpy.array for the Cu3 gate.""" + theta, phi, lam = self.params + theta, phi, lam = float(theta), float(phi), float(lam) + half_sine = numpy.sin(theta / 2) + half_cosine = numpy.cos(theta / 2) + return numpy.array([ + [1, 0, 0, 0], + [0, half_cosine, 0, -numpy.exp(1j * lam) * half_sine], + [0, 0, 1, 0], + [0, numpy.exp(1j * phi) * half_sine, 0, numpy.exp(1j * (phi + lam)) * half_cosine] + ]) + + +class Cu3Gate(CU3Gate, metaclass=CU3Meta): + """The deprecated CU3Gate class.""" + + def __init__(self, theta, phi, lam): + import warnings + warnings.warn('The class Cu3Gate is deprecated as of 0.14.0, and ' + 'will be removed no earlier than 3 months after that release date. ' + 'You should use the class CU3Gate instead.', + DeprecationWarning, stacklevel=2) + super().__init__(theta, phi, lam) @deprecate_arguments({'ctl': 'control_qubit', @@ -167,9 +218,7 @@ def cu3(self, theta, phi, lam, control_qubit, target_qubit, circuit.cu3(theta,phi,lam,0,1) circuit.draw() """ - return self.append(Cu3Gate(theta, phi, lam), - [control_qubit, target_qubit], - []) + return self.append(CU3Gate(theta, phi, lam), [control_qubit, target_qubit], []) QuantumCircuit.cu3 = cu3 diff --git a/qiskit/extensions/standard/x.py b/qiskit/extensions/standard/x.py index 93457b083fb3..25af98bbd4fd 100644 --- a/qiskit/extensions/standard/x.py +++ b/qiskit/extensions/standard/x.py @@ -20,16 +20,32 @@ from qiskit.circuit import Gate from qiskit.circuit import QuantumCircuit from qiskit.circuit import QuantumRegister +from qiskit.extensions.standard.h import HGate +from qiskit.extensions.standard.t import TGate +from qiskit.extensions.standard.t import TdgGate from qiskit.qasm import pi from qiskit.util import deprecate_arguments class XGate(Gate): - """Pauli X (bit-flip) gate.""" + r"""Pauli X (bit-flip) gate. - def __init__(self, label=None): + **Matrix Definition** + + The matrix for this gate is given by: + + .. math:: + + U_{\text{X}} = + \begin{bmatrix} + 0 & 1 \\ + 1 & 0 + \end{bmatrix} + """ + + def __init__(self, phase=0, label=None): """Create new X gate.""" - super().__init__("x", 1, [], label=label) + super().__init__('x', 1, [], phase=phase, label=label) def _define(self): """ @@ -38,37 +54,37 @@ def _define(self): } """ from qiskit.extensions.standard.u3 import U3Gate - definition = [] - q = QuantumRegister(1, "q") - rule = [ - (U3Gate(pi, 0, pi), [q[0]], []) + q = QuantumRegister(1, 'q') + self.definition = [ + (U3Gate(pi, 0, pi, phase=self.phase), [q[0]], []) ] - for inst in rule: - definition.append(inst) - self.definition = definition - def control(self, num_ctrl_qubits=1, label=None): + def control(self, num_ctrl_qubits=1, label=None, ctrl_state=None): """Controlled version of this gate. Args: num_ctrl_qubits (int): number of control qubits. label (str or None): An optional label for the gate [Default: None] + ctrl_state (int or str or None): control state expressed as integer, + string (e.g. '110'), or None. If None, use all 1s. Returns: ControlledGate: controlled version of this gate. """ - if num_ctrl_qubits == 1: - return CnotGate() - elif num_ctrl_qubits == 2: - return ToffoliGate() - return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label) + if ctrl_state is None: + if num_ctrl_qubits == 1: + return CXGate() + elif num_ctrl_qubits == 2: + return CCXGate() + return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label, + ctrl_state=ctrl_state) def inverse(self): """Invert this gate.""" - return XGate() # self-inverse + return XGate(phase=-self.phase) # self-inverse - def to_matrix(self): - """Return a Numpy.array for the X gate.""" + def _matrix_definition(self): + """Return a numpy.array for the X gate.""" return numpy.array([[0, 1], [1, 0]], dtype=complex) @@ -106,40 +122,85 @@ def x(self, qubit, *, q=None): # pylint: disable=unused-argument QuantumCircuit.x = x -class CnotGate(ControlledGate): - """controlled-NOT gate.""" +class CXMeta(type): + """A metaclass to ensure that CnotGate and CXGate are of the same type. - def __init__(self): - """Create new CNOT gate.""" - super().__init__("cx", 2, [], num_ctrl_qubits=1) + Can be removed when CnotGate gets removed. + """ + @classmethod + def __instancecheck__(mcs, inst): + return type(inst) in {CnotGate, CXGate} # pylint: disable=unidiomatic-typecheck + + +class CXGate(ControlledGate, metaclass=CXMeta): + r"""The controlled-X gate. + + **Matrix Definition** + + The matrix for this gate is given by: + + .. math:: + + U_{\text{CX}} = + I \otimes |0 \rangle\!\langle 0| + + U_{\text{X}} \otimes |1 \rangle\!\langle 1| + = + \begin{bmatrix} + 1 & 0 & 0 & 0 \\ + 0 & 0 & 0 & 1 \\ + 0 & 0 & 1 & 0 \\ + 0 & 1 & 0 & 0 + \end{bmatrix} + """ + + def __init__(self, phase=0, label=None): + """Create new cx gate.""" + super().__init__("cx", 2, [], phase=phase, label=label, + num_ctrl_qubits=1) self.base_gate = XGate() - def control(self, num_ctrl_qubits=1, label=None): + def control(self, num_ctrl_qubits=1, label=None, ctrl_state=None): """Controlled version of this gate. Args: num_ctrl_qubits (int): number of control qubits. label (str or None): An optional label for the gate [Default: None] + ctrl_state (int or str or None): control state expressed as integer, + string (e.g. '110'), or None. If None, use all 1s. Returns: ControlledGate: controlled version of this gate. """ - if num_ctrl_qubits == 1: - return ToffoliGate() - return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label) + if ctrl_state is None: + if num_ctrl_qubits == 1: + return CCXGate(phase=self.phase, label=label) + return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label, + ctrl_state=ctrl_state) def inverse(self): """Invert this gate.""" - return CnotGate() # self-inverse + return CXGate(phase=-self.phase) # self-inverse - def to_matrix(self): - """Return a Numpy.array for the Cx gate.""" + def _matrix_definition(self): + """Return a numpy.array for the CX gate.""" return numpy.array([[1, 0, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0], [0, 1, 0, 0]], dtype=complex) +class CnotGate(CXGate, metaclass=CXMeta): + """The deprecated CXGate class.""" + + def __init__(self): + import warnings + warnings.warn('The class CnotGate is deprecated as of 0.14.0, and ' + 'will be removed no earlier than 3 months after that release date. ' + 'You should use the class CXGate instead.', + DeprecationWarning, stacklevel=2) + super().__init__() + + @deprecate_arguments({'ctl': 'control_qubit', 'tgt': 'target_qubit'}) def cx(self, control_qubit, target_qubit, # pylint: disable=invalid-name @@ -166,22 +227,57 @@ def cx(self, control_qubit, target_qubit, # pylint: disable=invalid-name .. jupyter-execute:: - from qiskit.extensions.standard.cx import CnotGate - CnotGate().to_matrix() + from qiskit.extensions.standard.x import CXGate + CXGate().to_matrix() """ - return self.append(CnotGate(), [control_qubit, target_qubit], []) + return self.append(CXGate(), [control_qubit, target_qubit], []) +# support both cx and cnot in QuantumCircuits QuantumCircuit.cx = cx QuantumCircuit.cnot = cx -class ToffoliGate(ControlledGate): - """Toffoli gate.""" +class CCXMeta(type): + """A metaclass to ensure that CCXGate and ToffoliGate are of the same type. - def __init__(self): - """Create new Toffoli gate.""" - super().__init__("ccx", 3, [], num_ctrl_qubits=2) + Can be removed when ToffoliGate gets removed. + """ + @classmethod + def __instancecheck__(mcs, inst): + return type(inst) in {CCXGate, ToffoliGate} # pylint: disable=unidiomatic-typecheck + + +class CCXGate(ControlledGate, metaclass=CCXMeta): + r"""The double-controlled-not gate, also called Toffoli gate. + + **Matrix Definition** + + The matrix for this gate is given by: + + .. math:: + + U_{\text{CX}} =& + I \otimes |0, 0 \rangle\!\langle 0, 0| + + I \otimes |0, 1 \rangle\!\langle 0, 1| + + I \otimes |1, 0 \rangle\!\langle 1, 0| + + U_{\text{X}} \otimes |1, 1 \rangle\!\langle 1, 1| \\ + =& + \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 & 0 & 0 & 0 & 0 & 1 \\ + 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 & 1 & 0 & 0 & 0 & 0 + \end{bmatrix} + """ + def __init__(self, phase=0, label=None): + """Create new CCX gate.""" + super().__init__("ccx", 3, [], phase=0, label=None, + num_ctrl_qubits=2) self.base_gate = XGate() def _define(self): @@ -193,38 +289,31 @@ def _define(self): t b; t c; h c; cx a,b; t a; tdg b; cx a,b;} """ - from qiskit.extensions.standard.h import HGate - from qiskit.extensions.standard.t import TGate - from qiskit.extensions.standard.t import TdgGate - definition = [] - q = QuantumRegister(3, "q") - rule = [ - (HGate(), [q[2]], []), - (CnotGate(), [q[1], q[2]], []), + q = QuantumRegister(3, 'q') + self.definition = [ + (HGate(phase=self.phase), [q[2]], []), + (CXGate(), [q[1], q[2]], []), (TdgGate(), [q[2]], []), - (CnotGate(), [q[0], q[2]], []), + (CXGate(), [q[0], q[2]], []), (TGate(), [q[2]], []), - (CnotGate(), [q[1], q[2]], []), + (CXGate(), [q[1], q[2]], []), (TdgGate(), [q[2]], []), - (CnotGate(), [q[0], q[2]], []), + (CXGate(), [q[0], q[2]], []), (TGate(), [q[1]], []), (TGate(), [q[2]], []), (HGate(), [q[2]], []), - (CnotGate(), [q[0], q[1]], []), + (CXGate(), [q[0], q[1]], []), (TGate(), [q[0]], []), (TdgGate(), [q[1]], []), - (CnotGate(), [q[0], q[1]], []) + (CXGate(), [q[0], q[1]], []) ] - for inst in rule: - definition.append(inst) - self.definition = definition def inverse(self): """Invert this gate.""" - return ToffoliGate() # self-inverse + return CCXGate(phase=-self.phase) # self-inverse - def to_matrix(self): - """Return a Numpy.array for the Toffoli gate.""" + def _matrix_definition(self): + """Return a numpy.array for the CCX gate.""" return numpy.array([[1, 0, 0, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0, 0], [0, 0, 1, 0, 0, 0, 0, 0], @@ -235,6 +324,18 @@ def to_matrix(self): [0, 0, 0, 1, 0, 0, 0, 0]], dtype=complex) +class ToffoliGate(CCXGate, metaclass=CCXMeta): + """The deprecated CCXGate class.""" + + def __init__(self): + import warnings + warnings.warn('The class ToffoliGate is deprecated as of 0.14.0, and ' + 'will be removed no earlier than 3 months after that release date. ' + 'You should use the class CCXGate instead.', + DeprecationWarning, stacklevel=2) + super().__init__() + + @deprecate_arguments({'ctl1': 'control_qubit1', 'ctl2': 'control_qubit2', 'tgt': 'target_qubit'}) @@ -260,13 +361,14 @@ def ccx(self, control_qubit1, control_qubit2, target_qubit, .. jupyter-execute:: - from qiskit.extensions.standard.x import ToffoliGate - ToffoliGate().to_matrix() + from qiskit.extensions.standard.x import CCXGate + CCXGate().to_matrix() """ - return self.append(ToffoliGate(), + return self.append(CCXGate(), [control_qubit1, control_qubit2, target_qubit], []) +# support both ccx and toffoli as methods of QuantumCircuit QuantumCircuit.ccx = ccx QuantumCircuit.toffoli = ccx diff --git a/qiskit/extensions/standard/y.py b/qiskit/extensions/standard/y.py index 25f85bd34586..34551bc6ac8e 100644 --- a/qiskit/extensions/standard/y.py +++ b/qiskit/extensions/standard/y.py @@ -25,43 +25,56 @@ class YGate(Gate): - """Pauli Y (bit-phase-flip) gate.""" + r"""Pauli Y (bit-phase-flip) gate. - def __init__(self, label=None): + **Matrix Definition** + + The matrix for this gate is given by: + + .. math:: + + U_{\text{Z}} = + \begin{bmatrix} + 0 & -i \\ + i & 0 + \end{bmatrix} + """ + + def __init__(self, phase=0, label=None): """Create new Y gate.""" - super().__init__("y", 1, [], label=label) + super().__init__('y', 1, [], phase=phase, label=label) def _define(self): from qiskit.extensions.standard.u3 import U3Gate - definition = [] - q = QuantumRegister(1, "q") - rule = [ - (U3Gate(pi, pi/2, pi/2), [q[0]], []) + q = QuantumRegister(1, 'q') + self.definition = [ + (U3Gate(pi, pi/2, pi/2, phase=self.phase), [q[0]], []) ] - for inst in rule: - definition.append(inst) - self.definition = definition - def control(self, num_ctrl_qubits=1, label=None): + def control(self, num_ctrl_qubits=1, label=None, ctrl_state=None): """Controlled version of this gate. Args: num_ctrl_qubits (int): number of control qubits. label (str or None): An optional label for the gate [Default: None] + ctrl_state (int or str or None): control state expressed as integer, + string (e.g. '110'), or None. If None, use all 1s. Returns: ControlledGate: controlled version of this gate. """ - if num_ctrl_qubits == 1: - return CyGate() - return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label) + if ctrl_state is None: + if num_ctrl_qubits == 1: + return CYGate() + return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label, + ctrl_state=ctrl_state) def inverse(self): """Invert this gate.""" - return YGate() # self-inverse + return YGate(phase=-self.phase) # self-inverse - def to_matrix(self): - """Return a Numpy.array for the Y gate.""" + def _matrix_definition(self): + """Return a numpy.array for the Y gate.""" return numpy.array([[0, -1j], [1j, 0]], dtype=complex) @@ -99,12 +112,41 @@ def y(self, qubit, *, q=None): # pylint: disable=unused-argument QuantumCircuit.y = y -class CyGate(ControlledGate): - """controlled-Y gate.""" +class CYMeta(type): + """A metaclass to ensure that CyGate and CYGate are of the same type. - def __init__(self): + Can be removed when CyGate gets removed. + """ + @classmethod + def __instancecheck__(mcs, inst): + return type(inst) in {CYGate, CyGate} # pylint: disable=unidiomatic-typecheck + + +class CYGate(ControlledGate, metaclass=CYMeta): + r"""The controlled-Y gate. + + **Matrix Definition** + + The matrix for this gate is given by: + + .. math:: + + U_{\text{CT}} = + I \otimes |0 \rangle\!\langle 0| + + U_{\text{Y}} \otimes |1 \rangle\!\langle 1| + = + \begin{bmatrix} + 1 & 0 & 0 & 0 \\ + 0 & 0 & 0 & -i \\ + 0 & 0 & 1 & 0 \\ + 0 & i & 0 & 0 + \end{bmatrix} + """ + + def __init__(self, phase=0, label=None): """Create new CY gate.""" - super().__init__("cy", 2, [], num_ctrl_qubits=1) + super().__init__('cy', 2, [], phase=0, label=None, + num_ctrl_qubits=1) self.base_gate = YGate() def _define(self): @@ -113,21 +155,36 @@ def _define(self): """ from qiskit.extensions.standard.s import SGate from qiskit.extensions.standard.s import SdgGate - from qiskit.extensions.standard.x import CnotGate - definition = [] - q = QuantumRegister(2, "q") - rule = [ - (SdgGate(), [q[1]], []), - (CnotGate(), [q[0], q[1]], []), + from qiskit.extensions.standard.x import CXGate + q = QuantumRegister(2, 'q') + self.definition = [ + (SdgGate(phase=self.phase), [q[1]], []), + (CXGate(), [q[0], q[1]], []), (SGate(), [q[1]], []) ] - for inst in rule: - definition.append(inst) - self.definition = definition def inverse(self): """Invert this gate.""" - return CyGate() # self-inverse + return CYGate(phase=-self.phase) # self-inverse + + def _matrix_definition(self): + """Return a numpy.array for the CY gate.""" + return numpy.array([[1, 0, 0, 0], + [0, 0, 0, -1j], + [0, 0, 1, 0], + [0, 1j, 0, 0]], dtype=complex) + + +class CyGate(CYGate, metaclass=CYMeta): + """A deprecated CYGate class.""" + + def __init__(self): + import warnings + warnings.warn('The class CyGate is deprecated as of 0.14.0, and ' + 'will be removed no earlier than 3 months after that release date. ' + 'You should use the class CYGate instead.', + DeprecationWarning, stacklevel=2) + super().__init__() @deprecate_arguments({'ctl': 'control_qubit', @@ -152,7 +209,7 @@ def cy(self, control_qubit, target_qubit, # pylint: disable=invalid-name circuit.cy(0,1) circuit.draw() """ - return self.append(CyGate(), [control_qubit, target_qubit], []) + return self.append(CYGate(), [control_qubit, target_qubit], []) QuantumCircuit.cy = cy diff --git a/qiskit/extensions/standard/z.py b/qiskit/extensions/standard/z.py index 2c970b8781f5..90489a6b2078 100644 --- a/qiskit/extensions/standard/z.py +++ b/qiskit/extensions/standard/z.py @@ -25,43 +25,56 @@ class ZGate(Gate): - """Pauli Z (phase-flip) gate.""" + r"""Pauli Z (phase-flip) gate. - def __init__(self, label=None): + **Matrix Definition** + + The matrix for this gate is given by: + + .. math:: + + U_{\text{Z}} = + \begin{bmatrix} + 1 & 0 \\ + 0 & -1 + \end{bmatrix} + """ + + def __init__(self, phase=0, label=None): """Create new Z gate.""" - super().__init__("z", 1, [], label=label) + super().__init__('z', 1, [], phase=phase, label=label) def _define(self): from qiskit.extensions.standard.u1 import U1Gate - definition = [] - q = QuantumRegister(1, "q") - rule = [ - (U1Gate(pi), [q[0]], []) + q = QuantumRegister(1, 'q') + self.definition = [ + (U1Gate(pi, phase=self.phase), [q[0]], []) ] - for inst in rule: - definition.append(inst) - self.definition = definition - def control(self, num_ctrl_qubits=1, label=None): + def control(self, num_ctrl_qubits=1, label=None, ctrl_state=None): """Controlled version of this gate. Args: num_ctrl_qubits (int): number of control qubits. label (str or None): An optional label for the gate [Default: None] + ctrl_state (int or str or None): control state expressed as integer, + string (e.g. '110'), or None. If None, use all 1s. Returns: ControlledGate: controlled version of this gate. """ - if num_ctrl_qubits == 1: - return CzGate() - return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label) + if ctrl_state is None: + if num_ctrl_qubits == 1: + return CZGate() + return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label, + ctrl_state=ctrl_state) def inverse(self): """Invert this gate.""" - return ZGate() # self-inverse + return ZGate(phase=-self.phase) # self-inverse - def to_matrix(self): - """Return a Numpy.array for the X gate.""" + def _matrix_definition(self): + """Return a numpy.array for the Z gate.""" return numpy.array([[1, 0], [0, -1]], dtype=complex) @@ -99,12 +112,41 @@ def z(self, qubit, *, q=None): # pylint: disable=unused-argument QuantumCircuit.z = z -class CzGate(ControlledGate): - """controlled-Z gate.""" +class CZMeta(type): + """A metaclass to ensure that CzGate and CZGate are of the same type. + + Can be removed when CzGate gets removed. + """ + @classmethod + def __instancecheck__(mcs, inst): + return type(inst) in {CZGate, CzGate} # pylint: disable=unidiomatic-typecheck + + +class CZGate(ControlledGate, metaclass=CZMeta): + r"""The controlled-Z gate. - def __init__(self, label=None): + **Matrix Definition** + + The matrix for this gate is given by: + + .. math:: + + U_{\text{CZ}} = + I \otimes |0 \rangle\!\langle 0| + + U_{\text{Z}} \otimes |1 \rangle\!\langle 1| + = + \begin{bmatrix} + 1 & 0 & 0 & 0 \\ + 0 & 1 & 0 & 0 \\ + 0 & 0 & 1 & 0 \\ + 0 & 0 & 0 & -1 + \end{bmatrix} + """ + + def __init__(self, phase=0, label=None): """Create new CZ gate.""" - super().__init__("cz", 2, [], label=label, num_ctrl_qubits=1) + super().__init__('cz', 2, [], phase=phase, label=label, + num_ctrl_qubits=1) self.base_gate = ZGate() def _define(self): @@ -112,30 +154,38 @@ def _define(self): gate cz a,b { h b; cx a,b; h b; } """ from qiskit.extensions.standard.h import HGate - from qiskit.extensions.standard.x import CnotGate - definition = [] - q = QuantumRegister(2, "q") - rule = [ - (HGate(), [q[1]], []), - (CnotGate(), [q[0], q[1]], []), + from qiskit.extensions.standard.x import CXGate + q = QuantumRegister(2, 'q') + self.definition = [ + (HGate(phase=self.phase), [q[1]], []), + (CXGate(), [q[0], q[1]], []), (HGate(), [q[1]], []) ] - for inst in rule: - definition.append(inst) - self.definition = definition def inverse(self): """Invert this gate.""" - return CzGate() # self-inverse + return CZGate(phase=-self.phase) # self-inverse - def to_matrix(self): - """Return a Numpy.array for the Cz gate.""" + def _matrix_definition(self): + """Return a numpy.array for the CZ gate.""" return numpy.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, -1]], dtype=complex) +class CzGate(CZGate, metaclass=CZMeta): + """The deprecated CZGate class.""" + + def __init__(self): + import warnings + warnings.warn('The class CzGate is deprecated as of 0.14.0, and ' + 'will be removed no earlier than 3 months after that release date. ' + 'You should use the class CZGate instead.', + DeprecationWarning, stacklevel=2) + super().__init__() + + @deprecate_arguments({'ctl': 'control_qubit', 'tgt': 'target_qubit'}) def cz(self, control_qubit, target_qubit, # pylint: disable=invalid-name @@ -163,10 +213,10 @@ def cz(self, control_qubit, target_qubit, # pylint: disable=invalid-name .. jupyter-execute:: - from qiskit.extensions.standard.cz import CzGate - CzGate().to_matrix() + from qiskit.extensions.standard.z import CZGate + CZGate().to_matrix() """ - return self.append(CzGate(), [control_qubit, target_qubit], []) + return self.append(CZGate(), [control_qubit, target_qubit], []) QuantumCircuit.cz = cz diff --git a/qiskit/extensions/unitary.py b/qiskit/extensions/unitary.py index a5247c57a6ed..c5003da879be 100644 --- a/qiskit/extensions/unitary.py +++ b/qiskit/extensions/unitary.py @@ -22,13 +22,16 @@ from qiskit.circuit import Gate from qiskit.circuit import QuantumCircuit from qiskit.circuit import QuantumRegister +from qiskit.exceptions import QiskitError from qiskit.extensions.standard import U3Gate from qiskit.quantum_info.operators.predicates import matrix_equal from qiskit.quantum_info.operators.predicates import is_unitary_matrix -from qiskit.quantum_info.synthesis import euler_angles_1q -from qiskit.quantum_info.synthesis import two_qubit_cnot_decompose +from qiskit.quantum_info.synthesis.one_qubit_decompose import OneQubitEulerDecomposer +from qiskit.quantum_info.synthesis.two_qubit_decompose import two_qubit_cnot_decompose from qiskit.extensions.exceptions import ExtensionError +_DECOMPOSER1Q = OneQubitEulerDecomposer('U3') + class UnitaryGate(Gate): """Class for representing unitary gates""" @@ -103,52 +106,32 @@ def _define(self): """Calculate a subcircuit that implements this unitary.""" if self.num_qubits == 1: q = QuantumRegister(1, "q") - angles = euler_angles_1q(self.to_matrix()) - self.definition = [(U3Gate(*angles), [q[0]], [])] + theta, phi, lam = _DECOMPOSER1Q.angles(self.to_matrix()) + self.definition = [(U3Gate(theta, phi, lam), [q[0]], [])] elif self.num_qubits == 2: self.definition = two_qubit_cnot_decompose(self.to_matrix()) else: raise NotImplementedError("Not able to generate a subcircuit for " "a {}-qubit unitary".format(self.num_qubits)) - def control(self, num_ctrl_qubits=1, label=None): - """Return controlled version of gate + def control(self, num_ctrl_qubits=1, label=None, ctrl_state=None): + r"""Return controlled version of gate Args: num_ctrl_qubits (int): number of controls to add to gate (default=1) label (str): optional gate label + ctrl_state (int or str or None): The control state in decimal or as a + bit string (e.g. '1011'). If None, use 2**num_ctrl_qubits-1. Returns: UnitaryGate: controlled version of gate. Raises: - QiskitError: unrecognized mode + QiskitError: invalid ctrl_state """ - cmat = self._compute_control_matrix(self.to_matrix(), num_ctrl_qubits) + cmat = _compute_control_matrix(self.to_matrix(), num_ctrl_qubits) return UnitaryGate(cmat, label=label) - def _compute_control_matrix(self, base_mat, num_ctrl_qubits): - """ - Compute the controlled version of the input matrix with qiskit ordering. - - Args: - base_mat (ndarray): unitary to be controlled - num_ctrl_qubits (int): number of controls for new unitary - - Returns: - ndarray: controlled version of base matrix. - """ - num_target = int(numpy.log2(base_mat.shape[0])) - ctrl_dim = 2**num_ctrl_qubits - ctrl_grnd = numpy.repeat([[1], [0]], [1, ctrl_dim-1]) - full_mat_dim = ctrl_dim * base_mat.shape[0] - full_mat = numpy.zeros((full_mat_dim, full_mat_dim), dtype=base_mat.dtype) - ctrl_proj = numpy.diag(numpy.roll(ctrl_grnd, ctrl_dim - 1)) - full_mat = (numpy.kron(numpy.eye(2**num_target), - numpy.eye(ctrl_dim) - ctrl_proj) - + numpy.kron(base_mat, ctrl_proj)) - return full_mat - def qasm(self): """ The qasm for a custom unitary gate This is achieved by adding a custom gate that corresponds to the definition @@ -199,6 +182,52 @@ def qasm(self): return self._qasm_definition + self._qasmif(self._qasm_name) +def _compute_control_matrix(base_mat, num_ctrl_qubits, ctrl_state=None): + r""" + Compute the controlled version of the input matrix with qiskit ordering. + This function computes the controlled unitary with :math:`n` control qubits + and :math:`m` target qubits, + + .. math:: + + V_n^j(U_{2^m}) = (U_{2^m} \otimes |j\rangle\!\langle j|) + + (I_{2^m} \otimes (I_{2^n} - |j\rangle\!\langle j|)). + + where :math:`|j\rangle \in \mathcal{H}^{2^n}` is the control state. + + Args: + base_mat (ndarray): unitary to be controlled + num_ctrl_qubits (int): number of controls for new unitary + ctrl_state (int or str or None): The control state in decimal or as + a bitstring (e.g. '111'). If None, use 2**num_ctrl_qubits-1. + + Returns: + ndarray: controlled version of base matrix. + + Raises: + QiskitError: unrecognized mode or invalid ctrl_state + """ + num_target = int(numpy.log2(base_mat.shape[0])) + ctrl_dim = 2**num_ctrl_qubits + ctrl_grnd = numpy.repeat([[1], [0]], [1, ctrl_dim-1]) + if ctrl_state is None: + ctrl_state = ctrl_dim - 1 + elif isinstance(ctrl_state, str): + ctrl_state = int(ctrl_state, 2) + if isinstance(ctrl_state, int): + if not 0 <= ctrl_state < ctrl_dim: + raise QiskitError('Invalid control state value specified.') + else: + raise QiskitError('Invalid control state type specified.') + full_mat_dim = ctrl_dim * base_mat.shape[0] + full_mat = numpy.zeros((full_mat_dim, full_mat_dim), dtype=base_mat.dtype) + ctrl_proj = numpy.diag(numpy.roll(ctrl_grnd, ctrl_state)) + full_mat = (numpy.kron(numpy.eye(2**num_target), + numpy.eye(ctrl_dim) - ctrl_proj) + + numpy.kron(base_mat, ctrl_proj)) + return full_mat + + def unitary(self, obj, qubits, label=None): """Apply u2 to q.""" if isinstance(qubits, QuantumRegister): diff --git a/qiskit/providers/basejob.py b/qiskit/providers/basejob.py index 34d4df750da2..00e141a6a315 100644 --- a/qiskit/providers/basejob.py +++ b/qiskit/providers/basejob.py @@ -19,30 +19,73 @@ """ from abc import ABC, abstractmethod +from typing import Callable, Optional +import time + +from .jobstatus import JOB_FINAL_STATES +from .exceptions import JobTimeoutError +from .basebackend import BaseBackend class BaseJob(ABC): """Class to handle asynchronous jobs""" - def __init__(self, backend, job_id): + def __init__(self, backend: BaseBackend, job_id: str) -> None: """Initializes the asynchronous job. Args: - backend (BaseBackend): the backend used to run the job. - job_id (str): a unique id in the context of the backend used to run + backend: the backend used to run the job. + job_id: a unique id in the context of the backend used to run the job. """ self._job_id = job_id self._backend = backend - def job_id(self): + def job_id(self) -> str: """Return a unique id identifying the job.""" return self._job_id - def backend(self): + def backend(self) -> BaseBackend: """Return the backend where this job was executed.""" return self._backend + def wait_for_final_state( + self, + timeout: Optional[float] = None, + wait: float = 5, + callback: Optional[Callable] = None + ) -> None: + """Poll the job status until it progresses to a final state such as ``DONE`` or ``ERROR``. + + Args: + timeout: seconds to wait for the job. If ``None``, wait indefinitely. Default: None. + wait: seconds between queries. Default: 5. + callback: callback function invoked after each query. Default: None. + The following positional arguments are provided to the callback function: + + * job_id: job ID + * job_status: status of the job from the last query + * job: this BaseJob instance + + Note: different subclass might provide different arguments to + the callback function. + + Raises: + JobTimeoutError: if the job does not reach a final state before the + specified timeout. + """ + start_time = time.time() + status = self.status() + while status not in JOB_FINAL_STATES: + elapsed_time = time.time() - start_time + if timeout is not None and elapsed_time >= timeout: + raise JobTimeoutError( + 'Timeout while waiting for job {}.'.format(self.job_id())) + if callback: + callback(self.job_id(), status, self) + time.sleep(wait) + status = self.status() + @abstractmethod def submit(self): """Submit the job to the backend for execution.""" diff --git a/qiskit/pulse/__init__.py b/qiskit/pulse/__init__.py index f75cc2fbdde1..83fc5edaf210 100644 --- a/qiskit/pulse/__init__.py +++ b/qiskit/pulse/__init__.py @@ -13,56 +13,85 @@ # that they have been altered from the originals. """ -=============================== -OpenPulse (:mod:`qiskit.pulse`) -=============================== - .. currentmodule:: qiskit.pulse -Channels -======== +=========================== +Pulse (:mod:`qiskit.pulse`) +=========================== -.. autosummary:: - :toctree: ../stubs/ +Qiskit-Pulse is a pulse-level quantum programming kit. This lower level of programming offers the +user more control than programming with :py:class:`~qiskit.circuit.QuantumCircuit` s. - DriveChannel - MeasureChannel - AcquireChannel - ControlChannel - RegisterSlot - MemorySlot +Extracting the greatest performance from quantum hardware requires real-time pulse-level +instructions. Pulse answers that need: it enables the quantum physicist *user* to specify the +exact time dynamics of an experiment. It is especially powerful for error mitigation techniques. + +The input is given as arbitrary, time-ordered signals (see: :ref:`pulse-commands`) scheduled in +parallel over multiple virtual hardware or simulator resources (see: :ref:`pulse-channels`). The +system also allows the user to recover the time dynamics of the measured output. -Commands -======== +This is sufficient to allow the quantum physicist to explore and correct for noise in a quantum +system. + +.. _pulse-commands: + +Commands (:mod:`~qiskit.pulse.commands`) +======================================== .. autosummary:: :toctree: ../stubs/ - Instruction - Acquire - AcquireInstruction - FrameChange - PersistentValue SamplePulse - Snapshot - Kernel - Discriminator Delay - ParametricPulse - ParametricInstruction + FrameChange Gaussian GaussianSquare Drag ConstantPulse + Acquire + Snapshot + +.. _pulse-channels: + +Channels (:mod:`~qiskit.pulse.channels`) +======================================== + +Pulse is meant to be agnostic to the underlying hardware implementation, while still allowing +low-level control. Therefore, our signal channels are *virtual* hardware channels. The backend +which executes our programs is responsible for mapping these virtual channels to the proper +physical channel within the quantum control hardware. + +Channels are characterized by their type and their index. See each channel type below to learn more. + +.. autosummary:: + :toctree: ../stubs/ + + DriveChannel + MeasureChannel + AcquireChannel + ControlChannel + RegisterSlot + MemorySlot Schedules ========= +Schedules are Pulse programs. They describe instruction sequences for the control hardware. +An :class:`~qiskit.pulse.Instruction` is a :py:class:`~qiskit.pulse.commands.Command` which has +been assigned to its :class:`~qiskit.pulse.channels.Channel` (s). + .. autosummary:: :toctree: ../stubs/ Schedule - ScheduleComponent + Instruction + +.. autosummary:: + :hidden: + :toctree: ../stubs/ + + qiskit.pulse.commands + qiskit.pulse.channels Configuration ============= @@ -70,9 +99,27 @@ .. autosummary:: :toctree: ../stubs/ - CmdDef - LoConfig - LoRange + InstructionScheduleMap + +Rescheduling Utilities +====================== + +These utilities return modified :class:`~qiskit.pulse.Schedule` s. + +.. autosummary:: + :toctree: ../stubs/ + + ~reschedule.align_measures + ~reschedule.add_implicit_acquires + ~reschedule.pad + +Pulse Library +============= + +.. autosummary:: + :toctree: ../stubs/ + + ~qiskit.pulse.pulse_lib.discrete Exceptions ========== @@ -81,12 +128,13 @@ :toctree: ../stubs/ PulseError + """ from .channels import (DriveChannel, MeasureChannel, AcquireChannel, ControlChannel, RegisterSlot, MemorySlot) from .cmd_def import CmdDef -from .commands import (Instruction, Acquire, AcquireInstruction, FrameChange, +from .commands import (Acquire, AcquireInstruction, FrameChange, PersistentValue, SamplePulse, Snapshot, Kernel, Discriminator, Delay, ParametricPulse, ParametricInstruction, Gaussian, @@ -94,5 +142,6 @@ from .configuration import LoConfig, LoRange from .exceptions import PulseError from .instruction_schedule_map import InstructionScheduleMap +from .instructions import Instruction from .interfaces import ScheduleComponent from .schedule import Schedule diff --git a/qiskit/pulse/channels.py b/qiskit/pulse/channels.py index 18381ab3bc79..63d5355991ef 100644 --- a/qiskit/pulse/channels.py +++ b/qiskit/pulse/channels.py @@ -15,7 +15,7 @@ """ This module defines Pulse Channels. Channels include: - - transmit channels, which should subclass``PulseChannel`` + - transmit channels, which should subclass ``PulseChannel`` - receive channels, such as ``AcquireChannel`` - non-signal "channels" such as ``SnapshotChannel``, ``MemorySlot`` and ``RegisterChannel``. @@ -31,7 +31,7 @@ class Channel(metaclass=ABCMeta): """Base class of channels. Channels provide a Qiskit-side label for typical quantum control hardware signal channels. The final label -> physical channel mapping is the responsibility - of the hardware backend. For instance,``DriveChannel(0)`` holds instructions which the backend + of the hardware backend. For instance, ``DriveChannel(0)`` holds instructions which the backend should map to the signal line driving gate operations on the qubit labeled (indexed) 0. """ diff --git a/qiskit/pulse/cmd_def.py b/qiskit/pulse/cmd_def.py index f1e661f92537..7152ad659954 100644 --- a/qiskit/pulse/cmd_def.py +++ b/qiskit/pulse/cmd_def.py @@ -13,6 +13,8 @@ # that they have been altered from the originals. """ +Deprecated. Use InstructionScheduleMap instead. + Command definition module. Relates circuit gates to pulse commands. """ import warnings diff --git a/qiskit/pulse/commands/__init__.py b/qiskit/pulse/commands/__init__.py index 6ce0900b59da..edc750b23875 100644 --- a/qiskit/pulse/commands/__init__.py +++ b/qiskit/pulse/commands/__init__.py @@ -12,7 +12,31 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""Command classes for pulse.""" +""" +Supported command types in Pulse. + +.. autosummary:: + :toctree: ../stubs/ + + Acquire + FrameChange + SamplePulse + Snapshot + Delay + Gaussian + GaussianSquare + Drag + ConstantPulse + +Abstract Classes +---------------- +.. autosummary:: + :toctree: ../stubs/ + + ParametricPulse + Command + + """ from .instruction import Instruction from .acquire import Acquire, AcquireInstruction from .frame_change import FrameChange, FrameChangeInstruction diff --git a/qiskit/pulse/commands/instruction.py b/qiskit/pulse/commands/instruction.py index 114ccf85ef20..a2e3837394ef 100644 --- a/qiskit/pulse/commands/instruction.py +++ b/qiskit/pulse/commands/instruction.py @@ -12,257 +12,7 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Instruction = Leaf node of schedule. -""" -import warnings +"""Instruction = Leaf node of schedule. Deprecated path.""" +# pylint: disable=unused-import -from typing import Tuple, List, Iterable, Callable, Optional - -from qiskit.pulse.channels import Channel -from qiskit.pulse.interfaces import ScheduleComponent -from qiskit.pulse.schedule import Schedule -from qiskit.pulse.timeslots import Interval, Timeslot, TimeslotCollection - -# pylint: disable=missing-return-doc,missing-type-doc - - -class Instruction(ScheduleComponent): - """An abstract class for leaf nodes of schedule.""" - - def __init__(self, command, *channels: List[Channel], - name: Optional[str] = None): - """Instruction initializer. - - Args: - command: Pulse command to schedule - *channels: List of pulse channels to schedule with command - name: Name of Instruction - """ - self._command = command - self._name = name if name else self._command.name - - duration = command.duration - - self._timeslots = TimeslotCollection(*(Timeslot(Interval(0, duration), channel) - for channel in channels if channel is not None)) - - channels = self.channels - - @property - def name(self) -> str: - """Name of this instruction.""" - return self._name - - @property - def command(self): - """The associated command. - - Returns: Command - """ - return self._command - - @property - def channels(self) -> Tuple[Channel]: - """Returns channels that this schedule uses.""" - return self.timeslots.channels - - @property - def timeslots(self) -> TimeslotCollection: - """Occupied time slots by this instruction.""" - return self._timeslots - - @property - def start_time(self) -> int: - """Relative begin time of this instruction.""" - return self.timeslots.start_time - - @property - def stop_time(self) -> int: - """Relative end time of this instruction.""" - return self.timeslots.stop_time - - @property - def duration(self) -> int: - """Duration of this instruction.""" - return self.timeslots.duration - - @property - def _children(self) -> Tuple[ScheduleComponent]: - """Instruction has no child nodes.""" - return () - - @property - def instructions(self) -> Tuple[Tuple[int, 'Instruction']]: - """Iterable for getting instructions from Schedule tree.""" - return tuple(self._instructions()) - - def ch_duration(self, *channels: List[Channel]) -> int: - """Return duration of the supplied channels in this Instruction. - - Args: - *channels: Supplied channels - """ - return self.timeslots.ch_duration(*channels) - - def ch_start_time(self, *channels: List[Channel]) -> int: - """Return minimum start time for supplied channels. - - Args: - *channels: Supplied channels - """ - return self.timeslots.ch_start_time(*channels) - - def ch_stop_time(self, *channels: List[Channel]) -> int: - """Return maximum start time for supplied channels. - - Args: - *channels: Supplied channels - """ - return self.timeslots.ch_stop_time(*channels) - - def _instructions(self, time: int = 0) -> Iterable[Tuple[int, 'Instruction']]: - """Iterable for flattening Schedule tree. - - Args: - time: Shifted time of this node due to parent - - Yields: - Tuple[int, ScheduleComponent]: Tuple containing time `ScheduleComponent` starts - at and the flattened `ScheduleComponent` - """ - yield (time, self) - - def flatten(self) -> 'Instruction': - """Return itself as already single instruction.""" - return self - - def union(self, *schedules: List[ScheduleComponent], name: Optional[str] = None) -> 'Schedule': - """Return a new schedule which is the union of `self` and `schedule`. - - Args: - *schedules: Schedules to be take the union with this Instruction. - name: Name of the new schedule. Defaults to name of self - """ - if name is None: - name = self.name - return Schedule(self, *schedules, name=name) - - def shift(self: ScheduleComponent, time: int, name: Optional[str] = None) -> 'Schedule': - """Return a new schedule shifted forward by `time`. - - Args: - time: Time to shift by - name: Name of the new schedule. Defaults to name of self - """ - if name is None: - name = self.name - return Schedule((time, self), name=name) - - def insert(self, start_time: int, schedule: ScheduleComponent, buffer: bool = False, - name: Optional[str] = None) -> 'Schedule': - """Return a new schedule with `schedule` inserted within `self` at `start_time`. - - Args: - start_time: Time to insert the schedule schedule - schedule: Schedule to insert - buffer: Whether to obey buffer when inserting - name: Name of the new schedule. Defaults to name of self - """ - if buffer: - warnings.warn("Buffers are no longer supported. Please use an explicit Delay.") - return self.union((start_time, schedule), name=name) - - def append(self, schedule: ScheduleComponent, buffer: bool = False, - name: Optional[str] = None) -> 'Schedule': - """Return a new schedule with `schedule` inserted at the maximum time over - all channels shared between `self` and `schedule`. - - Args: - schedule: schedule to be appended - buffer: Whether to obey buffer when appending - name: Name of the new schedule. Defaults to name of self - """ - if buffer: - warnings.warn("Buffers are no longer supported. Please use an explicit Delay.") - common_channels = set(self.channels) & set(schedule.channels) - time = self.ch_stop_time(*common_channels) - return self.insert(time, schedule, name=name) - - def draw(self, dt: float = 1, style: Optional['SchedStyle'] = None, - filename: Optional[str] = None, interp_method: Optional[Callable] = None, - scale: float = 1, channels_to_plot: Optional[List[Channel]] = None, - plot_all: bool = False, plot_range: Optional[Tuple[float]] = None, - interactive: bool = False, table: bool = True, - label: bool = False, framechange: bool = True, - scaling: float = None, - channels: Optional[List[Channel]] = None): - """Plot the instruction. - - Args: - dt: Time interval of samples - style: A style sheet to configure plot appearance - filename: Name required to save pulse image - interp_method: A function for interpolation - scale: Relative visual scaling of waveform amplitudes - channels_to_plot: Deprecated, see `channels` - plot_all: Plot empty channels - plot_range: A tuple of time range to plot - interactive: When set true show the circuit in a new window - (this depends on the matplotlib backend being used supporting this) - table: Draw event table for supported commands - label: Label individual instructions - framechange: Add framechange indicators - scaling: Deprecated, see `scale` - channels: A list of channel names to plot - - Returns: - matplotlib.figure: A matplotlib figure object of the pulse schedule - """ - # pylint: disable=invalid-name, cyclic-import - if scaling is not None: - warnings.warn('The parameter "scaling" is being replaced by "scale"', - DeprecationWarning, 3) - scale = scaling - - from qiskit import visualization - - if channels_to_plot: - warnings.warn('The parameter "channels_to_plot" is being replaced by "channels"', - DeprecationWarning, 3) - channels = channels_to_plot - - return visualization.pulse_drawer(self, dt=dt, style=style, - filename=filename, interp_method=interp_method, - scale=scale, - plot_all=plot_all, plot_range=plot_range, - interactive=interactive, table=table, - label=label, framechange=framechange, - channels=channels) - - def __eq__(self, other: 'Instruction'): - """Check if this Instruction is equal to the `other` instruction. - - Equality is determined by the instruction sharing the same command and channels. - """ - return (self.command == other.command) and (set(self.channels) == set(other.channels)) - - def __hash__(self): - return hash((self.command.__hash__(), self.channels.__hash__())) - - def __add__(self, other: ScheduleComponent) -> 'Schedule': - """Return a new schedule with `other` inserted within `self` at `start_time`.""" - return self.append(other) - - def __or__(self, other: ScheduleComponent) -> 'Schedule': - """Return a new schedule which is the union of `self` and `other`.""" - return self.union(other) - - def __lshift__(self, time: int) -> 'Schedule': - """Return a new schedule which is shifted forward by `time`.""" - return self.shift(time) - - def __repr__(self): - return "%s(%s, %s)" % (self.__class__.__name__, - self._command, - ', '.join(str(ch) for ch in self.channels)) +from qiskit.pulse.instructions import Instruction diff --git a/qiskit/pulse/instruction_schedule_map.py b/qiskit/pulse/instruction_schedule_map.py index 16247c3b4e94..7a9d1d94fbf2 100644 --- a/qiskit/pulse/instruction_schedule_map.py +++ b/qiskit/pulse/instruction_schedule_map.py @@ -15,12 +15,18 @@ """ A convenient way to track reusable subschedules by name and qubit. -This can be used for scheduling circuits with custom definitions, for instance: +This can be used for scheduling circuits with custom definitions, for instance:: inst_map = InstructionScheduleMap() inst_map.add('new_inst', 0, qubit_0_new_inst_schedule) sched = schedule(quantum_circuit, backend, inst_map) + +An instance of this class is instantiated by Pulse-enabled backends and populated with defaults +(if available):: + + inst_map = backend.defaults().instruction_schedule_map + """ import warnings @@ -32,13 +38,15 @@ class InstructionScheduleMap(): - """Mapping from QuantumCircuit Instruction names to Schedules. In particular: + """Mapping from :py:class:`~qiskit.circuit.QuantumCircuit` + :py:class:`qiskit.circuit.Instruction` names and qubits to + :py:class:`~qiskit.pulse.Schedule` s. In particular, the mapping is formatted as type:: Dict[str, Dict[Tuple[int], Schedule]] - where the first key is the name of a circuit instruction (e.g. 'u1', 'measure'), the second - key is a tuple of qubit indices, and the final value is a Schedule implementing the requested - instruction. + where the first key is the name of a circuit instruction (e.g. ``'u1'``, ``'measure'``), the + second key is a tuple of qubit indices, and the final value is a Schedule implementing the + requested instruction. """ def __init__(self): @@ -50,9 +58,10 @@ def __init__(self): @property def instructions(self) -> List[str]: - """ - Return all instructions which have definitions. By default, these are typically the basis - gates along with other instructions such as measure and reset. + """Return all instructions which have definitions. + + By default, these are typically the basis gates along with other instructions such as + measure and reset. Returns: The names of all the circuit instructions which have Schedule definitions in this. @@ -60,8 +69,7 @@ def instructions(self) -> List[str]: return list(self._map.keys()) def qubits_with_instruction(self, instruction: str) -> List[Union[int, Tuple[int]]]: - """ - Return a list of the qubits for which the given instruction is defined. Single qubit + """Return a list of the qubits for which the given instruction is defined. Single qubit instructions return a flat list, and multiqubit instructions return a list of ordered tuples. @@ -71,6 +79,7 @@ def qubits_with_instruction(self, instruction: str) -> List[Union[int, Tuple[int Returns: Qubit indices which have the given instruction defined. This is a list of tuples if the instruction has an arity greater than 1, or a flat list of ints otherwise. + Raises: PulseError: If the instruction is not found. """ @@ -80,25 +89,24 @@ def qubits_with_instruction(self, instruction: str) -> List[Union[int, Tuple[int for qubits in sorted(self._map[instruction].keys())] def qubit_instructions(self, qubits: Union[int, Iterable[int]]) -> List[str]: - """ - Return a list of the instruction names that are defined by the backend for the given qubit - or qubits. + """Return a list of the instruction names that are defined by the backend for the given + qubit or qubits. Args: qubits: A qubit index, or a list or tuple of indices. Returns: - All the instructions which are defined on the qubits. For 1 qubit, all the 1Q - instructions defined. For multiple qubits, all the instructions which apply to that - whole set of qubits (e.g. qubits=[0, 1] may return ['cx']). + All the instructions which are defined on the qubits. + + For 1 qubit, all the 1Q instructions defined. For multiple qubits, all the instructions + which apply to that whole set of qubits (e.g. ``qubits=[0, 1]`` may return ``['cx']``). """ if _to_tuple(qubits) in self._qubit_instructions: return list(self._qubit_instructions[_to_tuple(qubits)]) return [] def has(self, instruction: str, qubits: Union[int, Iterable[int]]) -> bool: - """ - Is the instruction defined for the given qubits? + """Is the instruction defined for the given qubits? Args: instruction: The instruction for which to look. @@ -111,16 +119,12 @@ def has(self, instruction: str, qubits: Union[int, Iterable[int]]) -> bool: _to_tuple(qubits) in self._map[instruction] def assert_has(self, instruction: str, qubits: Union[int, Iterable[int]]) -> None: - """ - Convenience method to check that the given instruction is defined, and error if it is not. + """Error if the given instruction is not defined. Args: instruction: The instruction for which to look. qubits: The specific qubits for the instruction. - Returns: - None - Raises: PulseError: If the instruction is not defined on the qubits. """ @@ -138,8 +142,8 @@ def get(self, qubits: Union[int, Iterable[int]], *params: List[Union[int, float, complex]], **kwparams: Dict[str, Union[int, float, complex]]) -> Schedule: - """ - Return the defined Schedule for the given instruction on the given qubits. + """Return the defined :py:class:`~qiskit.pulse.Schedule` for the given instruction on + the given qubits. Args: instruction: Name of the instruction. @@ -157,8 +161,7 @@ def get(self, return schedule def get_parameters(self, instruction: str, qubits: Union[int, Iterable[int]]) -> Tuple[str]: - """ - Return the list of parameters taken by the given instruction on the given qubits. + """Return the list of parameters taken by the given instruction on the given qubits. Args: instruction: Name of the instruction. @@ -173,18 +176,14 @@ def get_parameters(self, instruction: str, qubits: Union[int, Iterable[int]]) -> def add(self, instruction: str, qubits: Union[int, Iterable[int]], - schedule: [Schedule, ParameterizedSchedule]) -> None: - """ - Add a new known instruction for the given qubits and its mapping to a pulse schedule. + schedule: Union[Schedule, ParameterizedSchedule]) -> None: + """Add a new known instruction for the given qubits and its mapping to a pulse schedule. Args: instruction: The name of the instruction to add. qubits: The qubits which the instruction applies to. schedule: The Schedule that implements the given instruction. - Returns: - None - Raises: PulseError: If the qubits are provided as an empty iterable. """ @@ -197,14 +196,11 @@ def add(self, self._qubit_instructions[qubits].add(instruction) def remove(self, instruction: str, qubits: Union[int, Iterable[int]]) -> None: - """Remove the given instruction from the defined instructions. + """Remove the given instruction from the listing of instructions defined in self. Args: instruction: The name of the instruction to add. qubits: The qubits which the instruction applies to. - - Returns: - None """ qubits = _to_tuple(qubits) self.assert_has(instruction, qubits) @@ -220,8 +216,8 @@ def pop(self, qubits: Union[int, Iterable[int]], *params: List[Union[int, float, complex]], **kwparams: Dict[str, Union[int, float, complex]]) -> Schedule: - """ - Remove and return the defined Schedule for the given instruction on the given qubits. + """Remove and return the defined ``Schedule`` for the given instruction on the given + qubits. Args: instruction: Name of the instruction. @@ -240,8 +236,7 @@ def pop(self, return schedule def cmds(self) -> List[str]: - """ - Deprecated. + """Deprecated. Returns: The names of all the circuit instructions which have Schedule definitions in this. @@ -251,8 +246,7 @@ def cmds(self) -> List[str]: return self.instructions def cmd_qubits(self, cmd_name: str) -> List[Union[int, Tuple[int]]]: - """ - Deprecated. + """Deprecated. Args: cmd_name: The name of the circuit instruction. @@ -278,14 +272,14 @@ def __str__(self): "".format(name=self.__class__.__name__, insts=instructions)) -def _to_tuple(values): - """ - Return the input as a tuple, even if it is an integer. +def _to_tuple(values: Union[int, Iterable[int]]) -> Tuple[int, ...]: + """Return the input as a tuple. Args: - values (Union[int, Iterable[int]]): An integer, or iterable of integers. + values: An integer, or iterable of integers. + Returns: - tuple: The input values as a sorted tuple. + The input values as a sorted tuple. """ try: return tuple(values) diff --git a/qiskit/pulse/instructions/__init__.py b/qiskit/pulse/instructions/__init__.py new file mode 100644 index 000000000000..820c670c856c --- /dev/null +++ b/qiskit/pulse/instructions/__init__.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""The ``instruction`` module holds the various ``Instruction`` s which are supported by +Qiskit Pulse. Instructions accept a list of operands unique to instructions of that type. +Instructions typically include at least one :py:class:`~qiskit.pulse.channels.Channel` as an +operand specifying where the instruction will be applied, and every instruction has a duration, +whether implicitly or explicitly defined. + +An instruction can be added to a :py:class:`~qiskit.pulse.Schedule`, which is a +sequence of scheduled Pulse ``Instruction`` s over many channels. +""" +from .instruction import Instruction diff --git a/qiskit/pulse/instructions/instruction.py b/qiskit/pulse/instructions/instruction.py new file mode 100644 index 000000000000..4e3d08a4ea82 --- /dev/null +++ b/qiskit/pulse/instructions/instruction.py @@ -0,0 +1,286 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" +``Instruction`` s are single operations within a :py:class:`~qiskit.pulse.Schedule`, and can be +used the same way as :py:class:`~qiskit.pulse.Schedule` s. + +For example:: + + duration = 10 + channel = DriveChannel(0) + sched = Schedule() + sched += Delay(duration, channel) # Delay is a specific subclass of Instruction +""" +import warnings + +from abc import ABC + +from typing import Tuple, List, Iterable, Callable, Optional, Union + +from qiskit.pulse.channels import Channel +from qiskit.pulse.interfaces import ScheduleComponent +from qiskit.pulse.schedule import Schedule +from qiskit.pulse.timeslots import Interval, Timeslot, TimeslotCollection + +# pylint: disable=missing-return-doc + +# TODO: After migrating instruction implementations, add property+abstractmethod operands + + +class Instruction(ScheduleComponent, ABC): + """The smallest schedulable unit: a single instruction. It has a fixed duration and specified + channels. + """ + + def __init__(self, duration: Union['Command', int], + *channels: Channel, + name: Optional[str] = None): + """Instruction initializer. + + Args: + duration: Length of time taken by the instruction in terms of dt. + *channels: List of pulse channels that this instruction operates on. + name: Display name for this instruction. + """ + self._name = name + + self._command = None + if not isinstance(duration, int): + # TODO: Add deprecation warning once all instructions are migrated + self._command = duration + if name is None: + self._name = self.command.name + duration = self.command.duration + + self._timeslots = TimeslotCollection(*(Timeslot(Interval(0, duration), channel) + for channel in channels if channel is not None)) + + @property + def name(self) -> str: + """Name of this instruction.""" + return self._name + + @property + def command(self) -> 'Command': + """The associated command.""" + return self._command + + @property + def channels(self) -> Tuple[Channel]: + """Returns channels that this schedule uses.""" + return self.timeslots.channels + + @property + def timeslots(self) -> TimeslotCollection: + """Occupied time slots by this instruction.""" + return self._timeslots + + @property + def start_time(self) -> int: + """Relative begin time of this instruction.""" + return self.timeslots.start_time + + @property + def stop_time(self) -> int: + """Relative end time of this instruction.""" + return self.timeslots.stop_time + + @property + def duration(self) -> int: + """Duration of this instruction.""" + return self.timeslots.duration + + @property + def _children(self) -> Tuple[ScheduleComponent]: + """Instruction has no child nodes.""" + return () + + @property + def instructions(self) -> Tuple[Tuple[int, 'Instruction']]: + """Iterable for getting instructions from Schedule tree.""" + return tuple(self._instructions()) + + def ch_duration(self, *channels: List[Channel]) -> int: + """Return duration of the supplied channels in this Instruction. + + Args: + *channels: Supplied channels + """ + return self.timeslots.ch_duration(*channels) + + def ch_start_time(self, *channels: List[Channel]) -> int: + """Return minimum start time for supplied channels. + + Args: + *channels: Supplied channels + """ + return self.timeslots.ch_start_time(*channels) + + def ch_stop_time(self, *channels: List[Channel]) -> int: + """Return maximum start time for supplied channels. + + Args: + *channels: Supplied channels + """ + return self.timeslots.ch_stop_time(*channels) + + def _instructions(self, time: int = 0) -> Iterable[Tuple[int, 'Instruction']]: + """Iterable for flattening Schedule tree. + + Args: + time: Shifted time of this node due to parent + + Yields: + Tuple[int, ScheduleComponent]: Tuple containing time `ScheduleComponent` starts + at and the flattened `ScheduleComponent` + """ + yield (time, self) + + def flatten(self) -> 'Instruction': + """Return itself as already single instruction.""" + return self + + def union(self, *schedules: List[ScheduleComponent], name: Optional[str] = None) -> 'Schedule': + """Return a new schedule which is the union of `self` and `schedule`. + + Args: + *schedules: Schedules to be take the union with this Instruction. + name: Name of the new schedule. Defaults to name of self + """ + if name is None: + name = self.name + return Schedule(self, *schedules, name=name) + + def shift(self: ScheduleComponent, time: int, name: Optional[str] = None) -> 'Schedule': + """Return a new schedule shifted forward by `time`. + + Args: + time: Time to shift by + name: Name of the new schedule. Defaults to name of self + """ + if name is None: + name = self.name + return Schedule((time, self), name=name) + + def insert(self, start_time: int, schedule: ScheduleComponent, + name: Optional[str] = None) -> 'Schedule': + """Return a new :class:`~qiskit.pulse.Schedule` with ``schedule`` inserted within + ``self`` at ``start_time``. + + Args: + start_time: Time to insert the schedule schedule + schedule: Schedule to insert + name: Name of the new schedule. Defaults to name of self + """ + return self.union((start_time, schedule), name=name) + + def append(self, schedule: ScheduleComponent, + name: Optional[str] = None) -> 'Schedule': + """Return a new :class:`~qiskit.pulse.Schedule` with ``schedule`` inserted at the + maximum time over all channels shared between ``self`` and ``schedule``. + + Args: + schedule: schedule to be appended + name: Name of the new schedule. Defaults to name of self + """ + common_channels = set(self.channels) & set(schedule.channels) + time = self.ch_stop_time(*common_channels) + return self.insert(time, schedule, name=name) + + def draw(self, dt: float = 1, style: Optional['SchedStyle'] = None, + filename: Optional[str] = None, interp_method: Optional[Callable] = None, + scale: float = 1, channels_to_plot: Optional[List[Channel]] = None, + plot_all: bool = False, plot_range: Optional[Tuple[float]] = None, + interactive: bool = False, table: bool = True, + label: bool = False, framechange: bool = True, + scaling: float = None, + channels: Optional[List[Channel]] = None): + """Plot the instruction. + + Args: + dt: Time interval of samples + style: A style sheet to configure plot appearance + filename: Name required to save pulse image + interp_method: A function for interpolation + scale: Relative visual scaling of waveform amplitudes + channels_to_plot: Deprecated, see `channels` + plot_all: Plot empty channels + plot_range: A tuple of time range to plot + interactive: When set true show the circuit in a new window + (this depends on the matplotlib backend being used supporting this) + table: Draw event table for supported instructions + label: Label individual instructions + framechange: Add framechange indicators + scaling: Deprecated, see `scale` + channels: A list of channel names to plot + + Returns: + matplotlib.figure: A matplotlib figure object of the pulse schedule + """ + # pylint: disable=invalid-name, cyclic-import + if scaling is not None: + warnings.warn('The parameter "scaling" is being replaced by "scale"', + DeprecationWarning, 3) + scale = scaling + + from qiskit import visualization + + if channels_to_plot: + warnings.warn('The parameter "channels_to_plot" is being replaced by "channels"', + DeprecationWarning, 3) + channels = channels_to_plot + + return visualization.pulse_drawer(self, dt=dt, style=style, + filename=filename, interp_method=interp_method, + scale=scale, + plot_all=plot_all, plot_range=plot_range, + interactive=interactive, table=table, + label=label, framechange=framechange, + channels=channels) + + def __eq__(self, other: 'Instruction'): + """Check if this Instruction is equal to the `other` instruction. + + Equality is determined by the instruction sharing the same operands and channels. + """ + if self.command: + # Backwards compatibility for Instructions with Commands + return (self.command == other.command) and (set(self.channels) == set(other.channels)) + return ((self.duration == other.duration) and + (set(self.channels) == set(other.channels)) and + (isinstance(other, type(self)))) + + def __hash__(self): + if self.command: + # Backwards compatibility for Instructions with Commands + return hash((hash(tuple(self.command)), self.channels.__hash__())) + return hash((hash(tuple(self.duration)), self.channels.__hash__())) + + def __add__(self, other: ScheduleComponent) -> 'Schedule': + """Return a new schedule with `other` inserted within `self` at `start_time`.""" + return self.append(other) + + def __or__(self, other: ScheduleComponent) -> 'Schedule': + """Return a new schedule which is the union of `self` and `other`.""" + return self.union(other) + + def __lshift__(self, time: int) -> 'Schedule': + """Return a new schedule which is shifted forward by `time`.""" + return self.shift(time) + + def __repr__(self): + return "%s(%s, %s)" % (self.__class__.__name__, + self.command if self.command else self.duration, + ', '.join(str(ch) for ch in self.channels)) diff --git a/qiskit/pulse/interfaces.py b/qiskit/pulse/interfaces.py index 31fef1ef5dd9..0210ca14b0da 100644 --- a/qiskit/pulse/interfaces.py +++ b/qiskit/pulse/interfaces.py @@ -15,8 +15,6 @@ """ ScheduleComponent, a common interface for components of schedule (Instruction and Schedule). """ -import warnings - from abc import ABCMeta, abstractmethod from typing import Tuple, List, Union, Optional @@ -54,12 +52,6 @@ def start_time(self) -> int: """Starting time of this schedule component.""" pass - @property - def buffer(self) -> int: - """Buffer for schedule. To be used when appending""" - warnings.warn("Buffers are no longer supported. Please use an explicit Delay.") - return 0 - @property @abstractmethod def stop_time(self) -> int: @@ -128,27 +120,24 @@ def shift(self: 'ScheduleComponent', time: int, @abstractmethod def insert(self, start_time: int, schedule: 'ScheduleComponent', - buffer: bool = False, name: Optional[str] = None) -> 'ScheduleComponent': """Return a new schedule with `schedule` inserted at `start_time` of `self`. Args: start_time: time to be inserted schedule: schedule to be inserted - buffer: Obey buffer when appending name: Name of the new schedule. Defaults to name of parent """ pass @abstractmethod - def append(self, schedule: 'ScheduleComponent', buffer: bool = False, + def append(self, schedule: 'ScheduleComponent', name: Optional[str] = None) -> 'ScheduleComponent': """Return a new schedule with `schedule` inserted at the maximum time over all channels shared between `self` and `schedule`. Args: schedule: schedule to be appended - buffer: Obey buffer when appending name: Name of the new schedule. Defaults to name of parent """ pass diff --git a/qiskit/pulse/parser.py b/qiskit/pulse/parser.py index 8b78ff2ba432..3000903ec740 100644 --- a/qiskit/pulse/parser.py +++ b/qiskit/pulse/parser.py @@ -14,8 +14,8 @@ # pylint: disable=invalid-name -"""Helper function to parse string expression given by backends.""" - +"""Parser for mathematical string expressions returned by backends.""" +from typing import Dict, List, Union import ast import copy import operator @@ -26,9 +26,8 @@ class PulseExpression(ast.NodeTransformer): - """Expression parser to evaluate parameter values. - """ - # valid functions + """Expression parser to evaluate parameter values.""" + _math_ops = { 'acos': cmath.acos, 'acosh': cmath.acosh, @@ -49,8 +48,8 @@ class PulseExpression(ast.NodeTransformer): 'pi': cmath.pi, 'e': cmath.e } + """Valid math functions.""" - # valid binary operations _binary_ops = { ast.Add: operator.add, ast.Sub: operator.sub, @@ -58,19 +57,20 @@ class PulseExpression(ast.NodeTransformer): ast.Div: operator.truediv, ast.Pow: operator.pow } + """Valid binary operations.""" - # valid unary operations _unary_ops = { ast.UAdd: operator.pos, ast.USub: operator.neg } + """Valid unary operations.""" - def __init__(self, source, partial_binding=False): + def __init__(self, source: Union[str, ast.Expression], partial_binding: bool = False): """Create new evaluator. Args: - source (str or ast.Expression): Expression of equation to evaluate. - partial_binding (bool): Allow partial bind of parameters. + source: Expression of equation to evaluate. + partial_binding: Allow partial bind of parameters. Raises: PulseError: When invalid string is specified. @@ -91,19 +91,23 @@ def __init__(self, source, partial_binding=False): self.visit(self._tree) @property - def params(self): - """Get parameters.""" + def params(self) -> List[str]: + """Get parameters. + + Returns: + A list of parameters in sorted order. + """ return sorted(self._params.copy()) - def __call__(self, *args, **kwargs): - """Get evaluated value with given parameters. + def __call__(self, *args, **kwargs) -> Union[float, complex, ast.Expression]: + """Evaluate the expression with the given values of the expression's parameters. Args: *args: Variable length parameter list. **kwargs: Arbitrary parameters. Returns: - float or complex or ast: Evaluated value. + Evaluated value. Raises: PulseError: When parameters are not bound. @@ -137,16 +141,16 @@ def __call__(self, *args, **kwargs): return expr.body.n @staticmethod - def _match_ops(opr, opr_dict, *args): + def _match_ops(opr: ast.AST, opr_dict: Dict, *args) -> Union[float, complex]: """Helper method to apply operators. Args: - opr (ast.AST): Operator of node. - opr_dict (dict): Mapper from ast to operator. + opr: Operator of node. + opr_dict: Mapper from ast to operator. *args: Arguments supplied to operator. Returns: - float or complex: Evaluated value. + Evaluated value. Raises: PulseError: When unsupported operation is specified. @@ -156,39 +160,39 @@ def _match_ops(opr, opr_dict, *args): return op_func(*args) raise PulseError('Operator %s is not supported.' % opr.__class__.__name__) - def visit_Expression(self, node): + def visit_Expression(self, node: ast.Expression) -> ast.Expression: """Evaluate children nodes of expression. Args: - node (ast.Expression): Expression to evaluate. + node: Expression to evaluate. Returns: - ast.Expression: Evaluated value. + Evaluated value. """ tmp_node = copy.deepcopy(node) tmp_node.body = self.visit(tmp_node.body) return tmp_node - def visit_Num(self, node): + def visit_Num(self, node: ast.Num) -> ast.Num: """Return number as it is. Args: - node (ast.Num): Number. + node: Number. Returns: - ast.Num: Number to return. + Input node. """ return node - def visit_Name(self, node): + def visit_Name(self, node: ast.Name) -> Union[ast.Name, ast.Num]: """Evaluate name and return ast.Num if it is bound. Args: - node (ast.Name): Name to evaluate. + node: Name to evaluate. Returns: - ast.Name or ast.Num: Evaluated value. + Evaluated value. Raises: PulseError: When parameter value is not a number. @@ -210,14 +214,14 @@ def visit_Name(self, node): self._params.add(node.id) return node - def visit_UnaryOp(self, node): + def visit_UnaryOp(self, node: ast.UnaryOp) -> Union[ast.UnaryOp, ast.Num]: """Evaluate unary operation and return ast.Num if operand is bound. Args: - node (ast.UnaryOp): Unary operation to evaluate. + node: Unary operation to evaluate. Returns: - ast.UnaryOp or ast.Num: Evaluated value. + Evaluated value. """ node.operand = self.visit(node.operand) if isinstance(node.operand, ast.Num): @@ -226,14 +230,14 @@ def visit_UnaryOp(self, node): return ast.copy_location(val, node) return node - def visit_BinOp(self, node): + def visit_BinOp(self, node: ast.BinOp) -> Union[ast.BinOp, ast.Num]: """Evaluate binary operation and return ast.Num if operands are bound. Args: - node (ast.BinOp): Binary operation to evaluate. + node: Binary operation to evaluate. Returns: - ast.BinOp or ast.Num: Evaluated value. + Evaluated value. """ node.left = self.visit(node.left) node.right = self.visit(node.right) @@ -243,14 +247,14 @@ def visit_BinOp(self, node): return ast.copy_location(val, node) return node - def visit_Call(self, node): + def visit_Call(self, node: ast.Call) -> Union[ast.Call, ast.Num]: """Evaluate function and return ast.Num if all arguments are bound. Args: - node (ast.Call): Function to evaluate. + node: Function to evaluate. Returns: - ast.Call or ast.Num: Evaluated value. + Evaluated value. Raises: PulseError: When unsupported or unsafe function is specified. @@ -273,20 +277,31 @@ def generic_visit(self, node): raise PulseError('Unsupported node: %s' % node.__class__.__name__) -def parse_string_expr(source, partial_binding=False): +def parse_string_expr(source: str, partial_binding: bool = False): """Safe parsing of string expression. Args: - source (str): String expression to parse. - partial_binding (bool): Allow partial bind of parameters. + source: String expression to parse. + partial_binding: Allow partial bind of parameters. Returns: PulseExpression: Returns a expression object. + + Example: + + expr = 'P1 + P2 + P3' + parsed_expr = parse_string_expr(expr, partial_binding=True) + + # create new PulseExpression + bound_two = parsed_expr(P1=1, P2=2) + # evaluate expression + value1 = bound_two(P3=3) + value2 = bound_two(P3=4) + value3 = bound_two(P3=5) + """ subs = [('numpy.', ''), ('np.', ''), ('math.', ''), ('cmath.', '')] for match, sub in subs: source = source.replace(match, sub) - expression = PulseExpression(source, partial_binding) - - return expression + return PulseExpression(source, partial_binding) diff --git a/qiskit/pulse/pulse_lib/__init__.py b/qiskit/pulse/pulse_lib/__init__.py index d7a0b99b9192..dd0e35a95fa8 100644 --- a/qiskit/pulse/pulse_lib/__init__.py +++ b/qiskit/pulse/pulse_lib/__init__.py @@ -12,6 +12,6 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""Module for builtin pulse_lib.""" +"""Module for builtin ``pulse_lib``.""" from .discrete import * diff --git a/qiskit/pulse/pulse_lib/continuous.py b/qiskit/pulse/pulse_lib/continuous.py index 519a907d9bb2..7a2601474acc 100644 --- a/qiskit/pulse/pulse_lib/continuous.py +++ b/qiskit/pulse/pulse_lib/continuous.py @@ -17,6 +17,7 @@ """Module for builtin continuous pulse functions.""" import functools +import warnings from typing import Union, Tuple, Optional import numpy as np @@ -42,42 +43,82 @@ def zero(times: np.ndarray) -> np.ndarray: return constant(times, 0) -def square(times: np.ndarray, amp: complex, period: float, phase: float = 0) -> np.ndarray: +def square(times: np.ndarray, amp: complex, freq: float = None, period: float = None, + phase: float = 0) -> np.ndarray: """Continuous square wave. Args: times: Times to output wave for. amp: Pulse amplitude. Wave range is [-amp, amp]. - period: Pulse period, units of dt. + freq: Pulse frequency. units of 1/dt. + period: Pulse period. units of dt. (Deprecated, use freq instead) phase: Pulse phase. + Raises: + ValueError: If both `freq` and `period` are given, or if both are None """ - x = times/period+phase/np.pi + if (freq is None) and (period is None): + raise ValueError('Both `freq` and `period` cannot be None') + if (freq is not None) and (period is not None): + raise ValueError('Both `freq` and `period` cannot be given') + if freq is None: + freq = 1./period + warnings.warn("The argument `period` is being deprecated." + " Use `freq` for frequency instead", + DeprecationWarning) + x = times*freq+phase/np.pi return amp*(2*(2*np.floor(x) - np.floor(2*x)) + 1).astype(np.complex_) -def sawtooth(times: np.ndarray, amp: complex, period: float, phase: float = 0) -> np.ndarray: +def sawtooth(times: np.ndarray, amp: complex, freq: float = None, period: float = None, + phase: float = 0) -> np.ndarray: """Continuous sawtooth wave. Args: times: Times to output wave for. amp: Pulse amplitude. Wave range is [-amp, amp]. - period: Pulse period, units of dt. + freq: Pulse frequency. units of 1/dt. + period: Pulse period. units of dt. (Deprecated, use freq instead) phase: Pulse phase. + Raises: + ValueError: If both `freq` and `period` are given, or if both are None """ - x = times/period+phase/np.pi + if (freq is None) and (period is None): + raise ValueError('Both `freq` and `period` cannot be None') + if (freq is not None) and (period is not None): + raise ValueError('Both `freq` and `period` cannot be given') + if freq is None: + freq = 1./period + warnings.warn("The argument `period` is being deprecated." + " Use `freq` for frequency instead", + DeprecationWarning) + x = times*freq+phase/np.pi return amp*2*(x-np.floor(1/2+x)).astype(np.complex_) -def triangle(times: np.ndarray, amp: complex, period: float, phase: float = 0) -> np.ndarray: +def triangle(times: np.ndarray, amp: complex, freq: float = None, period: float = None, + phase: float = 0) -> np.ndarray: """Continuous triangle wave. Args: times: Times to output wave for. amp: Pulse amplitude. Wave range is [-amp, amp]. - period: Pulse period, units of dt. + freq: Pulse frequency. units of 1/dt. + period: Pulse period. units of dt. (Deprecated, use freq instead) phase: Pulse phase. + Raises: + ValueError: If both `freq` and `period` are given, or if both are None """ - return amp*(-2*np.abs(sawtooth(times, 1, period, (phase-np.pi/2)/2)) + 1).astype(np.complex_) + if (freq is None) and (period is None): + raise ValueError('Both `freq` and `period` cannot be None') + if (freq is not None) and (period is not None): + raise ValueError('Both `freq` and `period` cannot be given') + if freq is None: + freq = 1./period + warnings.warn("The argument `period` is being deprecated." + " Use `freq` for frequency instead", + DeprecationWarning) + return amp*(-2*np.abs( + sawtooth(times, 1, freq, phase=(phase-np.pi/2)/2)) + 1).astype(np.complex_) def cos(times: np.ndarray, amp: complex, freq: float, phase: float = 0) -> np.ndarray: diff --git a/qiskit/pulse/pulse_lib/discrete.py b/qiskit/pulse/pulse_lib/discrete.py index 06950b0ca3d6..6e65d4bbbc20 100644 --- a/qiskit/pulse/pulse_lib/discrete.py +++ b/qiskit/pulse/pulse_lib/discrete.py @@ -18,6 +18,7 @@ Note the sampling strategy use for all discrete pulses is `midpoint`. """ +import warnings from typing import Optional from qiskit.pulse.pulse_lib import continuous @@ -30,9 +31,13 @@ def constant(duration: int, amp: complex, name: Optional[str] = None) -> 'SamplePulse': - """Generates constant-sampled `SamplePulse`. + r"""Generates constant-sampled :class:`~qiskit.pulse.SamplePulse`. - Applies `midpoint` sampling strategy to generate discrete pulse from continuous function. + For :math:`A=` ``amp``, samples from the function: + + .. math:: + + f(x) = A Args: duration: Duration of pulse. Must be greater than zero. @@ -46,7 +51,13 @@ def constant(duration: int, amp: complex, name: Optional[str] = None) -> 'Sample def zero(duration: int, name: Optional[str] = None) -> 'SamplePulse': - """Generates zero-sampled `SamplePulse`. + """Generates zero-sampled :class:`~qiskit.pulse.SamplePulse`. + + Samples from the function: + + .. math:: + + f(x) = 0 Args: duration: Duration of pulse. Must be greater than zero. @@ -58,65 +69,132 @@ def zero(duration: int, name: Optional[str] = None) -> 'SamplePulse': _sampled_square_pulse = samplers.midpoint(continuous.square) -def square(duration: int, amp: complex, period: float = None, +def square(duration: int, amp: complex, freq: float = None, period: float = None, phase: float = 0, name: Optional[str] = None) -> 'SamplePulse': - """Generates square wave `SamplePulse`. + r"""Generates square wave :class:`~qiskit.pulse.SamplePulse`. + + For :math:`A=` ``amp``, :math:`T=` ``period``, and :math:`\phi=` ``phase``, + applies the `midpoint` sampling strategy to generate a discrete pulse sampled from + the continuous function: + + .. math:: + + f(x) = A \text{sign}\left[ \sin\left(\frac{2 \pi x}{T} + 2\phi\right) \right] + + with the convention :math:`\text{sign}(0) = 1`. - Applies `midpoint` sampling strategy to generate discrete pulse from continuous function. Args: duration: Duration of pulse. Must be greater than zero. - amp: Pulse amplitude. Wave range is [-amp, amp]. - period: Pulse period, units of dt. If `None` defaults to single cycle. + amp: Pulse amplitude. Wave range is :math:`[-` ``amp`` :math:`,` ``amp`` :math:`]`. + freq: Pulse frequency, units of 1./dt. If ``None`` defaults to 1./duration. + period: Pulse period, units of dt. (Deprecated, use `freq` instead) phase: Pulse phase. name: Name of pulse. """ - if period is None: - period = duration + if (freq is None) and (period is None): + freq = 1./duration + elif freq is None: + freq = 1./period + warnings.warn("The argument `period` is being deprecated." + " Use `freq` for frequency instead", + DeprecationWarning) - return _sampled_square_pulse(duration, amp, period, phase=phase, name=name) + return _sampled_square_pulse(duration, amp, freq, phase=phase, name=name) _sampled_sawtooth_pulse = samplers.midpoint(continuous.sawtooth) -def sawtooth(duration: int, amp: complex, period: float = None, +def sawtooth(duration: int, amp: complex, freq: float = None, period: float = None, phase: float = 0, name: Optional[str] = None) -> 'SamplePulse': - """Generates sawtooth wave `SamplePulse`. + r"""Generates sawtooth wave :class:`~qiskit.pulse.SamplePulse`. + + For :math:`A=` ``amp``, :math:`T=` ``period``, and :math:`\phi=` ``phase``, + applies the `midpoint` sampling strategy to generate a discrete pulse sampled from + the continuous function: + + .. math:: + + f(x) = 2 A \left( g(x) - \left\lfloor \frac{1}{2} + g(x) \right\rfloor\right) + + where :math:`g(x) = x/T + \phi/\pi`. Args: duration: Duration of pulse. Must be greater than zero. - amp: Pulse amplitude. Wave range is [-amp, amp]. - period: Pulse period, units of dt. If `None` defaults to single cycle. + amp: Pulse amplitude. Wave range is :math:`[-` ``amp`` :math:`,` ``amp`` :math:`]`. + freq: Pulse frequency, units of 1./dt. If ``None`` defaults to 1./duration. + period: Pulse period, units of dt. (Deprecated, use `freq` instead) phase: Pulse phase. name: Name of pulse. + + Example: + .. jupyter-execute:: + + import matplotlib.pyplot as plt + from qiskit.pulse.pulse_lib import sawtooth + + duration = 100 + amp = 1 + period = duration + plt.plot(range(duration), sawtooth(duration, amp, period).samples) """ - if period is None: - period = duration + if (freq is None) and (period is None): + freq = 1./duration + elif freq is None: + freq = 1./period + warnings.warn("The argument `period` is being deprecated." + " Use `freq` for frequency instead", + DeprecationWarning) - return _sampled_sawtooth_pulse(duration, amp, period, phase=phase, name=name) + return _sampled_sawtooth_pulse(duration, amp, freq, phase=phase, name=name) _sampled_triangle_pulse = samplers.midpoint(continuous.triangle) -def triangle(duration: int, amp: complex, period: float = None, +def triangle(duration: int, amp: complex, freq: float = None, period: float = None, phase: float = 0, name: Optional[str] = None) -> 'SamplePulse': - """Generates triangle wave `SamplePulse`. + r"""Generates triangle wave :class:`~qiskit.pulse.SamplePulse`. + + For :math:`A=` ``amp``, :math:`T=` ``period``, and :math:`\phi=` ``phase``, + applies the `midpoint` sampling strategy to generate a discrete pulse sampled from + the continuous function: + + .. math:: + + f(x) = A \left(-2\left|\text{sawtooth}(x, A, T, \phi)\right| + 1\right) - Applies `midpoint` sampling strategy to generate discrete pulse from continuous function. + This a non-sinusoidal wave with linear ramping. Args: duration: Duration of pulse. Must be greater than zero. - amp: Pulse amplitude. Wave range is [-amp, amp]. - period: Pulse period, units of dt. If `None` defaults to single cycle. + amp: Pulse amplitude. Wave range is :math:`[-` ``amp`` :math:`,` ``amp`` :math:`]`. + freq: Pulse frequency, units of 1./dt. If ``None`` defaults to 1./duration. + period: Pulse period, units of dt. (Deprecated, use `freq` instead) phase: Pulse phase. name: Name of pulse. + + Example: + .. jupyter-execute:: + + import matplotlib.pyplot as plt + from qiskit.pulse.pulse_lib import triangle + + duration = 100 + amp = 1 + period = duration + plt.plot(range(duration), triangle(duration, amp, period).samples) """ - if period is None: - period = duration + if (freq is None) and (period is None): + freq = 1./duration + elif freq is None: + freq = 1./period + warnings.warn("The argument `period` is being deprecated." + " Use `freq` for frequency instead", + DeprecationWarning) - return _sampled_triangle_pulse(duration, amp, period, phase=phase, name=name) + return _sampled_triangle_pulse(duration, amp, freq, phase=phase, name=name) _sampled_cos_pulse = samplers.midpoint(continuous.cos) @@ -124,14 +202,20 @@ def triangle(duration: int, amp: complex, period: float = None, def cos(duration: int, amp: complex, freq: float = None, phase: float = 0, name: Optional[str] = None) -> 'SamplePulse': - """Generates cosine wave `SamplePulse`. + r"""Generates cosine wave :class:`~qiskit.pulse.SamplePulse`. - Applies `midpoint` sampling strategy to generate discrete pulse from continuous function. + For :math:`A=` ``amp``, :math:`\omega=` ``freq``, and :math:`\phi=` ``phase``, + applies the `midpoint` sampling strategy to generate a discrete pulse sampled from + the continuous function: + + .. math:: + + f(x) = A \cos(2 \pi \omega x + \phi) Args: duration: Duration of pulse. Must be greater than zero. amp: Pulse amplitude. - freq: Pulse frequency, units of 1/dt. If `None` defaults to single cycle. + freq: Pulse frequency, units of 1/dt. If ``None`` defaults to single cycle. phase: Pulse phase. name: Name of pulse. """ @@ -146,12 +230,20 @@ def cos(duration: int, amp: complex, freq: float = None, def sin(duration: int, amp: complex, freq: float = None, phase: float = 0, name: Optional[str] = None) -> 'SamplePulse': - """Generates sine wave `SamplePulse`. + r"""Generates sine wave :class:`~qiskit.pulse.SamplePulse`. + + For :math:`A=` ``amp``, :math:`\omega=` ``freq``, and :math:`\phi=` ``phase``, + applies the `midpoint` sampling strategy to generate a discrete pulse sampled from + the continuous function: + + .. math:: + + f(x) = A \sin(2 \pi \omega x + \phi) Args: duration: Duration of pulse. Must be greater than zero. amp: Pulse amplitude. - freq: Pulse frequency, units of 1/dt. If `None` defaults to single cycle. + freq: Pulse frequency, units of 1/dt. If ``None`` defaults to single cycle. phase: Pulse phase. name: Name of pulse. """ @@ -166,18 +258,32 @@ def sin(duration: int, amp: complex, freq: float = None, def gaussian(duration: int, amp: complex, sigma: float, name: Optional[str] = None, zero_ends: bool = True) -> 'SamplePulse': - r"""Generates unnormalized gaussian `SamplePulse`. + r"""Generates unnormalized gaussian :class:`~qiskit.pulse.SamplePulse`. + + For :math:`A=` ``amp`` and :math:`\sigma=` ``sigma``, applies the `midpoint` sampling strategy + to generate a discrete pulse sampled from the continuous function: + + .. math:: - Centered at `duration/2` and zeroed at `t=0` and `t=duration` to prevent large - initial/final discontinuities. + f(x) = A\exp\left(\left(\frac{x - \mu}{2\sigma}\right)^2 \right), - Applies `midpoint` sampling strategy to generate discrete pulse from continuous function. + with the center :math:`\mu=` ``duration/2``. - Integrated area under curve is $\Omega_g(amp, sigma) = amp \times np.sqrt(2\pi \sigma^2)$ + If ``zero_ends==True``, each output sample :math:`y` is modifed according to: + + .. math:: + + y \mapsto A\frac{y-y^*}{A-y^*}, + + where :math:`y^*` is the value of the endpoint samples. This sets the endpoints + to :math:`0` while preserving the amplitude at the center. If :math:`A=y^*`, + :math:`y` is set to :math:`1`. + + Integrated area under the full curve is ``amp * np.sqrt(2*np.pi*sigma**2)`` Args: duration: Duration of pulse. Must be greater than zero. - amp: Pulse amplitude at `duration/2`. + amp: Pulse amplitude at ``duration/2``. sigma: Width (standard deviation) of pulse. name: Name of pulse. zero_ends: If True, make the first and last sample zero, but rescale to preserve amp. @@ -195,13 +301,20 @@ def gaussian(duration: int, amp: complex, sigma: float, name: Optional[str] = No def gaussian_deriv(duration: int, amp: complex, sigma: float, name: Optional[str] = None) -> 'SamplePulse': - r"""Generates unnormalized gaussian derivative `SamplePulse`. + r"""Generates unnormalized gaussian derivative :class:`~qiskit.pulse.SamplePulse`. + + For :math:`A=` ``amp`` and :math:`\sigma=` ``sigma`` applies the `midpoint` sampling strategy + to generate a discrete pulse sampled from the continuous function: + + .. math:: - Applies `midpoint` sampling strategy to generate discrete pulse from continuous function. + f(x) = A\frac{(x - \mu)}{\sigma^2}\exp\left(\left(\frac{x - \mu}{2\sigma}\right)^2 \right) + + i.e. the derivative of the Gaussian function, with center :math:`\mu=` ``duration/2``. Args: duration: Duration of pulse. Must be greater than zero. - amp: Pulse amplitude at `center`. + amp: Pulse amplitude of corresponding Gaussian at the pulse center (``duration/2``). sigma: Width (standard deviation) of pulse. name: Name of pulse. """ @@ -214,11 +327,26 @@ def gaussian_deriv(duration: int, amp: complex, sigma: float, def sech(duration: int, amp: complex, sigma: float, name: str = None, zero_ends: bool = True) -> 'SamplePulse': - r"""Generates unnormalized sech `SamplePulse`. + r"""Generates unnormalized sech :class:`~qiskit.pulse.SamplePulse`. + + For :math:`A=` ``amp`` and :math:`\sigma=` ``sigma``, applies the `midpoint` sampling strategy + to generate a discrete pulse sampled from the continuous function: + + .. math:: + + f(x) = A\text{sech}\left(\frac{x-\mu}{\sigma} \right) + + with the center :math:`\mu=` ``duration/2``. + + If ``zero_ends==True``, each output sample :math:`y` is modifed according to: - Centered at `duration/2` and zeroed at `t=0` to prevent large initial discontinuity. + .. math:: - Applies `midpoint` sampling strategy to generate discrete pulse from continuous function. + y \mapsto A\frac{y-y^*}{A-y^*}, + + where :math:`y^*` is the value of the endpoint samples. This sets the endpoints + to :math:`0` while preserving the amplitude at the center. If :math:`A=y^*`, + :math:`y` is set to :math:`1`. Args: duration: Duration of pulse. Must be greater than zero. @@ -239,9 +367,16 @@ def sech(duration: int, amp: complex, sigma: float, name: str = None, def sech_deriv(duration: int, amp: complex, sigma: float, name: str = None) -> 'SamplePulse': - r"""Generates unnormalized sech derivative `SamplePulse`. + r"""Generates unnormalized sech derivative :class:`~qiskit.pulse.SamplePulse`. + + For :math:`A=` ``amp``, :math:`\sigma=` ``sigma``, and center :math:`\mu=` ``duration/2``, + applies the `midpoint` sampling strategy to generate a discrete pulse sampled from + the continuous function: - Applies `midpoint` sampling strategy to generate discrete pulse from continuous function. + .. math:: + f(x) = \frac{d}{dx}\left[A\text{sech}\left(\frac{x-\mu}{\sigma} \right)\right], + + i.e. the derivative of :math:`\text{sech}`. Args: duration: Duration of pulse. Must be greater than zero. @@ -259,25 +394,40 @@ def sech_deriv(duration: int, amp: complex, sigma: float, name: str = None) -> ' def gaussian_square(duration: int, amp: complex, sigma: float, risefall: Optional[float] = None, width: Optional[float] = None, name: Optional[str] = None, zero_ends: bool = True) -> 'SamplePulse': - """Generates gaussian square `SamplePulse`. + r"""Generates gaussian square :class:`~qiskit.pulse.SamplePulse`. + + For :math:`d=` ``duration``, :math:`A=` ``amp``, :math:`\sigma=` ``sigma``, + and :math:`r=` ``risefall``, applies the `midpoint` sampling strategy to + generate a discrete pulse sampled from the continuous function: - Centered at `duration/2` and zeroed at `t=0` and `t=duration` to prevent - large initial/final discontinuities. + .. math:: - Applies `midpoint` sampling strategy to generate discrete pulse from continuous function. + f(x) = \begin{cases} + g(x - r) ) & x\leq r \\ + A & r\leq x\leq d-r \\ + g(x - (d - r)) & d-r\leq x + \end{cases} + + where :math:`g(x)` is the Gaussian function sampled from in :meth:`gaussian` + with :math:`A=` ``amp``, :math:`\mu=1`, and :math:`\sigma=` ``sigma``. I.e. + :math:`f(x)` represents a square pulse with smooth Gaussian edges. + + If ``zero_ends == True``, the samples for the Gaussian ramps are remapped as in + :meth:`gaussian`. Args: duration: Duration of pulse. Must be greater than zero. amp: Pulse amplitude. sigma: Width (standard deviation) of Gaussian rise/fall portion of the pulse. risefall: Number of samples over which pulse rise and fall happen. Width of - square portion of pulse will be `duration-2*risefall`. - width: The duration of the embedded square pulse. Only one of `width` or `risefall` - should be specified since width = duration - 2 * risefall. + square portion of pulse will be ``duration-2*risefall``. + width: The duration of the embedded square pulse. Only one of ``width`` or ``risefall`` + should be specified as the functional form requires + ``width = duration - 2 * risefall``. name: Name of pulse. - zero_ends: If True, make the first and last sample zero, but rescale to preserve amp. + zero_ends: If ``True``, make the first and last sample zero, but rescale to preserve amp. Raises: - PulseError: If risefall and width arguments are inconsistent or not enough info. + PulseError: If ``risefall`` and ``width`` arguments are inconsistent or not enough info. """ if risefall is None and width is None: raise PulseError("gaussian_square missing required argument: 'width' or 'risefall'.") @@ -299,25 +449,41 @@ def gaussian_square(duration: int, amp: complex, sigma: float, def drag(duration: int, amp: complex, sigma: float, beta: float, name: Optional[str] = None, zero_ends: bool = True) -> 'SamplePulse': - r"""Generates Y-only correction DRAG `SamplePulse` for standard nonlinear oscillator (SNO) [1]. + r"""Generates Y-only correction DRAG :class:`~qiskit.pulse.SamplePulse` for standard nonlinear + oscillator (SNO) [1]. - Centered at `duration/2` and zeroed at `t=0` to prevent large initial discontinuity. + For :math:`A=` ``amp``, :math:`\sigma=` ``sigma``, and :math:`\beta=` ``beta``, applies the + `midpoint` sampling strategy to generate a discrete pulse sampled from the + continuous function: - Applies `midpoint` sampling strategy to generate discrete pulse from continuous function. + .. math:: - [1] Gambetta, J. M., Motzoi, F., Merkel, S. T. & Wilhelm, F. K. - Analytic control methods for high-fidelity unitary operations - in a weakly nonlinear oscillator. Phys. Rev. A 83, 012308 (2011). + f(x) = g(x) + i \beta h(x), + + where :math:`g(x)` is the function sampled in :meth:`gaussian`, and :math:`h(x)` + is the function sampled in :meth:`gaussian_deriv`. + + If ``zero_ends == True``, the samples from :math:`g(x)` are remapped as in :meth:`gaussian`. + + References: + 1. |citation1|_ + + .. _citation1: http://dx.doi.org/10.1103/PhysRevA.83.012308 + + .. |citation1| replace:: *Gambetta, J. M., Motzoi, F., Merkel, S. T. & Wilhelm, F. K. + "Analytic control methods for high-fidelity unitary operations + in a weakly nonlinear oscillator." Phys. Rev. A 83, 012308 (2011).* Args: duration: Duration of pulse. Must be greater than zero. - amp: Pulse amplitude at `center`. + amp: Pulse amplitude at center ``duration/2``. sigma: Width (standard deviation) of pulse. - beta: Y correction amplitude. For the SNO this is $\beta=-\frac{\lambda_1^2}{4\Delta_2}$. - Where $\lambds_1$ is the relative coupling strength between the first excited and second - excited states and $\Delta_2$ is the detuning between the respective excited states. + beta: Y correction amplitude. For the SNO this is + :math:`\beta=-\frac{\lambda_1^2}{4\Delta_2}`. Where :math:`\lambda_1` is the + relative coupling strength between the first excited and second excited states + and :math:`\Delta_2` is the detuning between the respective excited states. name: Name of pulse. - zero_ends: If True, make the first and last sample zero, but rescale to preserve amp. + zero_ends: If ``True``, make the first and last sample zero, but rescale to preserve amp. """ center = duration/2 zeroed_width = duration if zero_ends else None diff --git a/qiskit/pulse/reschedule.py b/qiskit/pulse/reschedule.py index 8c5460298d68..f23c92d4390b 100644 --- a/qiskit/pulse/reschedule.py +++ b/qiskit/pulse/reschedule.py @@ -177,7 +177,7 @@ def add_implicit_acquires(schedule: ScheduleComponent, meas_map: List[List[int]] def pad(schedule: Schedule, channels: Optional[Iterable[Channel]] = None, until: Optional[int] = None) -> Schedule: - """Pad the input ``Schedule`` with ``Delay``s on all unoccupied timeslots until ``until`` + """Pad the input ``Schedule`` with ``Delay`` s on all unoccupied timeslots until ``until`` if it is provided, otherwise until ``schedule.duration``. Args: diff --git a/qiskit/pulse/schedule.py b/qiskit/pulse/schedule.py index b0118dddccc8..8b9a9341a8ad 100644 --- a/qiskit/pulse/schedule.py +++ b/qiskit/pulse/schedule.py @@ -12,12 +12,19 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""Schedule.""" +"""The Schedule is one of the most fundamental objects to this pulse-level programming module. +A ``Schedule`` is a representation of a *program* in Pulse. Each schedule tracks the time of each +instruction occuring in parallel over multiple signal *channels*. +""" import abc +import itertools +import multiprocessing as mp +import sys from typing import List, Tuple, Iterable, Union, Dict, Callable, Set, Optional, Type import warnings +from qiskit.util import is_main_process from .timeslots import Interval from .channels import Channel from .interfaces import ScheduleComponent @@ -28,20 +35,32 @@ class Schedule(ScheduleComponent): - """Schedule of `ScheduleComponent`s. The composite node of a schedule tree.""" - # pylint: disable=missing-type-doc + """A quantum program *schedule* with exact time constraints for its instructions, operating + over all input signal *channels* and supporting special syntaxes for building. + """ + + # Counter for the number of instances in this class. + instances_counter = itertools.count() + # Prefix to use for auto naming. + prefix = 'sched' + def __init__(self, *schedules: List[Union[ScheduleComponent, Tuple[int, ScheduleComponent]]], name: Optional[str] = None): - """Create empty schedule. + """Create an empty schedule. Args: *schedules: Child Schedules of this parent Schedule. May either be passed as - the list of schedules, or a list of (start_time, schedule) pairs - name: Name of this schedule + the list of schedules, or a list of ``(start_time, schedule)`` pairs. + name: Name of this schedule. Defaults to an autogenerated string if not provided. Raises: - PulseError: If timeslots intercept. + PulseError: If the input schedules have instructions which overlap. """ + if name is None: + name = self.prefix + str(next(self.instances_counter)) + if sys.platform != "win32" and not is_main_process(): + name += '-{}'.format(mp.current_process().pid) + self._name = name timeslots = [] @@ -105,7 +124,7 @@ def _children(self) -> Tuple[Tuple[int, ScheduleComponent], ...]: @property def instructions(self) -> Tuple[Tuple[int, 'Instruction'], ...]: - """Get time-ordered instructions from Schedule tree.""" + """Get the time-ordered instructions from self.""" def key(time_inst_pair): inst = time_inst_pair[1] @@ -115,18 +134,18 @@ def key(time_inst_pair): return tuple(sorted(self._instructions(), key=key)) def ch_duration(self, *channels: List[Channel]) -> int: - """Return duration of schedule over supplied channels. + """Return the time of the end of the last instruction over the supplied channels. Args: - *channels: Supplied channels + *channels: Channels within ``self`` to include. """ return self.timeslots.ch_duration(*channels) def ch_start_time(self, *channels: List[Channel]) -> int: - """Return minimum start time over supplied channels. + """Return the time of the start of the first instruction over the supplied channels. Args: - *channels: Supplied channels + *channels: Channels within ``self`` to include. """ return self.timeslots.ch_start_time(*channels) @@ -134,7 +153,7 @@ def ch_stop_time(self, *channels: List[Channel]) -> int: """Return maximum start time over supplied channels. Args: - *channels: Supplied channels + *channels: Channels within ``self`` to include. """ return self.timeslots.ch_stop_time(*channels) @@ -142,22 +161,22 @@ def _instructions(self, time: int = 0) -> Iterable[Tuple[int, 'Instruction']]: """Iterable for flattening Schedule tree. Args: - time: Shifted time due to parent + time: Shifted time due to parent. Yields: - Tuple[int, Instruction]: Tuple containing time `Instruction` starts - at and the flattened `Instruction`. + Tuple containing the time each :class:`~qiskit.pulse.Instruction` + starts at and the flattened :class:`~qiskit.pulse.Instruction` s. """ for insert_time, child_sched in self._children: yield from child_sched._instructions(time + insert_time) def union(self, *schedules: Union[ScheduleComponent, Tuple[int, ScheduleComponent]], name: Optional[str] = None) -> 'Schedule': - """Return a new schedule which is the union of both `self` and `schedules`. + """Return a new schedule which is the union of both ``self`` and ``schedules``. Args: - *schedules: Schedules to be take the union with this `Schedule`. - name: Name of the new schedule. Defaults to name of self + *schedules: Schedules to be take the union with this ``Schedule``. + name: Name of the new schedule. Defaults to the name of self. """ if name is None: name = self.name @@ -170,10 +189,10 @@ def union(self, *schedules: Union[ScheduleComponent, Tuple[int, ScheduleComponen return new_sched def _union(self, other: Tuple[int, ScheduleComponent]) -> 'Schedule': - """Mutably union `self` and `other` Schedule with shift time. + """Mutably union ``self`` and ``other`` with shift time. Args: - other: Schedule with shift time to be take the union with this `Schedule`. + other: Schedule with shift time to be take the union with this ``Schedule``. """ shift_time, sched = other if isinstance(sched, Schedule): @@ -188,50 +207,47 @@ def _union(self, other: Tuple[int, ScheduleComponent]) -> 'Schedule': self._timeslots = self.timeslots.merge(sched_timeslots) def shift(self, time: int, name: Optional[str] = None) -> 'Schedule': - """Return a new schedule shifted forward by `time`. + """Return a new schedule shifted forward by ``time``. Args: - time: Time to shift by - name: Name of the new schedule. Defaults to name of self + time: Time to shift by. + name: Name of the new schedule. Defaults to the name of self. """ if name is None: name = self.name return Schedule((time, self), name=name) - def insert(self, start_time: int, schedule: ScheduleComponent, buffer: bool = False, + def insert(self, start_time: int, schedule: ScheduleComponent, name: Optional[str] = None) -> 'Schedule': - """Return a new schedule with `schedule` inserted within `self` at `start_time`. + """Return a new schedule with ``schedule`` inserted into ``self`` at ``start_time``. Args: - start_time: Time to insert the schedule - schedule: Schedule to insert - buffer: Whether to obey buffer when inserting - name: Name of the new schedule. Defaults to name of self + start_time: Time to insert the schedule. + schedule: Schedule to insert. + name: Name of the new schedule. Defaults to the name of self. """ - if buffer: - warnings.warn("Buffers are no longer supported. Please use an explicit Delay.") return self.union((start_time, schedule), name=name) - def append(self, schedule: ScheduleComponent, buffer: bool = False, + def append(self, schedule: ScheduleComponent, name: Optional[str] = None) -> 'Schedule': - r"""Return a new schedule with `schedule` inserted at the maximum time over - all channels shared between `self` and `schedule`. + r"""Return a new schedule with ``schedule`` inserted at the maximum time over + all channels shared between ``self`` and ``schedule``. + + .. math:: - $t = \textrm{max}({x.stop\_time |x \in self.channels \cap schedule.channels})$ + t = \textrm{max}(\texttt{x.stop_time} |\texttt{x} \in + \texttt{self.channels} \cap \texttt{schedule.channels}) Args: - schedule: schedule to be appended - buffer: Whether to obey buffer when appending - name: Name of the new schedule. Defaults to name of self + schedule: Schedule to be appended. + name: Name of the new ``Schedule``. Defaults to name of ``self``. """ - if buffer: - warnings.warn("Buffers are no longer supported. Please use an explicit Delay.") common_channels = set(self.channels) & set(schedule.channels) time = self.ch_stop_time(*common_channels) return self.insert(time, schedule, name=name) def flatten(self) -> 'Schedule': - """Return a new schedule which is the flattened schedule contained all `instructions`.""" + """Return a new schedule which is the flattened schedule contained all ``instructions``.""" return Schedule(*self.instructions, name=self.name) def filter(self, *filter_funcs: List[Callable], @@ -239,22 +255,22 @@ def filter(self, *filter_funcs: List[Callable], instruction_types: Optional[Iterable[Type['Instruction']]] = None, time_ranges: Optional[Iterable[Tuple[int, int]]] = None, intervals: Optional[Iterable[Interval]] = None) -> 'Schedule': - """ - Return a new Schedule with only the instructions from this Schedule which pass though the - provided filters; i.e. an instruction will be retained iff every function in filter_funcs - returns True, the instruction occurs on a channel type contained in channels, - the instruction type is contained in instruction_types, and the period over which the - instruction operates is fully contained in one specified in time_ranges or intervals. + """Return a new ``Schedule`` with only the instructions from this ``Schedule`` which pass + though the provided filters; i.e. an instruction will be retained iff every function in + ``filter_funcs`` returns ``True``, the instruction occurs on a channel type contained in + ``channels``, the instruction type is contained in ``instruction_types``, and the period + over which the instruction operates is *fully* contained in one specified in + ``time_ranges`` or ``intervals``. - If no arguments are provided, this schedule is returned. + If no arguments are provided, ``self`` is returned. Args: filter_funcs: A list of Callables which take a (int, ScheduleComponent) tuple and - return a bool - channels: For example, [DriveChannel(0), AcquireChannel(0)] - instruction_types: For example, [PulseInstruction, AcquireInstruction] - time_ranges: For example, [(0, 5), (6, 10)] - intervals: For example, [Interval(0, 5), Interval(6, 10)] + return a bool. + channels: For example, ``[DriveChannel(0), AcquireChannel(0)]``. + instruction_types: For example, ``[PulseInstruction, AcquireInstruction]``. + time_ranges: For example, ``[(0, 5), (6, 10)]``. + intervals: For example, ``[Interval(0, 5), Interval(6, 10)]``. """ composed_filter = self._construct_filter(*filter_funcs, channels=channels, @@ -269,18 +285,18 @@ def exclude(self, *filter_funcs: List[Callable], instruction_types: Optional[Iterable[Type['Instruction']]] = None, time_ranges: Optional[Iterable[Tuple[int, int]]] = None, intervals: Optional[Iterable[Interval]] = None) -> 'Schedule': - """ - Return a Schedule with only the instructions from this Schedule *failing* at least one of - the provided filters. This method is the complement of `self.filter`, so that: + """Return a Schedule with only the instructions from this Schedule *failing* at least one + of the provided filters. This method is the complement of ``self.filter``, so that:: + self.filter(args) | self.exclude(args) == self Args: filter_funcs: A list of Callables which take a (int, ScheduleComponent) tuple and - return a bool - channels: For example, [DriveChannel(0), AcquireChannel(0)] - instruction_types: For example, [PulseInstruction, AcquireInstruction] - time_ranges: For example, [(0, 5), (6, 10)] - intervals: For example, [Interval(0, 5), Interval(6, 10)] + return a bool. + channels: For example, ``[DriveChannel(0), AcquireChannel(0)]``. + instruction_types: For example, ``[PulseInstruction, AcquireInstruction]``. + time_ranges: For example, ``[(0, 5), (6, 10)]``. + intervals: For example, ``[Interval(0, 5), Interval(6, 10)]``. """ composed_filter = self._construct_filter(*filter_funcs, channels=channels, @@ -291,13 +307,12 @@ def exclude(self, *filter_funcs: List[Callable], new_sched_name="{name}-excluded".format(name=self.name)) def _apply_filter(self, filter_func: Callable, new_sched_name: str) -> 'Schedule': - """ - Return a Schedule containing only the instructions from this Schedule for which - filter_func returns True. + """Return a Schedule containing only the instructions from this Schedule for which + ``filter_func`` returns ``True``. Args: - filter_func: function of the form (int, ScheduleComponent) -> bool - new_sched_name: name of the returned Schedule + filter_func: Function of the form (int, ScheduleComponent) -> bool. + new_sched_name: Name of the returned ``Schedule``. """ subschedules = self.flatten()._children valid_subschedules = [sched for sched in subschedules if filter_func(sched)] @@ -308,21 +323,20 @@ def _construct_filter(self, *filter_funcs: List[Callable], instruction_types: Optional[Iterable[Type['Instruction']]] = None, time_ranges: Optional[Iterable[Tuple[int, int]]] = None, intervals: Optional[Iterable[Interval]] = None) -> Callable: - """ - Returns a boolean-valued function with input type (int, ScheduleComponent) that returns True - iff the input satisfies all of the criteria specified by the arguments; i.e. iff every - function in filter_funcs returns True, the instruction occurs on a channel type contained - in channels, the instruction type is contained in instruction_types, and the period over - which the instruction operates is fully contained in one specified in time_ranges or - intervals. + """Returns a boolean-valued function with input type ``(int, ScheduleComponent)`` that + returns ``True`` iff the input satisfies all of the criteria specified by the arguments; + i.e. iff every function in ``filter_funcs`` returns ``True``, the instruction occurs on a + channel type contained in ``channels``, the instruction type is contained in + ``instruction_types``, and the period over which the instruction operates is fully + contained in one specified in ``time_ranges`` or ``intervals``. Args: filter_funcs: A list of Callables which take a (int, ScheduleComponent) tuple and - return a bool - channels: For example, [DriveChannel(0), AcquireChannel(0)] - instruction_types: For example, [PulseInstruction, AcquireInstruction] - time_ranges: For example, [(0, 5), (6, 10)] - intervals: For example, [Interval(0, 5), Interval(6, 10)] + return a bool. + channels: For example, ``[DriveChannel(0), AcquireChannel(0)]``. + instruction_types: For example, ``[PulseInstruction, AcquireInstruction]``. + time_ranges: For example, ``[(0, 5), (6, 10)]``. + intervals: For example, ``[Interval(0, 5), Interval(6, 10)]``. """ def only_channels(channels: Set[Channel]) -> Callable: def channel_filter(time_inst: Tuple[int, 'Instruction']) -> bool: @@ -376,29 +390,29 @@ def draw(self, dt: float = 1, style: Optional['SchedStyle'] = None, filename: Name required to save pulse image interp_method: A function for interpolation scale: Relative visual scaling of waveform amplitudes, see Additional Information. - channel_scales: Channel independent scaling as a dictionary of `Channel` object. - channels_to_plot: Deprecated, see `channels` + channel_scales: Channel independent scaling as a dictionary of ``Channel`` object. + channels_to_plot: Deprecated, see ``channels`` plot_all: Plot empty channels plot_range: A tuple of time range to plot interactive: When set true show the circuit in a new window - (this depends on the matplotlib backend being used supporting this) + (this depends on the matplotlib backend being used supporting this) table: Draw event table for supported commands label: Label individual instructions framechange: Add framechange indicators - scaling: Deprecated, see `scale` + scaling: Deprecated, see ``scale`` channels: A list of channel names to plot show_framechange_channels: Plot channels with only framechanges Additional Information: If you want to manually rescale the waveform amplitude of channels one by one, - you can set `channel_scales` argument instead of `scale`. - The `channel_scales` should be given as a python dictionary:: + you can set ``channel_scales`` argument instead of ``scale``. + The ``channel_scales`` should be given as a python dictionary:: channel_scales = {pulse.DriveChannels(0): 10.0, pulse.MeasureChannels(0): 5.0} - When the channel to plot is not included in the `channel_scales` dictionary, - scaling factor of that channel is overwritten by the value of `scale` argument. + When the channel to plot is not included in the ``channel_scales`` dictionary, + scaling factor of that channel is overwritten by the value of ``scale` argument. In default, waveform amplitude is normalized by the maximum amplitude of the channel. The scaling factor is displayed under the channel name alias. @@ -430,9 +444,12 @@ def __eq__(self, other: ScheduleComponent) -> bool: """Test if two ScheduleComponents are equal. Equality is checked by verifying there is an equal instruction at every time - in `other` for every instruction in this Schedule. + in ``other`` for every instruction in this ``Schedule``. + + .. warning:: + + This does not check for logical equivalency. Ie., - Warning: This does not check for logical equivalency. Ie., ```python >>> (Delay(10)(DriveChannel(0)) + Delay(10)(DriveChannel(0)) == Delay(20)(DriveChannel(0))) @@ -461,15 +478,15 @@ def __eq__(self, other: ScheduleComponent) -> bool: return True def __add__(self, other: ScheduleComponent) -> 'Schedule': - """Return a new schedule with `other` inserted within `self` at `start_time`.""" + """Return a new schedule with ``other`` inserted within ``self`` at ``start_time``.""" return self.append(other) def __or__(self, other: ScheduleComponent) -> 'Schedule': - """Return a new schedule which is the union of `self` and `other`.""" + """Return a new schedule which is the union of ``self`` and ``other``.""" return self.union(other) def __lshift__(self, time: int) -> 'Schedule': - """Return a new schedule which is shifted forward by `time`.""" + """Return a new schedule which is shifted forward by ``time``.""" return self.shift(time) def __repr__(self): @@ -486,10 +503,10 @@ class ParameterizedSchedule: This should not be returned to users as it is currently only a helper class. This class is takes an input command definition that accepts - a set of parameters. Calling `bind` on the class will return a `Schedule`. + a set of parameters. Calling ``bind`` on the class will return a ``Schedule``. # TODO: In the near future this will be replaced with proper incorporation of parameters - into the `Schedule` class. + into the ``Schedule`` class. """ def __init__(self, *schedules, parameters: Optional[Dict[str, Union[float, complex]]] = None, diff --git a/qiskit/quantum_info/__init__.py b/qiskit/quantum_info/__init__.py index 540ee9354ef8..381a369f266f 100644 --- a/qiskit/quantum_info/__init__.py +++ b/qiskit/quantum_info/__init__.py @@ -22,42 +22,42 @@ ========= .. autosummary:: - :toctree: ../stubs/ + :toctree: ../stubs/ - Operator - Pauli - pauli_group - Quaternion + Operator + Pauli + pauli_group + Quaternion States ====== .. autosummary:: - :toctree: ../stubs/ + :toctree: ../stubs/ - Statevector - DensityMatrix + Statevector + DensityMatrix Channels ======== .. autosummary:: - :toctree: ../stubs/ + :toctree: ../stubs/ - Choi - SuperOp - Kraus - Stinespring - Chi - PTM + Choi + SuperOp + Kraus + Stinespring + Chi + PTM Measures ======== .. autosummary:: - :toctree: ../stubs/ + :toctree: ../stubs/ - state_fidelity + state_fidelity purity average_gate_fidelity process_fidelity @@ -81,30 +81,30 @@ ====== .. autosummary:: - :toctree: ../stubs/ + :toctree: ../stubs/ - random_unitary - random_state - random_density_matrix + random_unitary + random_state + random_density_matrix Analysis ========= .. autosummary:: - :toctree: ../stubs/ + :toctree: ../stubs/ - hellinger_fidelity + hellinger_fidelity Synthesis ========= .. autosummary:: - :toctree: ../stubs/ - - euler_angles_1q - two_qubit_cnot_decompose - TwoQubitBasisDecomposer + :toctree: ../stubs/ + OneQubitEulerDecomposer + TwoQubitBasisDecomposer + two_qubit_cnot_decompose + euler_angles_1q """ from .operators.operator import Operator @@ -120,6 +120,6 @@ mutual_information, shannon_entropy) from .states.states import basis_state, projector from .random import random_unitary, random_state, random_density_matrix -from .synthesis import (TwoQubitBasisDecomposer, euler_angles_1q, - two_qubit_cnot_decompose) +from .synthesis import (OneQubitEulerDecomposer, TwoQubitBasisDecomposer, + two_qubit_cnot_decompose, euler_angles_1q) from .analysis import hellinger_fidelity diff --git a/qiskit/quantum_info/operators/base_operator.py b/qiskit/quantum_info/operators/base_operator.py index 31231fa8d707..097ca1fd4860 100644 --- a/qiskit/quantum_info/operators/base_operator.py +++ b/qiskit/quantum_info/operators/base_operator.py @@ -16,6 +16,8 @@ Abstract BaseOperator class. """ +import copy +import warnings from abc import ABC, abstractmethod import numpy as np @@ -25,55 +27,57 @@ class BaseOperator(ABC): - """Abstract linear operator base class""" + """Abstract linear operator base class.""" ATOL = ATOL_DEFAULT RTOL = RTOL_DEFAULT MAX_TOL = 1e-4 - def __init__(self, rep, data, input_dims, output_dims): + def __init__(self, input_dims, output_dims): """Initialize an operator object.""" - if not isinstance(rep, str): - raise QiskitError("rep must be a string not a {}".format( - rep.__class__)) - self._rep = rep - self._data = data - # Shape lists the dimension of each subsystem starting from - # least significant through to most significant. - self._input_dims = tuple(input_dims) - self._output_dims = tuple(output_dims) - # The total input and output dimensions are given by the product - # of all subsystem dimension in the input_dims/output_dims. - self._input_dim = np.product(input_dims) - self._output_dim = np.product(output_dims) + # Dimension attributes + # Note that the tuples of input and output dims are ordered + # from least-significant to most-significant subsystems + self._qargs = None # qargs for composition, set with __call__ + self._input_dims = None # tuple of input dimensions of each subsystem + self._output_dims = None # tuple of output dimensions of each subsystem + self._input_dim = None # combined input dimension of all subsystems + self._output_dim = None # combined output dimension of all subsystems + self._set_dims(input_dims, output_dims) + + def __call__(self, qargs): + """Return a clone with qargs set""" + if isinstance(qargs, int): + qargs = [qargs] + n_qargs = len(qargs) + # We don't know if qargs will be applied to input our output + # dimensions so we just check it matches one of them. + if n_qargs not in (len(self._input_dims), len(self._output_dims)): + raise QiskitError( + "Length of qargs ({}) does not match number of input ({})" + " or output ({}) subsystems.".format( + n_qargs, len(self._input_dims), len(self._output_dims))) + # Make a shallow copy + ret = copy.copy(self) + ret._qargs = qargs + return ret def __eq__(self, other): - if (isinstance(other, self.__class__) - and self.input_dims() == other.input_dims() - and self.output_dims() == other.output_dims()): - return np.allclose( - self.data, other.data, rtol=self._rtol, atol=self._atol) - return False - - def __repr__(self): - return '{}({}, input_dims={}, output_dims={})'.format( - self.rep, self.data, self._input_dims, self._output_dims) + """Check types and subsystem dimensions are equal""" + return (isinstance(other, self.__class__) and + self._input_dims == other._input_dims and + self._output_dims == other._output_dims) @property - def rep(self): - """Return operator representation string.""" - return self._rep + def qargs(self): + """Return the qargs for the operator.""" + return self._qargs @property def dim(self): """Return tuple (input_shape, output_shape).""" return self._input_dim, self._output_dim - @property - def data(self): - """Return data.""" - return self._data - @property def _atol(self): """The absolute tolerance parameter for float comparisons.""" @@ -110,33 +114,40 @@ def _rtol(self, rtol): "Invalid rtol: must be less than {}.".format(max_tol)) self.__class__.RTOL = rtol - def _reshape(self, input_dims=None, output_dims=None): - """Reshape input and output dimensions of operator. + def reshape(self, input_dims=None, output_dims=None): + """Return a shallow copy with reshaped input and output subsystem dimensions. Arg: - input_dims (tuple): new subsystem input dimensions. - output_dims (tuple): new subsystem output dimensions. + input_dims (None or tuple): new subsystem input dimensions. + If None the original input dims will be preserved + [Default: None]. + output_dims (None or tuple): new subsystem output dimensions. + If None the original output dims will be preserved + [Default: None]. Returns: - Operator: returns self with reshaped input and output dimensions. + BaseOperator: returns self with reshaped input and output dimensions. Raises: QiskitError: if combined size of all subsystem input dimension or subsystem output dimensions is not constant. """ + clone = copy.copy(self) + if output_dims is None and input_dims is None: + return clone if input_dims is not None: if np.product(input_dims) != self._input_dim: raise QiskitError( - "Reshaped input_dims are incompatible with combined input dimension." - ) - self._input_dims = tuple(input_dims) + "Reshaped input_dims ({}) are incompatible with combined" + " input dimension ({}).".format(input_dims, self._input_dim)) + clone._input_dims = tuple(input_dims) if output_dims is not None: if np.product(output_dims) != self._output_dim: raise QiskitError( - "Reshaped input_dims are incompatible with combined input dimension." - ) - self._output_dims = tuple(output_dims) - return self + "Reshaped output_dims ({}) are incompatible with combined" + " output dimension ({}).".format(output_dims, self._output_dim)) + clone._output_dims = tuple(output_dims) + return clone def input_dims(self, qargs=None): """Return tuple of input dimension for specified subsystems.""" @@ -151,33 +162,51 @@ def output_dims(self, qargs=None): return tuple(self._output_dims[i] for i in qargs) def copy(self): - """Make a copy of current operator.""" - # pylint: disable=no-value-for-parameter - # The constructor of subclasses from raw data should be a copy - return self.__class__(self.data, self.input_dims(), self.output_dims()) + """Make a deep copy of current operator.""" + return copy.deepcopy(self) def adjoint(self): """Return the adjoint of the operator.""" return self.conjugate().transpose() @abstractmethod - def is_unitary(self, atol=None, rtol=None): - """Return True if operator is a unitary matrix.""" + def conjugate(self): + """Return the conjugate of the operator.""" pass @abstractmethod - def to_operator(self): - """Convert operator to matrix operator class""" + def transpose(self): + """Return the transpose of the operator.""" pass @abstractmethod - def conjugate(self): - """Return the conjugate of the operator.""" + def tensor(self, other): + """Return the tensor product operator self ⊗ other. + + Args: + other (BaseOperator): a operator subclass object. + + Returns: + BaseOperator: the tensor product operator self ⊗ other. + + Raises: + QiskitError: if other is not an operator. + """ pass @abstractmethod - def transpose(self): - """Return the transpose of the operator.""" + def expand(self, other): + """Return the tensor product operator other ⊗ self. + + Args: + other (BaseOperator): an operator object. + + Returns: + BaseOperator: the tensor product operator other ⊗ self. + + Raises: + QiskitError: if other is not an operator. + """ pass @abstractmethod @@ -249,82 +278,83 @@ def power(self, n): ret = ret.compose(self) return ret - @abstractmethod - def tensor(self, other): - """Return the tensor product operator self ⊗ other. + def add(self, other): + """Return the linear operator self + other. + + DEPRECATED: use ``operator + other`` instead. Args: - other (BaseOperator): a operator subclass object. + other (BaseOperator): an operator object. Returns: - BaseOperator: the tensor product operator self ⊗ other. - - Raises: - QiskitError: if other is not an operator. + BaseOperator: the operator self + other. """ - pass + warnings.warn("`BaseOperator.add` method is deprecated, use" + "`op + other` instead.", DeprecationWarning) + return self._add(other) - @abstractmethod - def expand(self, other): - """Return the tensor product operator other ⊗ self. + def subtract(self, other): + """Return the linear operator self - other. + + DEPRECATED: use ``operator - other`` instead. Args: other (BaseOperator): an operator object. Returns: - BaseOperator: the tensor product operator other ⊗ self. - - Raises: - QiskitError: if other is not an operator. + BaseOperator: the operator self - other. """ - pass + warnings.warn("`BaseOperator.subtract` method is deprecated, use" + "`op - other` instead", DeprecationWarning) + return self._add(-other) - @abstractmethod - def add(self, other): - """Return the linear operator self + other. + def multiply(self, other): + """Return the linear operator other * self. + + DEPRECATED: use ``other * operator`` instead. Args: - other (BaseOperator): an operator object. + other (complex): a complex number. Returns: - LinearOperator: the linear operator self + other. + BaseOperator: the linear operator other * self. Raises: - QiskitError: if other is not an operator, or has incompatible - dimensions. + NotImplementedError: if subclass does not support multiplication. """ - pass + warnings.warn("`BaseOperator.multiply` method is deprecated, use" + "the `other * op` instead", DeprecationWarning) + return self._multiply(other) - @abstractmethod - def subtract(self, other): - """Return the linear operator self - other. + def _add(self, other): + """Return the linear operator self + other. Args: other (BaseOperator): an operator object. Returns: - LinearOperator: the linear operator self - other. + BaseOperator: the operator self + other. Raises: - QiskitError: if other is not an operator, or has incompatible - dimensions. + NotImplementedError: if subclass does not support addition. """ - pass + raise NotImplementedError( + "{} does not support addition".format(type(self))) - @abstractmethod - def multiply(self, other): - """Return the linear operator self + other. + def _multiply(self, other): + """Return the linear operator other * self. Args: other (complex): a complex number. Returns: - Operator: the linear operator other * self. + BaseOperator: the linear operator other * self. Raises: - QiskitError: if other is not a valid complex number. + NotImplementedError: if subclass does not support multiplication. """ - pass + raise NotImplementedError( + "{} does not support scalar multiplication".format(type(self))) @classmethod def _automatic_dims(cls, dims, size): @@ -340,41 +370,84 @@ def _automatic_dims(cls, dims, size): return (dims,) return tuple(dims) - @classmethod - def _einsum_matmul(cls, tensor, mat, indices, shift=0, right_mul=False): - """Perform a contraction using Numpy.einsum + def _set_dims(self, input_dims, output_dims): + """Set dimension attributes""" + # Shape lists the dimension of each subsystem starting from + # least significant through to most significant. + self._input_dims = tuple(input_dims) + self._output_dims = tuple(output_dims) + # The total input and output dimensions are given by the product + # of all subsystem dimension in the input_dims/output_dims. + self._input_dim = np.product(input_dims) + self._output_dim = np.product(output_dims) + + def _get_compose_dims(self, other, qargs, front): + """Check dimensions are compatible for composition. Args: - tensor (np.array): a vector or matrix reshaped to a rank-N tensor. - mat (np.array): a matrix reshaped to a rank-2M tensor. - indices (list): tensor indices to contract with mat. - shift (int): shift for indices of tensor to contract [Default: 0]. - right_mul (bool): if True right multiply tensor by mat - (else left multiply) [Default: False]. + other (BaseOperator): another operator object. + qargs (None or list): compose qargs kwarg value. + front (bool): compose front kwarg value. Returns: - Numpy.ndarray: the matrix multiplied rank-N tensor. + tuple: the tuple (input_dims, output_dims) for the composed + operator. + Raises: + QiskitError: if operator dimensions are invalid for compose. + """ + if front: + output_dims = self._output_dims + if qargs is None: + if other._output_dim != self._input_dim: + raise QiskitError( + "Other operator combined output dimension ({}) does not" + " match current combined input dimension ({}).".format( + other._output_dim, self._input_dim)) + input_dims = other._input_dims + else: + if other._output_dims != self.input_dims(qargs): + raise QiskitError( + "Other operator output dimensions ({}) does not" + " match current subsystem input dimensions ({}).".format( + other._output_dims, self.input_dims(qargs))) + input_dims = list(self._input_dims) + for i, qubit in enumerate(qargs): + input_dims[qubit] = other._input_dims[i] + else: + input_dims = self._input_dims + if qargs is None: + if self._output_dim != other._input_dim: + raise QiskitError( + "Other operator combined input dimension ({}) does not" + " match current combined output dimension ({}).".format( + other._input_dim, self._output_dim)) + output_dims = other._output_dims + else: + if self.output_dims(qargs) != other._input_dims: + raise QiskitError( + "Other operator input dimensions ({}) does not" + " match current subsystem output dimension ({}).".format( + other._input_dims, self.output_dims(qargs))) + output_dims = list(self._output_dims) + for i, qubit in enumerate(qargs): + output_dims[qubit] = other._output_dims[i] + return input_dims, output_dims + + def _validate_add_dims(self, other): + """Check dimensions are compatible for addition. + + Args: + other (BaseOperator): another operator object. Raises: - QiskitError: if mat is not an even rank tensor. + QiskitError: if operators have incompatibile dimensions for addition. """ - rank = tensor.ndim - rank_mat = mat.ndim - if rank_mat % 2 != 0: + # For adding we only require that operators have the same total + # dimensions rather than each subsystem dimension matching. + if self.dim != other.dim: raise QiskitError( - "Contracted matrix must have an even number of indices.") - # Get einsum indices for tensor - indices_tensor = list(range(rank)) - for j, index in enumerate(indices): - indices_tensor[index + shift] = rank + j - # Get einsum indices for mat - mat_contract = list(reversed(range(rank, rank + len(indices)))) - mat_free = [index + shift for index in reversed(indices)] - if right_mul: - indices_mat = mat_contract + mat_free - else: - indices_mat = mat_free + mat_contract - return np.einsum(tensor, indices_tensor, mat, indices_mat) + "Cannot add operators with different shapes" + " ({} != {}).".format(self.dim, other.dim)) # Overloads def __matmul__(self, other): @@ -384,7 +457,7 @@ def __mul__(self, other): return self.dot(other) def __rmul__(self, other): - return self.multiply(other) + return self._multiply(other) def __pow__(self, n): return self.power(n) @@ -393,13 +466,13 @@ def __xor__(self, other): return self.tensor(other) def __truediv__(self, other): - return self.multiply(1 / other) + return self._multiply(1 / other) def __add__(self, other): - return self.add(other) + return self._add(other) def __sub__(self, other): - return self.subtract(other) + return self._add(-other) def __neg__(self): - return self.multiply(-1) + return self._multiply(-1) diff --git a/qiskit/quantum_info/operators/channel/chi.py b/qiskit/quantum_info/operators/channel/chi.py index 398fc7dee480..aaa416af10bf 100644 --- a/qiskit/quantum_info/operators/channel/chi.py +++ b/qiskit/quantum_info/operators/channel/chi.py @@ -18,7 +18,6 @@ Chi-matrix representation of a Quantum Channel. """ -from numbers import Number import numpy as np from qiskit.circuit.quantumcircuit import QuantumCircuit @@ -80,7 +79,7 @@ def __init__(self, data, input_dims=None, output_dims=None): # already a Chi matrix. if isinstance(data, (list, np.ndarray)): # Initialize from raw numpy or list matrix. - chi_mat = np.array(data, dtype=complex) + chi_mat = np.asarray(data, dtype=complex) # Determine input and output dimensions dim_l, dim_r = chi_mat.shape if dim_l != dim_r: @@ -112,7 +111,8 @@ def __init__(self, data, input_dims=None, output_dims=None): data = self._init_transformer(data) input_dim, output_dim = data.dim # Now that the input is an operator we convert it to a Chi object - chi_mat = _to_chi(data.rep, data._data, input_dim, output_dim) + rep = getattr(data, '_channel_rep', 'Operator') + chi_mat = _to_chi(rep, data._data, input_dim, output_dim) if input_dims is None: input_dims = data.input_dims() if output_dims is None: @@ -124,7 +124,7 @@ def __init__(self, data, input_dims=None, output_dims=None): # Check and format input and output dimensions input_dims = self._automatic_dims(input_dims, input_dim) output_dims = self._automatic_dims(output_dims, output_dim) - super().__init__('Chi', chi_mat, input_dims, output_dims) + super().__init__(chi_mat, input_dims, output_dims, 'Chi') @property def _bipartite_shape(self): @@ -161,8 +161,7 @@ def compose(self, other, qargs=None, front=False): Chi: The quantum channel self @ other. Raises: - QiskitError: if other cannot be converted to a Chi or has - incompatible dimensions. + QiskitError: if other has incompatible dimensions. Additional Information: Composition (``@``) is defined as `left` matrix multiplication for @@ -170,23 +169,14 @@ def compose(self, other, qargs=None, front=False): Setting ``front=True`` returns `right` matrix multiplication ``A * B`` and is equivalent to the :meth:`dot` method. """ - return super().compose(other, qargs=qargs, front=front) - - def dot(self, other, qargs=None): - """Return the right multiplied quantum channel self * other. - - Args: - other (QuantumChannel): a quantum channel. - qargs (list): a list of subsystem positions to compose other on. - - Returns: - Chi: The quantum channel self * other. - - Raises: - QiskitError: if other cannot be converted to a Chi or has - incompatible dimensions. - """ - return super().dot(other, qargs=qargs) + if qargs is None: + qargs = getattr(other, 'qargs', None) + if qargs is not None: + return Chi( + SuperOp(self).compose(other, qargs=qargs, front=front)) + # If no qargs we compose via Choi representation to avoid an additional + # representation conversion to SuperOp and then convert back to Chi + return Chi(Choi(self).compose(other, front=front)) def power(self, n): """The matrix power of the channel. @@ -243,62 +233,6 @@ def expand(self, other): data = np.kron(other.data, self._data) return Chi(data, input_dims, output_dims) - def add(self, other): - """Return the QuantumChannel self + other. - - Args: - other (QuantumChannel): a quantum channel. - - Returns: - Chi: the linear addition self + other as a Chi object. - - Raises: - QiskitError: if other is not a QuantumChannel subclass, or - has incompatible dimensions. - """ - if not isinstance(other, Chi): - other = Chi(other) - if self.dim != other.dim: - raise QiskitError("other QuantumChannel dimensions are not equal") - return Chi(self._data + other.data, self._input_dims, - self._output_dims) - - def subtract(self, other): - """Return the QuantumChannel self - other. - - Args: - other (QuantumChannel): a quantum channel. - - Returns: - Chi: the linear subtraction self - other as Chi object. - - Raises: - QiskitError: if other is not a QuantumChannel subclass, or - has incompatible dimensions. - """ - if not isinstance(other, Chi): - other = Chi(other) - if self.dim != other.dim: - raise QiskitError("other QuantumChannel dimensions are not equal") - return Chi(self._data - other.data, self._input_dims, - self._output_dims) - - def multiply(self, other): - """Return the QuantumChannel self + other. - - Args: - other (complex): a complex number. - - Returns: - Chi: the scalar multiplication other * self as a Chi object. - - Raises: - QiskitError: if other is not a valid scalar. - """ - if not isinstance(other, Number): - raise QiskitError("other is not a number") - return Chi(other * self._data, self._input_dims, self._output_dims) - def _evolve(self, state, qargs=None): """Evolve a quantum state by the quantum channel. @@ -315,39 +249,3 @@ def _evolve(self, state, qargs=None): specified quantum state subsystem dimensions. """ return SuperOp(self)._evolve(state, qargs) - - def _chanmul(self, other, qargs=None, left_multiply=False): - """Multiply two quantum channels. - - Args: - other (QuantumChannel): a quantum channel. - qargs (list): a list of subsystem positions to compose other on. - left_multiply (bool): If True return other * self - If False return self * other [Default:False] - - Returns: - Choi: The composition channel as a Chi object. - - Raises: - QiskitError: if other is not a QuantumChannel subclass, or - has incompatible dimensions. - """ - if qargs is not None: - return Chi( - SuperOp(self)._chanmul(other, - qargs=qargs, - left_multiply=left_multiply)) - - # Convert other to Choi since we convert via Choi - if not isinstance(other, Choi): - other = Choi(other) - # Check dimensions match up - if not left_multiply and self._input_dim != other._output_dim: - raise QiskitError( - 'input_dim of self must match output_dim of other') - if left_multiply and self._output_dim != other._input_dim: - raise QiskitError( - 'input_dim of other must match output_dim of self') - # Since we cannot directly multiply two channels in the Chi - # representation we convert to the Choi representation - return Chi(Choi(self)._chanmul(other, left_multiply=left_multiply)) diff --git a/qiskit/quantum_info/operators/channel/choi.py b/qiskit/quantum_info/operators/channel/choi.py index ee81ecc027de..0787277e8fc8 100644 --- a/qiskit/quantum_info/operators/channel/choi.py +++ b/qiskit/quantum_info/operators/channel/choi.py @@ -18,7 +18,6 @@ Choi-matrix representation of a Quantum Channel. """ -from numbers import Number import numpy as np from qiskit.circuit.quantumcircuit import QuantumCircuit @@ -88,7 +87,7 @@ def __init__(self, data, input_dims=None, output_dims=None): # already a Choi matrix. if isinstance(data, (list, np.ndarray)): # Initialize from raw numpy or list matrix. - choi_mat = np.array(data, dtype=complex) + choi_mat = np.asarray(data, dtype=complex) # Determine input and output dimensions dim_l, dim_r = choi_mat.shape if dim_l != dim_r: @@ -120,7 +119,8 @@ def __init__(self, data, input_dims=None, output_dims=None): data = self._init_transformer(data) input_dim, output_dim = data.dim # Now that the input is an operator we convert it to a Choi object - choi_mat = _to_choi(data.rep, data._data, input_dim, output_dim) + rep = getattr(data, '_channel_rep', 'Operator') + choi_mat = _to_choi(rep, data._data, input_dim, output_dim) if input_dims is None: input_dims = data.input_dims() if output_dims is None: @@ -128,7 +128,7 @@ def __init__(self, data, input_dims=None, output_dims=None): # Check and format input and output dimensions input_dims = self._automatic_dims(input_dims, input_dim) output_dims = self._automatic_dims(output_dims, output_dim) - super().__init__('Choi', choi_mat, input_dims, output_dims) + super().__init__(choi_mat, input_dims, output_dims, 'Choi') @property def _bipartite_shape(self): @@ -168,8 +168,7 @@ def compose(self, other, qargs=None, front=False): Choi: The quantum channel self @ other. Raises: - QiskitError: if other cannot be converted to a Choi or has - incompatible dimensions. + QiskitError: if other has incompatible dimensions. Additional Information: Composition (``@``) is defined as `left` matrix multiplication for @@ -177,25 +176,29 @@ def compose(self, other, qargs=None, front=False): Setting ``front=True`` returns `right` matrix multiplication ``A * B`` and is equivalent to the :meth:`dot` method. """ - return super().compose(other, qargs=qargs, front=front) - - def dot(self, other, qargs=None): - """Return the right multiplied quantum channel self * other. + if qargs is None: + qargs = getattr(other, 'qargs', None) + if qargs is not None: + return Choi( + SuperOp(self).compose(other, qargs=qargs, front=front)) - Args: - other (QuantumChannel): a quantum channel. - qargs (list or None): a list of subsystem positions to apply - other on. If None apply on all - subsystems [default: None]. + if not isinstance(other, Choi): + other = Choi(other) + input_dims, output_dims = self._get_compose_dims(other, qargs, front) + input_dim = np.product(input_dims) + output_dim = np.product(output_dims) - Returns: - Choi: The quantum channel self * other. + if front: + first = np.reshape(other._data, other._bipartite_shape) + second = np.reshape(self._data, self._bipartite_shape) + else: + first = np.reshape(self._data, self._bipartite_shape) + second = np.reshape(other._data, other._bipartite_shape) - Raises: - QiskitError: if other cannot be converted to a Choi or has - incompatible dimensions. - """ - return super().dot(other, qargs=qargs) + # Contract Choi matrices for composition + data = np.reshape(np.einsum('iAjB,AkBl->ikjl', first, second), + (input_dim * output_dim, input_dim * output_dim)) + return Choi(data, input_dims, output_dims) def power(self, n): """The matrix power of the channel. @@ -262,62 +265,6 @@ def expand(self, other): shape2=self._bipartite_shape) return Choi(data, input_dims, output_dims) - def add(self, other): - """Return the QuantumChannel self + other. - - Args: - other (QuantumChannel): a quantum channel. - - Returns: - Choi: the linear addition self + other as a Choi object. - - Raises: - QiskitError: if other cannot be converted to a channel or - has incompatible dimensions. - """ - if not isinstance(other, Choi): - other = Choi(other) - if self.dim != other.dim: - raise QiskitError("other QuantumChannel dimensions are not equal") - return Choi(self._data + other.data, self._input_dims, - self._output_dims) - - def subtract(self, other): - """Return the QuantumChannel self - other. - - Args: - other (QuantumChannel): a quantum channel. - - Returns: - Choi: the linear subtraction self - other as Choi object. - - Raises: - QiskitError: if other cannot be converted to a channel or - has incompatible dimensions. - """ - if not isinstance(other, Choi): - other = Choi(other) - if self.dim != other.dim: - raise QiskitError("other QuantumChannel dimensions are not equal") - return Choi(self._data - other.data, self._input_dims, - self._output_dims) - - def multiply(self, other): - """Return the QuantumChannel self + other. - - Args: - other (complex): a complex number. - - Returns: - Choi: the scalar multiplication other * self as a Choi object. - - Raises: - QiskitError: if other is not a valid scalar. - """ - if not isinstance(other, Number): - raise QiskitError("other is not a number") - return Choi(other * self._data, self._input_dims, self._output_dims) - def _evolve(self, state, qargs=None): """Evolve a quantum state by the quantum channel. @@ -334,56 +281,3 @@ def _evolve(self, state, qargs=None): specified quantum state subsystem dimensions. """ return SuperOp(self)._evolve(state, qargs) - - def _chanmul(self, other, qargs=None, left_multiply=False): - """Multiply two quantum channels. - - Args: - other (QuantumChannel): a quantum channel. - qargs (list): a list of subsystem positions to compose other on. - left_multiply (bool): If True return other * self - If False return self * other [Default:False] - - Returns: - Choi: The composition channel as a Choi object. - - Raises: - QiskitError: if other is not a QuantumChannel subclass, or - has incompatible dimensions. - """ - if qargs is not None: - return Choi( - SuperOp(self)._chanmul(other, - qargs=qargs, - left_multiply=left_multiply)) - - # Convert to Choi matrix - if not isinstance(other, Choi): - other = Choi(other) - # Check dimensions match up - if not left_multiply and self._input_dim != other._output_dim: - raise QiskitError( - 'input_dim of self must match output_dim of other') - if left_multiply and self._output_dim != other._input_dim: - raise QiskitError( - 'input_dim of other must match output_dim of self') - - if left_multiply: - first = np.reshape(self._data, self._bipartite_shape) - second = np.reshape(other._data, other._bipartite_shape) - input_dim = self._input_dim - input_dims = self.input_dims() - output_dim = other._output_dim - output_dims = other.output_dims() - else: - first = np.reshape(other._data, other._bipartite_shape) - second = np.reshape(self._data, self._bipartite_shape) - input_dim = other._input_dim - input_dims = other.input_dims() - output_dim = self._output_dim - output_dims = self.output_dims() - - # Contract Choi matrices for composition - data = np.reshape(np.einsum('iAjB,AkBl->ikjl', first, second), - (input_dim * output_dim, input_dim * output_dim)) - return Choi(data, input_dims, output_dims) diff --git a/qiskit/quantum_info/operators/channel/kraus.py b/qiskit/quantum_info/operators/channel/kraus.py index 23a4aaf16ca0..1e95aae22cc6 100644 --- a/qiskit/quantum_info/operators/channel/kraus.py +++ b/qiskit/quantum_info/operators/channel/kraus.py @@ -94,18 +94,18 @@ def __init__(self, data, input_dims=None, output_dims=None): # E(rho) = A * rho * A^\dagger if isinstance(data, np.ndarray) or np.array(data).ndim == 2: # Convert single Kraus op to general Kraus pair - kraus = ([np.array(data, dtype=complex)], None) + kraus = ([np.asarray(data, dtype=complex)], None) shape = kraus[0][0].shape # Check if single Kraus set [A_i] for channel: # E(rho) = sum_i A_i * rho * A_i^dagger elif isinstance(data, list) and len(data) > 0: # Get dimensions from first Kraus op - kraus = [np.array(data[0], dtype=complex)] + kraus = [np.asarray(data[0], dtype=complex)] shape = kraus[0].shape # Iterate over remaining ops and check they are same shape for i in data[1:]: - op = np.array(i, dtype=complex) + op = np.asarray(i, dtype=complex) if op.shape != shape: raise QiskitError( "Kraus operators are different dimensions.") @@ -117,10 +117,10 @@ def __init__(self, data, input_dims=None, output_dims=None): # E(rho) = sum_i A_i * rho * B_i^dagger elif isinstance(data, tuple) and len(data) == 2 and len(data[0]) > 0: - kraus_left = [np.array(data[0][0], dtype=complex)] + kraus_left = [np.asarray(data[0][0], dtype=complex)] shape = kraus_left[0].shape for i in data[0][1:]: - op = np.array(i, dtype=complex) + op = np.asarray(i, dtype=complex) if op.shape != shape: raise QiskitError( "Kraus operators are different dimensions.") @@ -130,7 +130,7 @@ def __init__(self, data, input_dims=None, output_dims=None): else: kraus_right = [] for i in data[1]: - op = np.array(i, dtype=complex) + op = np.asarray(i, dtype=complex) if op.shape != shape: raise QiskitError( "Kraus operators are different dimensions.") @@ -151,7 +151,8 @@ def __init__(self, data, input_dims=None, output_dims=None): data = self._init_transformer(data) input_dim, output_dim = data.dim # Now that the input is an operator we convert it to a Kraus - kraus = _to_kraus(data.rep, data._data, input_dim, output_dim) + rep = getattr(data, '_channel_rep', 'Operator') + kraus = _to_kraus(rep, data._data, input_dim, output_dim) if input_dims is None: input_dims = data.input_dims() if output_dims is None: @@ -164,11 +165,11 @@ def __init__(self, data, input_dims=None, output_dims=None): # Initialize either single or general Kraus if kraus[1] is None or np.allclose(kraus[0], kraus[1]): # Standard Kraus map - super().__init__('Kraus', (kraus[0], None), input_dims, - output_dims) + super().__init__((kraus[0], None), input_dims, + output_dims, 'Kraus') else: # General (non-CPTP) Kraus map - super().__init__('Kraus', kraus, input_dims, output_dims) + super().__init__(kraus, input_dims, output_dims, 'Kraus') @property def data(self): @@ -236,7 +237,33 @@ def compose(self, other, qargs=None, front=False): Setting ``front=True`` returns `right` matrix multiplication ``A * B`` and is equivalent to the :meth:`dot` method. """ - return super().compose(other, qargs=qargs, front=front) + if qargs is None: + qargs = getattr(other, 'qargs', None) + if qargs is not None: + return Kraus( + SuperOp(self).compose(other, qargs=qargs, front=front)) + + if not isinstance(other, Kraus): + other = Kraus(other) + input_dims, output_dims = self._get_compose_dims(other, qargs, front) + + if front: + ka_l, ka_r = self._data + kb_l, kb_r = other._data + else: + ka_l, ka_r = other._data + kb_l, kb_r = self._data + + kab_l = [np.dot(a, b) for a in ka_l for b in kb_l] + if ka_r is None and kb_r is None: + kab_r = None + elif ka_r is None: + kab_r = [np.dot(a, b) for a in ka_l for b in kb_r] + elif kb_r is None: + kab_r = [np.dot(a, b) for a in ka_r for b in kb_l] + else: + kab_r = [np.dot(a, b) for a in ka_r for b in kb_r] + return Kraus((kab_l, kab_r), input_dims, output_dims) def dot(self, other, qargs=None): """Return the right multiplied quantum channel self * other. @@ -303,14 +330,14 @@ def expand(self, other): """ return self._tensor_product(other, reverse=True) - def add(self, other): + def _add(self, other): """Return the QuantumChannel self + other. Args: other (QuantumChannel): a quantum channel subclass. Returns: - Kraus: the linear addition self + other as a Kraus object. + Kraus: the linear addition channel self + other. Raises: QiskitError: if other cannot be converted to a channel, or @@ -321,26 +348,8 @@ def add(self, other): # or convert to the Choi representation return Kraus(Choi(self).add(other)) - def subtract(self, other): - """Return the QuantumChannel self - other. - - Args: - other (QuantumChannel): a quantum channel subclass. - - Returns: - Kraus: the linear subtraction self - other as Kraus object. - - Raises: - QiskitError: if other cannot be converted to a channel, or - has incompatible dimensions. - """ - # Since we cannot directly subtract two channels in the Kraus - # representation we try and use the other channels method - # or convert to the Choi representation - return Kraus(Choi(self).subtract(other)) - - def multiply(self, other): - """Return the QuantumChannel self + other. + def _multiply(self, other): + """Return the QuantumChannel other * self. Args: other (complex): a complex number. @@ -357,7 +366,7 @@ def multiply(self, other): # kraus channel so we multiply via Choi representation if isinstance(other, complex) or other < 0: # Convert to Choi-matrix - return Kraus(Choi(self).multiply(other)) + return Kraus(Choi(self)._multiply(other)) # If the number is real we can update the Kraus operators # directly val = np.sqrt(other) @@ -425,57 +434,3 @@ def _tensor_product(self, other, reverse=False): kab_r = [np.kron(a, b) for a in ka_r for b in kb_r] data = (kab_l, kab_r) return Kraus(data, input_dims, output_dims) - - def _chanmul(self, other, qargs=None, left_multiply=False): - """Multiply two quantum channels. - - Args: - other (QuantumChannel): a quantum channel. - qargs (list): a list of subsystem positions to compose other on. - left_multiply (bool): If True return other * self - If False return self * other [Default:False] - - Returns: - Kraus: The composition channel as a Kraus object. - - Raises: - QiskitError: if other is not a QuantumChannel subclass, or - has incompatible dimensions. - """ - if qargs is not None: - return Kraus( - SuperOp(self)._chanmul(other, - qargs=qargs, - left_multiply=left_multiply)) - - if not isinstance(other, Kraus): - other = Kraus(other) - # Check dimensions match up - if not left_multiply and self._input_dim != other._output_dim: - raise QiskitError( - 'input_dim of self must match output_dim of other') - if left_multiply and self._output_dim != other._input_dim: - raise QiskitError( - 'input_dim of other must match output_dim of self') - - if left_multiply: - ka_l, ka_r = other._data - kb_l, kb_r = self._data - input_dim = self._input_dim - output_dim = other._output_dim - else: - ka_l, ka_r = self._data - kb_l, kb_r = other._data - input_dim = other._input_dim - output_dim = self._output_dim - - kab_l = [np.dot(a, b) for a in ka_l for b in kb_l] - if ka_r is None and kb_r is None: - kab_r = None - elif ka_r is None: - kab_r = [np.dot(a, b) for a in ka_l for b in kb_r] - elif kb_r is None: - kab_r = [np.dot(a, b) for a in ka_r for b in kb_l] - else: - kab_r = [np.dot(a, b) for a in ka_r for b in kb_r] - return Kraus((kab_l, kab_r), input_dim, output_dim) diff --git a/qiskit/quantum_info/operators/channel/ptm.py b/qiskit/quantum_info/operators/channel/ptm.py index cd9ae7d32bea..14c9144509d3 100644 --- a/qiskit/quantum_info/operators/channel/ptm.py +++ b/qiskit/quantum_info/operators/channel/ptm.py @@ -18,7 +18,6 @@ Pauli Transfer Matrix (PTM) representation of a Quantum Channel. """ -from numbers import Number import numpy as np from qiskit.circuit.quantumcircuit import QuantumCircuit @@ -90,7 +89,7 @@ def __init__(self, data, input_dims=None, output_dims=None): # already a Chi matrix. if isinstance(data, (list, np.ndarray)): # Should we force this to be real? - ptm = np.array(data, dtype=complex) + ptm = np.asarray(data, dtype=complex) # Determine input and output dimensions dout, din = ptm.shape if input_dims: @@ -116,7 +115,8 @@ def __init__(self, data, input_dims=None, output_dims=None): data = self._init_transformer(data) input_dim, output_dim = data.dim # Now that the input is an operator we convert it to a PTM object - ptm = _to_ptm(data.rep, data._data, input_dim, output_dim) + rep = getattr(data, '_channel_rep', 'Operator') + ptm = _to_ptm(rep, data._data, input_dim, output_dim) if input_dims is None: input_dims = data.input_dims() if output_dims is None: @@ -128,7 +128,7 @@ def __init__(self, data, input_dims=None, output_dims=None): # Check and format input and output dimensions input_dims = self._automatic_dims(input_dims, input_dim) output_dims = self._automatic_dims(output_dims, output_dim) - super().__init__('PTM', ptm, input_dims, output_dims) + super().__init__(ptm, input_dims, output_dims, 'PTM') @property def _bipartite_shape(self): @@ -165,8 +165,7 @@ def compose(self, other, qargs=None, front=False): PTM: The quantum channel self @ other. Raises: - QiskitError: if other cannot be converted to a PTM or has - incompatible dimensions. + QiskitError: if other has incompatible dimensions. Additional Information: Composition (``@``) is defined as `left` matrix multiplication for @@ -174,25 +173,21 @@ def compose(self, other, qargs=None, front=False): Setting ``front=True`` returns `right` matrix multiplication ``A * B`` and is equivalent to the :meth:`dot` method. """ - return super().compose(other, qargs=qargs, front=front) - - def dot(self, other, qargs=None): - """Return the right multiplied quantum channel self * other. - - Args: - other (QuantumChannel): a quantum channel. - qargs (list or None): a list of subsystem positions to apply - other on. If None apply on all - subsystems [default: None]. - - Returns: - PTM: The quantum channel self * other. + if qargs is None: + qargs = getattr(other, 'qargs', None) + if qargs is not None: + return PTM( + SuperOp(self).compose(other, qargs=qargs, front=front)) - Raises: - QiskitError: if other cannot be converted to a PTM or has - incompatible dimensions. - """ - return super().dot(other, qargs=qargs) + # Convert other to PTM + if not isinstance(other, PTM): + other = PTM(other) + input_dims, output_dims = self._get_compose_dims(other, qargs, front) + if front: + data = np.dot(self._data, other.data) + else: + data = np.dot(other.data, self._data) + return PTM(data, input_dims, output_dims) def power(self, n): """The matrix power of the channel. @@ -249,62 +244,6 @@ def expand(self, other): data = np.kron(other.data, self._data) return PTM(data, input_dims, output_dims) - def add(self, other): - """Return the QuantumChannel self + other. - - Args: - other (QuantumChannel): a quantum channel. - - Returns: - PTM: the linear addition self + other as a PTM object. - - Raises: - QiskitError: if other cannot be converted to a channel or - has incompatible dimensions. - """ - if not isinstance(other, PTM): - other = PTM(other) - if self.dim != other.dim: - raise QiskitError("other QuantumChannel dimensions are not equal") - return PTM(self._data + other.data, self._input_dims, - self._output_dims) - - def subtract(self, other): - """Return the QuantumChannel self - other. - - Args: - other (QuantumChannel): a quantum channel. - - Returns: - PTM: the linear subtraction self - other as PTM object. - - Raises: - QiskitError: if other cannot be converted to a channel or - has incompatible dimensions. - """ - if not isinstance(other, PTM): - other = PTM(other) - if self.dim != other.dim: - raise QiskitError("other QuantumChannel dimensions are not equal") - return PTM(self._data - other.data, self._input_dims, - self._output_dims) - - def multiply(self, other): - """Return the QuantumChannel self + other. - - Args: - other (complex): a complex number. - - Returns: - PTM: the scalar multiplication other * self as a PTM object. - - Raises: - QiskitError: if other is not a valid scalar. - """ - if not isinstance(other, Number): - raise QiskitError("other is not a number") - return PTM(other * self._data, self._input_dims, self._output_dims) - def _evolve(self, state, qargs=None): """Evolve a quantum state by the quantum channel. @@ -321,46 +260,3 @@ def _evolve(self, state, qargs=None): specified quantum state subsystem dimensions. """ return SuperOp(self)._evolve(state, qargs) - - def _chanmul(self, other, qargs=None, left_multiply=False): - """Multiply two quantum channels. - - Args: - other (QuantumChannel): a quantum channel. - qargs (list): a list of subsystem positions to compose other on. - left_multiply (bool): If True return other * self - If False return self * other [Default:False] - - Returns: - PTM: The composition channel as a PTM object. - - Raises: - QiskitError: if other is not a QuantumChannel subclass, or - has incompatible dimensions. - """ - if qargs is not None: - return PTM( - SuperOp(self)._chanmul(other, - qargs=qargs, - left_multiply=left_multiply)) - - # Convert other to PTM - if not isinstance(other, PTM): - other = PTM(other) - # Check dimensions match up - if not left_multiply and self._input_dim != other._output_dim: - raise QiskitError( - 'input_dim of self must match output_dim of other') - if left_multiply and self._output_dim != other._input_dim: - raise QiskitError( - 'input_dim of other must match output_dim of self') - if left_multiply: - # other * self - input_dim = self._input_dim - output_dim = other._output_dim - return PTM(np.dot(other.data, self._data), input_dim, output_dim) - - # self * other - input_dim = other._input_dim - output_dim = self._output_dim - return PTM(np.dot(self._data, other.data), input_dim, output_dim) diff --git a/qiskit/quantum_info/operators/channel/quantum_channel.py b/qiskit/quantum_info/operators/channel/quantum_channel.py index 24753c202b6a..a8ef337856ea 100644 --- a/qiskit/quantum_info/operators/channel/quantum_channel.py +++ b/qiskit/quantum_info/operators/channel/quantum_channel.py @@ -16,7 +16,9 @@ Abstract base class for Quantum Channels. """ +import copy from abc import abstractmethod +from numbers import Number import numpy as np from qiskit.exceptions import QiskitError @@ -32,6 +34,47 @@ class QuantumChannel(BaseOperator): """Quantum channel representation base class.""" + def __init__(self, data, input_dims=None, output_dims=None, + channel_rep=None): + """Initialize a quantum channel Superoperator operator. + + Args: + data (array or list): quantum channel data array. + input_dims (tuple): the input subsystem dimensions. + [Default: None] + output_dims (tuple): the output subsystem dimensions. + [Default: None] + channel_rep (str): quantum channel representation name string. + + Raises: + QiskitError: if arguments are invalid. + """ + # Set channel representation string + if not isinstance(channel_rep, str): + raise QiskitError("rep must be a string not a {}".format( + channel_rep.__class__)) + self._channel_rep = channel_rep + self._data = data + super().__init__(input_dims, output_dims) + + def __repr__(self): + return '{}({}, input_dims={}, output_dims={})'.format( + self._channel_rep, self._data, self._input_dims, + self._output_dims) + + def __eq__(self, other): + """Test if two QuantumChannels are equal.""" + if not super().__eq__(other): + return False + return np.allclose( + self.data, other.data, rtol=self._rtol, atol=self._atol) + + @property + def data(self): + """Return data.""" + return self._data + + @abstractmethod def compose(self, other, qargs=None, front=False): """Return the composed quantum channel self @ other. @@ -47,8 +90,7 @@ def compose(self, other, qargs=None, front=False): QuantumChannel: The quantum channel self @ other. Raises: - QiskitError: if other is not a QuantumChannel subclass, or has - incompatible dimensions. + QiskitError: if other has incompatible dimensions. Additional Information: Composition (``@``) is defined as `left` matrix multiplication for @@ -56,42 +98,66 @@ def compose(self, other, qargs=None, front=False): Setting ``front=True`` returns `right` matrix multiplication ``A * B`` and is equivalent to the :meth:`dot` method. """ - if front: - return self._chanmul(other, qargs, left_multiply=False) - return self._chanmul(other, qargs, left_multiply=True) + pass - def dot(self, other, qargs=None): - """Return the right multiplied quantum channel self * other. + def _add(self, other): + """Return the QuantumChannel self + other. Args: other (QuantumChannel): a quantum channel. - qargs (list or None): a list of subsystem positions to apply - other on. If None apply on all - subsystems [default: None]. Returns: - QuantumChannel: The quantum channel self * other. + QuantumChannel: the linear addition channel self + other. Raises: - QiskitError: if other is not a QuantumChannel subclass, or has - incompatible dimensions. + QiskitError: if other cannot be converted to a channel or + has incompatible dimensions. """ - return super().dot(other, qargs=qargs) + # NOTE: this method must be overriden for subclasses + # that don't have a linear matrix representation + # ie Kraus and Stinespring + if not isinstance(other, self.__class__): + other = self.__class__(other) + self._validate_add_dims(other) + ret = copy.copy(self) + ret._data = self._data + other._data + return ret + + def _multiply(self, other): + """Return the QuantumChannel other * self. + + Args: + other (complex): a complex number. + + Returns: + QuantumChannel: the scalar multiplication other * self. + + Raises: + QiskitError: if other is not a valid scalar. + """ + # NOTE: this method must be overriden for subclasses + # that don't have a linear matrix representation + # ie Kraus and Stinespring + if not isinstance(other, Number): + raise QiskitError("other is not a number") + ret = copy.copy(self) + ret._data = other * self._data + return ret def is_cptp(self, atol=None, rtol=None): """Return True if completely-positive trace-preserving (CPTP).""" - choi = _to_choi(self.rep, self._data, *self.dim) + choi = _to_choi(self._channel_rep, self._data, *self.dim) return self._is_cp_helper(choi, atol, rtol) and self._is_tp_helper( choi, atol, rtol) def is_tp(self, atol=None, rtol=None): """Test if a channel is completely-positive (CP)""" - choi = _to_choi(self.rep, self._data, *self.dim) + choi = _to_choi(self._channel_rep, self._data, *self.dim) return self._is_tp_helper(choi, atol, rtol) def is_cp(self, atol=None, rtol=None): """Test if Choi-matrix is completely-positive (CP)""" - choi = _to_choi(self.rep, self._data, *self.dim) + choi = _to_choi(self._channel_rep, self._data, *self.dim) return self._is_cp_helper(choi, atol, rtol) def is_unitary(self, atol=None, rtol=None): @@ -104,7 +170,7 @@ def is_unitary(self, atol=None, rtol=None): def to_operator(self): """Try to convert channel to a unitary representation Operator.""" - mat = _to_operator(self.rep, self._data, *self.dim) + mat = _to_operator(self._channel_rep, self._data, *self.dim) return Operator(mat, self.input_dims(), self.output_dims()) def to_instruction(self): @@ -132,7 +198,7 @@ def to_instruction(self): ) # Next we convert to the Kraus representation. Since channel is CPTP we know # that there is only a single set of Kraus operators - kraus, _ = _to_kraus(self.rep, self._data, *self.dim) + kraus, _ = _to_kraus(self._channel_rep, self._data, *self.dim) # If we only have a single Kraus operator then the channel is # a unitary channel so can be converted to a UnitaryGate. We do this by # converting to an Operator and using its to_instruction method @@ -197,25 +263,6 @@ def _evolve(self, state, qargs=None): """ pass - @abstractmethod - def _chanmul(self, other, qargs=None, left_multiply=False): - """Multiply two quantum channels. - - Args: - other (QuantumChannel): a quantum channel. - qargs (list): a list of subsystem positions to compose other on. - left_multiply (bool): If True return other * self - If False return self * other [Default:False] - - Returns: - QuantumChannel: The composition channel. - - Raises: - QiskitError: if other is not a QuantumChannel subclass, or - has incompatible dimensions. - """ - pass - @classmethod def _init_transformer(cls, data): """Convert input into a QuantumChannel subclass object or Operator object""" diff --git a/qiskit/quantum_info/operators/channel/stinespring.py b/qiskit/quantum_info/operators/channel/stinespring.py index 8b0b7212d920..bcd3ce814ccb 100644 --- a/qiskit/quantum_info/operators/channel/stinespring.py +++ b/qiskit/quantum_info/operators/channel/stinespring.py @@ -88,13 +88,13 @@ def __init__(self, data, input_dims=None, output_dims=None): if isinstance(data, (list, tuple, np.ndarray)): if not isinstance(data, tuple): # Convert single Stinespring set to length 1 tuple - stine = (np.array(data, dtype=complex), None) + stine = (np.asarray(data, dtype=complex), None) if isinstance(data, tuple) and len(data) == 2: if data[1] is None: - stine = (np.array(data[0], dtype=complex), None) + stine = (np.asarray(data[0], dtype=complex), None) else: - stine = (np.array(data[0], dtype=complex), - np.array(data[1], dtype=complex)) + stine = (np.asarray(data[0], dtype=complex), + np.asarray(data[1], dtype=complex)) dim_left, dim_right = stine[0].shape # If two Stinespring matrices check they are same shape @@ -123,8 +123,8 @@ def __init__(self, data, input_dims=None, output_dims=None): input_dim, output_dim = data.dim # Now that the input is an operator we convert it to a # Stinespring operator - stine = _to_stinespring(data.rep, data._data, input_dim, - output_dim) + rep = getattr(data, '_channel_rep', 'Operator') + stine = _to_stinespring(rep, data._data, input_dim, output_dim) if input_dims is None: input_dims = data.input_dims() if output_dims is None: @@ -136,15 +136,16 @@ def __init__(self, data, input_dims=None, output_dims=None): # Initialize either single or general Stinespring if stine[1] is None or (stine[1] == stine[0]).all(): # Standard Stinespring map - super().__init__('Stinespring', (stine[0], None), + super().__init__((stine[0], None), input_dims=input_dims, - output_dims=output_dims) + output_dims=output_dims, + channel_rep='Stinespring') else: # General (non-CPTP) Stinespring map - super().__init__('Stinespring', - stine, + super().__init__(stine, input_dims=input_dims, - output_dims=output_dims) + output_dims=output_dims, + channel_rep='Stinespring') @property def data(self): @@ -213,7 +214,15 @@ def compose(self, other, qargs=None, front=False): Setting ``front=True`` returns `right` matrix multiplication ``A * B`` and is equivalent to the :meth:`dot` method. """ - return super().compose(other, qargs=qargs, front=front) + if qargs is None: + qargs = getattr(other, 'qargs', None) + if qargs is not None: + return Stinespring( + SuperOp(self).compose(other, qargs=qargs, front=front)) + + # Otherwise we convert via Kraus representation rather than + # superoperator to avoid unnecessary representation conversions + return Stinespring(Kraus(self).compose(other, front=front)) def dot(self, other, qargs=None): """Return the right multiplied quantum channel self * other. @@ -281,15 +290,14 @@ def expand(self, other): """ return self._tensor_product(other, reverse=True) - def add(self, other): + def _add(self, other): """Return the QuantumChannel self + other. Args: other (QuantumChannel): a quantum channel subclass. Returns: - Stinespring: the linear addition self + other as a - Stinespring object. + Stinespring: the linear addition channel self + other. Raises: QiskitError: if other cannot be converted to a channel or @@ -299,33 +307,14 @@ def add(self, other): # representation we convert to the Choi representation return Stinespring(Choi(self).add(other)) - def subtract(self, other): - """Return the QuantumChannel self - other. - - Args: - other (QuantumChannel): a quantum channel subclass. - - Returns: - Stinespring: the linear subtraction self - other as - Stinespring object. - - Raises: - QiskitError: if other cannot be converted to a channel or - has incompatible dimensions. - """ - # Since we cannot directly subtract two channels in the Stinespring - # representation we convert to the Choi representation - return Stinespring(Choi(self).subtract(other)) - - def multiply(self, other): - """Return the QuantumChannel self + other. + def _multiply(self, other): + """Return the QuantumChannel other * self. Args: other (complex): a complex number. Returns: - Stinespring: the scalar multiplication other * self as a - Stinespring object. + Stinespring: the scalar multiplication other * self. Raises: QiskitError: if other is not a valid scalar. @@ -337,7 +326,7 @@ def multiply(self, other): # the Choi representation if isinstance(other, complex) or other < 1: # Convert to Choi-matrix - return Stinespring(Choi(self).multiply(other)) + return Stinespring(Choi(self)._multiply(other)) # If the number is real we can update the Kraus operators # directly num = np.sqrt(other) @@ -430,40 +419,3 @@ def _tensor_product(self, other, reverse=False): np.transpose(np.reshape(sab_r, shape_in), (0, 2, 1, 3, 4)), shape_out) return Stinespring((sab_l, sab_r), input_dims, output_dims) - - def _chanmul(self, other, qargs=None, left_multiply=False): - """Multiply two quantum channels. - - Args: - other (QuantumChannel): a quantum channel. - qargs (list): a list of subsystem positions to compose other on. - left_multiply (bool): If True return other * self - If False return self * other [Default:False] - - Returns: - Stinespring: The composition channel as a Stinespring object. - - Raises: - QiskitError: if other is not a QuantumChannel subclass, or - has incompatible dimensions. - """ - if qargs is not None: - return Stinespring( - SuperOp(self)._chanmul(other, - qargs=qargs, - left_multiply=left_multiply)) - - # Convert other to Kraus - if not isinstance(other, Kraus): - other = Kraus(other) - # Check dimensions match up - if not left_multiply and self._input_dim != other._output_dim: - raise QiskitError( - 'input_dim of self must match output_dim of other') - if left_multiply and self._output_dim != other._input_dim: - raise QiskitError( - 'input_dim of other must match output_dim of self') - # Since we cannot directly compose two channels in Stinespring - # representation we convert to the Kraus representation - return Stinespring( - Kraus(self)._chanmul(other, left_multiply=left_multiply)) diff --git a/qiskit/quantum_info/operators/channel/superop.py b/qiskit/quantum_info/operators/channel/superop.py index 388abdb25a38..762a8e5ebd4c 100644 --- a/qiskit/quantum_info/operators/channel/superop.py +++ b/qiskit/quantum_info/operators/channel/superop.py @@ -17,12 +17,12 @@ """ Superoperator representation of a Quantum Channel.""" -from numbers import Number import numpy as np from qiskit.circuit.quantumcircuit import QuantumCircuit from qiskit.circuit.instruction import Instruction from qiskit.exceptions import QiskitError +from qiskit.quantum_info.operators.operator import Operator from qiskit.quantum_info.operators.channel.quantum_channel import QuantumChannel from qiskit.quantum_info.operators.channel.transformations import _to_superop from qiskit.quantum_info.operators.channel.transformations import _bipartite_tensor @@ -80,7 +80,7 @@ def __init__(self, data, input_dims=None, output_dims=None): # already a superoperator. if isinstance(data, (list, np.ndarray)): # We initialize directly from superoperator matrix - super_mat = np.array(data, dtype=complex) + super_mat = np.asarray(data, dtype=complex) # Determine total input and output dimensions dout, din = super_mat.shape input_dim = int(np.sqrt(din)) @@ -106,8 +106,8 @@ def __init__(self, data, input_dims=None, output_dims=None): # Now that the input is an operator we convert it to a # SuperOp object input_dim, output_dim = data.dim - super_mat = _to_superop(data.rep, data._data, input_dim, - output_dim) + rep = getattr(data, '_channel_rep', 'Operator') + super_mat = _to_superop(rep, data._data, input_dim, output_dim) if input_dims is None: input_dims = data.input_dims() if output_dims is None: @@ -116,7 +116,7 @@ def __init__(self, data, input_dims=None, output_dims=None): # output dimensions input_dims = self._automatic_dims(input_dims, input_dim) output_dims = self._automatic_dims(output_dims, output_dim) - super().__init__('SuperOp', super_mat, input_dims, output_dims) + super().__init__(super_mat, input_dims, output_dims, 'SuperOp') @property def _shape(self): @@ -156,8 +156,7 @@ def compose(self, other, qargs=None, front=False): SuperOp: The quantum channel self @ other. Raises: - QiskitError: if other cannot be converted to a SuperOp or has - incompatible dimensions. + QiskitError: if other has incompatible dimensions. Additional Information: Composition (``@``) is defined as `left` matrix multiplication for @@ -165,25 +164,48 @@ def compose(self, other, qargs=None, front=False): Setting ``front=True`` returns `right` matrix multiplication ``A * B`` and is equivalent to the :meth:`dot` method. """ - return super().compose(other, qargs=qargs, front=front) - - def dot(self, other, qargs=None): - """Return the right multiplied quantum channel self * other. + if qargs is None: + qargs = getattr(other, 'qargs', None) + # Convert other to SuperOp + if not isinstance(other, SuperOp): + other = SuperOp(other) + # Validate dimensions are compatible and return the composed + # operator dimensions + input_dims, output_dims = self._get_compose_dims( + other, qargs, front) - Args: - other (QuantumChannel): a quantum channel. - qargs (list or None): a list of subsystem positions to apply - other on. If None apply on all - subsystems [default: None]. + # Full composition of superoperators + if qargs is None: + if front: + data = np.dot(self._data, other.data) + else: + data = np.dot(other.data, self._data) + return SuperOp(data, input_dims, output_dims) - Returns: - SuperOp: The quantum channel self * other. + # Compute tensor contraction indices from qargs + if front: + num_indices = len(self._input_dims) + shift = 2 * len(self._output_dims) + right_mul = True + else: + num_indices = len(self._output_dims) + shift = 0 + right_mul = False - Raises: - QiskitError: if other cannot be converted to a SuperOp or has - incompatible dimensions. - """ - return super().dot(other, qargs=qargs) + # Reshape current matrix + # Note that we must reverse the subsystem dimension order as + # qubit 0 corresponds to the right-most position in the tensor + # product, which is the last tensor wire index. + tensor = np.reshape(self.data, self._shape) + mat = np.reshape(other.data, other._shape) + # Add first set of indices + indices = [2 * num_indices - 1 - qubit for qubit in qargs + ] + [num_indices - 1 - qubit for qubit in qargs] + final_shape = [np.product(output_dims)**2, np.product(input_dims)**2] + data = np.reshape( + Operator._einsum_matmul(tensor, mat, indices, shift, right_mul), + final_shape) + return SuperOp(data, input_dims, output_dims) def power(self, n): """Return the compose of a QuantumChannel with itself n times. @@ -257,65 +279,6 @@ def expand(self, other): shape2=self._bipartite_shape) return SuperOp(data, input_dims, output_dims) - def add(self, other): - """Return the QuantumChannel self + other. - - Args: - other (QuantumChannel): a quantum channel. - - Returns: - SuperOp: the linear addition self + other as a SuperOp object. - - Raises: - QiskitError: if other cannot be converted to a channel or - has incompatible dimensions. - """ - # Convert other to SuperOp - if not isinstance(other, SuperOp): - other = SuperOp(other) - if self.dim != other.dim: - raise QiskitError("other QuantumChannel dimensions are not equal") - return SuperOp(self._data + other.data, self.input_dims(), - self.output_dims()) - - def subtract(self, other): - """Return the QuantumChannel self - other. - - Args: - other (QuantumChannel): a quantum channel. - - Returns: - SuperOp: the linear subtraction self - other as SuperOp object. - - Raises: - QiskitError: if other cannot be converted to a channel or - has incompatible dimensions. - """ - # Convert other to SuperOp - if not isinstance(other, SuperOp): - other = SuperOp(other) - if self.dim != other.dim: - raise QiskitError("other QuantumChannel dimensions are not equal") - return SuperOp(self._data - other.data, self.input_dims(), - self.output_dims()) - - def multiply(self, other): - """Return the QuantumChannel self + other. - - Args: - other (complex): a complex number. - - Returns: - SuperOp: the scalar multiplication other * self as a SuperOp object. - - Raises: - QiskitError: if other is not a valid scalar. - """ - if not isinstance(other, Number): - raise QiskitError("other is not a number") - return SuperOp(other * self._data, self.input_dims(), - self.output_dims()) - def _evolve(self, state, qargs=None): """Evolve a quantum state by the quantum channel. @@ -364,7 +327,7 @@ def _evolve(self, state, qargs=None): num_indices = len(state.dims()) indices = [num_indices - 1 - qubit for qubit in qargs ] + [2 * num_indices - 1 - qubit for qubit in qargs] - tensor = self._einsum_matmul(tensor, mat, indices) + tensor = Operator._einsum_matmul(tensor, mat, indices) # Replace evolved dimensions new_dims = list(state.dims()) for i, qubit in enumerate(qargs): @@ -374,113 +337,6 @@ def _evolve(self, state, qargs=None): tensor = np.reshape(tensor, (new_dim, new_dim)) return DensityMatrix(tensor, dims=new_dims) - def _chanmul(self, other, qargs=None, left_multiply=False): - """Multiply two quantum channels. - - Args: - other (QuantumChannel): a quantum channel. - qargs (list): a list of subsystem positions to compose other on. - left_multiply (bool): If True return other * self - If False return self * other [Default:False] - - Returns: - SuperOp: The composition channel as a SuperOp object. - - Raises: - QiskitError: if other is not a QuantumChannel subclass, or - has incompatible dimensions. - """ - # Convert other to SuperOp - if not isinstance(other, SuperOp): - other = SuperOp(other) - # Check dimensions are compatible - if left_multiply and self.output_dims( - qargs=qargs) != other.input_dims(): - raise QiskitError( - 'input_dims of other must match subsystem output_dims') - if not left_multiply and self.input_dims( - qargs=qargs) != other.output_dims(): - raise QiskitError( - 'output_dims of other must match subsystem input_dims') - - # Full composition of superoperators - if qargs is None: - if left_multiply: - # other * self - return SuperOp(np.dot(other.data, self._data), - input_dims=self.input_dims(), - output_dims=other.output_dims()) - # self * other - return SuperOp(np.dot(self._data, other.data), - input_dims=other.input_dims(), - output_dims=self.output_dims()) - # Composition on subsystem - return self._chanmul_subsystem(other, qargs, left_multiply) - - def _chanmul_subsystem(self, other, qargs, left_multiply=False): - """Matrix multiply on subsystem.""" - # Compute tensor contraction indices from qargs - input_dims = list(self.input_dims()) - output_dims = list(self.output_dims()) - if left_multiply: - num_indices = len(self.output_dims()) - shift = 0 - right_mul = False - for pos, qubit in enumerate(qargs): - output_dims[qubit] = other._output_dims[pos] - else: - num_indices = len(self.input_dims()) - shift = 2 * len(self.output_dims()) - right_mul = True - for pos, qubit in enumerate(qargs): - input_dims[qubit] = other._input_dims[pos] - # Reshape current matrix - # Note that we must reverse the subsystem dimension order as - # qubit 0 corresponds to the right-most position in the tensor - # product, which is the last tensor wire index. - tensor = np.reshape(self.data, self._shape) - mat = np.reshape(other.data, other._shape) - # Add first set of indices - indices = [2 * num_indices - 1 - qubit for qubit in qargs - ] + [num_indices - 1 - qubit for qubit in qargs] - final_shape = [np.product(output_dims)**2, np.product(input_dims)**2] - data = np.reshape( - self._einsum_matmul(tensor, mat, indices, shift, right_mul), - final_shape) - return SuperOp(data, input_dims, output_dims) - - def _compose_subsystem(self, other, qargs, front=False): - """Return the composition channel.""" - # Compute tensor contraction indices from qargs - input_dims = list(self.input_dims()) - output_dims = list(self.output_dims()) - if front: - num_indices = len(self.input_dims()) - shift = 2 * len(self.output_dims()) - right_mul = True - for pos, qubit in enumerate(qargs): - input_dims[qubit] = other._input_dims[pos] - else: - num_indices = len(self.output_dims()) - shift = 0 - right_mul = False - for pos, qubit in enumerate(qargs): - output_dims[qubit] = other._output_dims[pos] - # Reshape current matrix - # Note that we must reverse the subsystem dimension order as - # qubit 0 corresponds to the right-most position in the tensor - # product, which is the last tensor wire index. - tensor = np.reshape(self.data, self._shape) - mat = np.reshape(other.data, other._shape) - # Add first set of indices - indices = [2 * num_indices - 1 - qubit for qubit in qargs - ] + [num_indices - 1 - qubit for qubit in qargs] - final_shape = [np.product(output_dims)**2, np.product(input_dims)**2] - data = np.reshape( - self._einsum_matmul(tensor, mat, indices, shift, right_mul), - final_shape) - return SuperOp(data, input_dims, output_dims) - @classmethod def _init_instruction(cls, instruction): """Convert a QuantumCircuit or Instruction to a SuperOp.""" diff --git a/qiskit/quantum_info/operators/channel/transformations.py b/qiskit/quantum_info/operators/channel/transformations.py index 7e94a9ccdb69..7d9ea496ee27 100644 --- a/qiskit/quantum_info/operators/channel/transformations.py +++ b/qiskit/quantum_info/operators/channel/transformations.py @@ -208,7 +208,12 @@ def _choi_to_kraus(data, input_dim, output_dim, atol=ATOL_DEFAULT): # Check if hermitian matrix if is_hermitian_matrix(data, atol=atol): # Get eigen-decomposition of Choi-matrix - w, v = la.eigh(data) + # This should be a call to la.eigh, but there is an OpenBlas + # threading issue that is causing segfaults. + # Need schur here since la.eig does not + # guarentee orthogonality in degenerate subspaces + w, v = la.schur(data, output='complex') + w = w.diagonal().real # Check eigenvalues are non-negative if len(w[w < -atol]) == 0: # CP-map Kraus representation diff --git a/qiskit/quantum_info/operators/operator.py b/qiskit/quantum_info/operators/operator.py index bb492f33863e..10d7fa761a8c 100644 --- a/qiskit/quantum_info/operators/operator.py +++ b/qiskit/quantum_info/operators/operator.py @@ -16,6 +16,7 @@ Matrix Operator class. """ +import copy import re from numbers import Number @@ -23,7 +24,7 @@ from qiskit.circuit.quantumcircuit import QuantumCircuit from qiskit.circuit.instruction import Instruction -from qiskit.extensions.standard import IdGate, XGate, YGate, ZGate, HGate, SGate, TGate +from qiskit.extensions.standard import IGate, XGate, YGate, ZGate, HGate, SGate, TGate from qiskit.exceptions import QiskitError from qiskit.quantum_info.operators.predicates import is_unitary_matrix, matrix_equal from qiskit.quantum_info.operators.base_operator import BaseOperator @@ -72,7 +73,10 @@ def __init__(self, data, input_dims=None, output_dims=None): the input operator is not an N-qubit operator, it will assign a single subsystem with dimension specified by the shape of the input. """ - if isinstance(data, (QuantumCircuit, Instruction)): + if isinstance(data, (list, np.ndarray)): + # Default initialization from list or numpy array matrix + self._data = np.asarray(data, dtype=complex) + elif isinstance(data, (QuantumCircuit, Instruction)): # If the input is a Terra QuantumCircuit or Instruction we # perform a simulation to construct the unitary operator. # This will only work if the circuit or instruction can be @@ -80,33 +84,45 @@ def __init__(self, data, input_dims=None, output_dims=None): # 'to_matrix' method defined. Any other instructions such as # conditional gates, measure, or reset will cause an # exception to be raised. - mat = self._init_instruction(data).data + self._data = self._init_instruction(data).data elif hasattr(data, 'to_operator'): # If the data object has a 'to_operator' attribute this is given # higher preference than the 'to_matrix' method for initializing # an Operator object. data = data.to_operator() - mat = data.data + self._data = data.data if input_dims is None: - input_dims = data.input_dims() + input_dims = data._input_dims if output_dims is None: - output_dims = data.output_dims() + output_dims = data._output_dims elif hasattr(data, 'to_matrix'): # If no 'to_operator' attribute exists we next look for a # 'to_matrix' attribute to a matrix that will be cast into # a complex numpy matrix. - mat = np.array(data.to_matrix(), dtype=complex) - elif isinstance(data, (list, np.ndarray)): - # Finally we check if the input is a raw matrix in either a - # python list or numpy array format. - mat = np.array(data, dtype=complex) + self._array = np.asarray(data.to_matrix(), dtype=complex) else: raise QiskitError("Invalid input data format for Operator") # Determine input and output dimensions - dout, din = mat.shape + dout, din = self._data.shape output_dims = self._automatic_dims(output_dims, dout) input_dims = self._automatic_dims(input_dims, din) - super().__init__('Operator', mat, input_dims, output_dims) + super().__init__(input_dims, output_dims) + + def __repr__(self): + return 'Operator({}, input_dims={}, output_dims={})'.format( + self._data, self._input_dims, self._output_dims) + + def __eq__(self, other): + """Test if two Operators are equal.""" + if not super().__eq__(other): + return False + return np.allclose( + self.data, other.data, rtol=self._rtol, atol=self._atol) + + @property + def data(self): + """Return data.""" + return self._data @classmethod def from_label(cls, label): @@ -140,7 +156,7 @@ def from_label(cls, label): """ # Check label is valid label_mats = { - 'I': IdGate().to_matrix(), + 'I': IGate().to_matrix(), 'X': XGate().to_matrix(), 'Y': YGate().to_matrix(), 'Z': ZGate().to_matrix(), @@ -178,20 +194,25 @@ def to_operator(self): def to_instruction(self): """Convert to a UnitaryGate instruction.""" + # pylint: disable=cyclic-import from qiskit.extensions.unitary import UnitaryGate return UnitaryGate(self.data) def conjugate(self): """Return the conjugate of the operator.""" - return Operator(np.conj(self.data), - input_dims=self.input_dims(), - output_dims=self.output_dims()) + # Make a shallow copy and update array + ret = copy.copy(self) + ret._data = np.conj(self._data) + return ret def transpose(self): """Return the transpose of the operator.""" - return Operator(np.transpose(self.data), - input_dims=self.output_dims(), - output_dims=self.input_dims()) + # Make a shallow copy and update array + ret = copy.copy(self) + ret._data = np.transpose(self._data) + # Swap input and output dimensions + ret._set_dims(self._output_dims, self._input_dims) + return ret def compose(self, other, qargs=None, front=False): """Return the composed operator. @@ -207,15 +228,57 @@ def compose(self, other, qargs=None, front=False): Returns: Operator: The operator self @ other. + Raise: + QiskitError: if operators have incompatible dimensions for + composition. + Additional Information: Composition (``@``) is defined as `left` matrix multiplication for matrix operators. That is that ``A @ B`` is equal to ``B * A``. Setting ``front=True`` returns `right` matrix multiplication ``A * B`` and is equivalent to the :meth:`dot` method. """ + if qargs is None: + qargs = getattr(other, 'qargs', None) + if not isinstance(other, Operator): + other = Operator(other) + # Validate dimensions are compatible and return the composed + # operator dimensions + input_dims, output_dims = self._get_compose_dims( + other, qargs, front) + + # Full composition of operators + if qargs is None: + if front: + # Composition self * other + data = np.dot(self._data, other.data) + else: + # Composition other * self + data = np.dot(other.data, self._data) + return Operator(data, input_dims, output_dims) + + # Compose with other on subsystem if front: - return self._matmul(other, qargs, left_multiply=False) - return self._matmul(other, qargs, left_multiply=True) + num_indices = len(self._input_dims) + shift = len(self._output_dims) + right_mul = True + else: + num_indices = len(self._output_dims) + shift = 0 + right_mul = False + + # Reshape current matrix + # Note that we must reverse the subsystem dimension order as + # qubit 0 corresponds to the right-most position in the tensor + # product, which is the last tensor wire index. + tensor = np.reshape(self.data, self._shape) + mat = np.reshape(other.data, other._shape) + indices = [num_indices - 1 - qubit for qubit in qargs] + final_shape = [np.product(output_dims), np.product(input_dims)] + data = np.reshape( + Operator._einsum_matmul(tensor, mat, indices, shift, right_mul), + final_shape) + return Operator(data, input_dims, output_dims) def dot(self, other, qargs=None): """Return the right multiplied operator self * other. @@ -254,9 +317,9 @@ def power(self, n): raise QiskitError("Can only power with input_dims = output_dims.") # Override base class power so we can implement more efficiently # using Numpy.matrix_power - return Operator( - np.linalg.matrix_power(self.data, n), self.input_dims(), - self.output_dims()) + ret = copy.copy(self) + ret._data = np.linalg.matrix_power(self.data, n) + return ret def tensor(self, other): """Return the tensor product operator self ⊗ other. @@ -296,7 +359,7 @@ def expand(self, other): data = np.kron(other._data, self._data) return Operator(data, input_dims, output_dims) - def add(self, other): + def _add(self, other): """Return the operator self + other. Args: @@ -311,33 +374,13 @@ def add(self, other): """ if not isinstance(other, Operator): other = Operator(other) - if self.dim != other.dim: - raise QiskitError("other operator has different dimensions.") - return Operator(self.data + other.data, self.input_dims(), - self.output_dims()) - - def subtract(self, other): - """Return the operator self - other. - - Args: - other (Operator): an operator object. - - Returns: - Operator: the operator self - other. - - Raises: - QiskitError: if other is not an operator, or has incompatible - dimensions. - """ - if not isinstance(other, Operator): - other = Operator(other) - if self.dim != other.dim: - raise QiskitError("other operator has different dimensions.") - return Operator(self.data - other.data, self.input_dims(), - self.output_dims()) + self._validate_add_dims(other) + ret = copy.copy(self) + ret._data = self.data + other.data + return ret - def multiply(self, other): - """Return the operator self + other. + def _multiply(self, other): + """Return the operator other * self. Args: other (complex): a complex number. @@ -350,8 +393,9 @@ def multiply(self, other): """ if not isinstance(other, Number): raise QiskitError("other is not a number") - return Operator(other * self.data, self.input_dims(), - self.output_dims()) + ret = copy.copy(self) + ret._data = other * self._data + return ret def equiv(self, other, rtol=None, atol=None): """Return True if operators are equivalent up to global phase. @@ -384,106 +428,41 @@ def _shape(self): return tuple(reversed(self.output_dims())) + tuple( reversed(self.input_dims())) - def _matmul(self, other, qargs=None, left_multiply=False): - """Matrix multiply two operators + @classmethod + def _einsum_matmul(cls, tensor, mat, indices, shift=0, right_mul=False): + """Perform a contraction using Numpy.einsum Args: - other (Operator): an operator object. - qargs (list): a list of subsystem positions to compose other on. - left_multiply (bool): If True return other * self - If False return self * other [Default:False] + tensor (np.array): a vector or matrix reshaped to a rank-N tensor. + mat (np.array): a matrix reshaped to a rank-2M tensor. + indices (list): tensor indices to contract with mat. + shift (int): shift for indices of tensor to contract [Default: 0]. + right_mul (bool): if True right multiply tensor by mat + (else left multiply) [Default: False]. + Returns: - Operator: The output operator. + Numpy.ndarray: the matrix multiplied rank-N tensor. Raises: - QiskitError: if other cannot be converted to an Operator or has - incompatible dimensions. + QiskitError: if mat is not an even rank tensor. """ - # Convert to Operator - if not isinstance(other, Operator): - other = Operator(other) - # Check dimensions are compatible - if not left_multiply and self.input_dims(qargs=qargs) != other.output_dims(): - raise QiskitError( - 'output_dims of other must match subsystem input_dims') - if left_multiply and self.output_dims(qargs=qargs) != other.input_dims(): + rank = tensor.ndim + rank_mat = mat.ndim + if rank_mat % 2 != 0: raise QiskitError( - 'input_dims of other must match subsystem output_dims') - # Full composition of operators - if qargs is None: - if left_multiply: - # Composition other * self - input_dims = self.input_dims() - output_dims = other.output_dims() - data = np.dot(other.data, self._data) - else: - # Composition self * other - input_dims = other.input_dims() - output_dims = self.output_dims() - data = np.dot(self._data, other.data) - return Operator(data, input_dims, output_dims) - # Compose with other on subsystem - return self._matmul_subsystem(other, qargs, left_multiply) - - def _matmul_subsystem(self, other, qargs, left_multiply=False): - """Matrix multiply on subsystem.""" - # Compute tensor contraction indices from qargs - input_dims = list(self.input_dims()) - output_dims = list(self.output_dims()) - if left_multiply: - num_indices = len(self.output_dims()) - shift = 0 - right_mul = False - for pos, qubit in enumerate(qargs): - output_dims[qubit] = other._output_dims[pos] - else: - num_indices = len(self.input_dims()) - shift = len(self.output_dims()) - right_mul = True - for pos, qubit in enumerate(qargs): - input_dims[qubit] = other._input_dims[pos] - # Reshape current matrix - # Note that we must reverse the subsystem dimension order as - # qubit 0 corresponds to the right-most position in the tensor - # product, which is the last tensor wire index. - tensor = np.reshape(self.data, self._shape) - mat = np.reshape(other.data, other._shape) - indices = [num_indices - 1 - qubit for qubit in qargs] - final_shape = [np.product(output_dims), np.product(input_dims)] - data = np.reshape( - self._einsum_matmul(tensor, mat, indices, shift, right_mul), - final_shape) - return Operator(data, input_dims, output_dims) - - def _compose_subsystem(self, other, qargs, front=False): - """Return the composition channel.""" - # Compute tensor contraction indices from qargs - input_dims = list(self.input_dims()) - output_dims = list(self.output_dims()) - if front: - num_indices = len(self.input_dims()) - shift = len(self.output_dims()) - right_mul = True - for pos, qubit in enumerate(qargs): - input_dims[qubit] = other._input_dims[pos] + "Contracted matrix must have an even number of indices.") + # Get einsum indices for tensor + indices_tensor = list(range(rank)) + for j, index in enumerate(indices): + indices_tensor[index + shift] = rank + j + # Get einsum indices for mat + mat_contract = list(reversed(range(rank, rank + len(indices)))) + mat_free = [index + shift for index in reversed(indices)] + if right_mul: + indices_mat = mat_contract + mat_free else: - num_indices = len(self.output_dims()) - shift = 0 - right_mul = False - for pos, qubit in enumerate(qargs): - output_dims[qubit] = other._output_dims[pos] - # Reshape current matrix - # Note that we must reverse the subsystem dimension order as - # qubit 0 corresponds to the right-most position in the tensor - # product, which is the last tensor wire index. - tensor = np.reshape(self.data, self._shape) - mat = np.reshape(other.data, other._shape) - indices = [num_indices - 1 - qubit for qubit in qargs] - final_shape = [np.product(output_dims), np.product(input_dims)] - data = np.reshape( - self._einsum_matmul(tensor, mat, indices, shift, right_mul), - final_shape) - return Operator(data, input_dims, output_dims) + indices_mat = mat_free + mat_contract + return np.einsum(tensor, indices_tensor, mat, indices_mat) @classmethod def _init_instruction(cls, instruction): diff --git a/qiskit/quantum_info/operators/pauli.py b/qiskit/quantum_info/operators/pauli.py index c66d66f84039..d71ddc785e67 100644 --- a/qiskit/quantum_info/operators/pauli.py +++ b/qiskit/quantum_info/operators/pauli.py @@ -312,8 +312,8 @@ def to_operator(self): def to_instruction(self): """Convert to Pauli circuit instruction.""" from qiskit.circuit import QuantumCircuit, QuantumRegister - from qiskit.extensions.standard import IdGate, XGate, YGate, ZGate - gates = {'I': IdGate(), 'X': XGate(), 'Y': YGate(), 'Z': ZGate()} + from qiskit.extensions.standard import IGate, XGate, YGate, ZGate + gates = {'I': IGate(), 'X': XGate(), 'Y': YGate(), 'Z': ZGate()} label = self.to_label() n_qubits = self.numberofqubits qreg = QuantumRegister(n_qubits) diff --git a/qiskit/quantum_info/synthesis/__init__.py b/qiskit/quantum_info/synthesis/__init__.py index 7c874c90c1fa..8b3df9767700 100644 --- a/qiskit/quantum_info/synthesis/__init__.py +++ b/qiskit/quantum_info/synthesis/__init__.py @@ -14,4 +14,6 @@ """State and Unitary synthesis methods.""" -from .two_qubit_decompose import TwoQubitBasisDecomposer, euler_angles_1q, two_qubit_cnot_decompose +from .two_qubit_decompose import TwoQubitBasisDecomposer, two_qubit_cnot_decompose +from .one_qubit_decompose import OneQubitEulerDecomposer +from .two_qubit_decompose import euler_angles_1q # DEPRECATED diff --git a/qiskit/quantum_info/synthesis/ion_decompose.py b/qiskit/quantum_info/synthesis/ion_decompose.py index cb2c175c381b..b98a793a88d9 100644 --- a/qiskit/quantum_info/synthesis/ion_decompose.py +++ b/qiskit/quantum_info/synthesis/ion_decompose.py @@ -51,7 +51,7 @@ def cnot_rxx_decompose(plus_ry=True, plus_rxx=True): sgn_rxx = -1 circuit = QuantumCircuit(2) - circuit.append(RYGate(sgn_ry * np.pi / 2), [0]) + circuit.append(RYGate(sgn_ry * np.pi / 2, phase=0.25*np.pi), [0]) circuit.append(RXXGate(sgn_rxx * np.pi / 2), [0, 1]) circuit.append(RXGate(-sgn_rxx * np.pi / 2), [0]) circuit.append(RXGate(-sgn_rxx * sgn_ry * np.pi / 2), [1]) diff --git a/qiskit/quantum_info/synthesis/one_qubit_decompose.py b/qiskit/quantum_info/synthesis/one_qubit_decompose.py index e71bf88e83be..37d38e861e62 100644 --- a/qiskit/quantum_info/synthesis/one_qubit_decompose.py +++ b/qiskit/quantum_info/synthesis/one_qubit_decompose.py @@ -14,7 +14,7 @@ # pylint: disable=invalid-name """ -Decompose single-qubit unitary into Euler angles. +Decompose a single-qubit unitary via Euler angles. """ import math @@ -22,36 +22,89 @@ import scipy.linalg as la from qiskit.circuit.quantumcircuit import QuantumCircuit -from qiskit.extensions.standard import HGate, U3Gate, U1Gate, RXGate, RYGate, RZGate +from qiskit.extensions.standard import (U3Gate, U1Gate, RXGate, RYGate, RZGate, + RGate) from qiskit.exceptions import QiskitError -from qiskit.quantum_info.operators import Operator from qiskit.quantum_info.operators.predicates import is_unitary_matrix -DEFAULT_ATOL = 1e-12 +DEFAULT_SIMPLIFY_TOLERANCE = 1e-12 +DEFAULT_ATOL = 1e-7 class OneQubitEulerDecomposer: - """A class for decomposing 1-qubit unitaries into Euler angle rotations. - - Allowed basis and their decompositions are: - U3: U -> exp(1j*phase) * U3(theta, phi, lam) - U1X: U -> exp(1j*phase) * U1(lam).RX(pi/2).U1(theta+pi).RX(pi/2).U1(phi+pi) - ZYZ: U -> exp(1j*phase) * RZ(phi).RY(theta).RZ(lam) - ZXZ: U -> exp(1j*phase) * RZ(phi).RX(theta).RZ(lam) - XYX: U -> exp(1j*phase) * RX(phi).RY(theta).RX(lam) + r"""A class for decomposing 1-qubit unitaries into Euler angle rotations. + + The resulting decomposition is parameterized by 3 Euler rotation angle + parameters :math:`(\theta, \phi\ lambda)`, and a phase parameter + :math:`\gamma`. The value of the parameters for an input unitary depends + on the decomposition basis. Allowed bases and the resulting circuits are + shown in the following table. Note that for the non-Euler bases (U3, U1X, + RR), the ZYZ euler parameters are used. + + .. list-table:: Supported circuit bases + :widths: auto + :header-rows: 1 + + * - Basis + - Euler Angle Basis + - Decomposition Circuit + * - 'ZYZ' + - :math:`Z(\phi) Y(\theta) Z(\lambda)` + - :math:`e^{i\gamma} R_Z(\phi).R_Y(\theta).R_Z(\lambda)` + * - 'ZXZ' + - :math:`Z(\phi) X(\theta) Z(\lambda)` + - :math:`e^{i\gamma} R_Z(\phi).R_X(\theta).R_Z(\lambda)` + * - 'XYX' + - :math:`X(\phi) Y(\theta) X(\lambda)` + - :math:`e^{i\gamma} R_X(\phi).R_Y(\theta).R_X(\lambda)` + * - 'U3' + - :math:`Z(\phi) Y(\theta) Z(\lambda)` + - :math:`e^{i\gamma}{2}\right)\right)} U_3(\theta,\phi,\lambda)` + * - 'U1X' + - :math:`Z(\phi) Y(\theta) Z(\lambda)` + - :math:`e^{i \gamma}{2}\right)\right)} + :math:`U_1(\phi+\pi).R_X\left(\frac{\pi}{2}\right).U_1(\theta+\pi).` + :math:`R_X\left(\frac{\pi}{2}\right).U_1(\lambda)` + * - 'RR' + - :math:`Z(\phi) Y(\theta) Z(\lambda)` + - :math:`e^{i\gamma} R\left(-\pi,\frac{\phi-\lambda+\pi}{2}\right).` + :math:`R\left(\theta+\pi,\frac{\pi}{2}-\lambda\right)` """ def __init__(self, basis='U3'): - if basis not in ['U3', 'U1X', 'ZYZ', 'ZXZ', 'XYX']: - raise QiskitError("OneQubitEulerDecomposer: unsupported basis") - self._basis = basis + """Initialize decomposer + + Supported bases are: 'U3', 'U1X', 'RR', 'ZYZ', 'ZXZ', 'XYX'. + + Args: + basis (str): the decomposition basis [Default: 'U3] + + Raises: + QiskitError: If input basis is not recognized. + """ + # Default values + self._basis = 'U3' + self._params = self._params_u3 + self._circuit = self._circuit_u3 + # Set basis + self.basis = basis - def __call__(self, unitary_mat, simplify=True, atol=DEFAULT_ATOL): + def __call__(self, + unitary, + simplify=True, + simplify_tolerance=DEFAULT_SIMPLIFY_TOLERANCE, + phase_equal=True, + atol=DEFAULT_ATOL): """Decompose single qubit gate into a circuit. Args: - unitary_mat (array_like): 1-qubit unitary matrix - simplify (bool): remove zero-angle rotations [Default: True] - atol (float): absolute tolerance for checking angles zero. + unitary (Operator or Gate or array): 1-qubit unitary matrix + simplify (bool): reduce gate count in decomposition [Default: True]. + simplify_tolerance (float): absolute tolerance for checking + angles in simplify [Default: 1e-12]. + phase_equal (bool): verify the output circuit is phase equal + to the input matrix [Default: True]. + atol (bool): absolute tolerance for comparing synthesised circuit + matrix to input [Default: 1e-7]. Returns: QuantumCircuit: the decomposed single-qubit gate circuit @@ -59,188 +112,304 @@ def __call__(self, unitary_mat, simplify=True, atol=DEFAULT_ATOL): Raises: QiskitError: if input is invalid or synthesis fails. """ - if hasattr(unitary_mat, 'to_operator'): + if hasattr(unitary, 'to_operator'): # If input is a BaseOperator subclass this attempts to convert # the object to an Operator so that we can extract the underlying # numpy matrix from `Operator.data`. - unitary_mat = unitary_mat.to_operator().data - if hasattr(unitary_mat, 'to_matrix'): + unitary = unitary.to_operator().data + elif hasattr(unitary, 'to_matrix'): # If input is Gate subclass or some other class object that has # a to_matrix method this will call that method. - unitary_mat = unitary_mat.to_matrix() + unitary = unitary.to_matrix() # Convert to numpy array incase not already an array - unitary_mat = np.asarray(unitary_mat, dtype=complex) + unitary = np.asarray(unitary, dtype=complex) # Check input is a 2-qubit unitary - if unitary_mat.shape != (2, 2): + if unitary.shape != (2, 2): raise QiskitError("OneQubitEulerDecomposer: " "expected 2x2 input matrix") - if not is_unitary_matrix(unitary_mat): + if not is_unitary_matrix(unitary): raise QiskitError("OneQubitEulerDecomposer: " "input matrix is not unitary.") - circuit = self._circuit(unitary_mat, simplify=simplify, atol=atol) + theta, phi, lam, phase = self._params(unitary) + circuit = self._circuit(theta, phi, lam, + phase=phase, + simplify=simplify, + simplify_tolerance=simplify_tolerance) # Check circuit is correct - if not Operator(circuit).equiv(Operator(unitary_mat)): - raise QiskitError("OneQubitEulerDecomposer: " - "synthesis failed within required accuracy.") + self.check_equiv(unitary, circuit, + phase_equal=phase_equal, + atol=atol) return circuit - def _angles(self, unitary_mat): - """Return Euler angles for given basis.""" - if self._basis in ['U3', 'U1X', 'ZYZ']: - return self._angles_zyz(unitary_mat) - if self._basis == 'ZXZ': - return self._angles_zxz(unitary_mat) - if self._basis == 'XYX': - return self._angles_xyx(unitary_mat) - raise QiskitError("OneQubitEulerDecomposer: invalid basis") - - def _circuit(self, unitary_mat, simplify=True, atol=DEFAULT_ATOL): - # NOTE: The 4th variable is phase to be used later - theta, phi, lam, _ = self._angles(unitary_mat) - if self._basis == 'U3': - return self._circuit_u3(theta, phi, lam) - if self._basis == 'U1X': - return self._circuit_u1x(theta, - phi, - lam, - simplify=simplify, - atol=atol) - if self._basis == 'ZYZ': - return self._circuit_zyz(theta, - phi, - lam, - simplify=simplify, - atol=atol) - if self._basis == 'ZXZ': - return self._circuit_zxz(theta, - phi, - lam, - simplify=simplify, - atol=atol) - if self._basis == 'XYX': - return self._circuit_xyx(theta, - phi, - lam, - simplify=simplify, - atol=atol) - raise QiskitError("OneQubitEulerDecomposer: invalid basis") + @property + def basis(self): + """The decomposition basis.""" + return self._basis + + @basis.setter + def basis(self, basis): + """Set the decomposition basis.""" + basis_methods = { + 'U3': (self._params_u3, self._circuit_u3), + 'U1X': (self._params_u1x, self._circuit_u1x), + 'RR': (self._params_zyz, self._circuit_rr), + 'ZYZ': (self._params_zyz, self._circuit_zyz), + 'ZXZ': (self._params_zxz, self._circuit_zxz), + 'XYX': (self._params_xyx, self._circuit_xyx) + } + if basis not in basis_methods: + raise QiskitError("OneQubitEulerDecomposer: unsupported basis {}".format(basis)) + self._basis = basis + self._params, self._circuit = basis_methods[self._basis] + + def angles(self, unitary): + """Return the Euler angles for input array. + + Args: + unitary (np.ndarray): 2x2 unitary matrix. + + Returns: + tuple: (theta, phi, lambda). + """ + theta, phi, lam, _ = self._params(unitary) + return theta, phi, lam + + def angles_and_phase(self, unitary): + """Return the Euler angles and phase for input array. + + Args: + unitary (np.ndarray): 2x2 unitary matrix. + + Returns: + tuple: (theta, phi, lambda, phase). + """ + return self._params(unitary) + + def circuit(self, theta, phi, lam, phase=0, simplify=True, + simplify_tolerance=DEFAULT_SIMPLIFY_TOLERANCE): + """Return the basis circuit for the input parameters. + + Args: + theta (float): euler angle parameter + phi (float): euler angle parameter + lam (float): euler angle parameter + phase (float): gate phase parameter + simplify (bool): simplify output circuit [Default: True] + simplify_tolerance (float): absolute tolerance for checking + angles zero [Default: 1e-12]. + + Returns: + QuantumCircuit: the basis circuits. + """ + return self._circuit(theta, phi, lam, + phase=phase, + simplify=simplify, + simplify_tolerance=simplify_tolerance) @staticmethod - def _angles_zyz(unitary_mat): - """Return euler angles for special unitary matrix in ZYZ basis. + def check_equiv(unitary, circuit, phase_equal=False, atol=DEFAULT_ATOL): + """Check a circuit is equivalent to a unitary. - In this representation U = exp(1j * phase) * Rz(phi).Ry(theta).Rz(lam) + Args: + unitary (Operator or Gate or array): unitary operator. + circuit (QuantumCircuit or Instruction): decomposition circuit. + phase_equal (bool): require the decomposition to be global phase + equal [Default: False] + atol (float): absolute tolerance for checking matrix entries. + [Default: 1e-7] + + Raises: + QiskitError: if the input unitary and circuit are not equivalent. """ + # NOTE: this function isn't specific to this class so could be + # moved to another location for more general use. + + # pylint: disable=cyclic-import + from qiskit.quantum_info.operators import Operator + if phase_equal and not np.allclose(Operator(circuit).data, unitary, atol=atol): + raise QiskitError( + "Phase equal circuit synthesis failed within required accuracy." + ) + if not phase_equal and not Operator(circuit).equiv(unitary, atol=atol): + raise QiskitError( + "Circuit synthesis failed within required accuracy.") + + @staticmethod + def _params_zyz(mat): + """Return the euler angles and phase for the ZYZ basis.""" # We rescale the input matrix to be special unitary (det(U) = 1) # This ensures that the quaternion representation is real - coeff = la.det(unitary_mat)**(-0.5) + coeff = la.det(mat)**(-0.5) phase = -np.angle(coeff) - U = coeff * unitary_mat # U in SU(2) + su_mat = coeff * mat # U in SU(2) # OpenQASM SU(2) parameterization: # U[0, 0] = exp(-i(phi+lambda)/2) * cos(theta/2) # U[0, 1] = -exp(-i(phi-lambda)/2) * sin(theta/2) # U[1, 0] = exp(i(phi-lambda)/2) * sin(theta/2) # U[1, 1] = exp(i(phi+lambda)/2) * cos(theta/2) - theta = 2 * math.atan2(abs(U[1, 0]), abs(U[0, 0])) - phiplambda = 2 * np.angle(U[1, 1]) - phimlambda = 2 * np.angle(U[1, 0]) + theta = 2 * math.atan2(abs(su_mat[1, 0]), abs(su_mat[0, 0])) + phiplambda = 2 * np.angle(su_mat[1, 1]) + phimlambda = 2 * np.angle(su_mat[1, 0]) phi = (phiplambda + phimlambda) / 2.0 lam = (phiplambda - phimlambda) / 2.0 return theta, phi, lam, phase @staticmethod - def _angles_zxz(unitary_mat): - """Return euler angles for special unitary matrix in ZXZ basis. - - In this representation U = exp(1j * phase) * Rz(phi).Rx(theta).Rz(lam) - """ - theta, phi, lam, phase = OneQubitEulerDecomposer._angles_zyz(unitary_mat) + def _params_zxz(mat): + """Return the euler angles and phase for the ZXZ basis.""" + theta, phi, lam, phase = OneQubitEulerDecomposer._params_zyz(mat) return theta, phi + np.pi / 2, lam - np.pi / 2, phase @staticmethod - def _angles_xyx(unitary_mat): - """Return euler angles for special unitary matrix in XYX basis. - - In this representation U = exp(1j * phase) * Rx(phi).Ry(theta).Rx(lam) - """ + def _params_xyx(mat): + """Return the euler angles and phase for the XYX basis.""" # We use the fact that # Rx(a).Ry(b).Rx(c) = H.Rz(a).Ry(-b).Rz(c).H - had = HGate().to_matrix() - mat_zyz = np.dot(np.dot(had, unitary_mat), had) - theta, phi, lam, phase = OneQubitEulerDecomposer._angles_zyz(mat_zyz) + mat_zyz = 0.5 * np.array( + [[ + mat[0, 0] + mat[0, 1] + mat[1, 0] + mat[1, 1], + mat[0, 0] - mat[0, 1] + mat[1, 0] - mat[1, 1] + ], + [ + mat[0, 0] + mat[0, 1] - mat[1, 0] - mat[1, 1], + mat[0, 0] - mat[0, 1] - mat[1, 0] + mat[1, 1] + ]], + dtype=complex) + theta, phi, lam, phase = OneQubitEulerDecomposer._params_zyz(mat_zyz) return -theta, phi, lam, phase @staticmethod - def _circuit_u3(theta, phi, lam): - circuit = QuantumCircuit(1) - circuit.append(U3Gate(theta, phi, lam), [0]) - return circuit + def _params_u3(mat): + """Return the euler angles and phase for the U3 basis.""" + # The determinant of U3 gate depends on its params + # via det(u3(theta, phi, lam)) = exp(1j*(phi+lam)) + # Since the phase is wrt to a SU matrix we must rescale + # phase to correct this + theta, phi, lam, phase = OneQubitEulerDecomposer._params_zyz(mat) + return theta, phi, lam, phase - 0.5 * (phi + lam) @staticmethod - def _circuit_u1x(theta, phi, lam, simplify=True, atol=DEFAULT_ATOL): - # Check for U1 and U2 decompositions into minimimal - # required X90 pulses - if simplify and np.allclose([theta, phi], [0., 0.], atol=atol): - # zero X90 gate decomposition - circuit = QuantumCircuit(1) - circuit.append(U1Gate(lam), [0]) - return circuit - if simplify and np.isclose(theta, np.pi / 2, atol=atol): - # single X90 gate decomposition - circuit = QuantumCircuit(1) - circuit.append(U1Gate(lam - np.pi / 2), [0]) - circuit.append(RXGate(np.pi / 2), [0]) - circuit.append(U1Gate(phi + np.pi / 2), [0]) - return circuit - # General two-X90 gate decomposition - circuit = QuantumCircuit(1) - circuit.append(U1Gate(lam), [0]) - circuit.append(RXGate(np.pi / 2), [0]) - circuit.append(U1Gate(theta + np.pi), [0]) - circuit.append(RXGate(np.pi / 2), [0]) - circuit.append(U1Gate(phi + np.pi), [0]) - return circuit + def _params_u1x(mat): + """Return the euler angles and phase for the U1X basis.""" + # The determinant of this decomposition depends on its params + # Since the phase is wrt to a SU matrix we must rescale + # phase to correct this + theta, phi, lam, phase = OneQubitEulerDecomposer._params_zyz(mat) + return theta, phi, lam, phase - 0.5 * (theta + phi + lam) @staticmethod - def _circuit_zyz(theta, phi, lam, simplify=True, atol=DEFAULT_ATOL): + def _circuit_zyz(theta, + phi, + lam, + phase=0, + simplify=True, + simplify_tolerance=DEFAULT_SIMPLIFY_TOLERANCE): circuit = QuantumCircuit(1) - if simplify and np.isclose(theta, 0.0, atol=atol): - circuit.append(RZGate(phi + lam), [0]) + if simplify and np.isclose(theta, 0.0, atol=simplify_tolerance): + circuit.append(RZGate(phi + lam, phase=phase), [0]) return circuit - if not simplify or not np.isclose(lam, 0.0, atol=atol): + if not simplify or not np.isclose(lam, 0.0, atol=simplify_tolerance): circuit.append(RZGate(lam), [0]) - if not simplify or not np.isclose(theta, 0.0, atol=atol): - circuit.append(RYGate(theta), [0]) - if not simplify or not np.isclose(phi, 0.0, atol=atol): + if not simplify or not np.isclose(theta, 0.0, atol=simplify_tolerance): + circuit.append(RYGate(theta, phase=phase), [0]) + if not simplify or not np.isclose(phi, 0.0, atol=simplify_tolerance): circuit.append(RZGate(phi), [0]) return circuit @staticmethod - def _circuit_zxz(theta, phi, lam, simplify=False, atol=DEFAULT_ATOL): - if simplify and np.isclose(theta, 0.0, atol=atol): + def _circuit_zxz(theta, + phi, + lam, + phase=0, + simplify=False, + simplify_tolerance=DEFAULT_SIMPLIFY_TOLERANCE): + if simplify and np.isclose(theta, 0.0, atol=simplify_tolerance): circuit = QuantumCircuit(1) - circuit.append(RZGate(phi + lam), [0]) + circuit.append(RZGate(phi + lam, phase=phase), [0]) return circuit circuit = QuantumCircuit(1) - if not simplify or not np.isclose(lam, 0.0, atol=atol): + if not simplify or not np.isclose(lam, 0.0, atol=simplify_tolerance): circuit.append(RZGate(lam), [0]) - if not simplify or not np.isclose(theta, 0.0, atol=atol): - circuit.append(RXGate(theta), [0]) - if not simplify or not np.isclose(phi, 0.0, atol=atol): + if not simplify or not np.isclose(theta, 0.0, atol=simplify_tolerance): + circuit.append(RXGate(theta, phase=phase), [0]) + if not simplify or not np.isclose(phi, 0.0, atol=simplify_tolerance): circuit.append(RZGate(phi), [0]) return circuit @staticmethod - def _circuit_xyx(theta, phi, lam, simplify=True, atol=DEFAULT_ATOL): + def _circuit_xyx(theta, + phi, + lam, + phase=0, + simplify=True, + simplify_tolerance=DEFAULT_SIMPLIFY_TOLERANCE): circuit = QuantumCircuit(1) - if simplify and np.isclose(theta, 0.0, atol=atol): - circuit.append(RXGate(phi + lam), [0]) + if simplify and np.isclose(theta, 0.0, atol=simplify_tolerance): + circuit.append(RXGate(phi + lam, phase=phase), [0]) return circuit - if not simplify or not np.isclose(lam, 0.0, atol=atol): + if not simplify or not np.isclose(lam, 0.0, atol=simplify_tolerance): circuit.append(RXGate(lam), [0]) - if not simplify or not np.isclose(theta, 0.0, atol=atol): - circuit.append(RYGate(theta), [0]) - if not simplify or not np.isclose(phi, 0.0, atol=atol): + if not simplify or not np.isclose(theta, 0.0, atol=simplify_tolerance): + circuit.append(RYGate(theta, phase=phase), [0]) + if not simplify or not np.isclose(phi, 0.0, atol=simplify_tolerance): circuit.append(RXGate(phi), [0]) return circuit + + @staticmethod + def _circuit_u3(theta, + phi, + lam, + phase=0, + simplify=True, + simplify_tolerance=DEFAULT_SIMPLIFY_TOLERANCE): + # pylint: disable=unused-argument + circuit = QuantumCircuit(1) + circuit.append(U3Gate(theta, phi, lam, phase=phase), [0]) + return circuit + + @staticmethod + def _circuit_u1x(theta, + phi, + lam, + phase=0, + simplify=True, + simplify_tolerance=DEFAULT_SIMPLIFY_TOLERANCE): + # Shift theta and phi so decomposition is + # U1(phi).X90.U1(theta).X90.U1(lam) + theta += np.pi + phi += np.pi + # Check for decomposition into minimimal number required X90 pulses + if simplify and np.isclose(abs(theta), np.pi, atol=simplify_tolerance): + # Zero X90 gate decomposition + circuit = QuantumCircuit(1) + circuit.append(U1Gate(lam + phi + theta, phase=phase), [0]) + return circuit + if simplify and np.isclose(abs(theta), np.pi/2, atol=simplify_tolerance): + # single X90 gate decomposition + circuit = QuantumCircuit(1) + circuit.append(U1Gate(lam + theta, phase=phase), [0]) + circuit.append(RXGate(np.pi / 2), [0]) + circuit.append(U1Gate(phi + theta), [0]) + return circuit + # General two-X90 gate decomposition + circuit = QuantumCircuit(1) + circuit.append(U1Gate(lam, phase=phase), [0]) + circuit.append(RXGate(np.pi / 2), [0]) + circuit.append(U1Gate(theta), [0]) + circuit.append(RXGate(np.pi / 2), [0]) + circuit.append(U1Gate(phi), [0]) + return circuit + + @staticmethod + def _circuit_rr(theta, + phi, + lam, + phase=0, + simplify=True, + simplify_tolerance=DEFAULT_SIMPLIFY_TOLERANCE): + circuit = QuantumCircuit(1) + if not simplify or not np.isclose(theta, -np.pi, atol=simplify_tolerance): + circuit.append(RGate(theta + np.pi, np.pi / 2 - lam), [0]) + circuit.append(RGate(-np.pi, 0.5 * (phi - lam + np.pi), phase=phase), [0]) + return circuit diff --git a/qiskit/quantum_info/synthesis/two_qubit_decompose.py b/qiskit/quantum_info/synthesis/two_qubit_decompose.py index a921d9addad3..46b38bea8984 100644 --- a/qiskit/quantum_info/synthesis/two_qubit_decompose.py +++ b/qiskit/quantum_info/synthesis/two_qubit_decompose.py @@ -34,16 +34,18 @@ from qiskit.circuit.quantumregister import QuantumRegister from qiskit.circuit.quantumcircuit import QuantumCircuit from qiskit.extensions.standard.u3 import U3Gate -from qiskit.extensions.standard.x import CnotGate +from qiskit.extensions.standard.x import CXGate from qiskit.exceptions import QiskitError from qiskit.quantum_info.operators.predicates import is_unitary_matrix from qiskit.quantum_info.synthesis.weyl import weyl_coordinates +from qiskit.quantum_info.synthesis.one_qubit_decompose import OneQubitEulerDecomposer _CUTOFF_PRECISION = 1e-12 +_DECOMPOSER1Q = OneQubitEulerDecomposer('U3') def euler_angles_1q(unitary_matrix): - """Compute Euler angles for a single-qubit gate. + """DEPRECATED: Compute Euler angles for a single-qubit gate. Find angles (theta, phi, lambda) such that unitary_matrix = phase * Rz(phi) * Ry(theta) * Rz(lambda) @@ -57,6 +59,9 @@ def euler_angles_1q(unitary_matrix): Raises: QiskitError: if unitary_matrix not 2x2, or failure """ + warnings.warn("euler_angles_q1` is deprecated. " + "Use `synthesis.OneQubitEulerDecomposer().angles instead.", + DeprecationWarning) if unitary_matrix.shape != (2, 2): raise QiskitError("euler_angles_1q: expected 2x2 matrix") phase = la.det(unitary_matrix)**(-1.0/2.0) @@ -446,7 +451,7 @@ def __call__(self, target, basis_fidelity=None): best_nbasis = np.argmax(expected_fidelities) decomposition = self.decomposition_fns[best_nbasis](target_decomposed) - decomposition_angles = [euler_angles_1q(x) for x in decomposition] + decomposition_angles = [_DECOMPOSER1Q.angles(x) for x in decomposition] q = QuantumRegister(2) return_circuit = QuantumCircuit(q) @@ -477,4 +482,4 @@ def num_basis_gates(self, unitary): return np.argmax([trace_to_fid(traces[i]) * self.basis_fidelity**i for i in range(4)]) -two_qubit_cnot_decompose = TwoQubitBasisDecomposer(CnotGate()) +two_qubit_cnot_decompose = TwoQubitBasisDecomposer(CXGate()) diff --git a/qiskit/scheduler/methods/basic.py b/qiskit/scheduler/methods/basic.py index fa5f1317984f..50cc99e9b089 100644 --- a/qiskit/scheduler/methods/basic.py +++ b/qiskit/scheduler/methods/basic.py @@ -55,8 +55,6 @@ def as_soon_as_possible(circuit: QuantumCircuit, A schedule corresponding to the input ``circuit`` with pulses occurring as early as possible. """ - sched = Schedule(name=circuit.name) - qubit_time_available = defaultdict(int) def update_times(inst_qubits: List[int], time: int = 0) -> None: @@ -64,15 +62,20 @@ def update_times(inst_qubits: List[int], time: int = 0) -> None: for q in inst_qubits: qubit_time_available[q] = time + start_times = [] circ_pulse_defs = translate_gates_to_pulse_defs(circuit, schedule_config) for circ_pulse_def in circ_pulse_defs: - time = max(qubit_time_available[q] for q in circ_pulse_def.qubits) - if isinstance(circ_pulse_def.schedule, Barrier): - update_times(circ_pulse_def.qubits, time) - else: - sched = sched.insert(time, circ_pulse_def.schedule) - update_times(circ_pulse_def.qubits, time + circ_pulse_def.schedule.duration) - return sched + start_time = max(qubit_time_available[q] for q in circ_pulse_def.qubits) + stop_time = start_time + if not isinstance(circ_pulse_def.schedule, Barrier): + stop_time += circ_pulse_def.schedule.duration + + start_times.append(start_time) + update_times(circ_pulse_def.qubits, stop_time) + + timed_schedules = [(time, cpd.schedule) for time, cpd in zip(start_times, circ_pulse_defs) + if not isinstance(cpd.schedule, Barrier)] + return Schedule(*timed_schedules, name=circuit.name) def as_late_as_possible(circuit: QuantumCircuit, @@ -97,36 +100,29 @@ def as_late_as_possible(circuit: QuantumCircuit, A schedule corresponding to the input ``circuit`` with pulses occurring as late as possible. """ - sched = Schedule(name=circuit.name) - # Align channel end times. - circuit.barrier() - # We schedule in reverse order to get ALAP behaviour. We need to know how far out from t=0 any - # qubit will become occupied. We add positive shifts to these times as we go along. - # The time is initialized to 0 because all qubits are involved in the final barrier. - qubit_available_until = defaultdict(lambda: 0) - - def update_times(inst_qubits: List[int], shift: int = 0, inst_start_time: int = 0) -> None: + qubit_time_available = defaultdict(int) + + def update_times(inst_qubits: List[int], time: int = 0) -> None: """Update the time tracker for all inst_qubits to the given time.""" for q in inst_qubits: - qubit_available_until[q] = inst_start_time - for q in qubit_available_until.keys(): - if q not in inst_qubits: - # Uninvolved qubits might be free for the duration of the new instruction - qubit_available_until[q] += shift + qubit_time_available[q] = time + rev_stop_times = [] circ_pulse_defs = translate_gates_to_pulse_defs(circuit, schedule_config) for circ_pulse_def in reversed(circ_pulse_defs): - inst_sched = circ_pulse_def.schedule - # The new instruction should end when one of its qubits becomes occupied - inst_start_time = (min([qubit_available_until[q] for q in circ_pulse_def.qubits]) - - getattr(inst_sched, 'duration', 0)) # Barrier has no duration - # We have to translate qubit times forward when the inst_start_time is negative - shift_amount = max(0, -inst_start_time) - inst_start_time = max(inst_start_time, 0) + start_time = max(qubit_time_available[q] for q in circ_pulse_def.qubits) + stop_time = start_time if not isinstance(circ_pulse_def.schedule, Barrier): - sched = inst_sched.shift(inst_start_time).insert(shift_amount, sched, name=sched.name) - update_times(circ_pulse_def.qubits, shift_amount, inst_start_time) - return sched + stop_time += circ_pulse_def.schedule.duration + + rev_stop_times.append(stop_time) + update_times(circ_pulse_def.qubits, stop_time) + + last_stop = max(t for t in qubit_time_available.values()) + start_times = [last_stop - t for t in reversed(rev_stop_times)] + timed_schedules = [(time, cpd.schedule) for time, cpd in zip(start_times, circ_pulse_defs) + if not isinstance(cpd.schedule, Barrier)] + return Schedule(*timed_schedules, name=circuit.name) def translate_gates_to_pulse_defs(circuit: QuantumCircuit, diff --git a/qiskit/test/mock/backends/boeblingen/fake_boeblingen.py b/qiskit/test/mock/backends/boeblingen/fake_boeblingen.py index 04a6c8c55330..fad80b67aa5a 100644 --- a/qiskit/test/mock/backends/boeblingen/fake_boeblingen.py +++ b/qiskit/test/mock/backends/boeblingen/fake_boeblingen.py @@ -57,6 +57,7 @@ def __init__(self): open_pulse=False, memory=True, max_shots=8192, + max_experiments=900, gates=[GateConfig(name='TODO', parameters=[], qasm_def='TODO')], coupling_map=cmap, ) diff --git a/qiskit/test/mock/backends/johannesburg/fake_johannesburg.py b/qiskit/test/mock/backends/johannesburg/fake_johannesburg.py index 0f58dad6f207..3cb6a439aded 100644 --- a/qiskit/test/mock/backends/johannesburg/fake_johannesburg.py +++ b/qiskit/test/mock/backends/johannesburg/fake_johannesburg.py @@ -55,6 +55,7 @@ def __init__(self): open_pulse=False, memory=True, max_shots=8192, + max_experiments=900, gates=[GateConfig(name='TODO', parameters=[], qasm_def='TODO')], coupling_map=cmap, ) diff --git a/qiskit/test/mock/backends/melbourne/fake_melbourne.py b/qiskit/test/mock/backends/melbourne/fake_melbourne.py index 80569fe5d6a7..8510261c3376 100644 --- a/qiskit/test/mock/backends/melbourne/fake_melbourne.py +++ b/qiskit/test/mock/backends/melbourne/fake_melbourne.py @@ -48,6 +48,7 @@ def __init__(self): open_pulse=False, memory=False, max_shots=65536, + max_experiments=900, gates=[GateConfig(name='TODO', parameters=[], qasm_def='TODO')], coupling_map=cmap, ) diff --git a/qiskit/test/mock/backends/ourense/fake_ourense.py b/qiskit/test/mock/backends/ourense/fake_ourense.py index 035e3a958d36..763da0ab6377 100644 --- a/qiskit/test/mock/backends/ourense/fake_ourense.py +++ b/qiskit/test/mock/backends/ourense/fake_ourense.py @@ -46,6 +46,7 @@ def __init__(self): open_pulse=False, memory=False, max_shots=65536, + max_experiments=900, gates=[GateConfig(name='TODO', parameters=[], qasm_def='TODO')], coupling_map=cmap, ) diff --git a/qiskit/test/mock/backends/poughkeepsie/fake_poughkeepsie.py b/qiskit/test/mock/backends/poughkeepsie/fake_poughkeepsie.py index c711c33f3697..caf20e4b6fa6 100644 --- a/qiskit/test/mock/backends/poughkeepsie/fake_poughkeepsie.py +++ b/qiskit/test/mock/backends/poughkeepsie/fake_poughkeepsie.py @@ -55,6 +55,7 @@ def __init__(self): open_pulse=False, memory=True, max_shots=8192, + max_experiments=900, gates=[GateConfig(name='TODO', parameters=[], qasm_def='TODO')], coupling_map=cmap, ) diff --git a/qiskit/test/mock/backends/rochester/fake_rochester.py b/qiskit/test/mock/backends/rochester/fake_rochester.py index e76e2c6cd1b3..4d4cd8e458b4 100644 --- a/qiskit/test/mock/backends/rochester/fake_rochester.py +++ b/qiskit/test/mock/backends/rochester/fake_rochester.py @@ -55,6 +55,7 @@ def __init__(self): open_pulse=False, memory=True, max_shots=8192, + max_experiments=900, gates=[GateConfig(name='TODO', parameters=[], qasm_def='TODO')], coupling_map=cmap, ) diff --git a/qiskit/test/mock/backends/rueschlikon/fake_rueschlikon.py b/qiskit/test/mock/backends/rueschlikon/fake_rueschlikon.py index 7291d484d837..27f2138b5655 100644 --- a/qiskit/test/mock/backends/rueschlikon/fake_rueschlikon.py +++ b/qiskit/test/mock/backends/rueschlikon/fake_rueschlikon.py @@ -45,6 +45,7 @@ def __init__(self): open_pulse=False, memory=False, max_shots=65536, + max_experiments=900, gates=[GateConfig(name='TODO', parameters=[], qasm_def='TODO')], coupling_map=cmap, ) diff --git a/qiskit/test/mock/backends/singapore/fake_singapore.py b/qiskit/test/mock/backends/singapore/fake_singapore.py index 321aaab3361d..ec8aa0d5fe4f 100644 --- a/qiskit/test/mock/backends/singapore/fake_singapore.py +++ b/qiskit/test/mock/backends/singapore/fake_singapore.py @@ -57,6 +57,7 @@ def __init__(self): open_pulse=False, memory=True, max_shots=8192, + max_experiments=900, gates=[GateConfig(name='TODO', parameters=[], qasm_def='TODO')], coupling_map=cmap, ) diff --git a/qiskit/test/mock/backends/tenerife/fake_tenerife.py b/qiskit/test/mock/backends/tenerife/fake_tenerife.py index a59fe9293973..025c40b032f5 100644 --- a/qiskit/test/mock/backends/tenerife/fake_tenerife.py +++ b/qiskit/test/mock/backends/tenerife/fake_tenerife.py @@ -48,6 +48,7 @@ def __init__(self): open_pulse=False, memory=False, max_shots=65536, + max_experiments=900, gates=[GateConfig(name='TODO', parameters=[], qasm_def='TODO')], coupling_map=cmap, ) diff --git a/qiskit/test/mock/backends/tokyo/fake_tokyo.py b/qiskit/test/mock/backends/tokyo/fake_tokyo.py index 24511b4d4f66..52dcf912b553 100644 --- a/qiskit/test/mock/backends/tokyo/fake_tokyo.py +++ b/qiskit/test/mock/backends/tokyo/fake_tokyo.py @@ -61,6 +61,7 @@ def __init__(self): open_pulse=False, memory=False, max_shots=65536, + max_experiments=900, gates=[GateConfig(name='TODO', parameters=[], qasm_def='TODO')], coupling_map=cmap, ) diff --git a/qiskit/test/mock/backends/vigo/fake_vigo.py b/qiskit/test/mock/backends/vigo/fake_vigo.py index a0911a3f323d..c8b63388c4d9 100644 --- a/qiskit/test/mock/backends/vigo/fake_vigo.py +++ b/qiskit/test/mock/backends/vigo/fake_vigo.py @@ -46,6 +46,7 @@ def __init__(self): open_pulse=False, memory=False, max_shots=65536, + max_experiments=75, gates=[GateConfig(name='TODO', parameters=[], qasm_def='TODO')], coupling_map=cmap, ) diff --git a/qiskit/test/mock/backends/yorktown/fake_yorktown.py b/qiskit/test/mock/backends/yorktown/fake_yorktown.py index a6b6f10a693e..80bc1bffd92f 100644 --- a/qiskit/test/mock/backends/yorktown/fake_yorktown.py +++ b/qiskit/test/mock/backends/yorktown/fake_yorktown.py @@ -48,6 +48,7 @@ def __init__(self): open_pulse=False, memory=False, max_shots=65536, + max_experiments=75, gates=[GateConfig(name='TODO', parameters=[], qasm_def='TODO')], coupling_map=cmap, ) diff --git a/qiskit/test/mock/fake_backend.py b/qiskit/test/mock/fake_backend.py index 597a9c4bb73b..ce393e882290 100644 --- a/qiskit/test/mock/fake_backend.py +++ b/qiskit/test/mock/fake_backend.py @@ -12,17 +12,33 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. +# pylint: disable=no-name-in-module,import-error + """ Base class for dummy backends. """ -import uuid -import time +import warnings from qiskit.providers.models import BackendProperties from qiskit.providers import BaseBackend -from qiskit.result import Result -from .fake_job import FakeJob +from qiskit.exceptions import QiskitError + +try: + from qiskit import Aer + HAS_AER = True +except ImportError: + HAS_AER = False + from qiskit.providers.basicaer import BasicAer + + +class _Credentials(): + def __init__(self, token='123456', url='https://'): + self.token = token + self.url = url + self.hub = 'hub' + self.group = 'group' + self.project = 'project' class FakeBackend(BaseBackend): @@ -37,6 +53,7 @@ def __init__(self, configuration, time_alive=10): """ super().__init__(configuration) self.time_alive = time_alive + self._credentials = _Credentials() def properties(self): """Return backend properties""" @@ -97,15 +114,28 @@ def properties(self): return BackendProperties.from_dict(properties) def run(self, qobj): - job_id = str(uuid.uuid4()) - job = FakeJob(self, job_id, self.run_job, qobj) - job.submit() + """Main job in simulator""" + if HAS_AER: + if qobj.type == 'PULSE': + from qiskit.providers.aer.pulse import PulseSystemModel + system_model = PulseSystemModel.from_backend(self) + sim = Aer.get_backend('pulse_simulator') + job = sim.run(qobj, system_model) + else: + sim = Aer.get_backend('qasm_simulator') + from qiskit.providers.aer.noise import NoiseModel + noise_model = NoiseModel.from_backend(self) + job = sim.run(qobj, noise_model=noise_model) + else: + if qobj.type == 'PULSE': + raise QiskitError("Unable to run pulse schedules without " + "qiskit-aer installed") + warnings.warn("Aer not found using BasicAer and no noise", + RuntimeWarning) + sim = BasicAer.get_backend('qasm_simulator') + job = sim.run(qobj) return job - def run_job(self, job_id, qobj): - """Main dummy run loop""" - del qobj # unused - time.sleep(self.time_alive) - - return Result.from_dict( - {'job_id': job_id, 'result': [], 'status': 'COMPLETED'}) + def jobs(self, **kwargs): # pylint: disable=unused-argument + """Fake a job history""" + return [] diff --git a/qiskit/transpiler/passes/__init__.py b/qiskit/transpiler/passes/__init__.py index ccd920595298..352e93aaee3a 100644 --- a/qiskit/transpiler/passes/__init__.py +++ b/qiskit/transpiler/passes/__init__.py @@ -114,6 +114,7 @@ # routing from .routing import BasicSwap +from .routing import LayoutTransformation from .routing import LookaheadSwap from .routing import StochasticSwap diff --git a/qiskit/transpiler/passes/basis/ms_basis_decomposer.py b/qiskit/transpiler/passes/basis/ms_basis_decomposer.py index 7f0f9be9d8e1..21902baa53c6 100644 --- a/qiskit/transpiler/passes/basis/ms_basis_decomposer.py +++ b/qiskit/transpiler/passes/basis/ms_basis_decomposer.py @@ -18,7 +18,7 @@ from qiskit.exceptions import QiskitError from qiskit.converters import circuit_to_dag -from qiskit.extensions.standard import U3Gate, CnotGate +from qiskit.extensions.standard import U3Gate, CXGate from qiskit.transpiler.passes import Unroller from qiskit.quantum_info.synthesis.one_qubit_decompose import OneQubitEulerDecomposer @@ -28,7 +28,7 @@ class MSBasisDecomposer(TransformationPass): """Convert a circuit in ``U3, CX`` to ``Rx, Ry, Rxx`` without unrolling or simplification.""" - supported_input_gates = (U3Gate, CnotGate) + supported_input_gates = (U3Gate, CXGate) def __init__(self, basis_gates): """MSBasisDecomposer initializer. @@ -79,7 +79,7 @@ def run(self, dag): if isinstance(node.op, U3Gate): replacement_circuit = one_q_decomposer(node.op) - elif isinstance(node.op, CnotGate): + elif isinstance(node.op, CXGate): # N.B. We can't circuit_to_dag once outside the loop because # substitute_node_with_dag will modify the input DAG if the # node to be replaced is conditional. diff --git a/qiskit/transpiler/passes/layout/csp_layout.py b/qiskit/transpiler/passes/layout/csp_layout.py index d19bdb457c97..ec20dd342758 100644 --- a/qiskit/transpiler/passes/layout/csp_layout.py +++ b/qiskit/transpiler/passes/layout/csp_layout.py @@ -19,10 +19,52 @@ """ import random from time import time +from constraint import Problem, RecursiveBacktrackingSolver, AllDifferentConstraint from qiskit.transpiler.layout import Layout from qiskit.transpiler.basepasses import AnalysisPass -from qiskit.transpiler.exceptions import TranspilerError + + +class CustomSolver(RecursiveBacktrackingSolver): + """A wrap to RecursiveBacktrackingSolver to support ``call_limit``""" + + def __init__(self, call_limit=None, time_limit=None): + self.call_limit = call_limit + self.time_limit = time_limit + self.call_current = None + self.time_start = None + self.time_current = None + super().__init__() + + def limit_reached(self): + """Checks if a limit is reached.""" + if self.call_current is not None: + self.call_current += 1 + if self.call_current > self.call_limit: + return True + if self.time_start is not None: + self.time_current = time() - self.time_start + if self.time_current > self.time_limit: + return True + return False + + def getSolution(self, # pylint: disable=invalid-name + domains, constraints, vconstraints): + """Wrap RecursiveBacktrackingSolver.getSolution to add the limits.""" + if self.call_limit is not None: + self.call_current = 0 + if self.time_limit is not None: + self.time_start = time() + return super().getSolution(domains, constraints, vconstraints) + + def recursiveBacktracking(self, # pylint: disable=invalid-name + solutions, domains, vconstraints, assignments, single): + """Like ``constraint.RecursiveBacktrackingSolver.recursiveBacktracking`` but + limited in the amount of calls by ``self.call_limit`` """ + if self.limit_reached(): + return None + return super().recursiveBacktracking(solutions, domains, vconstraints, assignments, + single) class CSPLayout(AnalysisPass): @@ -60,11 +102,6 @@ def __init__(self, coupling_map, strict_direction=False, seed=None, call_limit=1 self.seed = seed def run(self, dag): - try: - from constraint import Problem, RecursiveBacktrackingSolver, AllDifferentConstraint - except ImportError: - raise TranspilerError('CSPLayout requires python-constraint to run. ' - 'Run pip install python-constraint') qubits = dag.qubits() cxs = set() @@ -73,47 +110,6 @@ def run(self, dag): qubits.index(gate.qargs[1]))) edges = self.coupling_map.get_edges() - class CustomSolver(RecursiveBacktrackingSolver): - """A wrap to RecursiveBacktrackingSolver to support ``call_limit``""" - - def __init__(self, call_limit=None, time_limit=None): - self.call_limit = call_limit - self.time_limit = time_limit - self.call_current = None - self.time_start = None - self.time_current = None - super().__init__() - - def limit_reached(self): - """Checks if a limit is reached.""" - if self.call_current is not None: - self.call_current += 1 - if self.call_current > self.call_limit: - return True - if self.time_start is not None: - self.time_current = time() - self.time_start - if self.time_current > self.time_limit: - return True - return False - - def getSolution(self, # pylint: disable=invalid-name - domains, constraints, vconstraints): - """Wrap RecursiveBacktrackingSolver.getSolution to add the limits.""" - if self.call_limit is not None: - self.call_current = 0 - if self.time_limit is not None: - self.time_start = time() - return super().getSolution(domains, constraints, vconstraints) - - def recursiveBacktracking(self, # pylint: disable=invalid-name - solutions, domains, vconstraints, assignments, single): - """Like ``constraint.RecursiveBacktrackingSolver.recursiveBacktracking`` but - limited in the amount of calls by ``self.call_limit`` """ - if self.limit_reached(): - return None - return super().recursiveBacktracking(solutions, domains, vconstraints, assignments, - single) - if self.time_limit is None and self.call_limit is None: solver = RecursiveBacktrackingSolver() else: @@ -139,9 +135,9 @@ def constraint(control, target): if solution is None: stop_reason = 'nonexistent solution' if isinstance(solver, CustomSolver): - if solver.time_limit is not None and solver.time_current >= self.time_limit: + if solver.time_current is not None and solver.time_current >= self.time_limit: stop_reason = 'time limit reached' - elif solver.call_limit is not None and solver.call_current >= self.call_limit: + elif solver.call_current is not None and solver.call_current >= self.call_limit: stop_reason = 'call limit reached' else: stop_reason = 'solution found' diff --git a/qiskit/transpiler/passes/optimization/consolidate_blocks.py b/qiskit/transpiler/passes/optimization/consolidate_blocks.py index 9b752acfd378..a256be84326f 100644 --- a/qiskit/transpiler/passes/optimization/consolidate_blocks.py +++ b/qiskit/transpiler/passes/optimization/consolidate_blocks.py @@ -20,7 +20,7 @@ from qiskit.dagcircuit import DAGCircuit from qiskit.quantum_info.operators import Operator from qiskit.quantum_info.synthesis import TwoQubitBasisDecomposer -from qiskit.extensions import UnitaryGate, CnotGate +from qiskit.extensions import UnitaryGate, CXGate from qiskit.transpiler.basepasses import TransformationPass from qiskit.transpiler.exceptions import TranspilerError @@ -37,7 +37,7 @@ class ConsolidateBlocks(TransformationPass): given such that blocks are in topological order. The blocks are collected by a previous pass, such as `Collect2qBlocks`. """ - def __init__(self, kak_basis_gate=CnotGate(), force_consolidate=False): + def __init__(self, kak_basis_gate=CXGate(), force_consolidate=False): """ConsolidateBlocks initializer. Args: diff --git a/qiskit/transpiler/passes/optimization/crosstalk_adaptive_schedule.py b/qiskit/transpiler/passes/optimization/crosstalk_adaptive_schedule.py index 52ee82152391..f22803a65dc4 100644 --- a/qiskit/transpiler/passes/optimization/crosstalk_adaptive_schedule.py +++ b/qiskit/transpiler/passes/optimization/crosstalk_adaptive_schedule.py @@ -40,7 +40,7 @@ HAS_Z3 = False from qiskit.transpiler.basepasses import TransformationPass from qiskit.dagcircuit import DAGCircuit -from qiskit.extensions.standard import U1Gate, U2Gate, U3Gate, CnotGate +from qiskit.extensions.standard import U1Gate, U2Gate, U3Gate, CXGate from qiskit.circuit import Measure from qiskit.extensions.standard.barrier import Barrier from qiskit.transpiler.exceptions import TranspilerError @@ -327,7 +327,7 @@ def basic_bounds(self): dur = self.bp_u2_dur[q_0] elif isinstance(gate.op, U3Gate): dur = self.bp_u3_dur[q_0] - elif isinstance(gate.op, CnotGate): + elif isinstance(gate.op, CXGate): dur = self.bp_cx_dur[self.cx_tuple(gate)] self.opt.add(self.gate_duration[gate] == dur) @@ -383,7 +383,7 @@ def fidelity_constraints(self): fid = math.log(1.0 - self.bp_u2_err[q_0]) elif isinstance(gate.op, U3Gate): fid = math.log(1.0 - self.bp_u3_err[q_0]) - elif isinstance(gate.op, CnotGate): + elif isinstance(gate.op, CXGate): fid = math.log(1.0 - self.bp_cx_err[self.cx_tuple(gate)]) self.opt.add(self.gate_fidelity[gate] == round(fid, NUM_PREC)) else: diff --git a/qiskit/transpiler/passes/optimization/remove_diagonal_gates_before_measure.py b/qiskit/transpiler/passes/optimization/remove_diagonal_gates_before_measure.py index dd7b7f673e76..fa3674dc53c8 100644 --- a/qiskit/transpiler/passes/optimization/remove_diagonal_gates_before_measure.py +++ b/qiskit/transpiler/passes/optimization/remove_diagonal_gates_before_measure.py @@ -16,7 +16,7 @@ from qiskit.circuit import Measure from qiskit.extensions.standard import RZGate, ZGate, TGate, SGate, TdgGate, SdgGate, U1Gate,\ - CzGate, CrzGate, Cu1Gate, RZZGate + CZGate, CRZGate, CU1Gate, RZZGate from qiskit.transpiler.basepasses import TransformationPass @@ -37,7 +37,7 @@ def run(self, dag): DAGCircuit: the optimized DAG. """ diagonal_1q_gates = (RZGate, ZGate, TGate, SGate, TdgGate, SdgGate, U1Gate) - diagonal_2q_gates = (CzGate, CrzGate, Cu1Gate, RZZGate) + diagonal_2q_gates = (CZGate, CRZGate, CU1Gate, RZZGate) nodes_to_remove = set() for measure in dag.op_nodes(Measure): diff --git a/qiskit/transpiler/passes/routing/__init__.py b/qiskit/transpiler/passes/routing/__init__.py index 147cb773cef3..c32baa354706 100644 --- a/qiskit/transpiler/passes/routing/__init__.py +++ b/qiskit/transpiler/passes/routing/__init__.py @@ -15,5 +15,6 @@ """Module containing transpiler mapping passes.""" from .basic_swap import BasicSwap +from .layout_transformation import LayoutTransformation from .lookahead_swap import LookaheadSwap from .stochastic_swap import StochasticSwap diff --git a/qiskit/transpiler/passes/routing/algorithms/__init__.py b/qiskit/transpiler/passes/routing/algorithms/__init__.py new file mode 100644 index 000000000000..1fa0d98cd131 --- /dev/null +++ b/qiskit/transpiler/passes/routing/algorithms/__init__.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +# Copyright 2019 Andrew M. Childs, Eddie Schoute, Cem M. Unsal +# +# 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. + +"""The permutation modules contains some functions for permuting on architectures given a mapping. + +A permutation function takes in a graph and a permutation of graph nodes, +and returns a sequence of SWAPs that implements that permutation on the graph. +""" + +from .token_swapper import ApproximateTokenSwapper diff --git a/qiskit/transpiler/passes/routing/algorithms/token_swapper.py b/qiskit/transpiler/passes/routing/algorithms/token_swapper.py new file mode 100644 index 000000000000..5a319ba69cce --- /dev/null +++ b/qiskit/transpiler/passes/routing/algorithms/token_swapper.py @@ -0,0 +1,241 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +# Copyright 2019 Andrew M. Childs, Eddie Schoute, Cem M. Unsal +# +# 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. + +"""Permutation algorithms for general graphs.""" + +import logging +from typing import TypeVar, Iterator, Mapping, Generic, MutableMapping, MutableSet, List, \ + Iterable, Optional, Union + +import networkx as nx +import numpy as np + +from .types import Swap, Permutation +from .util import PermutationCircuit, permutation_circuit + +_V = TypeVar('_V') +_T = TypeVar('_T') + +logger = logging.getLogger(__name__) + + +class ApproximateTokenSwapper(Generic[_V]): + """A class for computing approximate solutions to the Token Swapping problem. + + Internally caches the graph and associated datastructures for re-use. + """ + + def __init__(self, graph: nx.Graph, seed: Union[int, np.random.RandomState] = None) -> None: + """Construct an ApproximateTokenSwapping object. + + Args: + graph (nx.Graph): Undirected graph represented a coupling map. + seed (Union[int, np.random.RandomState]): Seed to use for random trials. + """ + self.graph = graph + # We need to fix the mapping from nodes in graph to nodes in shortest_paths. + # The nodes in graph don't have to integer nor contiguous, but those in a NumPy array are. + nodelist = list(graph.nodes()) + self.node_map = {node: i for i, node in enumerate(nodelist)} + self.shortest_paths = nx.floyd_warshall_numpy(graph, nodelist=nodelist) + if isinstance(seed, np.random.RandomState): + self.seed = seed + else: + self.seed = np.random.RandomState(seed) + + def distance(self, vertex0: _V, vertex1: _V) -> int: + """Compute the distance between two nodes in `graph`.""" + return self.shortest_paths[self.node_map[vertex0], self.node_map[vertex1]] + + def permutation_circuit(self, permutation: Permutation, + trials: int = 4) -> PermutationCircuit: + """Perform an approximately optimal Token Swapping algorithm to implement the permutation. + + Args: + permutation: The partial mapping to implement in swaps. + trials: The number of trials to try to perform the mapping. Minimize over the trials. + + Returns: + The circuit to implement the permutation + """ + sequential_swaps = self.map(permutation, trials=trials) + parallel_swaps = [[swap] for swap in sequential_swaps] + return permutation_circuit(parallel_swaps) + + def map(self, mapping: Mapping[_V, _V], + trials: int = 4) -> List[Swap[_V]]: + """Perform an approximately optimal Token Swapping algorithm to implement the permutation. + + Supports partial mappings (i.e. not-permutations) for graphs with missing tokens. + + Based on the paper: Approximation and Hardness for Token Swapping by Miltzow et al. (2016) + ArXiV: https://arxiv.org/abs/1602.05150 + and generalization based on our own work. + + Args: + mapping: The partial mapping to implement in swaps. + trials: The number of trials to try to perform the mapping. Minimize over the trials. + + Returns: + The swaps to implement the mapping + """ + tokens = dict(mapping) + digraph = nx.DiGraph() + sub_digraph = nx.DiGraph() # Excludes self-loops in digraph. + todo_nodes = {node for node, destination in tokens.items() if node != destination} + for node in self.graph.nodes: + self._add_token_edges(node, tokens, digraph, sub_digraph) + + trial_results = iter(list(self._trial_map(digraph.copy(), + sub_digraph.copy(), + todo_nodes.copy(), + tokens.copy())) + for _ in range(trials)) + + # Once we find a zero solution we stop. + def take_until_zero(results: Iterable[List[_T]]) -> Iterator[List[_T]]: + """Take results until one is emitted of length zero (and also emit that).""" + for result in results: + yield result + if not result: + break + + trial_results = take_until_zero(trial_results) + return min(trial_results, key=len) + + def _trial_map(self, + digraph: nx.DiGraph, + sub_digraph: nx.DiGraph, + todo_nodes: MutableSet[_V], + tokens: MutableMapping[_V, _V]) -> Iterator[Swap[_V]]: + """Try to map the tokens to their destinations and minimize the number of swaps.""" + + def swap(node0: _V, node1: _V) -> None: + """Swap two nodes, maintaining datastructures. + + Args: + node0: _V: + node1: _V: + + Returns: + + """ + self._swap(node0, node1, tokens, digraph, sub_digraph, todo_nodes) + + # Can't just iterate over todo_nodes, since it may change during iteration. + steps = 0 + while todo_nodes and steps <= 4 * self.graph.number_of_nodes() ** 2: + todo_node_id = self.seed.randint(0, len(todo_nodes)) + todo_node = tuple(todo_nodes)[todo_node_id] + + # Try to find a happy swap chain first by searching for a cycle, + # excluding self-loops. + # Note that if there are only unhappy swaps involving this todo_node, + # then an unhappy swap must be performed at some point. + # So it is not useful to globally search for all happy swap chains first. + try: + cycle = nx.find_cycle(sub_digraph, source=todo_node) + assert len(cycle) > 1, "The cycle was not happy." + # We iterate over the cycle in reversed order, starting at the last edge. + # The first edge is excluded. + for edge in cycle[-1:0:-1]: + yield edge + swap(edge[0], edge[1]) + steps += len(cycle) - 1 + except nx.NetworkXNoCycle: + # Try to find a node without a token to swap with. + try: + edge = next(edge for edge in nx.dfs_edges(sub_digraph, todo_node) + if edge[1] not in tokens) + # Swap predecessor and successor, because successor does not have a token + yield edge + swap(edge[0], edge[1]) + steps += 1 + except StopIteration: + # Unhappy swap case + cycle = nx.find_cycle(digraph, source=todo_node) + assert len(cycle) == 1, "The cycle was not unhappy." + unhappy_node = cycle[0][0] + # Find a node that wants to swap with this node. + try: + predecessor = next( + predecessor for predecessor in digraph.predecessors(unhappy_node) + if predecessor != unhappy_node) + except StopIteration: + logger.error("Unexpected StopIteration raised when getting predecessors" + "in unhappy swap case.") + return + yield unhappy_node, predecessor + swap(unhappy_node, predecessor) + steps += 1 + if todo_nodes: + raise RuntimeError("Too many iterations while approximating the Token Swaps.") + + def _add_token_edges(self, + node: _V, + tokens: Mapping[_V, _V], + digraph: nx.DiGraph, + sub_digraph: nx.DiGraph) -> None: + """Add diedges to the graph wherever a token can be moved closer to its destination.""" + if node not in tokens: + return + + if tokens[node] == node: + digraph.add_edge(node, node) + return + + for neighbor in self.graph.neighbors(node): + if self.distance(neighbor, tokens[node]) < self.distance(node, tokens[node]): + digraph.add_edge(node, neighbor) + sub_digraph.add_edge(node, neighbor) + + def _swap(self, node1: _V, node2: _V, + tokens: MutableMapping[_V, _V], + digraph: nx.DiGraph, + sub_digraph: nx.DiGraph, + todo_nodes: MutableSet[_V]) -> None: + """Swap two nodes, maintaining the data structures.""" + assert self.graph.has_edge(node1, + node2), "The swap is being performed on a non-existent edge." + # Swap the tokens on the nodes, taking into account no-token nodes. + token1 = tokens.pop(node1, None) # type: Optional[_V] + token2 = tokens.pop(node2, None) # type: Optional[_V] + if token2 is not None: + tokens[node1] = token2 + if token1 is not None: + tokens[node2] = token1 + # Recompute the edges incident to node 1 and 2 + for node in [node1, node2]: + digraph.remove_edges_from([(node, successor) for successor in digraph.successors(node)]) + sub_digraph.remove_edges_from( + [(node, successor) for successor in sub_digraph.successors(node)]) + self._add_token_edges(node, tokens, digraph, sub_digraph) + if node in tokens and tokens[node] != node: + todo_nodes.add(node) + elif node in todo_nodes: + todo_nodes.remove(node) diff --git a/qiskit/transpiler/passes/routing/algorithms/types.py b/qiskit/transpiler/passes/routing/algorithms/types.py new file mode 100644 index 000000000000..13f6314eac81 --- /dev/null +++ b/qiskit/transpiler/passes/routing/algorithms/types.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +# Copyright 2019 Andrew M. Childs, Eddie Schoute, Cem M. Unsal +# +# 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. + +"""Type definitions used within the permutation package.""" + +from typing import TypeVar, Dict, Tuple, NamedTuple, Union + +from qiskit.circuit import Qubit +from qiskit.dagcircuit import DAGCircuit + +PermuteElement = TypeVar('PermuteElement') +Permutation = Dict[PermuteElement, PermuteElement] +Swap = Tuple[PermuteElement, PermuteElement] + +# Represents a circuit for permuting to a mapping. +PermutationCircuit = NamedTuple('PermutationCircuit', + [('circuit', DAGCircuit), + ('inputmap', Dict[Union[int, Qubit], Qubit]) + # A mapping from architecture nodes to circuit registers. + ]) diff --git a/qiskit/transpiler/passes/routing/algorithms/util.py b/qiskit/transpiler/passes/routing/algorithms/util.py new file mode 100644 index 000000000000..1895d7adfdcd --- /dev/null +++ b/qiskit/transpiler/passes/routing/algorithms/util.py @@ -0,0 +1,103 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +# Copyright 2019 Andrew M. Childs, Eddie Schoute, Cem M. Unsal +# +# 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. + +"""Utility functions shared between permutation functionality.""" + +from typing import List, TypeVar, Iterable, MutableMapping, Optional + +from qiskit.circuit import QuantumRegister +from qiskit.dagcircuit import DAGCircuit +from qiskit.extensions import SwapGate + +from .types import Swap, PermutationCircuit + +_K = TypeVar('_K') +_V = TypeVar('_V') + + +def swap_permutation(swaps: Iterable[Iterable[Swap[_K]]], + mapping: MutableMapping[_K, _V], + allow_missing_keys: bool = False) -> None: + """Given a circuit of swaps, apply them to the permutation (in-place). + + Args: + swaps: param mapping: A mapping of Keys to Values, where the Keys are being swapped. + mapping: The permutation to have swaps applied to. + allow_missing_keys: Whether to allow swaps of missing keys in mapping. + """ + for swap_step in swaps: + for sw1, sw2 in swap_step: + # Take into account non-existent keys. + val1 = None # type: Optional[_V] + val2 = None # type: Optional[_V] + if allow_missing_keys: + val1 = mapping.pop(sw1, None) + val2 = mapping.pop(sw2, None) + else: + # Asserts that both keys exist + val1, val2 = mapping.pop(sw1), mapping.pop(sw2) + + if val1 is not None: + mapping[sw2] = val1 + if val2 is not None: + mapping[sw1] = val2 + + +def permutation_circuit(swaps: Iterable[List[Swap[_V]]]) -> PermutationCircuit: + """Produce a circuit description of a list of swaps. + With a given permutation and permuter you can compute the swaps using the permuter function + then feed it into this circuit function to obtain a circuit description. + Args: + swaps: An iterable of swaps to perform. + Returns: + A MappingCircuit with the circuit and a mapping of node to qubit in the circuit. + """ + # Construct a circuit with each unique node id becoming a quantum register of size 1. + dag = DAGCircuit() + swap_list = list(swaps) + + # Set of unique nodes used in the swaps. + nodes = { + swap_node + for swap_step in swap_list + for swap_nodes in swap_step + for swap_node in swap_nodes + } + + node_qargs = {node: QuantumRegister(1) for node in nodes} + for qubit in node_qargs.values(): + dag.add_qreg(qubit) + + inputmap = {node: q[0] for node, q in node_qargs.items()} + + # Apply swaps to the circuit. + for swap_step in swap_list: + for swap0, swap1 in swap_step: + dag.apply_operation_back(SwapGate(), [inputmap[swap0], inputmap[swap1]]) + + return PermutationCircuit(dag, inputmap) diff --git a/qiskit/transpiler/passes/routing/layout_transformation.py b/qiskit/transpiler/passes/routing/layout_transformation.py new file mode 100644 index 000000000000..fb2b39716f4e --- /dev/null +++ b/qiskit/transpiler/passes/routing/layout_transformation.py @@ -0,0 +1,108 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2018. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Map (with minimum effort) a DAGCircuit onto a `coupling_map` adding swap gates.""" +from typing import Union + +import numpy as np + +from qiskit.transpiler import Layout, CouplingMap +from qiskit.transpiler.basepasses import TransformationPass +from qiskit.transpiler.exceptions import TranspilerError +from qiskit.transpiler.passes.routing.algorithms import ApproximateTokenSwapper + + +class LayoutTransformation(TransformationPass): + """ Adds a Swap circuit for a given (partial) permutation to the circuit. + + This circuit is found by a 4-approximation algorithm for Token Swapping. + More details are available in the routing code. + """ + + def __init__(self, coupling_map: CouplingMap, + from_layout: Union[Layout, str], + to_layout: Union[Layout, str], + seed: Union[int, np.random.RandomState] = None, + trials=4): + """LayoutTransformation initializer. + + Args: + coupling_map (CouplingMap): + Directed graph representing a coupling map. + + from_layout (Union[Layout, str]): + The starting layout of qubits onto physical qubits. + If the type is str, look up `property_set` when this pass runs. + + to_layout (Union[Layout, str]): + The final layout of qubits on phyiscal qubits. + If the type is str, look up `property_set` when this pass runs. + + seed (Union[int, np.random.RandomState]): + Seed to use for random trials. + + trials (int): + How many randomized trials to perform, taking the best circuit as output. + """ + super().__init__() + self.coupling_map = coupling_map + self.from_layout = from_layout + self.to_layout = to_layout + graph = coupling_map.graph.to_undirected() + self.token_swapper = ApproximateTokenSwapper(graph, seed) + self.trials = trials + + def run(self, dag): + """Apply the specified partial permutation to the circuit. + + Args: + dag (DAGCircuit): DAG to transform the layout of. + + Returns: + DAGCircuit: The DAG with transformed layout. + + Raises: + TranspilerError: if the coupling map or the layout are not compatible with the DAG. + Or if either of string from/to_layout is not found in `property_set`. + """ + if len(dag.qregs) != 1 or dag.qregs.get('q', None) is None: + raise TranspilerError('LayoutTransform runs on physical circuits only') + + if len(dag.qubits()) > len(self.coupling_map.physical_qubits): + raise TranspilerError('The layout does not match the amount of qubits in the DAG') + + from_layout = self.from_layout + if isinstance(from_layout, str): + try: + from_layout = self.property_set[from_layout] + except Exception: + raise TranspilerError('No {} (from_layout) in property_set.'.format(from_layout)) + + to_layout = self.to_layout + if isinstance(to_layout, str): + try: + to_layout = self.property_set[to_layout] + except Exception: + raise TranspilerError('No {} (to_layout) in property_set.'.format(to_layout)) + + # Find the permutation between the initial physical qubits and final physical qubits. + permutation = {pqubit: to_layout.get_virtual_bits()[vqubit] + for vqubit, pqubit in from_layout.get_virtual_bits().items()} + + perm_circ = self.token_swapper.permutation_circuit(permutation, self.trials) + + edge_map = {vqubit: dag.qubits()[pqubit] + for (pqubit, vqubit) in perm_circ.inputmap.items()} + dag.compose_back(perm_circ.circuit, edge_map=edge_map) + return dag diff --git a/qiskit/transpiler/passes/utils/check_cx_direction.py b/qiskit/transpiler/passes/utils/check_cx_direction.py index edcfbf31649b..958d82c90634 100644 --- a/qiskit/transpiler/passes/utils/check_cx_direction.py +++ b/qiskit/transpiler/passes/utils/check_cx_direction.py @@ -15,7 +15,7 @@ """Check if the CNOTs follow the right direction with respect to the coupling map.""" from qiskit.transpiler.basepasses import AnalysisPass -from qiskit.extensions.standard.x import CnotGate +from qiskit.extensions.standard.x import CXGate class CheckCXDirection(AnalysisPass): @@ -53,7 +53,7 @@ def run(self, dag): physical_q0 = gate.qargs[0].index physical_q1 = gate.qargs[1].index - if isinstance(gate.op, CnotGate) and ( + if isinstance(gate.op, CXGate) and ( physical_q0, physical_q1) not in edges: self.property_set['is_direction_mapped'] = False return diff --git a/qiskit/transpiler/passes/utils/cx_direction.py b/qiskit/transpiler/passes/utils/cx_direction.py index 9fb988a138f3..c45de6e7f352 100644 --- a/qiskit/transpiler/passes/utils/cx_direction.py +++ b/qiskit/transpiler/passes/utils/cx_direction.py @@ -21,7 +21,7 @@ from qiskit.circuit import QuantumRegister from qiskit.dagcircuit import DAGCircuit -from qiskit.extensions.standard import U2Gate, CnotGate +from qiskit.extensions.standard import U2Gate, CXGate class CXDirection(TransformationPass): @@ -91,7 +91,7 @@ def run(self, dag): sub_dag.apply_operation_back(U2Gate(0, pi), [sub_qr[1]], []) # Flips the cx - sub_dag.apply_operation_back(CnotGate(), [sub_qr[1], sub_qr[0]], []) + sub_dag.apply_operation_back(CXGate(), [sub_qr[1], sub_qr[0]], []) # Add H gates after sub_dag.apply_operation_back(U2Gate(0, pi), [sub_qr[0]], []) diff --git a/qiskit/transpiler/passmanager.py b/qiskit/transpiler/passmanager.py index a0d87cdc1203..8e6721180c29 100644 --- a/qiskit/transpiler/passmanager.py +++ b/qiskit/transpiler/passmanager.py @@ -12,9 +12,11 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""PassManager class for the transpiler.""" +"""Manager for a set of Passes and their scheduling during transpilation.""" import warnings +from typing import Union, List, Callable, Dict, Any + import dill from qiskit.visualization import pass_manager_drawer @@ -26,18 +28,27 @@ class PassManager: - """A PassManager schedules the passes""" + """Manager for a set of Passes and their scheduling during transpilation.""" - def __init__(self, passes=None, max_iteration=1000, callback=None): - """Initialize an empty PassManager object (with no passes scheduled). + def __init__( + self, + passes: Union[BasePass, List[BasePass]] = None, + max_iteration: int = 1000, + callback: Callable = None + ): + """Initialize an empty `PassManager` object (with no passes scheduled). Args: - passes (list[BasePass] or BasePass): A pass set (as defined in ``append()``) - to be added to the pass manager schedule. The default is None. - max_iteration (int): The schedule looping iterates until the condition is met or until - max_iteration is reached. - callback (func): DEPRECATED - A callback function that will be called after each - pass execution. + passes: A pass set (as defined in :py:func:`qiskit.transpiler.PassManager.append`) + to be added to the pass manager schedule. + max_iteration: The maximum number of iterations the schedule will be looped if the + condition is not met. + callback: DEPRECATED - A callback function that will be called after each pass + execution. + + .. deprecated :: + The ``callback`` parameter is deprecated in favor of + ``PassManager.run(..., callback=callback, ...). """ self.callback = None @@ -54,28 +65,27 @@ def __init__(self, passes=None, max_iteration=1000, callback=None): self.max_iteration = max_iteration self.property_set = None - def append(self, passes, max_iteration=None, **flow_controller_conditions): + def append( + self, + passes: Union[BasePass, List[BasePass]], + max_iteration: int = None, + **flow_controller_conditions: Any + ) -> None: """Append a Pass Set to the schedule of passes. Args: - passes (list[BasePass] or BasePass): A set of passes (a pass set) to be added - to schedule. A pass set is a list of passes that are controlled by the same - flow controller. If a single pass is provided, the pass set will only have that - pass a single element. - max_iteration (int): max number of iterations of passes. Default: 1000 - flow_controller_conditions (kwargs): See add_flow_controller(): Dictionary of - control flow plugins. Default: - - * do_while (callable property_set -> boolean): The passes repeat until the - callable returns False. - Default: `lambda x: False # i.e. passes run once` - - * condition (callable property_set -> boolean): The passes run only if the - callable returns True. - Default: `lambda x: True # i.e. passes run` + passes: A set of passes (a pass set) to be added to schedule. A pass set is a list of + passes that are controlled by the same flow controller. If a single pass is + provided, the pass set will only have that pass a single element. + max_iteration: max number of iterations of passes. + flow_controller_conditions: control flow plugins. Raises: TranspilerError: if a pass in passes is not a proper pass. + + See Also: + ``RunningPassManager.add_flow_controller()`` for more information about the control + flow plugins. """ if max_iteration: # TODO remove this argument from append @@ -84,26 +94,28 @@ def append(self, passes, max_iteration=None, **flow_controller_conditions): passes = PassManager._normalize_passes(passes) self._pass_sets.append({'passes': passes, 'flow_controllers': flow_controller_conditions}) - def replace(self, index, passes, max_iteration=None, **flow_controller_conditions): - """Replace a particular pass in the scheduler + def replace( + self, + index: int, + passes: Union[BasePass, List[BasePass]], + max_iteration: int = None, + **flow_controller_conditions: Any + ) -> None: + """Replace a particular pass in the scheduler. Args: - index (int): Pass index to replace, based on the position in passes(). - passes (list[BasePass] or BasePass): A pass set (as defined in ``append()``) - to be added to the pass manager schedule - max_iteration (int): max number of iterations of passes. Default: 1000 - flow_controller_conditions (kwargs): See add_flow_controller(): Dictionary of - control flow plugins. Default: - - * do_while (callable property_set -> boolean): The passes repeat until the - callable returns False. - Default: `lambda x: False # i.e. passes run once` - - * condition (callable property_set -> boolean): The passes run only if the - callable returns True. - Default: `lambda x: True # i.e. passes run` + index: Pass index to replace, based on the position in passes(). + passes: A pass set (as defined in :py:func:`qiskit.transpiler.PassManager.append`) + to be added to the pass manager schedule. + max_iteration: max number of iterations of passes. + flow_controller_conditions: control flow plugins. + Raises: TranspilerError: if a pass in passes is not a proper pass. + + See Also: + ``RunningPassManager.add_flow_controller()`` for more information about the control + flow plugins. """ if max_iteration: # TODO remove this argument from append @@ -148,7 +160,7 @@ def __add__(self, other): other.__class__)) @staticmethod - def _normalize_passes(passes): + def _normalize_passes(passes: Union[BasePass, List[BasePass]]) -> List[BasePass]: if isinstance(passes, BasePass): passes = [passes] @@ -157,17 +169,21 @@ def _normalize_passes(passes): raise TranspilerError('%s is not a pass instance' % pass_.__class__) return passes - def run(self, circuits, output_name=None, callback=None): - """Run all the passes on circuit or circuits + def run( + self, + circuits: Union[QuantumCircuit, List[QuantumCircuit]], + output_name: str = None, + callback: Callable = None + ) -> Union[QuantumCircuit, List[QuantumCircuit]]: + """Run all the passes on the specified ``circuits``. Args: - circuits (QuantumCircuit or list[QuantumCircuit]): circuit(s) to - transform via all the registered passes. - output_name (str): The output circuit name. If not given, the same as the - input circuit - callback (func): A callback function that will be called after each - pass execution. The function will be called with 5 keyword - arguments:: + circuits: Circuit(s) to transform via all the registered passes. + output_name: The output circuit name. If ``None``, it will be set to the same as the + input circuit name. + callback: A callback function that will be called after each pass execution. The + function will be called with 5 keyword arguments:: + pass_ (Pass): the pass being run dag (DAGCircuit): the dag output of the pass time (float): the time to execute the pass @@ -190,31 +206,44 @@ def callback_func(**kwargs): property_set = kwargs['property_set'] count = kwargs['count'] ... + Returns: - QuantumCircuit or list[QuantumCircuit]: Transformed circuit(s). + The transformed circuit(s). """ if isinstance(circuits, QuantumCircuit): return self._run_single_circuit(circuits, output_name, callback) else: return self._run_several_circuits(circuits, output_name, callback) - def _create_running_passmanager(self): + def _create_running_passmanager(self) -> RunningPassManager: running_passmanager = RunningPassManager(self.max_iteration) for pass_set in self._pass_sets: running_passmanager.append(pass_set['passes'], **pass_set['flow_controllers']) return running_passmanager @staticmethod - def _in_parallel(circuit, pm_dill=None): - """ Used by _run_several_circuits. """ + def _in_parallel(circuit, pm_dill=None) -> QuantumCircuit: + """Task used by the parallel map tools from ``_run_several_circuits``.""" running_passmanager = dill.loads(pm_dill)._create_running_passmanager() result = running_passmanager.run(circuit) return result - def _run_several_circuits(self, circuits, output_name=None, callback=None): - """Run all the passes on each of the circuits in the circuits list + def _run_several_circuits( + self, + circuits: List[QuantumCircuit], + output_name: str = None, + callback: Callable = None + ) -> List[QuantumCircuit]: + """Run all the passes on the specified ``circuits``. + + Args: + circuits: Circuits to transform via all the registered passes. + output_name: The output circuit name. If ``None``, it will be set to the same as the + input circuit name. + callback: A callback function that will be called after each pass execution. + Returns: - list[QuantumCircuit]: Transformed circuits. + The transformed circuits. """ # TODO support for List(output_name) and List(callback) del output_name @@ -223,44 +252,22 @@ def _run_several_circuits(self, circuits, output_name=None, callback=None): return parallel_map(PassManager._in_parallel, circuits, task_kwargs={'pm_dill': dill.dumps(self)}) - def _run_single_circuit(self, circuit, output_name=None, callback=None): - """Run all the passes on a QuantumCircuit + def _run_single_circuit( + self, + circuit: QuantumCircuit, + output_name: str = None, + callback: Callable = None + ) -> QuantumCircuit: + """Run all the passes on a ``circuit``. Args: - circuit (QuantumCircuit): circuit to transform via all the registered passes - output_name (str): The output circuit name. If not given, the same as the - input circuit - callback (func): A callback function that will be called after each - pass execution. The function will be called with 5 keyword - arguments: - pass_ (Pass): the pass being run - dag (DAGCircuit): the dag output of the pass - time (float): the time to execute the pass - property_set (PropertySet): the property set - count (int): the index for the pass execution - - The exact arguments pass expose the internals of the pass - manager and are subject to change as the pass manager internals - change. If you intend to reuse a callback function over - multiple releases be sure to check that the arguments being - passed are the same. - - To use the callback feature you define a function that will - take in kwargs dict and access the variables. For example:: - - def callback_func(**kwargs): - pass_ = kwargs['pass_'] - dag = kwargs['dag'] - time = kwargs['time'] - property_set = kwargs['property_set'] - count = kwargs['count'] - ... - - PassManager(callback=callback_func) - + circuit: Circuit to transform via all the registered passes. + output_name: The output circuit name. If ``None``, it will be set to the same as the + input circuit name. + callback: A callback function that will be called after each pass execution. Returns: - QuantumCircuit: Transformed circuit. + The transformed circuit. """ running_passmanager = self._create_running_passmanager() if callback is None and self.callback: # TODO to remove with __init__(callback) @@ -269,34 +276,39 @@ def callback_func(**kwargs): self.property_set = running_passmanager.property_set return result - def draw(self, filename=None, style=None, raw=False): - """ - Draws the pass manager. + def draw( + self, + filename: str = None, + style: Dict = None, + raw: bool = False + ) -> Union['PIL.Image', None]: + """Draw the pass manager. - This function needs `pydot `, which in turn needs - Graphviz ` to be installed. + This function needs `pydot `__, which in turn needs + `Graphviz `__ to be installed. Args: - filename (str or None): file path to save image to - style (dict or OrderedDict): keys are the pass classes and the values are - the colors to make them. An example can be seen in the DEFAULT_STYLE. An ordered - dict can be used to ensure a priority coloring when pass falls into multiple - categories. Any values not included in the provided dict will be filled in from - the default dict - raw (Bool) : True if you want to save the raw Dot output not an image. The - default is False. + filename: file path to save image to. + style: keys are the pass classes and the values are the colors to make them. An + example can be seen in the DEFAULT_STYLE. An ordered dict can be used to ensure + a priority coloring when pass falls into multiple categories. Any values not + included in the provided dict will be filled in from the default dict. + raw: If ``True``, save the raw Dot output instead of the image. + Returns: - PIL.Image or None: an in-memory representation of the pass manager. Or None if - no image was generated or PIL is not installed. + an in-memory representation of the pass manager, or ``None`` if no image was generated + or PIL is not installed. + Raises: ImportError: when nxpd or pydot not installed. """ return pass_manager_drawer(self, filename=filename, style=style, raw=raw) - def passes(self): + def passes(self) -> List[Dict[str, BasePass]]: """Return a list structure of the appended passes and its options. - Returns (list): A list of pass sets as defined in ``append()``. + Returns: + A list of pass sets, as defined in ``append()``. """ ret = [] for pass_set in self._pass_sets: diff --git a/qiskit/transpiler/preset_passmanagers/level2.py b/qiskit/transpiler/preset_passmanagers/level2.py index a25adc017781..280c6db870d9 100644 --- a/qiskit/transpiler/preset_passmanagers/level2.py +++ b/qiskit/transpiler/preset_passmanagers/level2.py @@ -29,6 +29,7 @@ from qiskit.transpiler.passes import CXDirection from qiskit.transpiler.passes import SetLayout from qiskit.transpiler.passes import DenseLayout +from qiskit.transpiler.passes import CSPLayout from qiskit.transpiler.passes import BarrierBeforeFinalMeasurements from qiskit.transpiler.passes import StochasticSwap from qiskit.transpiler.passes import FullAncillaAllocation @@ -119,6 +120,8 @@ def _opt_control(property_set): pm2.append(_unroll) if coupling_map: pm2.append(_given_layout) + pm2.append(CSPLayout(coupling_map, call_limit=1000, time_limit=10), + condition=_choose_layout_condition) pm2.append(_choose_layout, condition=_choose_layout_condition) pm2.append(_embed) pm2.append(_swap_check) diff --git a/qiskit/transpiler/preset_passmanagers/level3.py b/qiskit/transpiler/preset_passmanagers/level3.py index 3a5848a414a5..04f67a8107d0 100644 --- a/qiskit/transpiler/preset_passmanagers/level3.py +++ b/qiskit/transpiler/preset_passmanagers/level3.py @@ -28,6 +28,7 @@ from qiskit.transpiler.passes import CXDirection from qiskit.transpiler.passes import SetLayout from qiskit.transpiler.passes import DenseLayout +from qiskit.transpiler.passes import CSPLayout from qiskit.transpiler.passes import NoiseAdaptiveLayout from qiskit.transpiler.passes import StochasticSwap from qiskit.transpiler.passes import BarrierBeforeFinalMeasurements @@ -130,6 +131,8 @@ def _opt_control(property_set): pm3.append(_unroll) if coupling_map: pm3.append(_given_layout) + pm3.append(CSPLayout(coupling_map, call_limit=10000, time_limit=60), + condition=_choose_layout_condition) pm3.append(_choose_layout, condition=_choose_layout_condition) pm3.append(_embed) pm3.append(_swap_check) diff --git a/qiskit/util.py b/qiskit/util.py index dd4094e1e59f..65f2a5e5955b 100644 --- a/qiskit/util.py +++ b/qiskit/util.py @@ -13,6 +13,7 @@ """Common utilities for Qiskit.""" +import multiprocessing as mp import platform import re import socket @@ -123,3 +124,19 @@ def _rename_kwargs(func_name, kwargs, kwarg_map): DeprecationWarning, stacklevel=3) kwargs[new_arg] = kwargs.pop(old_arg) + + +def is_main_process(): + """Checks whether the current process is the main one""" + + return not ( + isinstance(mp.current_process(), + (mp.context.ForkProcess, mp.context.SpawnProcess)) + + # In python 3.5 and 3.6, processes created by "ProcessPoolExecutor" are not + # mp.context.ForkProcess or mp.context.SpawnProcess. As a workaround, + # "name" of the process is checked instead. + or (sys.version_info[0] == 3 + and (sys.version_info[1] == 5 or sys.version_info[1] == 6) + and mp.current_process().name != 'MainProcess') + ) diff --git a/qiskit/validation/jsonschema/schema_validation.py b/qiskit/validation/jsonschema/schema_validation.py index 35024d9403fc..c0d5cb5bf6ec 100644 --- a/qiskit/validation/jsonschema/schema_validation.py +++ b/qiskit/validation/jsonschema/schema_validation.py @@ -131,21 +131,38 @@ def validate_json_against_schema(json_dict, schema, SchemaValidationError: Raised if validation fails. """ - try: - if isinstance(schema, str): - schema_name = schema - schema = _SCHEMAS[schema_name] - validator = _get_validator(schema_name) - validator.validate(json_dict) - else: + if isinstance(schema, str): + schema_name = schema + schema = _SCHEMAS[schema_name] + validator = _get_validator(schema_name) + errors = list(validator.iter_errors(json_dict)) + if errors: + best_match_error = jsonschema.exceptions.best_match(errors) + failure_path = list(best_match_error.absolute_path) + if len(failure_path) > 1: + failure_path = failure_path[:-1] + error_path = "" + for component in failure_path: + if isinstance(component, int): + error_path += "[%s]" % component + else: + error_path += "['%s']" % component + if failure_path: + err_message = "Validation failed. Possibly at %s" % error_path + err_message += " because of %s" % best_match_error.message + else: + err_message = "Validation failed. " + err_message += "Possibly because %s" % best_match_error.message + raise SchemaValidationError(err_message) + else: + try: jsonschema.validate(json_dict, schema) - except jsonschema.ValidationError as err: - if err_msg is None: - err_msg = "JSON failed validation. Set Qiskit log level to DEBUG " \ - "for further information." - newerr = SchemaValidationError(err_msg) - newerr.__cause__ = _SummaryValidationError(err) - logger.debug('%s', _format_causes(err)) + except jsonschema.ValidationError as err: + if err_msg is None: + err_msg = ("JSON failed validation. Set Qiskit log level to " + "DEBUG for further information.") + newerr = SchemaValidationError(err_msg) + newerr.__cause__ = _SummaryValidationError(err) raise newerr diff --git a/qiskit/visualization/__init__.py b/qiskit/visualization/__init__.py index 8138598cc329..a54c24b49991 100644 --- a/qiskit/visualization/__init__.py +++ b/qiskit/visualization/__init__.py @@ -80,6 +80,14 @@ pass_manager_drawer +Single Qubit State Transition Visualizations +============================================ + +.. autosummary:: + :toctree: ../stubs/ + + visualize_transition + Exceptions ========== @@ -100,6 +108,7 @@ plot_state_paulivec, plot_state_qsphere) +from qiskit.visualization.transition_visualization import visualize_transition from .pulse_visualization import pulse_drawer from .circuit_visualization import circuit_drawer, qx_color_scheme from .dag_visualization import dag_drawer diff --git a/qiskit/visualization/gate_map.py b/qiskit/visualization/gate_map.py index dc57757c7e7d..388422f928b7 100644 --- a/qiskit/visualization/gate_map.py +++ b/qiskit/visualization/gate_map.py @@ -148,6 +148,14 @@ def plot_gate_map(backend, figsize=None, mpl_data[5] = [[1, 0], [0, 1], [1, 1], [1, 2], [2, 1]] + mpl_data[28] = [[0, 2], [0, 3], [0, 4], [0, 5], [0, 6], + [1, 2], [1, 6], + [2, 0], [2, 1], [2, 2], [2, 3], [2, 4], + [2, 5], [2, 6], [2, 7], [2, 8], + [3, 0], [3, 4], [3, 8], + [4, 0], [4, 1], [4, 2], [4, 3], [4, 4], + [4, 5], [4, 6], [4, 7], [4, 8]] + mpl_data[53] = [[0, 2], [0, 3], [0, 4], [0, 5], [0, 6], [1, 2], [1, 6], [2, 0], [2, 1], [2, 2], [2, 3], [2, 4], diff --git a/qiskit/visualization/matplotlib.py b/qiskit/visualization/matplotlib.py index ab482a7cd774..a53475029369 100644 --- a/qiskit/visualization/matplotlib.py +++ b/qiskit/visualization/matplotlib.py @@ -910,7 +910,7 @@ def _draw_ops(self, verbose=False): else: self._line(qreg_b, qreg_t, zorder=PORDER_LINE + 1) # control gate - elif op.name in ['cy', 'ch', 'cu3', 'cu1', 'crz']: + elif op.name in ['cy', 'ch', 'cu3', 'crz']: disp = op.name.replace('c', '') color = None @@ -937,6 +937,16 @@ def _draw_ops(self, verbose=False): # add qubit-qubit wiring self._line(qreg_b, qreg_t) + + # cu1 gate + elif op.name == 'cu1': + self._ctrl_qubit(q_xy[0]) + self._ctrl_qubit(q_xy[1]) + self._sidetext(qreg_b, text='U1 ({})'.format(param)) + + # add qubit-qubit wiring + self._line(qreg_b, qreg_t) + # swap gate elif op.name == 'swap': self._swap(q_xy[0]) diff --git a/qiskit/visualization/qcstyle.py b/qiskit/visualization/qcstyle.py index 94371cb6d5b2..02b80c6811bf 100644 --- a/qiskit/visualization/qcstyle.py +++ b/qiskit/visualization/qcstyle.py @@ -47,7 +47,7 @@ def __init__(self): self.sfs = 8 self.colored_add_width = 0.2 self.disptex = { - 'id': 'Id', + 'id': 'I', 'u0': 'U_0', 'u1': 'U_1', 'u2': 'U_2', @@ -151,7 +151,7 @@ def __init__(self): self.colored_add_width = 0.2 self.sfs = 8 self.disptex = { - 'id': 'Id', + 'id': 'I', 'u0': 'U_0', 'u1': 'U_1', 'u2': 'U_2', diff --git a/qiskit/visualization/text.py b/qiskit/visualization/text.py index 731b0a12c284..d6c46378b189 100644 --- a/qiskit/visualization/text.py +++ b/qiskit/visualization/text.py @@ -707,7 +707,12 @@ def label_for_box(instruction, controlled=False): else: label = instruction.name params = TextDrawing.params_for_label(instruction) + + # generate correct label for the box + if label == 'id': + label = 'i' label = label.capitalize() + if params: label += "(%s)" % ','.join(params) return label diff --git a/qiskit/visualization/transition_visualization.py b/qiskit/visualization/transition_visualization.py new file mode 100644 index 000000000000..ce9b21f893d2 --- /dev/null +++ b/qiskit/visualization/transition_visualization.py @@ -0,0 +1,316 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2018. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" +Visualization function for animation of state transitions by applying gates to single qubit. +""" +import sys +from math import sin, cos, acos, sqrt +import numpy as np + +try: + import matplotlib + from matplotlib import pyplot as plt + from matplotlib import animation + from mpl_toolkits.mplot3d import Axes3D + from qiskit.visualization.bloch import Bloch + from qiskit.visualization.exceptions import VisualizationError + HAS_MATPLOTLIB = True +except ImportError: + HAS_MATPLOTLIB = False + +try: + from IPython.display import HTML + HAS_IPYTHON = True +except ImportError: + HAS_IPYTHON = False + + +def _normalize(v, tolerance=0.00001): + """Makes sure magnitude of the vector is 1 with given tolerance""" + + mag2 = sum(n * n for n in v) + if abs(mag2 - 1.0) > tolerance: + mag = sqrt(mag2) + v = tuple(n / mag for n in v) + return np.array(v) + + +class _Quaternion: + """For calculating vectors on unit sphere""" + def __init__(self): + self._val = None + + @staticmethod + def from_axisangle(theta, v): + """Create quaternion from axis""" + v = _normalize(v) + + new_quaternion = _Quaternion() + new_quaternion._axisangle_to_q(theta, v) + return new_quaternion + + @staticmethod + def from_value(value): + """Create quaternion from vector""" + new_quaternion = _Quaternion() + new_quaternion._val = value + return new_quaternion + + def _axisangle_to_q(self, theta, v): + """Convert axis and angle to quaternion""" + x = v[0] + y = v[1] + z = v[2] + + w = cos(theta/2.) + x = x * sin(theta/2.) + y = y * sin(theta/2.) + z = z * sin(theta/2.) + + self._val = np.array([w, x, y, z]) + + def __mul__(self, b): + """Multiplication of quaternion with quaternion or vector""" + + if isinstance(b, _Quaternion): + return self._multiply_with_quaternion(b) + elif isinstance(b, (list, tuple, np.ndarray)): + if len(b) != 3: + raise Exception("Input vector has invalid length {0}".format(len(b))) + return self._multiply_with_vector(b) + else: + raise Exception("Multiplication with unknown type {0}".format(type(b))) + + def _multiply_with_quaternion(self, q_2): + """Multiplication of quaternion with quaternion""" + w_1, x_1, y_1, z_1 = self._val + w_2, x_2, y_2, z_2 = q_2._val + w = w_1 * w_2 - x_1 * x_2 - y_1 * y_2 - z_1 * z_2 + x = w_1 * x_2 + x_1 * w_2 + y_1 * z_2 - z_1 * y_2 + y = w_1 * y_2 + y_1 * w_2 + z_1 * x_2 - x_1 * z_2 + z = w_1 * z_2 + z_1 * w_2 + x_1 * y_2 - y_1 * x_2 + + result = _Quaternion.from_value(np.array((w, x, y, z))) + return result + + def _multiply_with_vector(self, v): + """Multiplication of quaternion with vector""" + q_2 = _Quaternion.from_value(np.append((0.0), v)) + return (self * q_2 * self.get_conjugate())._val[1:] + + def get_conjugate(self): + """Conjugation of quaternion""" + w, x, y, z = self._val + result = _Quaternion.from_value(np.array((w, -x, -y, -z))) + return result + + def __repr__(self): + theta, v = self.get_axisangle() + return "(({0}; {1}, {2}, {3}))".format(theta, v[0], v[1], v[2]) + + def get_axisangle(self): + """Returns angle and vector of quaternion""" + w, v = self._val[0], self._val[1:] + theta = acos(w) * 2.0 + + return theta, _normalize(v) + + def tolist(self): + """Converts quaternion to a list""" + return self._val.tolist() + + def vector_norm(self): + """Calculates norm of quaternion""" + _, v = self.get_axisangle() + return np.linalg.norm(v) + + +def visualize_transition(circuit, + trace=False, + saveas=None, + fpg=100, + spg=2): + """ + Creates animation showing transitions between states of a single + qubit by applying quantum gates. + + Args: + circuit (QuantumCircuit): Qiskit single-qubit QuantumCircuit. Gates supported are + h,x, y, z, rx, ry, rz, s, sdg, t, tdg and u1. + trace (bool): Controls whether to display tracing vectors - history of 10 past vectors + at each step of the animation. + saveas (str): User can choose to save the animation as a video to their filesystem. + This argument is a string of path with filename and extension (e.g. "movie.mp4" to + save the video in current working directory). + fpg (int): Frames per gate. Finer control over animation smoothness and computiational + needs to render the animation. Works well for tkinter GUI as it is, for jupyter GUI + it might be preferable to choose fpg between 5-30. + spg (int): Seconds per gate. How many seconds should animation of individual gate + transitions take. + + Returns: + IPython.core.display.HTML: + If arg jupyter is set to True. Otherwise opens tkinter GUI and returns + after the GUI is closed. + + Raises: + ImportError: Must have Matplotlib (and/or IPython) installed. + VisualizationError: Given gate(s) are not supported. + + """ + jupyter = False + if ('ipykernel' in sys.modules) and ('spyder' not in sys.modules): + jupyter = True + + if not HAS_MATPLOTLIB: + raise ImportError("Must have Matplotlib installed.") + if not HAS_IPYTHON and jupyter is True: + raise ImportError("Must have IPython installed.") + if len(circuit.qubits) != 1: + raise VisualizationError("Only one qubit circuits are supported") + + frames_per_gate = fpg + time_between_frames = (spg*1000)/fpg + + # quaternions of gates which don't take parameters + gates = dict() + gates['x'] = ('x', _Quaternion.from_axisangle(np.pi / frames_per_gate, [1, 0, 0]), '#1abc9c') + gates['y'] = ('y', _Quaternion.from_axisangle(np.pi / frames_per_gate, [0, 1, 0]), '#2ecc71') + gates['z'] = ('z', _Quaternion.from_axisangle(np.pi / frames_per_gate, [0, 0, 1]), '#3498db') + gates['s'] = ('s', _Quaternion.from_axisangle(np.pi / 2 / frames_per_gate, + [0, 0, 1]), '#9b59b6') + gates['sdg'] = ('sdg', _Quaternion.from_axisangle(-np.pi / 2 / frames_per_gate, [0, 0, 1]), + '#8e44ad') + gates['h'] = ('h', _Quaternion.from_axisangle(np.pi / frames_per_gate, _normalize([1, 0, 1])), + '#34495e') + gates['t'] = ('t', _Quaternion.from_axisangle(np.pi / 4 / frames_per_gate, [0, 0, 1]), + '#e74c3c') + gates['tdg'] = ('tdg', _Quaternion.from_axisangle(-np.pi / 4 / frames_per_gate, [0, 0, 1]), + '#c0392b') + + implemented_gates = ['h', 'x', 'y', 'z', 'rx', 'ry', 'rz', 's', 'sdg', 't', 'tdg', 'u1'] + simple_gates = ['h', 'x', 'y', 'z', 's', 'sdg', 't', 'tdg'] + list_of_circuit_gates = [] + + for gate in circuit._data: + if gate[0].name not in implemented_gates: + raise VisualizationError("Gate {0} is not supported".format(gate[0].name)) + if gate[0].name in simple_gates: + list_of_circuit_gates.append(gates[gate[0].name]) + else: + theta = gate[0].params[0] + rad = np.deg2rad(theta) + if gate[0].name == 'rx': + quaternion = _Quaternion.from_axisangle(rad / frames_per_gate, [1, 0, 0]) + list_of_circuit_gates.append(('rx:'+str(theta), quaternion, '#16a085')) + elif gate[0].name == 'ry': + quaternion = _Quaternion.from_axisangle(rad / frames_per_gate, [0, 1, 0]) + list_of_circuit_gates.append(('ry:'+str(theta), quaternion, '#27ae60')) + elif gate[0].name == 'rz': + quaternion = _Quaternion.from_axisangle(rad / frames_per_gate, [0, 0, 1]) + list_of_circuit_gates.append(('rz:'+str(theta), quaternion, '#2980b9')) + elif gate[0].name == 'u1': + quaternion = _Quaternion.from_axisangle(rad / frames_per_gate, [0, 0, 1]) + list_of_circuit_gates.append(('u1:'+str(theta), quaternion, '#f1c40f')) + + if len(list_of_circuit_gates) == 0: + raise VisualizationError("Nothing to visualize.") + + starting_pos = _normalize(np.array([0, 0, 1])) + + fig = plt.figure(figsize=(5, 5)) + _ax = Axes3D(fig) + _ax.set_xlim(-10, 10) + _ax.set_ylim(-10, 10) + sphere = Bloch(axes=_ax) + + class Namespace: + """Helper class serving as scope container""" + def __init__(self): + self.new_vec = [] + self.last_gate = -2 + self.colors = [] + self.pnts = [] + + namespace = Namespace() + namespace.new_vec = starting_pos + + def animate(i): + sphere.clear() + + # starting gate count from -1 which is the initial vector + gate_counter = (i-1) // frames_per_gate + if gate_counter != namespace.last_gate: + namespace.pnts.append([[], [], []]) + namespace.colors.append(list_of_circuit_gates[gate_counter][2]) + + # starts with default vector [0,0,1] + if i == 0: + sphere.add_vectors(namespace.new_vec) + namespace.pnts[0][0].append(namespace.new_vec[0]) + namespace.pnts[0][1].append(namespace.new_vec[1]) + namespace.pnts[0][2].append(namespace.new_vec[2]) + namespace.colors[0] = 'r' + sphere.make_sphere() + return _ax + + namespace.new_vec = list_of_circuit_gates[gate_counter][1] * namespace.new_vec + + namespace.pnts[gate_counter+1][0].append(namespace.new_vec[0]) + namespace.pnts[gate_counter+1][1].append(namespace.new_vec[1]) + namespace.pnts[gate_counter+1][2].append(namespace.new_vec[2]) + + sphere.add_vectors(namespace.new_vec) + if trace: + # sphere.add_vectors(namespace.points) + for point_set in namespace.pnts: + sphere.add_points([point_set[0], point_set[1], point_set[2]]) + + sphere.vector_color = [list_of_circuit_gates[gate_counter][2]] + sphere.point_color = namespace.colors + sphere.point_marker = 'o' + + annotation_text = list_of_circuit_gates[gate_counter][0] + annotationvector = [1.4, -0.45, 1.7] + sphere.add_annotation(annotationvector, + annotation_text, + color=list_of_circuit_gates[gate_counter][2], + fontsize=30, + horizontalalignment='left') + + sphere.make_sphere() + + namespace.last_gate = gate_counter + return _ax + + def init(): + sphere.vector_color = ['r'] + return _ax + + ani = animation.FuncAnimation(fig, + animate, + range(frames_per_gate * len(list_of_circuit_gates)+1), + init_func=init, + blit=False, + repeat=False, + interval=time_between_frames) + + if saveas: + ani.save(saveas, fps=30) + if jupyter: + # This is necessary to overcome matplotlib memory limit + matplotlib.rcParams['animation.embed_limit'] = 50 + return HTML(ani.to_jshtml()) + plt.show() + plt.close(fig) + return None diff --git a/releasenotes/notes/add-open-controls-bb21111b866ada43.yaml b/releasenotes/notes/add-open-controls-bb21111b866ada43.yaml new file mode 100644 index 000000000000..8efb4bde8f38 --- /dev/null +++ b/releasenotes/notes/add-open-controls-bb21111b866ada43.yaml @@ -0,0 +1,19 @@ +--- +features: + - | + Add ability to specify control conditioned on a qubit being in the + ground state. The state of the control qubits is represented by a + decimal integer. For example: + + from qiskit import QuantumCircuit + from qiskit.extensions.standard import XGate + + qc = QuantumCircuit(4) + cgate = XGate().control(3, ctrl_state=6) + qc.append(cgate, [0, 1, 2, 3]) + + Creates a four qubit gate where the fourth qubit gets flipped if + the first qubit is in the ground state and the second and third + qubits are in the excited state. If ctrl_state is None, the + default, control is conditioned on all control qubits being + excited. diff --git a/releasenotes/notes/base-operator-update-b0d824738b4bfd4d.yaml b/releasenotes/notes/base-operator-update-b0d824738b4bfd4d.yaml new file mode 100644 index 000000000000..29540876d5eb --- /dev/null +++ b/releasenotes/notes/base-operator-update-b0d824738b4bfd4d.yaml @@ -0,0 +1,17 @@ +--- +features: + - | + Adds a `reshape` method to the `BaseOperator` class that allows return a + shallow copy of an operator subclass with reshaped subsystem input or + output dimensions. The combined dimensions of all subsystems must be the + same as the original operator or an exception will be raised. + - | + Simplifies BaseOperator class so that addition, subtraction and scalar + multiplication are no longer abstract methods. Their base class definitions + now raise a NotImplementedError, so subclasses don't need to implement + these methods if they don't support them. +deprecations: + - | + The `BaseOperator` methods have been deprecated: `add`, + `subtract`, `multiply`. Use operators `+`, `-`, `*` to obtain previous + functionality. diff --git a/releasenotes/notes/boolean-logic-circuit-library-0f913cc04063210b.yaml b/releasenotes/notes/boolean-logic-circuit-library-0f913cc04063210b.yaml new file mode 100644 index 000000000000..8246ef980151 --- /dev/null +++ b/releasenotes/notes/boolean-logic-circuit-library-0f913cc04063210b.yaml @@ -0,0 +1,11 @@ +--- +prelude: > + A library of quantum circuits is introduced in ``qiskit.circuit.library``. + This library contains useful circuits which can be used for experiments or + be used as subcircuits in building up other circuits. The contents of this + library will grow over time and better implementations will be introduced. +features: + - | + A few simple circuits for basic boolean logic operations such as ``shift``, + ``permutation``, and ``inner_product`` are added to + ``qiskit.circuit.library``. diff --git a/releasenotes/notes/channel-module-deprecations-in-pulse-ffc384b325b077f1.yaml b/releasenotes/notes/channel-module-deprecations-in-pulse-ffc384b325b077f1.yaml index eae8962c6f0e..a63c9abdfd54 100644 --- a/releasenotes/notes/channel-module-deprecations-in-pulse-ffc384b325b077f1.yaml +++ b/releasenotes/notes/channel-module-deprecations-in-pulse-ffc384b325b077f1.yaml @@ -1,9 +1,10 @@ --- upgrade: - | - Channel ``buffer`` option was deprecated in Terra 0.11.0 and has now been - removed. To add a delay on a channel, specify it explicitly in your - Schedule with a Delay:: + The pulse ``buffer`` option found in :class:`qiskit.pulse.Channel` and + :class:`qiskit.pulse.Schedule` was deprecated in Terra 0.11.0 and has now + been removed. To add a delay on a channel or in a schedule, specify it + explicitly in your Schedule with a Delay:: sched = Schedule() sched += Delay(5)(DriveChannel(0)) diff --git a/releasenotes/notes/consistent-gate-naming-efa669c7a8d3a654.yaml b/releasenotes/notes/consistent-gate-naming-efa669c7a8d3a654.yaml new file mode 100644 index 000000000000..d87636c94538 --- /dev/null +++ b/releasenotes/notes/consistent-gate-naming-efa669c7a8d3a654.yaml @@ -0,0 +1,31 @@ +--- +deprecations: + - | + The gates are named more consistently. + * the Pauli gates all have one uppercase letter only (I, X, Y, Z) + * the parameterized Pauli gates (i.e. rotations) prepend the uppercase letter R (RX, RY, RZ) + * a controlled version prepends the uppercase letter C (CX, CRX, CCX) + * gates are named according to their action, not their alternative names (CCX, not Toffoli) + + This is a list of the changed names in the format `new_name <- old_name`: + + class | name | qc._ + -------------------------+------------------+---------------------- + CCXGate <- ToffoliGate | ccx | ccx, toffoli + CRXGate <- CrxGate | crx | crx + CRYGate <- CryGate | cry | cry + CRZGate <- CrzGate | crz | crz + CSwapGate <- FredkinGate | cswap | cswap, fredkin + CU1Gate <- Cu1Gate | cu1 | cu1 + CU3Gate <- Cu3Gate | cu3 | cu3 + CXGate <- CnotGate | cx | cx, cnot + CYGate <- CyGate | cy | cy + CZGate <- CzGate | cz | cz + DiagonalGate <- DiagGate | diagonal <- diag | diagonal <- diag_gate + IGate <- IdGate | id | i,id <- iden + Isometry | isometry <- iso | isometry,iso <- iso + UCGate <- UCG | multiplexer | uc <- ucg + UCPauliRotGate <- UCRot | | + UCRXGate <- UCX | ucrx <- ucrotX | ucrx <- ucx + UCRYGate <- UCY | ucry <- ucrotY | ucry <- ucy + UCRZGate <- UCZ | ucrz <- ucrotZ | ucrz <- ucz diff --git a/releasenotes/notes/continuous-waves-defined-by-frequency-a86b4cc623a2e43b.yaml b/releasenotes/notes/continuous-waves-defined-by-frequency-a86b4cc623a2e43b.yaml new file mode 100644 index 000000000000..e3a4ba1be1b2 --- /dev/null +++ b/releasenotes/notes/continuous-waves-defined-by-frequency-a86b4cc623a2e43b.yaml @@ -0,0 +1,5 @@ +deprecations: + - | + The argument `period` in functions `square`, `sawtooth`, and `triangle` in + `pulse.pulse_lib.continuous` and `pulse.pulse_lib.discrete` is deprecated. + Use frequency (`freq`) instead. diff --git a/releasenotes/notes/copy-rhs-on-extending-qc-9e65804b6b0ab4da.yaml b/releasenotes/notes/copy-rhs-on-extending-qc-9e65804b6b0ab4da.yaml new file mode 100644 index 000000000000..9a822a74c2e4 --- /dev/null +++ b/releasenotes/notes/copy-rhs-on-extending-qc-9e65804b6b0ab4da.yaml @@ -0,0 +1,8 @@ +--- +fixes: + - | + When extending a `QuantumCircuit` instance (extendee) with another circuit (extension), + the circuit is taken via reference. If a circuit is extended with itself that + leads to an infinite loop as extendee and extension are the same. + This bug is resolved by copying the extension if it is the same object as + the extendee. \ No newline at end of file diff --git a/releasenotes/notes/csplayout_level_2and3-df6b06d05a34263f.yaml b/releasenotes/notes/csplayout_level_2and3-df6b06d05a34263f.yaml new file mode 100644 index 000000000000..e6ae7789c9af --- /dev/null +++ b/releasenotes/notes/csplayout_level_2and3-df6b06d05a34263f.yaml @@ -0,0 +1,11 @@ +--- +features: + - | + The pass :class:`qiskit.transpiler.passes.CSPLayout` that was introduced in 0.11 + is being added to the default pipeline for levels 2 and 3. For level 2, there is a + call limit of 1,000 and a timeout of 10 seconds. For level 3, the call limit is 10,000 + and the timeout is 1 minute. +upgrade: + - | + Since the pass :class:`qiskit.transpiler.passes.CSPLayout` is part of the levels 2 and 3, + the package `python-constraint` is not longer optional. diff --git a/releasenotes/notes/default-schedule-name-51ba198cf08978cd.yaml b/releasenotes/notes/default-schedule-name-51ba198cf08978cd.yaml new file mode 100644 index 000000000000..2238bf7d05ab --- /dev/null +++ b/releasenotes/notes/default-schedule-name-51ba198cf08978cd.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Fixes a case in :meth:`qiskit.result.Result.get_counts`, where the results for an expirement + could not be referenced if the experiment was initialized as a Schedule without a name. + Refer to `#2753 `_ for more details. diff --git a/releasenotes/notes/fake-backend-run-37d48dbf0e9de6f3.yaml b/releasenotes/notes/fake-backend-run-37d48dbf0e9de6f3.yaml new file mode 100644 index 000000000000..61bdea0d5823 --- /dev/null +++ b/releasenotes/notes/fake-backend-run-37d48dbf0e9de6f3.yaml @@ -0,0 +1,12 @@ +--- +features: + - | + The mock backends in ``qiskit.test.mock`` now have a functional ``run()`` + method that will return results similar to the real devices. If qiskit-aer + is installed a simulation will be run with a noise model built from the + device snapshot in the fake backend. Otherwise, + :class:`qiskit.basicaer.QasmSimulatorPy` will be used to run an ideal + simulation. Additionally, if a pulse experiment is passed to ``run`` and + qiskit-aer is installed the PulseSimulator will be used to simulate the + pulse schedules. + diff --git a/releasenotes/notes/fix-propagation-of-substituted-parameters-78bc9ece47013dbb.yaml b/releasenotes/notes/fix-propagation-of-substituted-parameters-78bc9ece47013dbb.yaml new file mode 100644 index 000000000000..23cb746099d8 --- /dev/null +++ b/releasenotes/notes/fix-propagation-of-substituted-parameters-78bc9ece47013dbb.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + Previously, _substitute_parameters (i.e. replacing Parameter objects in a circuit with + new Parameter objects) prior to decomposing a circuit would result in + the substituted values not correctly being substituted into the decomposed gates. + This has been resolved such that binding and decomposition may occur in any order. \ No newline at end of file diff --git a/releasenotes/notes/fix3684-69e230f98546deb6.yaml b/releasenotes/notes/fix3684-69e230f98546deb6.yaml new file mode 100644 index 000000000000..1b89c54fe16a --- /dev/null +++ b/releasenotes/notes/fix3684-69e230f98546deb6.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + The MPL drawer has been fixed and renders cU1 gate correctly. + Fixes `issue #3684 ` \ No newline at end of file diff --git a/releasenotes/notes/job-wait-for-final-state-b9951d21aad5d3c2.yaml b/releasenotes/notes/job-wait-for-final-state-b9951d21aad5d3c2.yaml new file mode 100644 index 000000000000..28b7d5ad7797 --- /dev/null +++ b/releasenotes/notes/job-wait-for-final-state-b9951d21aad5d3c2.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + The :class:`qiskit.providers.BaseJob` class now has a new method + :meth:`qiskit.providers.BaseJob.wait_for_final_state` that polls for the job status until the job + reaches a final state (such as ``DONE`` or ``ERROR``). One of the parameters + this method takes is a callback function that will be invoked during each + polling. diff --git a/releasenotes/notes/one-qubit-synthesis-c5e323717b8dfe4d.yaml b/releasenotes/notes/one-qubit-synthesis-c5e323717b8dfe4d.yaml new file mode 100644 index 000000000000..3b41493bfdf9 --- /dev/null +++ b/releasenotes/notes/one-qubit-synthesis-c5e323717b8dfe4d.yaml @@ -0,0 +1,16 @@ +--- +features: + - | + Improves the `quantum_info.synthesis.OneQubitEulerDecomposer` class by + adding methods `angles`, `angles_and_phase` methods for + returning relevant parameters without validation, and the `__call__` + method which performs full synthesis with validation. + - | + Adds `RR` decomposition basis to the + `quantum_info.synthesis.OneQubitEulerDecomposer` for decomposiing an + arbitrary 2x2 unitary into a two `RGate` circuit. +deprecations: + - | + Deprecates `quantum_info.synthesis.euler_angles_1q` It is superseded by the + `qauntum_info.synthesis.OneQubitEulerDecomposer` class which provides the + same functionality though `OneQubitEulerDecomposer().angles(mat)`. diff --git a/releasenotes/notes/operator-qargs-aeb2254b5a643013.yaml b/releasenotes/notes/operator-qargs-aeb2254b5a643013.yaml new file mode 100644 index 000000000000..973896186e27 --- /dev/null +++ b/releasenotes/notes/operator-qargs-aeb2254b5a643013.yaml @@ -0,0 +1,16 @@ +--- +features: + - | + Adds the ability to set `qargs` to BaseOperator subclass objects. This is done + using the call method `op(qargs)` and returns a shallow copy of the original + object with a qargs property set. When such an object is used with the + ``compose`` or ``dot`` methods the internal value for qargs will be used + when the `qargs` method kwarg is not used. This allows for subsystem + composition using binary operators for example: + + .. code:: + + init = Operator.from_label('III') + x = Operator.from_label('X') + h = Opeator.from_label('H') + init @ x([0]) @ h([1]) diff --git a/releasenotes/notes/quibit-transition-visualization-a62d0d119569fa05.yaml b/releasenotes/notes/quibit-transition-visualization-a62d0d119569fa05.yaml new file mode 100644 index 000000000000..9508b9ff53d5 --- /dev/null +++ b/releasenotes/notes/quibit-transition-visualization-a62d0d119569fa05.yaml @@ -0,0 +1,44 @@ +--- +features: + - | + Single-qubit gate transition visualization tool. Give + it a single-qubit circuit and it will return an animation + of qubit state transitions. A video codec must be installed + on the system in order to use this feature (ffmpeg). + + Gates h,x, y, z, rx, ry, rz, s, sdg, t, tdg, u1 supported. + + Argument fpg controls how many frames will be drawn per gate + and spg controls how many seconds animation will spend drawing + each gate transition. Default values make animation very smooth + but it takes longer to render in jupyter notebook because the + animation must be rendered with a video codec. + + Optional argument trace controls whether to show trailing points + or not. + + + Non-jupyter example:: + + from qiskit.visualization import visualize_transition + from qiskit import * + + qc = QuantumCircuit(1) + qc.h(0) + qc.ry(70,0) + qc.rx(90,0) + qc.rz(120,0) + + visualize_transition(qc) + + Jupyter example with trace:: + from qiskit.visualization import visualize_transition + from qiskit import * + + qc = QuantumCircuit(1) + qc.h(0) + qc.ry(70,0) + qc.rx(90,0) + qc.rz(120,0) + + visualize_transition(qc, fpg=20, spg=1, trace=True) diff --git a/releasenotes/notes/unify-instructions-and-commands-aaa6d8724b8a29d3.yaml b/releasenotes/notes/unify-instructions-and-commands-aaa6d8724b8a29d3.yaml new file mode 100644 index 000000000000..df88107a748d --- /dev/null +++ b/releasenotes/notes/unify-instructions-and-commands-aaa6d8724b8a29d3.yaml @@ -0,0 +1,24 @@ +--- +features: + - | + There has been a significant simplification to the style in which Pulse + instructions are built. + + With the previous style, ``Command`` s were called with channels to make + an :py:class:`~qiskit.pulse.instruction.Instruction`. The usage of both + commands and instructions was a point of confusion. This was the previous + style:: + + sched += Delay(5)(DriveChannel(0)) + sched += ShiftPhase(np.pi)(DriveChannel(0)) + + or, equivalently (though less used):: + + sched += DelayInstruction(Delay(5), DriveChannel(0)) + sched += ShiftPhaseInstruction(ShiftPhase(np.pi), DriveChannel(0)) + + Now, rather than build a command *and* an instruction, each command has + been migrated into an instruction:: + + sched += Delay(5, DriveChannel(0)) + sched += ShiftPhase(np.pi, DriveChannel(0)) diff --git a/requirements.txt b/requirements.txt index 290286ccd736..4532f6128d31 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,3 +9,4 @@ psutil>=5 scipy>=1.0 sympy>=1.3 dill>=0.3 +python-constraint>=1.4 diff --git a/setup.py b/setup.py index 4a039fac5dd8..ef388b0b73b3 100755 --- a/setup.py +++ b/setup.py @@ -37,6 +37,7 @@ "scipy>=1.0", "sympy>=1.3", "dill>=0.3", + "python-constraint>=1.4", ] # Add Cython extensions here diff --git a/test/python/circuit/test_circuit_data.py b/test/python/circuit/test_circuit_data.py index 421d89433bdb..d3ee18bc4f35 100644 --- a/test/python/circuit/test_circuit_data.py +++ b/test/python/circuit/test_circuit_data.py @@ -15,7 +15,7 @@ """Test operations on circuit.data.""" from qiskit.circuit import QuantumCircuit, QuantumRegister -from qiskit.extensions.standard import HGate, XGate, CnotGate +from qiskit.extensions.standard import HGate, XGate, CXGate from qiskit.test import QiskitTestCase from qiskit.circuit.exceptions import CircuitError @@ -39,7 +39,7 @@ def test_getitem_by_insertion_order(self): data = qc.data self.assertEqual(data[0], (HGate(), [qr[0]], [])) - self.assertEqual(data[1], (CnotGate(), [qr[0], qr[1]], [])) + self.assertEqual(data[1], (CXGate(), [qr[0], qr[1]], [])) self.assertEqual(data[2], (HGate(), [qr[1]], [])) def test_count_gates(self): @@ -88,7 +88,7 @@ def test_index_gates(self): qc.h(0) self.assertEqual(qc.data.index((HGate(), [qr[0]], [])), 0) - self.assertEqual(qc.data.index((CnotGate(), [qr[0], qr[1]], [])), 1) + self.assertEqual(qc.data.index((CXGate(), [qr[0], qr[1]], [])), 1) self.assertEqual(qc.data.index((HGate(), [qr[1]], [])), 2) def test_iter(self): @@ -102,7 +102,7 @@ def test_iter(self): iter_ = iter(qc.data) self.assertEqual(next(iter_), (HGate(), [qr[0]], [])) - self.assertEqual(next(iter_), (CnotGate(), [qr[0], qr[1]], [])) + self.assertEqual(next(iter_), (CXGate(), [qr[0], qr[1]], [])) self.assertEqual(next(iter_), (HGate(), [qr[1]], [])) self.assertRaises(StopIteration, next, iter_) @@ -129,9 +129,9 @@ def test_slice(self): (HGate(), [qr[0]], []), ]) self.assertEqual(cx_slice, [ - (CnotGate(), [qr[0], qr[1]], []), - (CnotGate(), [qr[1], qr[0]], []), - (CnotGate(), [qr[0], qr[1]], []), + (CXGate(), [qr[0], qr[1]], []), + (CXGate(), [qr[1], qr[0]], []), + (CXGate(), [qr[0], qr[1]], []), ]) def test_copy(self): @@ -323,7 +323,7 @@ def test_append_is_validated(self): qc = QuantumCircuit(qr) qc.data.append((HGate(), [qr[0]], [])) - qc.data.append((CnotGate(), [0, 1], [])) + qc.data.append((CXGate(), [0, 1], [])) qc.data.append((HGate(), [qr[1]], [])) expected_qc = QuantumCircuit(qr) @@ -343,7 +343,7 @@ def test_insert_is_validated(self): qc = QuantumCircuit(qr) qc.data.insert(0, (HGate(), [qr[0]], [])) - qc.data.insert(1, (CnotGate(), [0, 1], [])) + qc.data.insert(1, (CXGate(), [0, 1], [])) qc.data.insert(2, (HGate(), [qr[1]], [])) expected_qc = QuantumCircuit(qr) @@ -363,7 +363,7 @@ def test_extend_is_validated(self): qc = QuantumCircuit(qr) qc.data.extend([(HGate(), [qr[0]], []), - (CnotGate(), [0, 1], []), + (CXGate(), [0, 1], []), (HGate(), [qr[1]], [])]) expected_qc = QuantumCircuit(qr) @@ -383,7 +383,7 @@ def test_setting_data_is_validated(self): qc = QuantumCircuit(qr) qc.data = [(HGate(), [qr[0]], []), - (CnotGate(), [0, 1], []), + (CXGate(), [0, 1], []), (HGate(), [qr[1]], [])] expected_qc = QuantumCircuit(qr) diff --git a/test/python/circuit/test_circuit_load_from_qasm.py b/test/python/circuit/test_circuit_load_from_qasm.py index 973b2f2dd53c..e1e08fce3e2c 100644 --- a/test/python/circuit/test_circuit_load_from_qasm.py +++ b/test/python/circuit/test_circuit_load_from_qasm.py @@ -26,11 +26,11 @@ class LoadFromQasmTest(QiskitTestCase): def setUp(self): self.qasm_file_name = 'entangled_registers.qasm' - self.qasm_file_path = self._get_resource_path( - 'qasm/' + self.qasm_file_name, Path.EXAMPLES) + self.qasm_file_path = self._get_resource_path('qasm/' + self.qasm_file_name, Path.EXAMPLES) def test_qasm_file(self): - """Test qasm_file and get_circuit. + """ + Test qasm_file and get_circuit. If all is correct we should get the qasm file loaded in _qasm_file_path """ @@ -48,8 +48,54 @@ def test_qasm_file(self): q_circuit_2.measure(qr_b, cr_d) self.assertEqual(q_circuit, q_circuit_2) + def test_loading_all_qelib1_gates(self): + """Test setting up a circuit with all gates defined in qiskit/qasm/libs/qelib1.inc.""" + all_gates_qasm = self._get_resource_path('all_gates.qasm', Path.QASMS) + qasm_circuit = QuantumCircuit.from_qasm_file(all_gates_qasm) + + # the hardware primitives + ref_circuit = QuantumCircuit(3, 3) + ref_circuit.u3(0.2, 0.1, 0.6, 0) + ref_circuit.u2(0.1, 0.6, 0) + ref_circuit.u1(0.6, 0) + ref_circuit.id(0) + ref_circuit.cx(0, 1) + # the standard single qubit gates + ref_circuit.x(0) + ref_circuit.y(0) + ref_circuit.z(0) + ref_circuit.h(0) + ref_circuit.s(0) + ref_circuit.t(0) + ref_circuit.sdg(0) + ref_circuit.tdg(0) + # the standard rotations + ref_circuit.rx(0.1, 0) + ref_circuit.ry(0.1, 0) + ref_circuit.rz(0.1, 0) + # the barrier + ref_circuit.barrier() + # the standard user-defined gates + ref_circuit.swap(0, 1) + ref_circuit.cswap(0, 1, 2) + ref_circuit.cy(0, 1) + ref_circuit.cz(0, 1) + ref_circuit.ch(0, 1) + ref_circuit.cu1(0.6, 0, 1) + ref_circuit.cu3(0.2, 0.1, 0.6, 0, 1) + ref_circuit.ccx(0, 1, 2) + ref_circuit.crx(0.6, 0, 1) + ref_circuit.cry(0.6, 0, 1) + ref_circuit.crz(0.6, 0, 1) + ref_circuit.rxx(0.2, 0, 1) + ref_circuit.rzz(0.2, 0, 1) + ref_circuit.measure([0, 1, 2], [0, 1, 2]) + + self.assertEqual(qasm_circuit, ref_circuit) + def test_fail_qasm_file(self): - """Test fail_qasm_file. + """ + Test fail_qasm_file. If all is correct we should get a QiskitError """ @@ -57,7 +103,8 @@ def test_fail_qasm_file(self): QuantumCircuit.from_qasm_file, "") def test_fail_qasm_string(self): - """Test fail_qasm_string. + """ + Test fail_qasm_string. If all is correct we should get a QiskitError """ @@ -65,7 +112,8 @@ def test_fail_qasm_string(self): QuantumCircuit.from_qasm_str, "") def test_qasm_text(self): - """Test qasm_text and get_circuit. + """ + Test qasm_text and get_circuit. If all is correct we should get the qasm file loaded from the string """ @@ -102,7 +150,8 @@ def test_qasm_text(self): self.assertEqual(q_circuit, ref) def test_qasm_text_conditional(self): - """Test qasm_text and get_circuit when conditionals are present. + """ + Test qasm_text and get_circuit when conditionals are present. """ qasm_string = '\n'.join(["OPENQASM 2.0;", "include \"qelib1.inc\";", @@ -125,8 +174,11 @@ def test_qasm_text_conditional(self): self.assertEqual(q_circuit, ref) def test_opaque_gate(self): - """Test parse an opaque gate - See https://github.com/Qiskit/qiskit-terra/issues/1566""" + """ + Test parse an opaque gate + + See https://github.com/Qiskit/qiskit-terra/issues/1566. + """ qasm_string = '\n'.join(["OPENQASM 2.0;", "include \"qelib1.inc\";", @@ -137,13 +189,13 @@ def test_opaque_gate(self): qr = QuantumRegister(3, 'q') expected = QuantumCircuit(qr) - expected.append(Gate(name='my_gate', num_qubits=2, params=[1, 2, 3]), [qr[1], qr[2]]) + expected.append(Gate(name='my_gate', num_qubits=2, + params=[1, 2, 3]), [qr[1], qr[2]]) self.assertEqual(circuit, expected) def test_qasm_example_file(self): - """Loads qasm/example.qasm. - """ + """Loads qasm/example.qasm.""" qasm_filename = self._get_resource_path('example.qasm', Path.QASMS) expected_circuit = QuantumCircuit.from_qasm_str('\n'.join(["OPENQASM 2.0;", "include \"qelib1.inc\";", @@ -172,7 +224,7 @@ def test_qasm_example_file(self): self.assertEqual(len(q_circuit.qregs), 2) def test_qasm_qas_string_order(self): - """ Test that gates are returned in qasm in ascending order""" + """Test that gates are returned in qasm in ascending order.""" expected_qasm = '\n'.join(["OPENQASM 2.0;", "include \"qelib1.inc\";", "qreg q[3];", diff --git a/test/python/circuit/test_circuit_operations.py b/test/python/circuit/test_circuit_operations.py index 4afc9d59ddf2..4f00778aa492 100644 --- a/test/python/circuit/test_circuit_operations.py +++ b/test/python/circuit/test_circuit_operations.py @@ -25,6 +25,22 @@ class TestCircuitOperations(QiskitTestCase): """QuantumCircuit Operations tests.""" + def test_adding_self(self): + """Test that qc += qc finishes, which can be prone to infinite while-loops. + + This can occur e.g. when a user tries + >>> other_qc = qc + >>> other_qc += qc # or qc2.extend(qc) + """ + qc = QuantumCircuit(1) + qc.x(0) # must contain at least one operation to end up in a infinite while-loop + + # attempt addition, times out if qc is added via reference + qc += qc + + # finally, qc should contain two X gates + self.assertEqual(['x', 'x'], [x[0].name for x in qc.data]) + def test_combine_circuit_common(self): """Test combining two circuits with same registers. """ diff --git a/test/python/circuit/test_circuit_properties.py b/test/python/circuit/test_circuit_properties.py index ac4b395681e6..bb4190a8720f 100644 --- a/test/python/circuit/test_circuit_properties.py +++ b/test/python/circuit/test_circuit_properties.py @@ -17,8 +17,10 @@ """Test Qiskit's inverse gate operation.""" import unittest +import numpy as np from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit from qiskit.test import QiskitTestCase +from qiskit.circuit.exceptions import CircuitError # pylint: disable=unused-import from qiskit.extensions.simulator import snapshot @@ -26,6 +28,37 @@ class TestCircuitProperties(QiskitTestCase): """QuantumCircuit properties tests.""" + def test_qarg_numpy_int(self): + """Test castable to integer args for QuantumCircuit. + """ + n = np.int64(12) + qc1 = QuantumCircuit(n) + self.assertEqual(qc1.n_qubits, 12) + self.assertEqual(type(qc1), QuantumCircuit) + + def test_carg_numpy_int(self): + """Test castable to integer cargs for QuantumCircuit. + """ + n = np.int64(12) + c1 = ClassicalRegister(n) + qc1 = QuantumCircuit(c1) + c_regs = qc1.cregs + self.assertEqual(c_regs[0], c1) + self.assertEqual(type(qc1), QuantumCircuit) + + def test_carg_numpy_int_2(self): + """Test castable to integer cargs for QuantumCircuit. + """ + qc1 = QuantumCircuit(12, np.int64(12)) + c_regs = qc1.cregs + self.assertEqual(c_regs[0], ClassicalRegister(12, 'c')) + self.assertEqual(type(qc1), QuantumCircuit) + + def test_qarg_numpy_int_exception(self): + """Test attempt to pass non-castable arg to QuantumCircuit. + """ + self.assertRaises(CircuitError, QuantumCircuit, 'string') + def test_circuit_depth_empty(self): """Test depth of empty circuity """ diff --git a/test/python/circuit/test_controlled_gate.py b/test/python/circuit/test_controlled_gate.py index d0ad34219a07..0a03074d4c73 100644 --- a/test/python/circuit/test_controlled_gate.py +++ b/test/python/circuit/test_controlled_gate.py @@ -17,88 +17,91 @@ import unittest from inspect import signature +from test import combine import numpy as np from numpy import pi -import scipy from ddt import ddt, data from qiskit import QuantumRegister, QuantumCircuit, execute, BasicAer, QiskitError from qiskit.test import QiskitTestCase from qiskit.circuit import ControlledGate +from qiskit.circuit.exceptions import CircuitError from qiskit.quantum_info.operators.predicates import matrix_equal, is_unitary_matrix +from qiskit.quantum_info.random import random_unitary +from qiskit.quantum_info.states import Statevector import qiskit.circuit.add_control as ac from qiskit.transpiler.passes import Unroller from qiskit.converters.circuit_to_dag import circuit_to_dag from qiskit.converters.dag_to_circuit import dag_to_circuit from qiskit.quantum_info import Operator -from qiskit.extensions.standard import (CnotGate, XGate, YGate, ZGate, U1Gate, - CyGate, CzGate, Cu1Gate, SwapGate, - ToffoliGate, HGate, RZGate, RXGate, - RYGate, CryGate, CrxGate, FredkinGate, - U3Gate, CHGate, CrzGate, Cu3Gate, +from qiskit.extensions.standard import (CXGate, XGate, YGate, ZGate, U1Gate, + CYGate, CZGate, CU1Gate, SwapGate, + CCXGate, HGate, RZGate, RXGate, + RYGate, CRYGate, CRXGate, CSwapGate, + U3Gate, CHGate, CRZGate, CU3Gate, MSGate, Barrier, RCCXGate, RCCCXGate) -from qiskit.extensions.unitary import UnitaryGate +from qiskit.extensions.unitary import _compute_control_matrix import qiskit.extensions.standard as allGates @ddt class TestControlledGate(QiskitTestCase): - """ControlledGate tests.""" + """Tests for controlled gates and the ControlledGate class.""" def test_controlled_x(self): """Test creation of controlled x gate""" - self.assertEqual(XGate().control(), CnotGate()) + self.assertEqual(XGate().control(), CXGate()) def test_controlled_y(self): """Test creation of controlled y gate""" - self.assertEqual(YGate().control(), CyGate()) + self.assertEqual(YGate().control(), CYGate()) def test_controlled_z(self): """Test creation of controlled z gate""" - self.assertEqual(ZGate().control(), CzGate()) + self.assertEqual(ZGate().control(), CZGate()) def test_controlled_h(self): - """Test creation of controlled h gate""" + """Test the creation of a controlled H gate.""" self.assertEqual(HGate().control(), CHGate()) def test_controlled_u1(self): - """Test creation of controlled u1 gate""" + """Test the creation of a controlled U1 gate.""" theta = 0.5 - self.assertEqual(U1Gate(theta).control(), Cu1Gate(theta)) + self.assertEqual(U1Gate(theta).control(), CU1Gate(theta)) def test_controlled_rz(self): - """Test creation of controlled rz gate""" + """Test the creation of a controlled RZ gate.""" theta = 0.5 - self.assertEqual(RZGate(theta).control(), CrzGate(theta)) + self.assertEqual(RZGate(theta).control(), CRZGate(theta)) def test_controlled_ry(self): - """Test creation of controlled ry gate""" + """Test the creation of a controlled RY gate.""" theta = 0.5 - self.assertEqual(RYGate(theta).control(), CryGate(theta)) + self.assertEqual(RYGate(theta).control(), CRYGate(theta)) def test_controlled_rx(self): - """Test creation of controlled rx gate""" + """Test the creation of a controlled RX gate.""" theta = 0.5 - self.assertEqual(RXGate(theta).control(), CrxGate(theta)) + self.assertEqual(RXGate(theta).control(), CRXGate(theta)) def test_controlled_u3(self): - """Test creation of controlled u3 gate""" + """Test the creation of a controlled U3 gate.""" theta, phi, lamb = 0.1, 0.2, 0.3 self.assertEqual(U3Gate(theta, phi, lamb).control(), - Cu3Gate(theta, phi, lamb)) + CU3Gate(theta, phi, lamb)) def test_controlled_cx(self): """Test creation of controlled cx gate""" - self.assertEqual(CnotGate().control(), ToffoliGate()) + self.assertEqual(CXGate().control(), CCXGate()) def test_controlled_swap(self): """Test creation of controlled swap gate""" - self.assertEqual(SwapGate().control(), FredkinGate()) + self.assertEqual(SwapGate().control(), CSwapGate()) def test_circuit_append(self): - """Test appending controlled gate to quantum circuit""" + """Test appending a controlled gate to a quantum circuit.""" circ = QuantumCircuit(5) - inst = CnotGate() + inst = CXGate() circ.append(inst.control(), qargs=[0, 2, 1]) circ.append(inst.control(2), qargs=[0, 3, 1, 2]) circ.append(inst.control().control(), qargs=[0, 3, 1, 2]) # should be same as above @@ -114,21 +117,21 @@ def test_circuit_append(self): gate = instr[0] self.assertTrue(isinstance(gate, ControlledGate)) - def test_definition_specification(self): - """Test instantiation with explicit definition""" + def test_swap_definition_specification(self): + """Test the instantiation of a controlled swap gate with explicit definition.""" swap = SwapGate() cswap = ControlledGate('cswap', 3, [], num_ctrl_qubits=1, definition=swap.definition) self.assertEqual(swap.definition, cswap.definition) def test_multi_controlled_composite_gate(self): - """Test multi controlled composite gate""" + """Test a multi controlled composite gate. """ num_ctrl = 3 # create composite gate sub_q = QuantumRegister(2) cgate = QuantumCircuit(sub_q, name='cgate') cgate.h(sub_q[0]) - cgate.crz(pi/2, sub_q[0], sub_q[1]) + cgate.crz(pi / 2, sub_q[0], sub_q[1]) cgate.swap(sub_q[0], sub_q[1]) cgate.u3(0.1, 0.2, 0.3, sub_q[1]) cgate.t(sub_q[0]) @@ -138,7 +141,7 @@ def test_multi_controlled_composite_gate(self): control = QuantumRegister(num_ctrl) target = QuantumRegister(num_target) qc = QuantumCircuit(control, target) - qc.append(cont_gate, control[:]+target[:]) + qc.append(cont_gate, control[:] + target[:]) simulator = BasicAer.get_backend('unitary_simulator') op_mat = execute(cgate, simulator).result().get_unitary(0) cop_mat = _compute_control_matrix(op_mat, num_ctrl) @@ -146,7 +149,7 @@ def test_multi_controlled_composite_gate(self): self.assertTrue(matrix_equal(cop_mat, ref_mat, ignore_phase=True)) def test_single_controlled_composite_gate(self): - """Test singly controlled composite gate""" + """Test a singly controlled composite gate.""" num_ctrl = 1 # create composite gate sub_q = QuantumRegister(2) @@ -156,10 +159,10 @@ def test_single_controlled_composite_gate(self): num_target = cgate.width() gate = cgate.to_gate() cont_gate = gate.control(num_ctrl_qubits=num_ctrl) - control = QuantumRegister(num_ctrl) - target = QuantumRegister(num_target) + control = QuantumRegister(num_ctrl, 'control') + target = QuantumRegister(num_target, 'target') qc = QuantumCircuit(control, target) - qc.append(cont_gate, control[:]+target[:]) + qc.append(cont_gate, control[:] + target[:]) simulator = BasicAer.get_backend('unitary_simulator') op_mat = execute(cgate, simulator).result().get_unitary(0) cop_mat = _compute_control_matrix(op_mat, num_ctrl) @@ -167,7 +170,7 @@ def test_single_controlled_composite_gate(self): self.assertTrue(matrix_equal(cop_mat, ref_mat, ignore_phase=True)) def test_multi_control_u3(self): - """test multi controlled u3 gate""" + """Test the matrix representation of the controlled and controlled-controlled U3 gate.""" import qiskit.extensions.standard.u3 as u3 num_ctrl = 3 @@ -194,7 +197,7 @@ def test_multi_control_u3(self): width = 3 qr = QuantumRegister(width) qc_cu3 = QuantumCircuit(qr) - cu3gate = u3.Cu3Gate(alpha, beta, gamma) + cu3gate = u3.CU3Gate(alpha, beta, gamma) c_cu3 = cu3gate.control(1) qc_cu3.append(c_cu3, qr, []) @@ -228,7 +231,7 @@ def test_multi_control_u3(self): self.assertTrue(matrix_equal(target, decomp, ignore_phase=True)) def test_multi_control_u1(self): - """Test multi controlled u1 gate""" + """Test the matrix representation of the controlled and controlled-controlled U1 gate.""" import qiskit.extensions.standard.u1 as u1 num_ctrl = 3 @@ -255,7 +258,7 @@ def test_multi_control_u1(self): width = 3 qr = QuantumRegister(width) qc_cu1 = QuantumCircuit(qr) - cu1gate = u1.Cu1Gate(theta) + cu1gate = u1.CU1Gate(theta) c_cu1 = cu1gate.control(1) qc_cu1.append(c_cu1, qr, []) @@ -288,12 +291,160 @@ def test_multi_control_u1(self): self.log.info(info) self.assertTrue(matrix_equal(target, decomp, ignore_phase=True)) - def test_rotation_gates(self): - """Test controlled rotation gates""" + @data(1, 2, 3, 4) + def test_multi_controlled_u1_matrix(self, num_controls): + """Test the matrix representation of the multi-controlled CU1 gate. + + Based on the test moved here from Aqua: + https://github.com/Qiskit/qiskit-aqua/blob/769ca8f/test/aqua/test_mcu1.py + """ + + # registers for the circuit + q_controls = QuantumRegister(num_controls) + q_target = QuantumRegister(1) + + # iterate over all possible combinations of control qubits + for ctrl_state in range(2**num_controls): + bitstr = bin(ctrl_state)[2:].zfill(num_controls)[::-1] + lam = 0.3165354 * pi + qc = QuantumCircuit(q_controls, q_target) + for idx, bit in enumerate(bitstr): + if bit == '0': + qc.x(q_controls[idx]) + + qc.mcu1(lam, q_controls, q_target[0]) + + # for idx in subset: + for idx, bit in enumerate(bitstr): + if bit == '0': + qc.x(q_controls[idx]) + + backend = BasicAer.get_backend('unitary_simulator') + simulated = execute(qc, backend).result().get_unitary(qc) + + base = U1Gate(lam).to_matrix() + expected = _compute_control_matrix(base, num_controls, ctrl_state=ctrl_state) + with self.subTest(msg='control state = {}'.format(ctrl_state)): + self.assertTrue(matrix_equal(simulated, expected)) + + @data(1, 2, 3, 4) + def test_multi_control_toffoli_matrix_clean_ancillas(self, num_controls): + """Test the multi-control Toffoli gate with clean ancillas. + + Based on the test moved here from Aqua: + https://github.com/Qiskit/qiskit-aqua/blob/769ca8f/test/aqua/test_mct.py + """ + # set up circuit + q_controls = QuantumRegister(num_controls) + q_target = QuantumRegister(1) + qc = QuantumCircuit(q_controls, q_target) + + if num_controls > 2: + num_ancillas = num_controls - 2 + q_ancillas = QuantumRegister(num_controls) + qc.add_register(q_ancillas) + else: + num_ancillas = 0 + + # apply hadamard on control qubits and toffoli gate + qc.mct(q_controls, q_target[0], None, mode='basic') + + # execute the circuit and obtain statevector result + backend = BasicAer.get_backend('unitary_simulator') + simulated = execute(qc, backend).result().get_unitary(qc) + + # compare to expectation + if num_ancillas > 0: + simulated = simulated[:2**(num_controls + 1), :2**(num_controls + 1)] + + base = XGate().to_matrix() + expected = _compute_control_matrix(base, num_controls) + self.assertTrue(matrix_equal(simulated, expected)) + + @data(1, 2, 3, 4, 5) + def test_multi_control_toffoli_matrix_basic_dirty_ancillas(self, num_controls): + """Test the multi-control Toffoli gate with dirty ancillas (basic-dirty-ancilla). + + Based on the test moved here from Aqua: + https://github.com/Qiskit/qiskit-aqua/blob/769ca8f/test/aqua/test_mct.py + """ + q_controls = QuantumRegister(num_controls) + q_target = QuantumRegister(1) + qc = QuantumCircuit(q_controls, q_target) + + q_ancillas = None + if num_controls <= 2: + num_ancillas = 0 + else: + num_ancillas = num_controls - 2 + q_ancillas = QuantumRegister(num_ancillas) + qc.add_register(q_ancillas) + + qc.mct(q_controls, q_target[0], q_ancillas, mode='basic-dirty-ancilla') + + simulated = execute(qc, BasicAer.get_backend('unitary_simulator')).result().get_unitary(qc) + if num_ancillas > 0: + simulated = simulated[:2**(num_controls + 1), :2**(num_controls + 1)] + + base = XGate().to_matrix() + expected = _compute_control_matrix(base, num_controls) + self.assertTrue(matrix_equal(simulated, expected, atol=1e-8)) + + @data(1, 2, 3, 4, 5) + def test_multi_control_toffoli_matrix_advanced_dirty_ancillas(self, num_controls): + """Test the multi-control Toffoli gate with dirty ancillas (advanced). + + Based on the test moved here from Aqua: + https://github.com/Qiskit/qiskit-aqua/blob/769ca8f/test/aqua/test_mct.py + """ + q_controls = QuantumRegister(num_controls) + q_target = QuantumRegister(1) + qc = QuantumCircuit(q_controls, q_target) + + q_ancillas = None + if num_controls <= 4: + num_ancillas = 0 + else: + num_ancillas = 1 + q_ancillas = QuantumRegister(num_ancillas) + qc.add_register(q_ancillas) + + qc.mct(q_controls, q_target[0], q_ancillas, mode='advanced') + + simulated = execute(qc, BasicAer.get_backend('unitary_simulator')).result().get_unitary(qc) + if num_ancillas > 0: + simulated = simulated[:2**(num_controls + 1), :2**(num_controls + 1)] + + base = XGate().to_matrix() + expected = _compute_control_matrix(base, num_controls) + self.assertTrue(matrix_equal(simulated, expected, atol=1e-8)) + + @data(1, 2, 3) + def test_multi_control_toffoli_matrix_noancilla_dirty_ancillas(self, num_controls): + """Test the multi-control Toffoli gate with dirty ancillas (noancilla). + + Based on the test moved here from Aqua: + https://github.com/Qiskit/qiskit-aqua/blob/769ca8f/test/aqua/test_mct.py + """ + q_controls = QuantumRegister(num_controls) + q_target = QuantumRegister(1) + qc = QuantumCircuit(q_controls, q_target) + + qc.mct(q_controls, q_target[0], None, mode='noancilla') + + simulated = execute(qc, BasicAer.get_backend('unitary_simulator')).result().get_unitary(qc) + + base = XGate().to_matrix() + expected = _compute_control_matrix(base, num_controls) + self.assertTrue(matrix_equal(simulated, expected, atol=1e-8)) + + def test_single_controlled_rotation_gates(self): + """Test the controlled rotation gates controlled on one qubit.""" import qiskit.extensions.standard.u1 as u1 import qiskit.extensions.standard.rx as rx import qiskit.extensions.standard.ry as ry import qiskit.extensions.standard.rz as rz + num_ctrl = 2 num_target = 1 qreg = QuantumRegister(num_ctrl + num_target) @@ -353,9 +504,107 @@ def test_rotation_gates(self): self.log.info('%s gate count: %d', uqc.name, uqc.size()) self.assertTrue(uqc.size() <= 93) # this limit could be changed + @combine(num_controls=[1, 2, 4], + base_gate_name=['x', 'y', 'z'], + use_basis_gates=[True, False]) + def test_multi_controlled_rotation_gate_matrices(self, num_controls, base_gate_name, + use_basis_gates): + """Test the multi controlled rotation gates without ancillas. + + Based on the test moved here from Aqua: + https://github.com/Qiskit/qiskit-aqua/blob/769ca8f/test/aqua/test_mcr.py + """ + q_controls = QuantumRegister(num_controls) + q_target = QuantumRegister(1) + + # iterate over all possible combinations of control qubits + for ctrl_state in range(2**num_controls): + bitstr = bin(ctrl_state)[2:].zfill(num_controls)[::-1] + theta = 0.871236 * pi + qc = QuantumCircuit(q_controls, q_target) + for idx, bit in enumerate(bitstr): + if bit == '0': + qc.x(q_controls[idx]) + + # call mcrx/mcry/mcrz + if base_gate_name == 'y': + qc.mcry(theta, q_controls, q_target[0], None, mode='noancilla', + use_basis_gates=use_basis_gates) + else: # case 'x' or 'z' only support the noancilla mode and do not have this keyword + getattr(qc, 'mcr' + base_gate_name)(theta, q_controls, q_target[0], + use_basis_gates=use_basis_gates) + + for idx, bit in enumerate(bitstr): + if bit == '0': + qc.x(q_controls[idx]) + + backend = BasicAer.get_backend('unitary_simulator') + simulated = execute(qc, backend).result().get_unitary(qc) + + if base_gate_name == 'x': + rot_mat = RXGate(theta).to_matrix() + elif base_gate_name == 'y': + rot_mat = RYGate(theta).to_matrix() + else: # case 'z' + rot_mat = U1Gate(theta).to_matrix() + + expected = _compute_control_matrix(rot_mat, num_controls, ctrl_state=ctrl_state) + with self.subTest(msg='control state = {}'.format(ctrl_state)): + self.assertTrue(matrix_equal(simulated, expected)) + + @combine(num_controls=[1, 2, 4], use_basis_gates=[True, False]) + def test_multi_controlled_y_rotation_matrix_basic_mode(self, num_controls, use_basis_gates): + """Test the multi controlled Y rotation using the mode 'basic'. + + Based on the test moved here from Aqua: + https://github.com/Qiskit/qiskit-aqua/blob/769ca8f/test/aqua/test_mcr.py + """ + + # get the number of required ancilla qubits + if num_controls <= 2: + num_ancillas = 0 + else: + num_ancillas = num_controls - 2 + + q_controls = QuantumRegister(num_controls) + q_target = QuantumRegister(1) + + for ctrl_state in range(2**num_controls): + bitstr = bin(ctrl_state)[2:].zfill(num_controls)[::-1] + theta = 0.871236 * pi + if num_ancillas > 0: + q_ancillas = QuantumRegister(num_ancillas) + qc = QuantumCircuit(q_controls, q_target, q_ancillas) + else: + qc = QuantumCircuit(q_controls, q_target) + q_ancillas = None + + for idx, bit in enumerate(bitstr): + if bit == '0': + qc.x(q_controls[idx]) + + qc.mcry(theta, q_controls, q_target[0], q_ancillas, mode='basic', + use_basis_gates=use_basis_gates) + + for idx, bit in enumerate(bitstr): + if bit == '0': + qc.x(q_controls[idx]) + + rot_mat = RYGate(theta).to_matrix() + + backend = BasicAer.get_backend('unitary_simulator') + simulated = execute(qc, backend).result().get_unitary(qc) + if num_ancillas > 0: + simulated = simulated[:2**(num_controls + 1), :2**(num_controls+1)] + + expected = _compute_control_matrix(rot_mat, num_controls, ctrl_state=ctrl_state) + + with self.subTest(msg='control state = {}'.format(ctrl_state)): + self.assertTrue(matrix_equal(simulated, expected)) + @data(1, 2, 3, 4) def test_inverse_x(self, num_ctrl_qubits): - """test inverting ControlledGate""" + """Test inverting the controlled X gate.""" cnx = XGate().control(num_ctrl_qubits) inv_cnx = cnx.inverse() result = Operator(cnx).compose(Operator(inv_cnx)) @@ -364,12 +613,12 @@ def test_inverse_x(self, num_ctrl_qubits): @data(1, 2, 3) def test_inverse_circuit(self, num_ctrl_qubits): - """test inverting ControlledGate""" + """Test inverting a controlled gate based on a circuit definition.""" qc = QuantumCircuit(3) qc.h(0) qc.cx(0, 1) qc.cx(1, 2) - qc.rx(np.pi/4, [0, 1, 2]) + qc.rx(np.pi / 4, [0, 1, 2]) gate = qc.to_gate() cgate = gate.control(num_ctrl_qubits) inv_cgate = cgate.inverse() @@ -379,7 +628,7 @@ def test_inverse_circuit(self, num_ctrl_qubits): @data(1, 2, 3, 4, 5) def test_controlled_unitary(self, num_ctrl_qubits): - """test controlled unitary""" + """Test the matrix data of an Operator, which is based on a controlled gate.""" num_target = 1 q_target = QuantumRegister(num_target) qc1 = QuantumCircuit(q_target) @@ -398,19 +647,58 @@ def test_controlled_unitary(self, num_ctrl_qubits): @data(1, 2, 3, 4, 5) def test_controlled_random_unitary(self, num_ctrl_qubits): - """test controlled unitary""" + """Test the matrix data of an Operator based on a random UnitaryGate.""" num_target = 2 - base_gate = UnitaryGate(scipy.stats.unitary_group.rvs(num_target)) + base_gate = random_unitary(2**num_target).to_instruction() base_mat = base_gate.to_matrix() cgate = base_gate.control(num_ctrl_qubits) test_op = Operator(cgate) cop_mat = _compute_control_matrix(base_mat, num_ctrl_qubits) self.assertTrue(matrix_equal(cop_mat, test_op.data, ignore_phase=True)) + @data(1, 2, 3) + def test_open_controlled_unitary_matrix(self, num_ctrl_qubits): + """test open controlled unitary matrix""" + # verify truth table + num_target_qubits = 2 + num_qubits = num_ctrl_qubits + num_target_qubits + target_op = Operator(XGate()) + for i in range(num_target_qubits - 1): + target_op = target_op.tensor(XGate()) + + for i in range(2**num_qubits): + input_bitstring = bin(i)[2:].zfill(num_qubits) + input_target = input_bitstring[0:num_target_qubits] + input_ctrl = input_bitstring[-num_ctrl_qubits:] + phi = Statevector.from_label(input_bitstring) + cop = Operator(_compute_control_matrix(target_op.data, + num_ctrl_qubits, + ctrl_state=input_ctrl)) + for j in range(2**num_qubits): + output_bitstring = bin(j)[2:].zfill(num_qubits) + output_target = output_bitstring[0:num_target_qubits] + output_ctrl = output_bitstring[-num_ctrl_qubits:] + psi = Statevector.from_label(output_bitstring) + cxout = np.dot(phi.data, psi.evolve(cop).data) + if input_ctrl == output_ctrl: + # flip the target bits + cond_output = ''.join([str(int(not int(a))) for a in input_target]) + else: + cond_output = input_target + if cxout == 1: + self.assertTrue( + (output_ctrl == input_ctrl) and + (output_target == cond_output)) + else: + self.assertTrue( + ((output_ctrl == input_ctrl) and + (output_target != cond_output)) or + output_ctrl != input_ctrl) + def test_base_gate_setting(self): """ - Test all gates in standard extensions which are of type ControlledGate - have a base gate setting. + Test all gates in standard extensions which are of type ControlledGate and have a base gate + setting. """ params = [0.1 * i for i in range(10)] for gate_class in ControlledGate.__subclasses__(): @@ -422,8 +710,8 @@ def test_base_gate_setting(self): def test_all_inverses(self): """ - Test all gates in standard extensions except those that cannot be - controlled or are being deprecated. + Test all gates in standard extensions except those that cannot be controlled or are being + deprecated. """ gate_classes = [cls for name, cls in allGates.__dict__.items() if isinstance(cls, type)] @@ -431,7 +719,7 @@ def test_all_inverses(self): # only verify basic gates right now, as already controlled ones # will generate differing definitions with self.subTest(i=cls): - if issubclass(cls, ControlledGate) or cls == allGates.IdGate: + if issubclass(cls, ControlledGate) or issubclass(cls, allGates.IGate): continue try: sig = signature(cls) @@ -450,19 +738,17 @@ def test_all_inverses(self): @data(1, 2, 3) def test_controlled_standard_gates(self, num_ctrl_qubits): - """ - Test controlled versions of all standard gates. - """ + """Test the controlled versions of all standard gates.""" gate_classes = [cls for name, cls in allGates.__dict__.items() if isinstance(cls, type)] - theta = pi/2 + theta = pi / 2 for cls in gate_classes: with self.subTest(i=cls): sig = signature(cls) numargs = len([param for param in sig.parameters.values() - if param.kind == param.POSITIONAL_ONLY - or (param.kind == param.POSITIONAL_OR_KEYWORD - and param.default is param.empty)]) + if param.kind == param.POSITIONAL_ONLY or + (param.kind == param.POSITIONAL_OR_KEYWORD and + param.default is param.empty)]) args = [theta] * numargs if cls in [MSGate, Barrier]: args[0] = 2 @@ -516,28 +802,103 @@ def test_relative_phase_toffoli_gates(self, num_ctrl_qubits): # compare simulated matrix with the matrix representation provided by the class self.assertTrue(matrix_equal(simulated_mat, repr_mat)) - -def _compute_control_matrix(base_mat, num_ctrl_qubits): - """ - Compute the controlled version of the input matrix with qiskit ordering. - - Args: - base_mat (ndarray): unitary to be controlled - num_ctrl_qubits (int): number of controls for new unitary - - Returns: - ndarray: controlled version of base matrix. - """ - num_target = int(np.log2(base_mat.shape[0])) - ctrl_dim = 2**num_ctrl_qubits - ctrl_grnd = np.repeat([[1], [0]], [1, ctrl_dim-1]) - full_mat_dim = ctrl_dim * base_mat.shape[0] - full_mat = np.zeros((full_mat_dim, full_mat_dim), dtype=base_mat.dtype) - ctrl_proj = np.diag(np.roll(ctrl_grnd, ctrl_dim - 1)) - full_mat = (np.kron(np.eye(2**num_target), - np.eye(ctrl_dim) - ctrl_proj) - + np.kron(base_mat, ctrl_proj)) - return full_mat + def test_open_controlled_gate(self): + """ + Test controlled gates with control on '0' + """ + base_gate = XGate() + base_mat = base_gate.to_matrix() + num_ctrl_qubits = 3 + + ctrl_state = 5 + cgate = base_gate.control(num_ctrl_qubits, ctrl_state=ctrl_state) + target_mat = _compute_control_matrix(base_mat, num_ctrl_qubits, ctrl_state=ctrl_state) + self.assertEqual(Operator(cgate), Operator(target_mat)) + + ctrl_state = None + cgate = base_gate.control(num_ctrl_qubits, ctrl_state=ctrl_state) + target_mat = _compute_control_matrix(base_mat, num_ctrl_qubits, ctrl_state=ctrl_state) + self.assertEqual(Operator(cgate), Operator(target_mat)) + + ctrl_state = 0 + cgate = base_gate.control(num_ctrl_qubits, ctrl_state=ctrl_state) + target_mat = _compute_control_matrix(base_mat, num_ctrl_qubits, ctrl_state=ctrl_state) + self.assertEqual(Operator(cgate), Operator(target_mat)) + + ctrl_state = 7 + cgate = base_gate.control(num_ctrl_qubits, ctrl_state=ctrl_state) + target_mat = _compute_control_matrix(base_mat, num_ctrl_qubits, ctrl_state=ctrl_state) + self.assertEqual(Operator(cgate), Operator(target_mat)) + + ctrl_state = '110' + cgate = base_gate.control(num_ctrl_qubits, ctrl_state=ctrl_state) + target_mat = _compute_control_matrix(base_mat, num_ctrl_qubits, ctrl_state=ctrl_state) + self.assertEqual(Operator(cgate), Operator(target_mat)) + + def test_open_controlled_gate_raises(self): + """ + Test controlled gates with open controls raises if ctrl_state isn't allowed. + """ + base_gate = XGate() + num_ctrl_qubits = 3 + with self.assertRaises(CircuitError): + base_gate.control(num_ctrl_qubits, ctrl_state=-1) + with self.assertRaises(CircuitError): + base_gate.control(num_ctrl_qubits, ctrl_state=2**num_ctrl_qubits) + with self.assertRaises(CircuitError): + base_gate.control(num_ctrl_qubits, ctrl_state='201') + + def test_deprecated_gates(self): + """Test types of the deprecated gate classes.""" + + import qiskit.extensions.standard.i as i + import qiskit.extensions.standard.rx as rx + import qiskit.extensions.standard.ry as ry + import qiskit.extensions.standard.rz as rz + import qiskit.extensions.standard.swap as swap + import qiskit.extensions.standard.u1 as u1 + import qiskit.extensions.standard.u3 as u3 + import qiskit.extensions.standard.x as x + import qiskit.extensions.standard.y as y + import qiskit.extensions.standard.z as z + + import qiskit.extensions.quantum_initializer.diagonal as diagonal + import qiskit.extensions.quantum_initializer.uc as uc + import qiskit.extensions.quantum_initializer.uc_pauli_rot as uc_pauli_rot + import qiskit.extensions.quantum_initializer.ucrx as ucrx + import qiskit.extensions.quantum_initializer.ucry as ucry + import qiskit.extensions.quantum_initializer.ucrz as ucrz + + theta, phi, lam = 0.1, 0.2, 0.3 + diag = [1, 1] + unitary = np.array([[1, 0], [0, 1]]) + renamend_gates = [ + (diagonal.DiagonalGate, diagonal.DiagGate, [diag]), + (i.IGate, i.IdGate, []), + (rx.CRXGate, rx.CrxGate, [theta]), + (ry.CRYGate, ry.CryGate, [theta]), + (rz.CRZGate, rz.CrzGate, [theta]), + (swap.CSwapGate, swap.FredkinGate, []), + (u1.CU1Gate, u1.Cu1Gate, [theta]), + (u3.CU3Gate, u3.Cu3Gate, [theta, phi, lam]), + (uc.UCGate, uc.UCG, [[unitary]]), + (uc_pauli_rot.UCPauliRotGate, uc_pauli_rot.UCRot, [[theta], 'X']), + (ucrx.UCRXGate, ucrx.UCX, [[theta]]), + (ucry.UCRYGate, ucry.UCY, [[theta]]), + (ucrz.UCRZGate, ucrz.UCZ, [[theta]]), + (x.CXGate, x.CnotGate, []), + (x.CCXGate, x.ToffoliGate, []), + (y.CYGate, y.CyGate, []), + (z.CZGate, z.CzGate, []), + ] + for new, old, params in renamend_gates: + with self.subTest(msg='comparing {} and {}'.format(new, old)): + # assert old gate class derives from new + self.assertTrue(issubclass(old, new)) + + # assert both are representatives of one another + self.assertTrue(isinstance(new(*params), old)) + self.assertTrue(isinstance(old(*params), new)) if __name__ == '__main__': diff --git a/test/python/circuit/test_diag.py b/test/python/circuit/test_diagonal_gate.py similarity index 95% rename from test/python/circuit/test_diag.py rename to test/python/circuit/test_diagonal_gate.py index 4b0bfbefcb3e..6be0aebb9f7b 100644 --- a/test/python/circuit/test_diag.py +++ b/test/python/circuit/test_diagonal_gate.py @@ -25,10 +25,11 @@ from qiskit.quantum_info.operators.predicates import matrix_equal -class TestDiagGate(QiskitTestCase): +class TestDiagonalGate(QiskitTestCase): """ Diagonal gate tests. """ + def test_diag_gate(self): """Test diagonal gates.""" for phases in [[0, 0], [0, 0.8], [0, 0, 1, 1], [0, 1, 0.5, 1], @@ -40,7 +41,7 @@ def test_diag_gate(self): num_qubits = int(np.log2(len(diag))) q = QuantumRegister(num_qubits) qc = QuantumCircuit(q) - qc.diag_gate(diag, q[0:num_qubits]) + qc.diagonal(diag, q[0:num_qubits]) # Decompose the gate qc = transpile(qc, basis_gates=['u1', 'u3', 'u2', 'cx', 'id']) # Simulate the decomposed gate diff --git a/test/python/circuit/test_extensions_standard.py b/test/python/circuit/test_extensions_standard.py index ecf2020d3c69..bb60bc667078 100644 --- a/test/python/circuit/test_extensions_standard.py +++ b/test/python/circuit/test_extensions_standard.py @@ -29,10 +29,10 @@ from qiskit.quantum_info.operators.predicates import matrix_equal, is_unitary_matrix from qiskit.extensions.standard import ( - HGate, CHGate, IdGate, RGate, RXGate, CrxGate, RYGate, CryGate, RZGate, - CrzGate, SGate, SdgGate, FredkinGate, TGate, TdgGate, U1Gate, Cu1Gate, - U2Gate, U3Gate, Cu3Gate, XGate, CnotGate, ToffoliGate, YGate, CyGate, - ZGate, CzGate + HGate, CHGate, IGate, RGate, RXGate, CRXGate, RYGate, CRYGate, RZGate, + CRZGate, SGate, SdgGate, CSwapGate, TGate, TdgGate, U1Gate, CU1Gate, + U2Gate, U3Gate, CU3Gate, XGate, CXGate, CCXGate, YGate, CYGate, + ZGate, CZGate ) @@ -383,33 +383,33 @@ def test_h_reg_inv(self): self.assertEqual(instruction_set.qargs[1], [self.qr[1]]) def test_iden(self): - self.circuit.iden(self.qr[1]) + self.circuit.i(self.qr[1]) op, _, _ = self.circuit[0] self.assertEqual(op.name, 'id') self.assertEqual(op.params, []) def test_iden_wires(self): - self.circuit.iden(1) + self.circuit.i(1) op, _, _ = self.circuit[0] self.assertEqual(op.name, 'id') self.assertEqual(op.params, []) def test_iden_invalid(self): qc = self.circuit - self.assertRaises(CircuitError, qc.iden, self.cr[0]) - self.assertRaises(CircuitError, qc.iden, self.cr) - self.assertRaises(CircuitError, qc.iden, (self.qr, 3)) - self.assertRaises(CircuitError, qc.iden, (self.qr, 'a')) - self.assertRaises(CircuitError, qc.iden, .0) + self.assertRaises(CircuitError, qc.i, self.cr[0]) + self.assertRaises(CircuitError, qc.i, self.cr) + self.assertRaises(CircuitError, qc.i, (self.qr, 3)) + self.assertRaises(CircuitError, qc.i, (self.qr, 'a')) + self.assertRaises(CircuitError, qc.i, .0) def test_iden_reg(self): - instruction_set = self.circuit.iden(self.qr) + instruction_set = self.circuit.i(self.qr) self.assertEqual(len(instruction_set.instructions), 3) self.assertEqual(instruction_set.instructions[0].name, 'id') self.assertEqual(instruction_set.qargs[1], [self.qr[1]]) def test_iden_reg_inv(self): - instruction_set = self.circuit.iden(self.qr).inverse() + instruction_set = self.circuit.i(self.qr).inverse() self.assertEqual(len(instruction_set.instructions), 3) self.assertEqual(instruction_set.instructions[0].name, 'id') self.assertEqual(instruction_set.qargs[1], [self.qr[1]]) @@ -1390,18 +1390,18 @@ class TestQubitKeywordArgRenaming(QiskitTestCase): @data( ('h', HGate, 0, [('q', 'qubit')]), ('ch', CHGate, 0, [('ctl', 'control_qubit'), ('tgt', 'target_qubit')]), - ('iden', IdGate, 0, [('q', 'qubit')]), + ('id', IGate, 0, [('q', 'qubit')]), ('r', RGate, 2, [('q', 'qubit')]), ('rx', RXGate, 1, [('q', 'qubit')]), - ('crx', CrxGate, 1, [('ctl', 'control_qubit'), ('tgt', 'target_qubit')]), + ('crx', CRXGate, 1, [('ctl', 'control_qubit'), ('tgt', 'target_qubit')]), ('ry', RYGate, 1, [('q', 'qubit')]), - ('cry', CryGate, 1, [('ctl', 'control_qubit'), ('tgt', 'target_qubit')]), + ('cry', CRYGate, 1, [('ctl', 'control_qubit'), ('tgt', 'target_qubit')]), ('rz', RZGate, 1, [('q', 'qubit')]), - ('crz', CrzGate, 1, [('ctl', 'control_qubit'), ('tgt', 'target_qubit')]), + ('crz', CRZGate, 1, [('ctl', 'control_qubit'), ('tgt', 'target_qubit')]), ('s', SGate, 0, [('q', 'qubit')]), ('sdg', SdgGate, 0, [('q', 'qubit')]), ('cswap', - FredkinGate, + CSwapGate, 0, [('ctl', 'control_qubit'), ('tgt1', 'target_qubit1'), @@ -1409,22 +1409,22 @@ class TestQubitKeywordArgRenaming(QiskitTestCase): ('t', TGate, 0, [('q', 'qubit')]), ('tdg', TdgGate, 0, [('q', 'qubit')]), ('u1', U1Gate, 1, [('q', 'qubit')]), - ('cu1', Cu1Gate, 1, [('ctl', 'control_qubit'), ('tgt', 'target_qubit')]), + ('cu1', CU1Gate, 1, [('ctl', 'control_qubit'), ('tgt', 'target_qubit')]), ('u2', U2Gate, 2, [('q', 'qubit')]), ('u3', U3Gate, 3, [('q', 'qubit')]), - ('cu3', Cu3Gate, 3, [('ctl', 'control_qubit'), ('tgt', 'target_qubit')]), + ('cu3', CU3Gate, 3, [('ctl', 'control_qubit'), ('tgt', 'target_qubit')]), ('x', XGate, 0, [('q', 'qubit')]), - ('cx', CnotGate, 0, [('ctl', 'control_qubit'), ('tgt', 'target_qubit')]), + ('cx', CXGate, 0, [('ctl', 'control_qubit'), ('tgt', 'target_qubit')]), ('ccx', - ToffoliGate, + CCXGate, 0, [('ctl1', 'control_qubit1'), ('ctl2', 'control_qubit2'), ('tgt', 'target_qubit')]), ('y', YGate, 0, [('q', 'qubit')]), - ('cy', CyGate, 0, [('ctl', 'control_qubit'), ('tgt', 'target_qubit')]), + ('cy', CYGate, 0, [('ctl', 'control_qubit'), ('tgt', 'target_qubit')]), ('z', ZGate, 0, [('q', 'qubit')]), - ('cz', CzGate, 0, [('ctl', 'control_qubit'), ('tgt', 'target_qubit')]), + ('cz', CZGate, 0, [('ctl', 'control_qubit'), ('tgt', 'target_qubit')]), ) # pylint: enable=bad-whitespace def test_kwarg_deprecation(self, instr_name, inst_class, n_params, kwarg_map): diff --git a/test/python/circuit/test_gate_power.py b/test/python/circuit/test_gate_power.py index 442f28c18b4c..483da2177af1 100644 --- a/test/python/circuit/test_gate_power.py +++ b/test/python/circuit/test_gate_power.py @@ -18,12 +18,10 @@ import unittest from ddt import ddt, data from numpy import array, eye -from numpy.testing import assert_allclose, assert_array_almost_equal -from numpy.linalg import matrix_power from qiskit.test import QiskitTestCase -from qiskit.extensions import SGate, UnitaryGate, CnotGate -from qiskit.circuit import Gate +from qiskit.extensions import SGate, UnitaryGate, CXGate +from qiskit.circuit import Gate, QuantumCircuit from qiskit.quantum_info.operators import Operator @@ -39,7 +37,7 @@ def test_sgate_int(self, n): self.assertEqual(result.label, 's^%s' % n) self.assertIsInstance(result, UnitaryGate) - assert_array_almost_equal(result.to_matrix(), matrix_power(SGate().to_matrix(), n)) + self.assertEqual(Operator(result), Operator(SGate()).power(n)) results = { 1.5: array([[1, 0], [0, -0.70710678 + 0.70710678j]], dtype=complex), @@ -57,7 +55,7 @@ def test_sgate_float(self, n): expected = self.results[n] self.assertEqual(result.label, 's^%s' % n) self.assertIsInstance(result, UnitaryGate) - assert_array_almost_equal(result.to_matrix(), expected) + self.assertEqual(Operator(result), Operator(expected)) @ddt @@ -76,11 +74,11 @@ class TestPowerIntCX(QiskitTestCase): def test_cx_int(self, n): """Test CX.power() method. """ - result = CnotGate().power(n) + result = CXGate().power(n) self.assertEqual(result.label, 'cx^' + str(n)) self.assertIsInstance(result, UnitaryGate) - assert_array_almost_equal(result.to_matrix(), self.results[n]) + self.assertEqual(Operator(result), Operator(self.results[n])) class TestGateSqrt(QiskitTestCase): @@ -89,15 +87,15 @@ class TestGateSqrt(QiskitTestCase): def test_unitary_sqrt(self): """Test UnitaryGate.power(1/2) method. """ - expected = array([[0.70710678118, 0.70710678118], - [-0.70710678118, 0.70710678118]], dtype=complex) + expected = array([[0.5+0.5j, 0.5+0.5j], + [-0.5-0.5j, 0.5+0.5j]], dtype=complex) result = UnitaryGate([[0, 1j], [-1j, 0]]).power(1 / 2) self.assertEqual(result.label, 'unitary^0.5') self.assertEqual(len(result.definition), 1) self.assertIsInstance(result, Gate) - assert_allclose(result.definition[0][0].to_matrix(), expected) + self.assertEqual(Operator(result), Operator(expected)) def test_standard_sqrt(self): """Test standard Gate.power(1/2) method. @@ -110,7 +108,25 @@ def test_standard_sqrt(self): self.assertEqual(result.label, 's^0.5') self.assertEqual(len(result.definition), 1) self.assertIsInstance(result, Gate) - assert_allclose(result.definition[0][0].to_matrix(), expected) + self.assertEqual(Operator(result), Operator(expected)) + + def test_composite_sqrt(self): + """Test composite Gate.power(1/2) method. + """ + circ = QuantumCircuit(1, name='my_gate') + circ.rz(0.1, 0) + circ.rx(0.2, 0) + gate = circ.to_gate() + + expected = array([[0.99874948+6.25390559e-05j, 0.00374609-4.98542083e-02j], + [-0.00124974-4.99791301e-02j, 0.99750443+4.98542083e-02j]]) + + result = gate.power(1 / 2) + + self.assertEqual(result.label, 'my_gate^0.5') + self.assertEqual(len(result.definition), 1) + self.assertIsInstance(result, Gate) + self.assertEqual(Operator(result), Operator(expected)) @ddt @@ -125,8 +141,8 @@ def test_direct_root(self, degree): self.assertEqual(result.label, 's^' + str(1 / degree)) self.assertEqual(len(result.definition), 1) self.assertIsInstance(result, Gate) - assert_allclose(matrix_power(result.definition[0][0].to_matrix(), degree), - SGate().to_matrix()) + self.assertEqual(Operator(result).power(degree), + Operator(SGate())) @data(2.1, 3.2, 4.3, 5.4, 6.5, 7.6, 8.7, 9.8, 0.2) def test_float_gt_one(self, exponent): @@ -137,7 +153,7 @@ def test_float_gt_one(self, exponent): self.assertEqual(len(result.definition), 1) self.assertIsInstance(result, Gate) # SGate().to_matrix() is diagonal so `**` is equivalent. - assert_allclose(SGate().to_matrix() ** exponent, result.definition[0][0].to_matrix()) + self.assertEqual(Operator(SGate().to_matrix() ** exponent), Operator(result)) def test_minus_zero_two(self, exponent=-0.2): """Test Sgate^(-0.2)""" @@ -146,9 +162,9 @@ def test_minus_zero_two(self, exponent=-0.2): self.assertEqual(result.label, 's^' + str(exponent)) self.assertEqual(len(result.definition), 1) self.assertIsInstance(result, Gate) - assert_allclose(array([[1, 0], - [0, 0.95105652 - 0.30901699j]], dtype=complex), - result.definition[0][0].to_matrix()) + self.assertEqual(Operator(array([[1, 0], + [0, 0.95105652 - 0.30901699j]], dtype=complex)), + Operator(result)) @ddt @@ -163,17 +179,17 @@ def test_invariant1_int(self, n): self.assertEqual(result.label, 'unitary^' + str(n)) self.assertEqual(len(result.definition), 1) self.assertIsInstance(result, Gate) - assert_allclose(SGate().to_matrix(), result.definition[0][0].to_matrix()) + self.assertTrue(Operator(SGate()), Operator(result)) @data(-3, -2, -1, 1, 2, 3) def test_invariant2(self, n): """Test op^(n) * op^(-n) == I """ - result = Operator(SGate().power(n)) @ Operator(SGate().power(-n)) + result = Operator(SGate()).power(n) @ Operator(SGate()).power(-n) expected = Operator(eye(2)) self.assertEqual(len(result.data), len(expected.data)) - assert_array_almost_equal(result.data, expected.data) + self.assertEqual(result, expected) if __name__ == '__main__': diff --git a/test/python/circuit/test_instruction_repeat.py b/test/python/circuit/test_instruction_repeat.py index 57595110c6a9..97fa9413c8be 100644 --- a/test/python/circuit/test_instruction_repeat.py +++ b/test/python/circuit/test_instruction_repeat.py @@ -21,7 +21,7 @@ from qiskit.transpiler import PassManager from qiskit import QuantumRegister, QuantumCircuit, ClassicalRegister from qiskit.test import QiskitTestCase -from qiskit.extensions import SGate, U3Gate, UnitaryGate, CnotGate +from qiskit.extensions import SGate, U3Gate, UnitaryGate, CXGate from qiskit.circuit import Instruction, Measure, Gate from qiskit.transpiler.passes import Unroller from qiskit.circuit.exceptions import CircuitError @@ -68,11 +68,11 @@ def test_standard_2Q_two(self): """ qr = QuantumRegister(2, 'qr') expected_circ = QuantumCircuit(qr) - expected_circ.append(CnotGate(), [qr[0], qr[1]]) - expected_circ.append(CnotGate(), [qr[0], qr[1]]) + expected_circ.append(CXGate(), [qr[0], qr[1]]) + expected_circ.append(CXGate(), [qr[0], qr[1]]) expected = expected_circ.to_instruction() - result = CnotGate().repeat(2) + result = CXGate().repeat(2) self.assertEqual(result.name, 'cx*2') self.assertEqual(result.definition, expected.definition) @@ -83,10 +83,10 @@ def test_standard_2Q_one(self): """ qr = QuantumRegister(2, 'qr') expected_circ = QuantumCircuit(qr) - expected_circ.append(CnotGate(), [qr[0], qr[1]]) + expected_circ.append(CXGate(), [qr[0], qr[1]]) expected = expected_circ.to_instruction() - result = CnotGate().repeat(1) + result = CXGate().repeat(1) self.assertEqual(result.name, 'cx*1') self.assertEqual(result.definition, expected.definition) @@ -205,7 +205,7 @@ def test_standard_2Q_minus_one(self): """Test standard 2Q gate.repeat(-1) method. Raises, since n<1. """ with self.assertRaises(CircuitError) as context: - _ = CnotGate().repeat(-1) + _ = CXGate().repeat(-1) self.assertIn('strictly positive integer', str(context.exception)) def test_measure_minus_one(self): @@ -219,7 +219,7 @@ def test_standard_2Q_zero(self): """Test standard 2Q gate.repeat(0) method. Raises, since n<1. """ with self.assertRaises(CircuitError) as context: - _ = CnotGate().repeat(0) + _ = CXGate().repeat(0) self.assertIn('strictly positive integer', str(context.exception)) diff --git a/test/python/circuit/test_instructions.py b/test/python/circuit/test_instructions.py index 4be158ef2de2..93195cc40e39 100644 --- a/test/python/circuit/test_instructions.py +++ b/test/python/circuit/test_instructions.py @@ -24,7 +24,7 @@ from qiskit.circuit import QuantumCircuit from qiskit.circuit import QuantumRegister, ClassicalRegister from qiskit.extensions.standard.h import HGate -from qiskit.extensions.standard.x import CnotGate +from qiskit.extensions.standard.x import CXGate from qiskit.extensions.standard.s import SGate from qiskit.extensions.standard.t import TGate from qiskit.test import QiskitTestCase @@ -53,7 +53,7 @@ def test_instructions_equal(self): self.assertFalse(uop1 == uop3) self.assertTrue(HGate() == HGate()) - self.assertFalse(HGate() == CnotGate()) + self.assertFalse(HGate() == CXGate()) self.assertFalse(hop1 == HGate()) eop1 = Instruction('kraus', 1, 0, [np.array([[1, 0], [0, 1]])]) @@ -117,7 +117,7 @@ def circuit_instruction_circuit_roundtrip(self): circ1 = QuantumCircuit(q, c, name='circuit1') circ1.h(q[0]) circ1.crz(0.1, q[0], q[1]) - circ1.iden(q[1]) + circ1.i(q[1]) circ1.u3(0.1, 0.2, -0.2, q[0]) circ1.barrier() circ1.measure(q, c) @@ -165,13 +165,13 @@ def test_mirror_gate(self): circ = QuantumCircuit(q, c, name='circ') circ.h(q[0]) circ.crz(0.1, q[0], q[1]) - circ.iden(q[1]) + circ.i(q[1]) circ.u3(0.1, 0.2, -0.2, q[0]) gate = circ.to_instruction() circ = QuantumCircuit(q, c, name='circ') circ.u3(0.1, 0.2, -0.2, q[0]) - circ.iden(q[1]) + circ.i(q[1]) circ.crz(0.1, q[0], q[1]) circ.h(q[0]) gate_mirror = circ.to_instruction() @@ -229,12 +229,12 @@ def test_inverse_composite_gate(self): circ = QuantumCircuit(q, name='circ') circ.h(q[0]) circ.crz(0.1, q[0], q[1]) - circ.iden(q[1]) + circ.i(q[1]) circ.u3(0.1, 0.2, -0.2, q[0]) gate = circ.to_instruction() circ = QuantumCircuit(q, name='circ') circ.u3(-0.1, 0.2, -0.2, q[0]) - circ.iden(q[1]) + circ.i(q[1]) circ.crz(-0.1, q[0], q[1]) circ.h(q[0]) gate_inverse = circ.to_instruction() @@ -252,12 +252,12 @@ def test_inverse_recursive(self): qr1 = QuantumRegister(4) circ1 = QuantumCircuit(qr1, name='circuit1') circ1.cu1(-0.1, qr1[0], qr1[2]) - circ1.iden(qr1[1]) + circ1.i(qr1[1]) circ1.append(little_gate, [qr1[2], qr1[3]]) circ_inv = QuantumCircuit(qr1, name='circ1_dg') circ_inv.append(little_gate.inverse(), [qr1[2], qr1[3]]) - circ_inv.iden(qr1[1]) + circ_inv.i(qr1[1]) circ_inv.cu1(0.1, qr1[0], qr1[2]) self.assertEqual(circ1.inverse(), circ_inv) diff --git a/test/python/circuit/test_isometry.py b/test/python/circuit/test_isometry.py index 50476ba7ec8e..ace679e093fd 100644 --- a/test/python/circuit/test_isometry.py +++ b/test/python/circuit/test_isometry.py @@ -48,7 +48,7 @@ def test_isometry(self): n = num_q_input + num_q_ancilla_for_output q = QuantumRegister(n) qc = QuantumCircuit(q) - qc.iso(iso, q[:num_q_input], q[num_q_input:]) + qc.isometry(iso, q[:num_q_input], q[num_q_input:]) # Verify the circuit can be decomposed self.assertIsInstance(qc.decompose(), QuantumCircuit) diff --git a/test/python/circuit/test_library.py b/test/python/circuit/test_library.py new file mode 100644 index 000000000000..225c2801dc83 --- /dev/null +++ b/test/python/circuit/test_library.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Test library of quantum circuits.""" + +from qiskit.test import QiskitTestCase +from qiskit.circuit import QuantumCircuit +from qiskit.circuit.exceptions import CircuitError +from qiskit.circuit.library import Permutation, XOR, InnerProduct + + +class TestBooleanLogicLibrary(QiskitTestCase): + """Test library of boolean logic quantum circuits.""" + + def test_permutation(self): + """Test permutation circuit.""" + circuit = Permutation(n_qubits=4, pattern=[1, 0, 3, 2]) + expected = QuantumCircuit(4) + expected.swap(0, 1) + expected.swap(2, 3) + self.assertEqual(circuit, expected) + + def test_permutation_bad(self): + """Test that [0,..,n-1] permutation is required (no -1 for last element)""" + self.assertRaises(CircuitError, Permutation, 4, [1, 0, -1, 2]) + + def test_xor(self): + """Test xor circuit.""" + circuit = XOR(n_qubits=3, amount=4) + expected = QuantumCircuit(3) + expected.x(2) + self.assertEqual(circuit, expected) + + def test_inner_product(self): + """Test inner product circuit.""" + circuit = InnerProduct(n_qubits=3) + expected = QuantumCircuit(*circuit.qregs) + expected.cz(0, 3) + expected.cz(1, 4) + expected.cz(2, 5) + self.assertEqual(circuit, expected) diff --git a/test/python/circuit/test_parameters.py b/test/python/circuit/test_parameters.py index d940c16a4245..9e0b63162c3e 100644 --- a/test/python/circuit/test_parameters.py +++ b/test/python/circuit/test_parameters.py @@ -438,8 +438,8 @@ def test_repeated_gates_to_dag_and_back(self): bound_test_qc = test_qc.bind_parameters({theta: 1}) self.assertEqual(len(bound_test_qc.parameters), 0) - @data('gate', 'instruction') - def test_decompose_propagates_bound_parameters(self, target_type): + @combine(target_type=['gate', 'instruction'], parameter_type=['numbers', 'parameters']) + def test_decompose_propagates_bound_parameters(self, target_type, parameter_type): """Verify bind-before-decompose preserves bound values.""" # ref: https://github.com/Qiskit/qiskit-terra/issues/2482 theta = Parameter('th') @@ -454,21 +454,35 @@ def test_decompose_propagates_bound_parameters(self, target_type): qc2 = QuantumCircuit(1) qc2.append(inst, [0]) - bound_qc2 = qc2.bind_parameters({theta: 0.5}) - - self.assertEqual(qc2.parameters, {theta}) - self.assertEqual(bound_qc2.parameters, set()) + if parameter_type == 'numbers': + bound_qc2 = qc2.bind_parameters({theta: 0.5}) + expected_parameters = set() + expected_qc2 = QuantumCircuit(1) + expected_qc2.rx(0.5, 0) + else: + phi = Parameter('ph') + bound_qc2 = qc2.copy() + bound_qc2._substitute_parameters({theta: phi}) + expected_parameters = {phi} + expected_qc2 = QuantumCircuit(1) + expected_qc2.rx(phi, 0) decomposed_qc2 = bound_qc2.decompose() - expected_qc2 = QuantumCircuit(1) - expected_qc2.rx(0.5, 0) + with self.subTest(msg='testing parameters of initial circuit'): + self.assertEqual(qc2.parameters, {theta}) - self.assertEqual(decomposed_qc2.parameters, set()) - self.assertEqual(decomposed_qc2, expected_qc2) + with self.subTest(msg='testing parameters of bound circuit'): + self.assertEqual(bound_qc2.parameters, expected_parameters) - @data('gate', 'instruction') - def test_decompose_propagates_deeply_bound_parameters(self, target_type): + with self.subTest(msg='testing parameters of deep decomposed bound circuit'): + self.assertEqual(decomposed_qc2.parameters, expected_parameters) + + with self.subTest(msg='testing deep decomposed circuit'): + self.assertEqual(decomposed_qc2, expected_qc2) + + @combine(target_type=['gate', 'instruction'], parameter_type=['numbers', 'parameters']) + def test_decompose_propagates_deeply_bound_parameters(self, target_type, parameter_type): """Verify bind-before-decompose preserves deeply bound values.""" theta = Parameter('th') qc1 = QuantumCircuit(1) @@ -490,19 +504,32 @@ def test_decompose_propagates_deeply_bound_parameters(self, target_type): qc3 = QuantumCircuit(1) qc3.append(inst, [0]) - bound_qc3 = qc3.bind_parameters({theta: 0.5}) + if parameter_type == 'numbers': + bound_qc3 = qc3.bind_parameters({theta: 0.5}) + expected_parameters = set() + expected_qc3 = QuantumCircuit(1) + expected_qc3.rx(0.5, 0) + else: + phi = Parameter('ph') + bound_qc3 = qc3.copy() + bound_qc3._substitute_parameters({theta: phi}) + expected_parameters = {phi} + expected_qc3 = QuantumCircuit(1) + expected_qc3.rx(phi, 0) + + deep_decomposed_qc3 = bound_qc3.decompose().decompose() - self.assertEqual(qc3.parameters, {theta}) - self.assertEqual(bound_qc3.parameters, set()) + with self.subTest(msg='testing parameters of initial circuit'): + self.assertEqual(qc3.parameters, {theta}) - decomposed_qc3 = bound_qc3.decompose() - deep_decomposed_qc3 = decomposed_qc3.decompose() + with self.subTest(msg='testing parameters of bound circuit'): + self.assertEqual(bound_qc3.parameters, expected_parameters) - expected_qc3 = QuantumCircuit(1) - expected_qc3.rx(0.5, 0) + with self.subTest(msg='testing parameters of deep decomposed bound circuit'): + self.assertEqual(deep_decomposed_qc3.parameters, expected_parameters) - self.assertEqual(deep_decomposed_qc3.parameters, set()) - self.assertEqual(deep_decomposed_qc3, expected_qc3) + with self.subTest(msg='testing deep decomposed circuit'): + self.assertEqual(deep_decomposed_qc3, expected_qc3) @data('gate', 'instruction') def test_executing_parameterized_instruction_bound_early(self, target_type): diff --git a/test/python/circuit/test_ucg.py b/test/python/circuit/test_uc.py similarity index 90% rename from test/python/circuit/test_ucg.py rename to test/python/circuit/test_uc.py index 240c8009c167..46748513e276 100644 --- a/test/python/circuit/test_ucg.py +++ b/test/python/circuit/test_uc.py @@ -26,7 +26,7 @@ import numpy as np from scipy.linalg import block_diag -from qiskit.extensions.quantum_initializer.ucg import UCG +from qiskit.extensions.quantum_initializer.uc import UCGate from qiskit import QuantumCircuit, QuantumRegister, BasicAer, execute from qiskit.test import QiskitTestCase @@ -45,8 +45,8 @@ up_to_diagonal_list = [True, False] -class TestUCG(QiskitTestCase): - """Qiskit UCG tests.""" +class TestUCGate(QiskitTestCase): + """Qiskit UCGate tests.""" def test_ucg(self): """Test uniformly controlled gates.""" @@ -55,7 +55,7 @@ def test_ucg(self): num_con = int(np.log2(len(squs))) q = QuantumRegister(num_con + 1) qc = QuantumCircuit(q) - qc.ucg(squs, q[1:], q[0], up_to_diagonal=up_to_diagonal) + qc.uc(squs, q[1:], q[0], up_to_diagonal=up_to_diagonal) # Decompose the gate qc = transpile(qc, basis_gates=['u1', 'u3', 'u2', 'cx', 'id']) # Simulate the decomposed gate @@ -63,7 +63,7 @@ def test_ucg(self): result = execute(qc, simulator).result() unitary = result.get_unitary(qc) if up_to_diagonal: - ucg = UCG(squs, up_to_diagonal=up_to_diagonal) + ucg = UCGate(squs, up_to_diagonal=up_to_diagonal) unitary = np.dot(np.diagflat(ucg._get_diagonal()), unitary) unitary_desired = _get_ucg_matrix(squs) self.assertTrue(matrix_equal(unitary_desired, unitary, ignore_phase=True)) diff --git a/test/python/circuit/test_ucx_y_z.py b/test/python/circuit/test_ucx_y_z.py index 801ad0fbf017..a5c3e3c8a111 100644 --- a/test/python/circuit/test_ucx_y_z.py +++ b/test/python/circuit/test_ucx_y_z.py @@ -36,8 +36,8 @@ rot_axis_list = ["X", "Y", "Z"] -class TestUCXYZ(QiskitTestCase): - """Qiskit tests for UCX, UCY and UCZ rotations gates.""" +class TestUCRXYZ(QiskitTestCase): + """Qiskit tests for UCRXGate, UCRYGate and UCRZGate rotations gates.""" def test_ucy(self): """ Test the decomposition of uniformly controlled rotations.""" @@ -47,11 +47,11 @@ def test_ucy(self): q = QuantumRegister(num_contr + 1) qc = QuantumCircuit(q) if rot_axis == "X": - qc.ucx(angles, q[1:num_contr + 1], q[0]) + qc.ucrx(angles, q[1:num_contr + 1], q[0]) elif rot_axis == "Y": - qc.ucy(angles, q[1:num_contr + 1], q[0]) + qc.ucry(angles, q[1:num_contr + 1], q[0]) else: - qc.ucz(angles, q[1:num_contr + 1], q[0]) + qc.ucrz(angles, q[1:num_contr + 1], q[0]) # Decompose the gate qc = transpile(qc, basis_gates=['u1', 'u3', 'u2', 'cx', 'id']) # Simulate the decomposed gate diff --git a/test/python/compiler/test_assembler.py b/test/python/compiler/test_assembler.py index 8b84c13d6143..b10f5bcdd315 100644 --- a/test/python/compiler/test_assembler.py +++ b/test/python/compiler/test_assembler.py @@ -804,8 +804,10 @@ def test_single_and_deprecated_acquire_styles(self): # The Qobj IDs will be different n_qobj = assemble(new_style_schedule, backend) n_qobj.qobj_id = None + n_qobj.experiments[0].header.name = None d_qobj = assemble(deprecated_style_schedule, backend) d_qobj.qobj_id = None + d_qobj.experiments[0].header.name = None self.assertEqual(n_qobj, d_qobj) assembled_acquire = n_qobj.experiments[0].instructions[0] diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index 11db9b58a534..d050ec94cca0 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -25,7 +25,7 @@ from qiskit.compiler import transpile from qiskit.converters import circuit_to_dag from qiskit.dagcircuit.exceptions import DAGCircuitError -from qiskit.extensions.standard import CnotGate +from qiskit.extensions.standard import CXGate from qiskit.test import QiskitTestCase, Path from qiskit.test.mock import FakeMelbourne, FakeRueschlikon from qiskit.transpiler import Layout, CouplingMap @@ -118,7 +118,7 @@ def test_transpile_non_adjacent_layout(self): initial_layout=initial_layout) for gate, qargs, _ in new_circuit.data: - if isinstance(gate, CnotGate): + if isinstance(gate, CXGate): self.assertIn([x.index for x in qargs], coupling_map) def test_transpile_qft_grid(self): @@ -138,7 +138,7 @@ def test_transpile_qft_grid(self): coupling_map=coupling_map) for gate, qargs, _ in new_circuit.data: - if isinstance(gate, CnotGate): + if isinstance(gate, CXGate): self.assertIn([x.index for x in qargs], coupling_map) def test_already_mapped_1(self): diff --git a/test/python/pickles/TestsBasicSwap_a_cx_to_map.pickle b/test/python/pickles/TestsBasicSwap_a_cx_to_map.pickle index 2dc1af8cf87721d5df256e54ba6b750397c03828..402b5218e2f8d10a531d66cb5fc13150de27697e 100644 GIT binary patch literal 1563 zcma)6X?N5{5Zv{_Arml%1I8ha%_Xi)LK2SLU}6ly3qBIWA>@&E@F>=5w^mYEcyHb# zpZvG!9_`q%$pgMv-tOtD?&_L(I~*g)H zH!IsA$|xyv9Q5j2p*S_6Ltf3_%lKW<6YU)10S^27DG+zERz+~6Sw;bl+NO6Y)068- zUdSFvLx2vBQQH$5pS%7(La|vK|9H6(dfCF^1OZKIoOB2C08>WfJb5GWg*RM@YKAx! z;7eCA^R&iSt~#zJO*Y9|vX2?BRz_;VL#XN(0w>KGeNIi#?(XcyjZ=~5Ni0%pKxdBq?%)r?pVzoc zCis+^0m`;37pVT8#(nF`7@i_CDze}MSIsNNU({%_$?jF&GO>pmk8Etqc;rj5i6zBD zFKet2sM;~;>h_?2s%aYZFSSS&k4=TI@#MXVr`r|J)GSpzR}ZNokmf>wSg#F@lxen< zYP&|0Oryi)8v`2FuB!nXV;YY2G;$lWG~;Y&^JYnUe8NIRsYJ1RrCSF~-g(4t9G_q1 h;|@v!|3~9h^?+6weT~<7RK1C*c-<)rydkGG-T?`B^EChf literal 1518 zcma)6X?N2w5KLN5bCjdpP+CrNh5NoKP{1_YU=CwsM^pyK>BKe!-qU;VsehYYIclKr zfG=^htDVu#j`VFfMv{L_ibkAthqyEHKEmB z&0orZv!W~7IYt7k@t0E|?qsbBac#4V0<5#ATp&eP&LnvuyQBpHI#^HV9M#z1`pXE# zX0h?#;tWDZdMp-?Y6~-rx&v{h6da3x*DGT(- GX^kI%3+GP& diff --git a/test/python/pickles/TestsBasicSwap_handle_measurement.pickle b/test/python/pickles/TestsBasicSwap_handle_measurement.pickle index 27eb154a547de1d8d5f76b86df5a4a50a8b765bb..be2ff4084ad4462b0e4646ab34a0900d8eb44520 100644 GIT binary patch literal 1975 zcma)7_kY_&9JQTJg$@E`hf)ee3Bl~W3DAP5=>o(M?n%DpDYZ2;)4%POmkFqQpSEyoSG3n_LkC(a*3uz@`3n&0kJ?rH84GzpJtn<%7f; zyUc5r!5EM;$R#dkOp`;7rMsw+Jr~cmO7;p)_DY4TTyi=z*`YFxCR-ZfYTD^FiQ^=W z?HZcu6k!ntxOQB}bv+x%#W>65fSbk{I=GH)Pb*yS`rFu^UBzA-*niWF1ddvBxPcAr zQ@GLP3hU5-{igj{d`99Xuj;d@>dgUeacP>g?->QxrM4S%hYFPT^Lw zO0!<8!sZojv)DutN`_~>20v$Hfc6Nt+tB|_LF61P1#}1dzEk0@jWs3Qy|$+Jn3@K- zm*z!)`$o9`V)nCBV#PVekJ8Hn?Bzj)hw5JRROpR(m>soj^B$=jJ?hdt&2cV|jqo^W zp+BMUWX%HYSWA40iNty%))(y|t(FCar|XQGPM#U-Y6D)c+S=5k@`es)lBXp=K-1jWH`m*Z5|L$bBoaFm&VoJ^9CX?K z2?fC-{6Cz^%Ic(q%#Rd$c2Xip;sGHZi<2&$AZ>q9fwagY*0vr7jl~LYSxgUu#+DQk zi>)07F+!hoAyS29c0{%0S5}h;&2jPrT4Y9M7`Z~Rf${b#|g2LC@^|%M$xTdn-DtxEu7b@4f+TSbu zU@_a39~FMGnC;5X3cpy)y57x7EUyvC$8R?mVIuMCdI6bxMsG0d&S8D+=-=adyMy02 eh~E|dC@;0r#-9p*WnphXHb^9NF1bFoT literal 1902 zcma)7=bzg|5Vd`&x`Z_P5ki3DlmkrfodXgOF=>D(ido5<8xdK)XGz2fEL>=&h2GoW z%Ir#OoiC6dUu0``-Y#DpJemqE z*Wb`+G*Z!*nDKY>UlM6Ak=?!&SuvD-ndaEwZ{&q;Pvim{18j0_E;-6&n#F@OYiBv* z#IV;MIZNpvKPYnPU^6G#qOjHVH)(=IgffBC@n;R4JQb&L&S=bbXvS}GFdAW-@9>|r z=yy?sah_qj*P4W)#Dp&M8vbU@u_A_|pJ7LUo&HvhAEdpKa#zRSQSn6iFtKF2y{6e1 zicEHwStRycx){H#Hko~dt7yB|B-TT$;cD8|DZ(NQaLuGe>&7vZ z%W;;=A*rLL%Ha1g)lEVvL zgP-#ySh)av)at)7;@fd06KCbY@1Wnu4f}Ug$v6pBM zX=gm8@N~U%$`eZyPdww2Ynt@z7|)TGcTnL_4VzN+e5L9I3-ZQz(WpAC(5XRFs$QB_ zbwuH4y>m*{v5BhVtcm8h>=R?WY};#HS6u?m;qo$Y@CvJbRl(OT(+9BHfY++AHT5xg zUCWr|lMvtyn&Zj6xt=#lM3%*oNUY(VC4I0R1k8U@q00vQKbVT8J_T%L+W4GNz5LW3j^9Hn!I2VvN(I8<8mVIT1}2pVq|> z$mQZ!NHPKjF;vJdLgY1~Fh+lemKkxzAVvyjFG8HF5%1DG=Xj4|Mkr|xF}SH2-Vg8r z=}Tc%;k;(pZyWKUYtHsZ3Loq8R;%NyZ-Y-1KD9A>2aFXyvoU)Ie6H|?jg_qtCUs;| ze;ME_(v!HL@U>1onZY-%>Fl=(-)Z=TDz)zR_X3`cq5cSbhfwUZa+#Zd znVFR~At}GY7t4CH^WM(v+tIalggCz(7riLXqLQ8{MOu`j`qf#hURSE9gT~40Xlk@v ze_x}~NX1B^?a$NSIE{-~B!fg;8J7k2`TGeR3`HTZKfnRk>d0v!(>xxhc`q+SIuzNk zr<~POqL2;_68j;I!>)gTvl9`@1WwsJdVO5_r^cn#I|o3uvZR^Op# zT8;)N!??&X?=`ofC^66xui+nL#)-(pD96zNxA}(&h{oxVsvv>e`(+s54!h|+&B^3O zoEI{q?hv7aJ89bs8h5$=A!4yn9Q*Hd)9dq=4tEpLagBT2x*p(!>2n@mmAKcdn`(-< zFTnk-V&#s;1Fkxzj+>Up=}=Cw=rzhvEtq&$<3STwi(ZRR&RNoU$YSj>lmst&%`y}L z9-iS5yYYWJtb9vMoOSxCj?5*tlNyh@%IT}QzN!v`&A|)tn40%miaM^S%a5z7uTQwN zwT=wXo8ifuX-%CKzmj>1WS-Vo-YwJOL!086e~LUyBK{EyunD3fORN)~BPSRm8Ia6Z zNIXv%pL|F@8f-A~!p-+nZ|6nY&Py6E@7@kuaEe!GH%(jetEOZ&(`&Bks4f-#`V4Pa z5o@fG6nU9R=S=;?nZqO`}q!$Q_q?GU9~`G_ugLKC=q!cM##TK2+R@k2F3eA+@V`f2a6QRELWHRGp=a&rHVW8o>=2U+iQI zRF^WoRBMzWOh%{?-H{X4g zIDM;@=5Bwjp)JPJjD4f=t;OuAT+_I2F)MwPoLEgwN#k!&h%k}(Zm)sNSmgyK?wsQ( z%dZ!Bxr6UX@CS__s}Wvt{G>6aw*!NXp^EX)%DtC8J1!1Z?8K- AmH+?% literal 1759 zcma)7>7Ud@5S@cF$f2$XsN;_75j;j+@IFviISf0nfEtfDb~4R&Ad{JwV;Rs^@ESz@ zpRMYocXaWGewj>Ob-mYJ^}2Gi9U;!I#zilRv#6veN|6@jsD5?Us@Ih&>Y#D-7MdC@ z*WcD?G*U5=X#4xJ6$^=f|-($GsWg{|9v8S)vev?3j`+SF+=9dOkwl*hQuRYT}5+X4~%oDSs_i(aD))q;t4HJ&hWwdl17<(wsrCoR@4LrL(W*DOO3;Hep&wj2Mq z?&R0R#JNa6Rgaz_Hdo`QtDL?vjbJD6B0Q`1dM!oOR#f5VR8`aGUD{Sh26$nH-o13D z&PqBiDv=jS?>-4y7S>y-Y5}y9!DavwsTh4S7J^RH_3*+yq7_ZaM)#(9ge-FV%a#s!OQ+Kn74X1J)h z5SKJQAtCjT!+$zI{Aa2|hxgSgWqfWjzR>vcu8gnd8G-6j#@Ff`WeiNlHyYxujBuV2 zsU^x7sx``x(p)A`QFB}lu&(%ODvg*Kj@Uw6sczs^jf9VzrQJ(p>Eu8sA!sr5XE9LtD&t z<$H}EEM}z-kQ1x9C~3eA3K1p}H@6zd3{GBP;?8-Vqx^b_mpiycfVI8@QwKyGt`=iLPLW=F5WD gR^8H!$-&0*1b=A!=~A>o(I!P(s_V7OGAzU1Uu&8+UH||9 diff --git a/test/python/pickles/TestsLookaheadSwap_a_cx_to_map.pickle b/test/python/pickles/TestsLookaheadSwap_a_cx_to_map.pickle index 1528506f8ca058bb6e67c49c1ae29ff97c77aebd..d574203cdd92ef8000d57b3ee9336f4cac710481 100644 GIT binary patch literal 2188 zcma)8`I{3(5Y6s#4(P6kh-VkxvPoW7zk08_s(TL(1&HF^Q8E@p?I7h(q^gyq&HSgcHTyZ9 ztvV>JI)nkjpzF;kl}atu3^C*_;;*V2bgIc@wW+2s$D7MYwXPC{xjyE(Gm|t!6{qdc z*_3}`KGQBBEOhmdH=p=M@cl?)T1_jXg)ZI%vaGSPCvSz>vBsel!xrg5|{#M$MctcJ$nIX=#H^-?Xl&m)}g z>ScPlS&v%vu!GU^Kw2}a7Z5Hqt9rCNnASpuN6V$OrufSe9~aG388lQJM}cZsBhI)K z)xpJ_Y9-+kCi_2oa+Ya#m-2p>u*$j%aG7RPnyp;!>XlmfuOM70VqMlK1_T=;TxGF> zfiAALLc&fGwqiclcq~p-tFGGhG3~5hr;^aYHB53X;X2owCq2F(&wU%%TyU{DGk6T?6a~t7yiCLRf zTCXwa9fUhA+VjCC|oG3 zSkDg9nveT3c^e32Yhfc{Q^7*T)fm>9nncZ5HcU;9k<3Lm%6_*fE=d zjx+9M+V5wo2M7-qwGXoPpaZX8`9rMS8|DR>_aJFE?9e~V6E1g52J)FFAs%5&hP=kd zS)CB%(b@Ina(tHa7@=B}BLmpMCVtsiY=E5*7AU zau;E@#R`?&B?HQ5)##$B!v_dY34L#R54tAi5aDTwIgoe#euF+kc-Epl zdg=Nuo-_WQCv^Yi?}cf9hmG_uuzD9W6Vw_bU z$?iW^BK{8hc*CeXMtD=g#NTnkTLph_Xa3$Hyel!sb9%X(-XpwkF`+*;P2a@_VfLQz zMH=HnA0HX@CkP)G)PItx|CI2VP@U*g|2g3ciwV6r`jYUK#j=7blO`}}zV`8radDFH zZNbHNnTziUKgjlzeJ*|^{A4jnBhG#%{9-YiW<&B+XF8X(x2H z=AT&1v`Yv}T|Me8=Dm3U9M6$7X|sy779}y3m5Y5SXc%ZYL$aqjtvae1<0Ky^drKJz z+O2w&MC}&FDn?_q9`1=+88luoBB44{F;4NZVy?!#5S?%;ij%OzmEdd+PGzMl38%T< z5>8=PvFg9Ko4W3>bU2-fRuj$;%SEOF)|i^c(M*Ul%cEHhjl;8iobBqBT5_L5IM>yy z^lGynwd!FP6XjxBGppwj&Nr)iqCAq;LWU>Gg|w#l%L*SC%vBjQR2)ZvYFH!Aq!iV` zg`8?F;UXscKYMbPS$7xnewVP$x(jfLW>cE2TVQ>bZ`}uTur#f^%hA_G*m5Y;M$5FujF;sxK48y z>E-2u=2p30k5x3E|9Vru4TKvcy&TR)!i_9nud%{!a!t(5gj*zLV^&(d!JxMiZnJ1# zIJ=U{ulI3#4|mL`Ki;RNot^wAb1}*J??hw2r&TA6z7AxueBFiAIG59k(P)X`39=1Xv{+~DM2!p2M8qe{egVSH8D>U4ob{{ysu{rdWi6hMf>#9*F8LI{5?l_{$KuHnDy5) z$`2D>6dD<G4E!s`~}tom?vrLhw6*Yoj)QF)Z`ri6*V zV}!Sc{Jov|dx!9@#2m}%<+^!~@V>=_{^%@y4}SF+7PD!_c_gw|GUOMnny6YM#IJU~?0*2}8I=ID?rf7+RetW0a0kCJ`R{~3 avimjT_>=HgT+3eJhT7jw6a3BhQt2NxqPxlf diff --git a/test/python/pickles/TestsLookaheadSwap_handle_measurement.pickle b/test/python/pickles/TestsLookaheadSwap_handle_measurement.pickle index 8b3dc7f56807c521e3a55e78ae128997fef9f9c6..3fbe935ea78dd1bf0eba22bd3eed97f621df052a 100644 GIT binary patch literal 3415 zcma)9cbwEz5WYeU{ScIXAn1yEb`-lF3c7MqENh8pb}#Z4_V(^evT~?Hv9NZqH^kn1 z?}EMeUa)sT?7hvr7>p*gy> zCWp86Vbns!dh<|8s7jveZazSVR7VapHaUTV zjy5_aOfI)AaPxk)CGXey0k3nsrn)w(J~iG6Tnn8!MHfQXM5>c4&pBx~2dg2~O>gEN zlFerWUW;xGA)!?{zKib33RRt|Vos(FJyI6`$QPO$QAlS4A3c**B`A~AP_JY~sUd7GkKQ)=q`J!bmV8r~p{60#E7~U$csa9K-(+PdMuY4iaIpqsvN+9J8m9@;@4wwf zX8W_*H3@4al+~cRHK@==CGXUR0jg)RQn60O+6JnchC*6b6plB|#~>SnOBG$Ed!9Qj z>j$pK?PISNhH#dlgkgzP7v9-KQEMdr`*J-DQ_OZ4&Kr#&texOmi%4Lk9t1yon2S-# zsN=f6>)2Q~p@u5Sogl22P_=4=)}PHcxosGotSqE;bbZ1GI;ut|t0HVeLY={?3u%|( z(a8$FCgkT}8ygj|@v8Ep;j<=&Lug?W&b=vNGm$I3jtJO1s%)iHb_=eol%5MZM(f#< zu$AN%vttQc^J+Cxu{*_{wn=EtwuJ43Gd7YXF1IJ_U@-BvrDpC(*vX(JCv4MU#uytr zuWBNbb9_IWadM_jR=o_Yg?dggj<5@F_J7tyyR`RiS6)A!Fv0XLgWZg&-4jZ7PZIVJ zspBJ4f=whe7)(r!)66{ylMEU&Rm5Z??w?M%vL=Lwd`P}a#-Qm{k ztBaW;>?e7pHTx3|h_&Xxur)T}AmQv^)|y7b!3Hy9=Md5cld(&ML&kQbHT&Ag6w&lw zX8f5>t2LD`p(U3!9YUBEw7GdP9>ZcQ+Il(?hL}2!Z|fkw@|MbG$%!(WrZg@N!JuD+%?2X1z*yO;|6nJ~CE@E>9li;>^ey2#;jE0hv_h}^p74V~ zO&xwD{A93_sOT&%xAC)f_zU4zAxRy6Bm5q#!yn;U{FCsPaDIzYauvJ^{_G*IlCLHr zEs;o-j#QULM9?qoZBU|w$>>=O@$m2;m#~H5I@bukhW~4X`>n0fk$m;j U;r|dhXI4ug@J2DjU+Hy!17D;@Q2+n{ literal 3336 zcma)9cbpSd5WYfPdhbV543@Kj*b66uMozl1tmh`T+y-(vzTHF)WCRqA4aBa9y(1Rv z4MedQ5W6Dw3Sw8pKJ#{8l11g`Kku?L-<$cqnR&bK=9DLp^rj{=m5HRA$nsxgt#l?^ zA3Pdk{l_ZbYoKKCT$B>ZBC#eVB_(OA-bQ(>BmZ=)bZyG6skbdJ>)Q2pI)f&$rtGMx zwK5i(nrIf$x$MS_o%WIqX|K}D@IK3}t!y-^(hZrBR>n5aoRhR5w2Z`>Nr03Uw^J~x zW35y;dvY?J%j_Wo%3|qIJyRAL(p;4YKiM#zUKES>BXLDO1~MNvyfVm}a?F zy@z%t+Q(YT{)Tj|AF@MrtX&W%k#SQxSjT9o0>&-RuBl^^=(KGyF<(h%R?>ygHR2f6 zj-p%2(HqgtX%j7Vn2*C8x;q`JvvE}{SZ=dAG0LC$F&ugF=;}BV5SyHnnr-OiKL_|kW28i zwYVvcYn#aAkagspc@klIo~F~fm|dgPo8MwiCr92%GYB&a$k5y9WTiuTGs|a5gj2MV z*@QVEC3Ahusf5!6GrLI1Ji_VPCQ3*tIfIbX){v5U(x+J_&dlK~M^5+IgmWZU=tQ3@ zI{XtoU&UKMI8U=%NH{;l?gF3Pg@lU)voK({K<&JkaEZ3+chjYW%d{=ej>*gD%r|kl zIw6Y)R|rT>-D1L(k}`kZuJX^@)r4yVvp7h(NbS6qaGkbFN;!qs6K>G9P|6%`bY%E$ zBHUc)RcPjJ31;qAU*{6SZ4ye3(NejbPU^71#Lr6M zX2LH5k~(Z5{2HpmZ~oc)o$!ZXwgf4q3V#y*(l(AMvD^~A=8#*;tM&+$&4uqp&Mjl7 z?cdD!o9(&f{A{{ScoorZQ~p72GhU0OyUmq>`|Y++fR^gHVv4{6PU19_vsKtBJ7S**)ykrR<@cJ;Tmm+T32s**omiY}@n4)W470e5<`){KU1#vRBezI#{x4Q38A AQvd(} diff --git a/test/python/pickles/TestsLookaheadSwap_initial_layout.pickle b/test/python/pickles/TestsLookaheadSwap_initial_layout.pickle index 90be0799bc1062ea7c0c34a7f0100a4ed5a5b47c..5ce235a2a1281285682655fef6ac8159b4378e86 100644 GIT binary patch literal 2888 zcma)8XP6X45WN5b0|FuS|Xse12Kbx%*P&LHcL&jvM_Y#|f!H$pcb zgpKiEYee+7Hd?h%(RT(a2~~EoO+`gT-fi?yo$Sm%v-xb0b#tklJEfAs)l?bkTBk|EEj0Mp$U(QYImTWT zyoRhFcm+0tt6A8XnRX{^Vkg^kMze|@|NXeOcBDy%O*v6d!e%15l2ZY_w5@)2vWLwR zv4>jW77n(wRd*$|w<2t9s~)PSrk~B%doAdls0`D3bsNIAdR6sKRK?hKgzXJh9i}~o zdnYQww97xbIoM%s=9!%9``L_}GvcgTG1|h8T)q#%=4AipnK;WD#eMnteuSNj;tY0H zEL5?)U2N4yN&Xtbu0pmSa}-g6?MB$$U?NJe{)9aYRwk;5JqgIAVPg{fTa~HHtqXp+_>O1YxMHv^o_@k_{|@7{;oqjH)_T6{Z~=6;U2e z7-3|MB#bJPHQH9gwS+N*qa|u&+%lQcV+cuu8Ck~?jx$)RES^I(#&d9d5htuqREt%c zjPy91{zO8mTu&A2$+Y04QZ+8CNp|4{IX^N%A!iPeG@lsyJkTJ|M&KdCm>e2uZhNXh zkoxuWiRpMuXDp$ioQ`bh7O2vR$!c$9X2wPCjwj@dyN!f=nY&H48n4}LCP1PZOWZ9G ze1jQx143x9GItBI;m0|cP{c$f=WGjMlGL(dc9k;ElVzT}>XDwJ>rN$*VKaXFvIS2!WjlDWhWy&#X(W;>6wHxB}mSRS%kBsWa|lWwyk5% zA)G5Qv*KMeQ={h*&Nrx(loR3t!i5HFEh)RGh>Nrj7ZWa7$A?ST_%K^5oI|)w(#Y1H zOSrtuhby8^UP-u0V&=xAWCvVLxW-^yRn5`at|eS&&{k5+SbUi6;Cjt`9^nQF5+CLh zZY=ZRrpSkz3Aaeh{J5kjzLjvB!KCCoo$YqQ9R@8cS;U>%hr0-Ouj9i#YkXLsnJ*;V zD`~`sMTGmxe7HaI;Q_*f60@kphldCc8%#)vH;)h=HJG$(Vf0R7cH+YV2ajpyiwTcQ znE0@S@I;vpPewjGMR;0bmX!GL4B=UWNlEeMIl}V>lahRlXl4@y*%GU{R7|f`Alkk?oq@*Z+oA8doqGq_dbViqc*TH*Q?J~mq zWokc&)P6|#NFHBSqV{9LCk8WWKP7x-Fexn#d`|enV5W3eKBl5;lgC$_RN$s_9=v*hn+3AgYSC;yu8P4wQ3a(qkp&QC|zviMT?y-6Ds+`QkM&3T2I zXvrF?uZv`{=KLNtXC>heiCIz7 zoK=KB4Q86Nn(&vw#G&P_^T6-;mHb9rm8e#$LwzGlP3Yf@zX|`?YB^I`$q}nKVs)ZA J4AWt{=3hGy)vEvi literal 2843 zcma)8XP6UJ6x~7{5D*a&8&(F?Rj~_VSrs&}R5g|{PLg3?B%9rnnSlkl3KoLBg4h+Y zR}i}*_J+M;@4X<3xatq@eUq0>;QP=&DQE87d(OG{m6^3EWcsE)lhea zK_Q(%vLZm7!h@nk3wOqmK<8^j@1|(Tlv- znDGNI&t`Bn3mY-hK7@_!co)uSo}%x6zpkwvZ_;5CPSlUEsYtHoR6u`itDl+fVY67# zL#=Rg2V2;xkCNJ361K8cU)4|3&*aixD+a`>!=!$?HDMe5R1JvL6tQgy+Zn7jOnM9t zh*gD2mw)tju>IQ1Q(4#dGbuM~#98%Xw1pkG{6K=u$^Oqf#VqZL2l4xZ2|F6aDeRJ*3wv^sy$E~T@lG-sSvToru}_2Q)llq*!oG^9O?8h|DITf)RL=&*b23!> zJdCivl$RzBCmg_%RDUDpKwHNgL^xPthDUa*VH&L`9AZ$@p+gCW8LT9R)k}wlIv7#F z$n|ORr@AfH1pbJW9nPGNAjE8C3G(>dA5XKrw;|Sv`Bu%i@ z7)^g7;Yf)ZSL~0BX<12oFlk zk}@A2B0Ov`Atl~CLU`0*(yqnPF~jV{heZw^)6AC=9+xojVHx3x3Ll<~e0YlRw8Shc z^Who7vj&rr;>~k}=M5$$m$sKI-~}%_-uVpk@uGv5w94g#mn&4h5~+NZ@S3DrUZ(PO z!W#xND&Hi$WiTlz%HJluW3Z?ht}dO@rQdb%o>sep@P38b41ZG629}3(M_v(&->n_4f1ZzZ^>l6d|mX&8l`W77JlGNKN5b5u8fJ6 zAye=(;TM|^RWWZR;a5>0Be;t2TSaqzkD9ZZ@Q1{#Dr?Rf!k-2+&1ob2WiWAQWoaJx q6kpBf-kMmgY76xxD=}qA3;rhjW2==+Wi>~v;fS_aZ5SrQWZl2yRme2} diff --git a/test/python/pickles/TestsStochasticSwap_a_cx_to_map.pickle b/test/python/pickles/TestsStochasticSwap_a_cx_to_map.pickle index 2cfd35193b9a3a8d9cc14859733ddd6864d49ba0..583c2fe3026c0edd8dd8c802dd32235a660940c7 100644 GIT binary patch literal 1393 zcma)6X;;%g6z#$qR1ihP4Z$tAR8bN4ttx1wvZ-;2agq*9j&0~|l4JFpIY&SBZ~JD_ zfS?Ec&?b4yeed15Z+;Yg1ksluF8M*zPw0t+g>h2vd>TvXXF2s6$W8r3UZP;yUAbH? zWOa_BJwUI_^;>Sd;nrCLU3NEF+$xJ1x*hbGSrOifc^Cy7VN{A@7FJoaS`xim0e>VufeEjbhpaaf-s0F6$6ztpdnbqvB%1ZqMfG(IoNOalfmBzt7(DZO5!;NkqL?>{8Ui$M zklG%RIAq#=gpzqN`tNj2^l}D=!vr)Yam4J*102;PN5OZFV^%RGRS$98!3k5S%Ht9z zO))CQv^NN=yoCuXmv~}C`zIw%X}_4T3W>+bF=6Esk2yHq#+jW>);WukCN~y3Pn@M1 z=Oj$kl%lN@fGI)Tib47jf#=0=MGR=xE{Ji0zPJ@OLEcn!->Fh*XVgHB=z(5laPPDW_PvPb?{6Jut<%7~u{cw()4U zS}mh&r)3^fnX<%_?PUr|wJkjTy~r~vVh@r-ZGj&*YuPHy(JH7tO^vjDF~>aFl=MAX zBzIj|p6}jIr=1tnjwSJOdpl|eTUh7_Lyuh1Bdeibsi7xn=*2dcgi_VA#0s67*rue_ zEhW9yN_rz^=t$n`3|r#e9~tkrGCqh&(%wfgOBoK=3jxwwgij7!F~+pT*Ot!?n3!hv zkYYsN1e{jdjhR>D@G=sz&EE{req&MH94dZQg&Xi`nyLg(ucZ`YO=3Or(wnBnzHKBi I1f72A7k!bsG5`Po literal 1348 zcma)6Ygf}i5G^gwpaP1BF9e@KrJ^FfpH)FCm6sYHF>ca@%`r{6O>(TBv*+li{%vPB z4OR4@AKJ~%?A)2TduP8FJowRPKQ4NH!%JvI!ooPIwYSDfx-F$%1G(uR=#t2rR(CF! z3t5e$U=7kIbG)V#*PR;MLbugJ7N^Q$h8`QeW|o9EV;)9+J&cM`%)%;bRExq`TwpOb z&_~ey5(B2ytD*zu@&HCfqdQf;;fH=qp%|=)g4IPw1ol`4tzlAgkhp#vVQ;x>+vEk> zw6C1A`V`<6Yp_~`AsfTi02#b`SWOW}Dv4`je^%uVDjIy#k7C{+E(p-T0jhIU;-G2u z6H4aA*uTRyv#S{#4iV6}#9_1D4{$`Y8u{Nij+P54i@J$pHjbM@WuA~YVTv&^uDyO( zK_q zC5oSwxSWyZ;ff%x1Tl2g6f;8M&q-WU9L%PzYumiU^~|=-3(>|}xRDj|H+e>1*p{(I zf2l}s68tTR+omuS14NRV19b>@h@m_&lv51dC5Du!?wMjjjB*?ITX?Wro~BU>>v5Xo zAtiYvQR*bgE4?=H_}3IqD1|jd4yAuDZUos7Jf$H}0(V)&olTl8EbN}yO4BY<+Gi5a zJ89LFHBoL~y>9GUt;UDdIiXk_Ra&!Ur3+7-!nzY0F0&j+kZEFx8ySI;Wv^V&(=MpE3fn u+18JNI-vc=vU(fTc3mAez^EHn!b_i`6r(D^BR73Z0`|3@#Mq!aF8&16$*!aT diff --git a/test/python/pickles/TestsStochasticSwap_handle_measurement.pickle b/test/python/pickles/TestsStochasticSwap_handle_measurement.pickle index 5491cda0cc0c620378c494806a53b0438bf9c291..c92e09a325e2fac6def689c206da7f47d84c1953 100644 GIT binary patch literal 1822 zcma)7>7Ud@5S^Lj%*x>k-iV9GX4iGR?+YAt!N|xZ#v_iMOtTfqWcN)H8PHY`C0r_h zGS!{*E@9ChUow;Gey?8FtLj{wiV$aKoAs)N@2CA1Yf zuD_|(YNcW%G3C$jUlQpsk%N&GSuvI)ndaE!Z{~%;P~-xe18i|^E;-3%n#C(=*3ELp ziSe*IaeDre$fbj=oMM~8cGusc;fV-k0;lg!8!~w&PUD=hnC@2_R!L+M4|pw_@;kgI znqY_T@Sn684N!z}o?)lgSqnvp3GMP){#H%7D#l`zVRwKB{p}jRk`Aj3Gkt$o%@gJ0 z#F9PawJR|u$S24p_HaytL)J1_){(vcf7<%65A$IkQFzoPr%%&;s*cfSrU@RSonD(* zC$YB2X{KLeSuV$H8lTX?lgzzO;VIYO#(Z`a`>nA5hMN{RVd?NR z6U{0daJj--8t{y(sGDg%@pXst6^A=e-s`=VX9xiI=SOdvy>whpPs9nRCCQ@ao2z5?;Hrrmvft z26%%OMSwR;yk+@qY3g=%j#h2$;BDr9N8w#v3f(>R2E14EwzXI9GcTK|&H8{AnEJ!J zd80&RSsaPPy5jV7S33BJ`41`_;wsz^re;~U>SM(pSm%3PBUNvpi6@x$lAgzb!jDZvV-kf)rOgdpS~M!75<{a>Dx7IT8!DG;mE_U_EwakE#7{=$ zS%sttY^WSmHjC}~!hNMs^c z16-q?@ON{|RJXO{TW*kxFp;=!JJF7;xz6z~s^6m7da1ISAdMO_i>5-$ z_182Sja2j{X8axeN+RtgvfGy;D~7T!(;RF3wY<>niCkc9fOW3jOOA4xX7M1++F8yx zG3>QR&QdzamqjietmjQOC~S27b($a%p-kX({7r^VUW?N>XEZi-XvS}GFdAXA@9;}n z^t&j+IM1-fYfVB?VnR=O4S&7nSQSIj&#*PXHh-hW57J&qxxM3Wt$3n*m{_tMUehFo zB9q+}7KxqzU5sB=@-$bnOJTQ5PKS)#CbN(53~lk6#CnJ|?4j+QA}qoH&rVwOh#f<@ z5@)#_vL9TDgXfrcufp@Lzk&JeD)w1X{|-0qp0#v%fr(}nUUa#WS{m?@aW;!@NxbY; z7Mkw865v&r_LAOvPT@6|_R*}dI8J+Vgn6%7gl5%M*l$*8-fLB{1%=mbY^De$hv&Tp zKj&nCH^w+%r9Wvn$T?B=v(0-SRCsf`pM*mX_w%snXMiKLAOgHK#@m+Lwxya<=TteZ z4&GtjcNN~#ZO{`^CE)#vx2b*lfO*+OZPbV4GWAD~@0AI<*!0M$k$4io4db(|x*fJs^t~c3BCz8$Xmz$KubM86% zQ~zz}-XxS35BNiy%{MdO%zT-dzk3N%eJ?EsNm?WoKT*lNtR~H?xLLn$)>Z-U^grkr zbp7yv=XtrDDD=W({4>qdGL_jVlMkjM)_e3taY+!Ha%)gjrfEPCh-{X??4u!%IxGZ1A$*^aH%&bk^x#3aQQW;8ig#IeXvWO=qXopv$O@Su=R6#d=k&7+wuJRV*W% z+QHlV3!f;dt3ruu%@aOzoHlsJ7HXwz9Plo&-n7VnEb4o7Vn_>4$@gi6sR#RclT2!z zCNgU^6PxxE34Fl(XAC~%8vjpB!?G*nI>&DqeAEh=;A3L_h*kTv&l-GcHQ1(0i_vK97=705Uf5}uA48HP7SXHQ=TDwKo z5nr=%T~^Ms%6-Gi*#X@2X@!)3wt*$9In=`JR)6 zi)^sll9WZ(u1vry<2KHLyrreWRmSX!#Jq5$EsI=xay?BsLoVmK!HvD;*j=6DW-~0V zMz}{CGCa;*);C7+ zu^5#y&J?m%w)UpDBV-+%UEHuY$-dsP?gA6;KR1}x2e0Os89>MN4QuXg(bATM%=IkI zR57Sc;;OsI0-BjhgQ`B=quKg4whiw4JaTKiuq!-%OF@tNl{-13?e!Vv1`m9;dTi~m OwM+e=S5!!`T4*A literal 1549 zcma)6Yj@K|5cLBHY#=-uAiP4{02M7b#pV5mKtmCjXIm8z?n>TdRb<&$D+R+jdk%cc z-)44YVF(=P2V2(6-a9+jcjjF^M5JCtSu>2%FsCPSo@9A%^vWI;uZu;Np>q5^sv0%N z+fb=g65bQ2dsFl?N}?>{aVO?)`gw*8-bM;`x;*39=wp*p))B)@Br57Bs;M%bba~os zN_J<7XM$mZ*f(ozalB0i9rHlMU~O;Ga*1VIq@Q52E$d#Df)ZOjM$aVgb&v;9rm)Se zehh`N4NbWfZ^BRvc*=VU+kNctwooAKCtbQWG45>Vfsb9~E!U}$ij_!ZA|*M9kl_cq z=d{Ld$Jk*4yj#$iY9 zkuz4~DCvqJ=G;mi$Z4BzX#8mNa?Y($)L`Z{j+C)_9taB0xz#-2K8~*8*!qk21Xp<~ z*!|HHKM{wcaok+gNI5!ypC#!nr|3hX{vvm_<)l^egq$Vnlk0iIm@5^9JT5h3i{>w4 zXcGT|#;)xU8ARAs&qEU>kdA8#ApvGOabJwa06CB8``x zYWzw6mHf;Sj01p=U*URj1j(UkmFv;^MvSF#J?K+3q&wRgmRt` zU3rlZLfByf`BGuo$BLZcHWJ#1^byGx_jXX1#TF5?4mueRVuAlk$;z!^*Niz}^X#G7 zI_CAMInMBk`j0hw#gZ#H5{c; + + + diff --git a/test/python/quantum_info/operators/channel/test_chi.py b/test/python/quantum_info/operators/channel/test_chi.py index 3f26a7682ef0..b7c80dd01a86 100644 --- a/test/python/quantum_info/operators/channel/test_chi.py +++ b/test/python/quantum_info/operators/channel/test_chi.py @@ -14,6 +14,7 @@ """Tests for Chi quantum channel representation class.""" +import copy import unittest import numpy as np from numpy.testing import assert_allclose @@ -65,11 +66,17 @@ def test_equal(self): def test_copy(self): """Test copy method""" - mat = np.eye(4) - orig = Chi(mat) - cpy = orig.copy() - cpy._data[0, 0] = 0.0 - self.assertFalse(cpy == orig) + mat = np.eye(2) + with self.subTest("Deep copy"): + orig = Chi(mat) + cpy = orig.copy() + cpy._data[0, 0] = 0.0 + self.assertFalse(cpy == orig) + with self.subTest("Shallow copy"): + orig = Chi(mat) + clone = copy.copy(orig) + clone._data[0, 0] = 0.0 + self.assertTrue(clone == orig) def test_is_cptp(self): """Test is_cptp method.""" @@ -246,52 +253,37 @@ def test_add(self): """Test add method.""" mat1 = 0.5 * self.chiI mat2 = 0.5 * self.depol_chi(1) - targ = Chi(mat1 + mat2) - chan1 = Chi(mat1) chan2 = Chi(mat2) - self.assertEqual(chan1.add(chan2), targ) - self.assertEqual(chan1 + chan2, targ) - def test_add_except(self): - """Test add method raises exceptions.""" - chan1 = Chi(self.chiI) - chan2 = Chi(np.eye(16)) - self.assertRaises(QiskitError, chan1.add, chan2) - self.assertRaises(QiskitError, chan1.add, 5) + targ = Chi(mat1 + mat2) + self.assertEqual(chan1._add(chan2), targ) + self.assertEqual(chan1 + chan2, targ) - def test_subtract(self): - """Test subtract method.""" - mat1 = 0.5 * self.chiI - mat2 = 0.5 * self.depol_chi(1) targ = Chi(mat1 - mat2) - - chan1 = Chi(mat1) - chan2 = Chi(mat2) - self.assertEqual(chan1.subtract(chan2), targ) self.assertEqual(chan1 - chan2, targ) - def test_subtract_except(self): - """Test subtract method raises exceptions.""" + def test_add_except(self): + """Test add method raises exceptions.""" chan1 = Chi(self.chiI) chan2 = Chi(np.eye(16)) - self.assertRaises(QiskitError, chan1.subtract, chan2) - self.assertRaises(QiskitError, chan1.subtract, 5) + self.assertRaises(QiskitError, chan1._add, chan2) + self.assertRaises(QiskitError, chan1._add, 5) def test_multiply(self): """Test multiply method.""" chan = Chi(self.chiI) val = 0.5 targ = Chi(val * self.chiI) - self.assertEqual(chan.multiply(val), targ) + self.assertEqual(chan._multiply(val), targ) self.assertEqual(val * chan, targ) def test_multiply_except(self): """Test multiply method raises exceptions.""" chan = Chi(self.chiI) - self.assertRaises(QiskitError, chan.multiply, 's') + self.assertRaises(QiskitError, chan._multiply, 's') self.assertRaises(QiskitError, chan.__rmul__, 's') - self.assertRaises(QiskitError, chan.multiply, chan) + self.assertRaises(QiskitError, chan._multiply, chan) self.assertRaises(QiskitError, chan.__rmul__, chan) def test_negate(self): diff --git a/test/python/quantum_info/operators/channel/test_choi.py b/test/python/quantum_info/operators/channel/test_choi.py index b6eb3248ab13..ef6d85d2db06 100644 --- a/test/python/quantum_info/operators/channel/test_choi.py +++ b/test/python/quantum_info/operators/channel/test_choi.py @@ -16,6 +16,7 @@ """Tests for Choi quantum channel representation class.""" +import copy import unittest import numpy as np from numpy.testing import assert_allclose @@ -73,11 +74,25 @@ def test_equal(self): def test_copy(self): """Test copy method""" + mat = np.eye(2) + with self.subTest("Deep copy"): + orig = Choi(mat) + cpy = orig.copy() + cpy._data[0, 0] = 0.0 + self.assertFalse(cpy == orig) + with self.subTest("Shallow copy"): + orig = Choi(mat) + clone = copy.copy(orig) + clone._data[0, 0] = 0.0 + self.assertTrue(clone == orig) + + def test_clone(self): + """Test clone method""" mat = np.eye(4) orig = Choi(mat) - cpy = orig.copy() - cpy._data[0, 0] = 0.0 - self.assertFalse(cpy == orig) + clone = copy.copy(orig) + clone._data[0, 0] = 0.0 + self.assertTrue(clone == orig) def test_is_cptp(self): """Test is_cptp method.""" @@ -328,52 +343,35 @@ def test_add(self): """Test add method.""" mat1 = 0.5 * self.choiI mat2 = 0.5 * self.depol_choi(1) - targ = Choi(mat1 + mat2) - chan1 = Choi(mat1) chan2 = Choi(mat2) - self.assertEqual(chan1.add(chan2), targ) + targ = Choi(mat1 + mat2) + self.assertEqual(chan1._add(chan2), targ) self.assertEqual(chan1 + chan2, targ) - - def test_add_except(self): - """Test add method raises exceptions.""" - chan1 = Choi(self.choiI) - chan2 = Choi(np.eye(8)) - self.assertRaises(QiskitError, chan1.add, chan2) - self.assertRaises(QiskitError, chan1.add, 5) - - def test_subtract(self): - """Test subtract method.""" - mat1 = 0.5 * self.choiI - mat2 = 0.5 * self.depol_choi(1) targ = Choi(mat1 - mat2) - - chan1 = Choi(mat1) - chan2 = Choi(mat2) - self.assertEqual(chan1.subtract(chan2), targ) self.assertEqual(chan1 - chan2, targ) - def test_subtract_except(self): - """Test subtract method raises exceptions.""" + def test_add_except(self): + """Test add method raises exceptions.""" chan1 = Choi(self.choiI) chan2 = Choi(np.eye(8)) - self.assertRaises(QiskitError, chan1.subtract, chan2) - self.assertRaises(QiskitError, chan1.subtract, 5) + self.assertRaises(QiskitError, chan1._add, chan2) + self.assertRaises(QiskitError, chan1._add, 5) def test_multiply(self): """Test multiply method.""" chan = Choi(self.choiI) val = 0.5 targ = Choi(val * self.choiI) - self.assertEqual(chan.multiply(val), targ) + self.assertEqual(chan._multiply(val), targ) self.assertEqual(val * chan, targ) def test_multiply_except(self): """Test multiply method raises exceptions.""" chan = Choi(self.choiI) - self.assertRaises(QiskitError, chan.multiply, 's') + self.assertRaises(QiskitError, chan._multiply, 's') self.assertRaises(QiskitError, chan.__rmul__, 's') - self.assertRaises(QiskitError, chan.multiply, chan) + self.assertRaises(QiskitError, chan._multiply, chan) self.assertRaises(QiskitError, chan.__rmul__, chan) def test_negate(self): diff --git a/test/python/quantum_info/operators/channel/test_kraus.py b/test/python/quantum_info/operators/channel/test_kraus.py index 121c66d3f960..406b62678dcf 100644 --- a/test/python/quantum_info/operators/channel/test_kraus.py +++ b/test/python/quantum_info/operators/channel/test_kraus.py @@ -14,13 +14,14 @@ """Tests for Kraus quantum channel representation class.""" +import copy import unittest import numpy as np from numpy.testing import assert_allclose from qiskit import QiskitError from qiskit.quantum_info.states import DensityMatrix -from qiskit.quantum_info.operators.channel import Kraus +from qiskit.quantum_info import Kraus from .channel_test_case import ChannelTestCase @@ -79,11 +80,25 @@ def test_equal(self): def test_copy(self): """Test copy method""" + mat = np.eye(2) + with self.subTest("Deep copy"): + orig = Kraus(mat) + cpy = orig.copy() + cpy._data[0][0][0, 0] = 0.0 + self.assertFalse(cpy == orig) + with self.subTest("Shallow copy"): + orig = Kraus(mat) + clone = copy.copy(orig) + clone._data[0][0][0, 0] = 0.0 + self.assertTrue(clone == orig) + + def test_clone(self): + """Test clone method""" mat = np.eye(4) orig = Kraus(mat) - cpy = orig.copy() - cpy._data[0][0][0, 0] = 0.0 - self.assertFalse(cpy == orig) + clone = copy.copy(orig) + clone._data[0][0][0, 0] = 0.0 + self.assertTrue(clone == orig) def test_is_cptp(self): """Test is_cptp method.""" @@ -316,7 +331,7 @@ def test_add(self): chan1 = Kraus(kraus1) chan2 = Kraus(kraus2) targ = (rho @ chan1) + (rho @ chan2) - chan = chan1.add(chan2) + chan = chan1._add(chan2) self.assertEqual(rho @ chan, targ) chan = chan1 + chan2 self.assertEqual(rho @ chan, targ) @@ -324,7 +339,7 @@ def test_add(self): # Random Single-Kraus maps chan = Kraus((kraus1, kraus2)) targ = 2 * (rho @ chan) - chan = chan.add(chan) + chan = chan._add(chan) self.assertEqual(rho @ chan, targ) def test_subtract(self): @@ -337,15 +352,13 @@ def test_subtract(self): chan1 = Kraus(kraus1) chan2 = Kraus(kraus2) targ = (rho @ chan1) - (rho @ chan2) - chan = chan1.subtract(chan2) - self.assertEqual(rho @ chan, targ) chan = chan1 - chan2 self.assertEqual(rho @ chan, targ) # Random Single-Kraus maps chan = Kraus((kraus1, kraus2)) targ = 0 * (rho @ chan) - chan = chan.subtract(chan) + chan = chan - chan self.assertEqual(rho @ chan, targ) def test_multiply(self): @@ -358,7 +371,7 @@ def test_multiply(self): # Single Kraus set chan1 = Kraus(kraus1) targ = val * (rho @ chan1) - chan = chan1.multiply(val) + chan = chan1._multiply(val) self.assertEqual(rho @ chan, targ) chan = val * chan1 self.assertEqual(rho @ chan, targ) @@ -366,7 +379,7 @@ def test_multiply(self): # Double Kraus set chan2 = Kraus((kraus1, kraus2)) targ = val * (rho @ chan2) - chan = chan2.multiply(val) + chan = chan2._multiply(val) self.assertEqual(rho @ chan, targ) chan = val * chan2 self.assertEqual(rho @ chan, targ) @@ -374,9 +387,9 @@ def test_multiply(self): def test_multiply_except(self): """Test multiply method raises exceptions.""" chan = Kraus(self.depol_kraus(1)) - self.assertRaises(QiskitError, chan.multiply, 's') + self.assertRaises(QiskitError, chan._multiply, 's') self.assertRaises(QiskitError, chan.__rmul__, 's') - self.assertRaises(QiskitError, chan.multiply, chan) + self.assertRaises(QiskitError, chan._multiply, chan) self.assertRaises(QiskitError, chan.__rmul__, chan) def test_negate(self): diff --git a/test/python/quantum_info/operators/channel/test_linearops.py b/test/python/quantum_info/operators/channel/test_linearops.py index 0ad620f92312..a6cb27f3bbde 100644 --- a/test/python/quantum_info/operators/channel/test_linearops.py +++ b/test/python/quantum_info/operators/channel/test_linearops.py @@ -49,7 +49,7 @@ def _compare_add_to_superop(self, rep, dim, samples, unitary=False): sop1 = self.rand_matrix(dim * dim, dim * dim) sop2 = self.rand_matrix(dim * dim, dim * dim) targ = SuperOp(sop1 + sop2) - channel = SuperOp(rep(SuperOp(sop1)).add(rep(SuperOp(sop2)))) + channel = SuperOp(rep(SuperOp(sop1))._add(rep(SuperOp(sop2)))) self.assertEqual(channel, targ) def _compare_subtract_to_superop(self, rep, dim, samples, unitary=False): @@ -64,7 +64,7 @@ def _compare_subtract_to_superop(self, rep, dim, samples, unitary=False): sop1 = self.rand_matrix(dim * dim, dim * dim) sop2 = self.rand_matrix(dim * dim, dim * dim) targ = SuperOp(sop1 - sop2) - channel = SuperOp(rep(SuperOp(sop1)).subtract(rep(SuperOp(sop2)))) + channel = SuperOp(rep(SuperOp(sop1))._add(rep(-SuperOp(sop2)))) self.assertEqual(channel, targ) def _compare_multiply_to_superop(self, rep, dim, samples, unitary=False): @@ -77,7 +77,7 @@ def _compare_multiply_to_superop(self, rep, dim, samples, unitary=False): sop1 = self.rand_matrix(dim * dim, dim * dim) val = 0.7 targ = SuperOp(val * sop1) - channel = SuperOp(rep(SuperOp(sop1)).multiply(val)) + channel = SuperOp(rep(SuperOp(sop1))._multiply(val)) self.assertEqual(channel, targ) def _compare_negate_to_superop(self, rep, dim, samples, unitary=False): @@ -97,14 +97,14 @@ def _check_add_other_reps(self, chan): current_rep = chan.__class__ other_reps = [Operator, Choi, SuperOp, Kraus, Stinespring, Chi, PTM] for rep in other_reps: - self.assertEqual(current_rep, chan.add(rep(chan)).__class__) + self.assertEqual(current_rep, chan._add(rep(chan)).__class__) def _check_subtract_other_reps(self, chan): """Check subtraction works for other representations""" current_rep = chan.__class__ other_reps = [Operator, Choi, SuperOp, Kraus, Stinespring, Chi, PTM] for rep in other_reps: - self.assertEqual(current_rep, chan.subtract(rep(chan)).__class__) + self.assertEqual(current_rep, chan._add(-rep(chan)).__class__) def test_choi_add(self): """Test addition of Choi matrices is correct.""" diff --git a/test/python/quantum_info/operators/channel/test_ptm.py b/test/python/quantum_info/operators/channel/test_ptm.py index fc8ff5254410..1b5cf89fec88 100644 --- a/test/python/quantum_info/operators/channel/test_ptm.py +++ b/test/python/quantum_info/operators/channel/test_ptm.py @@ -14,6 +14,7 @@ """Tests for PTM quantum channel representation class.""" +import copy import unittest import numpy as np from numpy.testing import assert_allclose @@ -66,10 +67,24 @@ def test_equal(self): def test_copy(self): """Test copy method""" mat = np.eye(4) + with self.subTest("Deep copy"): + orig = PTM(mat) + cpy = orig.copy() + cpy._data[0, 0] = 0.0 + self.assertFalse(cpy == orig) + with self.subTest("Shallow copy"): + orig = PTM(mat) + clone = copy.copy(orig) + clone._data[0, 0] = 0.0 + self.assertTrue(clone == orig) + + def test_clone(self): + """Test clone method""" + mat = np.eye(4) orig = PTM(mat) - cpy = orig.copy() - cpy._data[0, 0] = 0.0 - self.assertFalse(cpy == orig) + clone = copy.copy(orig) + clone._data[0, 0] = 0.0 + self.assertTrue(clone == orig) def test_is_cptp(self): """Test is_cptp method.""" @@ -231,52 +246,37 @@ def test_add(self): """Test add method.""" mat1 = 0.5 * self.ptmI mat2 = 0.5 * self.depol_ptm(1) - targ = PTM(mat1 + mat2) chan1 = PTM(mat1) chan2 = PTM(mat2) - self.assertEqual(chan1.add(chan2), targ) - self.assertEqual(chan1 + chan2, targ) - def test_add_except(self): - """Test add method raises exceptions.""" - chan1 = PTM(self.ptmI) - chan2 = PTM(np.eye(16)) - self.assertRaises(QiskitError, chan1.add, chan2) - self.assertRaises(QiskitError, chan1.add, 5) - - def test_subtract(self): - """Test subtract method.""" - mat1 = 0.5 * self.ptmI - mat2 = 0.5 * self.depol_ptm(1) + targ = PTM(mat1 + mat2) + self.assertEqual(chan1._add(chan2), targ) + self.assertEqual(chan1 + chan2, targ) targ = PTM(mat1 - mat2) - - chan1 = PTM(mat1) - chan2 = PTM(mat2) - self.assertEqual(chan1.subtract(chan2), targ) self.assertEqual(chan1 - chan2, targ) - def test_subtract_except(self): - """Test subtract method raises exceptions.""" + def test_add_except(self): + """Test add method raises exceptions.""" chan1 = PTM(self.ptmI) chan2 = PTM(np.eye(16)) - self.assertRaises(QiskitError, chan1.subtract, chan2) - self.assertRaises(QiskitError, chan1.subtract, 5) + self.assertRaises(QiskitError, chan1._add, chan2) + self.assertRaises(QiskitError, chan1._add, 5) def test_multiply(self): """Test multiply method.""" chan = PTM(self.ptmI) val = 0.5 targ = PTM(val * self.ptmI) - self.assertEqual(chan.multiply(val), targ) + self.assertEqual(chan._multiply(val), targ) self.assertEqual(val * chan, targ) def test_multiply_except(self): """Test multiply method raises exceptions.""" chan = PTM(self.ptmI) - self.assertRaises(QiskitError, chan.multiply, 's') + self.assertRaises(QiskitError, chan._multiply, 's') self.assertRaises(QiskitError, chan.__rmul__, 's') - self.assertRaises(QiskitError, chan.multiply, chan) + self.assertRaises(QiskitError, chan._multiply, chan) self.assertRaises(QiskitError, chan.__rmul__, chan) def test_negate(self): diff --git a/test/python/quantum_info/operators/channel/test_stinespring.py b/test/python/quantum_info/operators/channel/test_stinespring.py index 9e790753fc27..526afdb4ee0a 100644 --- a/test/python/quantum_info/operators/channel/test_stinespring.py +++ b/test/python/quantum_info/operators/channel/test_stinespring.py @@ -14,13 +14,14 @@ """Tests for Stinespring quantum channel representation class.""" +import copy import unittest import numpy as np from numpy.testing import assert_allclose from qiskit import QiskitError from qiskit.quantum_info.states import DensityMatrix -from qiskit.quantum_info.operators.channel import Stinespring +from qiskit.quantum_info import Stinespring from .channel_test_case import ChannelTestCase @@ -74,10 +75,24 @@ def test_equal(self): def test_copy(self): """Test copy method""" mat = np.eye(4) + with self.subTest("Deep copy"): + orig = Stinespring(mat) + cpy = orig.copy() + cpy._data[0][0, 0] = 0.0 + self.assertFalse(cpy == orig) + with self.subTest("Shallow copy"): + orig = Stinespring(mat) + clone = copy.copy(orig) + clone._data[0][0, 0] = 0.0 + self.assertTrue(clone == orig) + + def test_clone(self): + """Test clone method""" + mat = np.eye(4) orig = Stinespring(mat) - cpy = orig.copy() - cpy._data[0][0, 0] = 0.0 - self.assertFalse(cpy == orig) + clone = copy.copy(orig) + clone._data[0][0, 0] = 0.0 + self.assertTrue(clone == orig) def test_is_cptp(self): """Test is_cptp method.""" @@ -309,7 +324,7 @@ def test_add(self): chan1 = Stinespring(stine1, input_dims=2, output_dims=4) chan2 = Stinespring(stine2, input_dims=2, output_dims=4) rho_targ = (rho_init @ chan1) + (rho_init @ chan2) - chan = chan1.add(chan2) + chan = chan1._add(chan2) self.assertEqual(rho_init.evolve(chan), rho_targ) chan = chan1 + chan2 self.assertEqual(rho_init.evolve(chan), rho_targ) @@ -317,7 +332,7 @@ def test_add(self): # Random Single-Stinespring maps chan = Stinespring((stine1, stine2)) rho_targ = 2 * (rho_init @ chan) - chan = chan.add(chan) + chan = chan._add(chan) self.assertEqual(rho_init.evolve(chan), rho_targ) def test_subtract(self): @@ -330,15 +345,13 @@ def test_subtract(self): chan1 = Stinespring(stine1, input_dims=2, output_dims=4) chan2 = Stinespring(stine2, input_dims=2, output_dims=4) rho_targ = (rho_init @ chan1) - (rho_init @ chan2) - chan = chan1.subtract(chan2) - self.assertEqual(rho_init.evolve(chan), rho_targ) chan = chan1 - chan2 self.assertEqual(rho_init.evolve(chan), rho_targ) # Random Single-Stinespring maps chan = Stinespring((stine1, stine2)) rho_targ = 0 * (rho_init @ chan) - chan = chan.subtract(chan) + chan = chan - chan self.assertEqual(rho_init.evolve(chan), rho_targ) def test_multiply(self): @@ -351,7 +364,7 @@ def test_multiply(self): # Single Stinespring set chan1 = Stinespring(stine1, input_dims=2, output_dims=4) rho_targ = val * (rho_init @ chan1) - chan = chan1.multiply(val) + chan = chan1._multiply(val) self.assertEqual(rho_init.evolve(chan), rho_targ) chan = val * chan1 self.assertEqual(rho_init.evolve(chan), rho_targ) @@ -359,7 +372,7 @@ def test_multiply(self): # Double Stinespring set chan2 = Stinespring((stine1, stine2), input_dims=2, output_dims=4) rho_targ = val * (rho_init @ chan2) - chan = chan2.multiply(val) + chan = chan2._multiply(val) self.assertEqual(rho_init.evolve(chan), rho_targ) chan = val * chan2 self.assertEqual(rho_init.evolve(chan), rho_targ) @@ -367,9 +380,9 @@ def test_multiply(self): def test_multiply_except(self): """Test multiply method raises exceptions.""" chan = Stinespring(self.depol_stine(1)) - self.assertRaises(QiskitError, chan.multiply, 's') + self.assertRaises(QiskitError, chan._multiply, 's') self.assertRaises(QiskitError, chan.__rmul__, 's') - self.assertRaises(QiskitError, chan.multiply, chan) + self.assertRaises(QiskitError, chan._multiply, chan) self.assertRaises(QiskitError, chan.__rmul__, chan) def test_negate(self): diff --git a/test/python/quantum_info/operators/channel/test_superop.py b/test/python/quantum_info/operators/channel/test_superop.py index 5bb015a3b6a2..186b5cfe3553 100644 --- a/test/python/quantum_info/operators/channel/test_superop.py +++ b/test/python/quantum_info/operators/channel/test_superop.py @@ -14,6 +14,7 @@ """Tests for SuperOp quantum channel representation class.""" +import copy import unittest import numpy as np from numpy.testing import assert_allclose @@ -90,10 +91,24 @@ def test_equal(self): def test_copy(self): """Test copy method""" mat = np.eye(4) + with self.subTest("Deep copy"): + orig = SuperOp(mat) + cpy = orig.copy() + cpy._data[0, 0] = 0.0 + self.assertFalse(cpy == orig) + with self.subTest("Shallow copy"): + orig = SuperOp(mat) + clone = copy.copy(orig) + clone._data[0, 0] = 0.0 + self.assertTrue(clone == orig) + + def test_clone(self): + """Test clone method""" + mat = np.eye(4) orig = SuperOp(mat) - cpy = orig.copy() - cpy._data[0, 0] = 0.0 - self.assertFalse(cpy == orig) + clone = copy.copy(orig) + clone._data[0, 0] = 0.0 + self.assertTrue(clone == orig) def test_evolve(self): """Test evolve method.""" @@ -329,34 +344,41 @@ def test_compose_subsystem(self): full_op = SuperOp(mat_c).tensor(SuperOp(mat_b)).tensor(SuperOp(mat_a)) targ = np.dot(full_op.data, mat) self.assertEqual(op.compose(op3, qargs=[0, 1, 2]), SuperOp(targ)) + self.assertEqual(op @ op3([0, 1, 2]), SuperOp(targ)) # op3 qargs=[2, 1, 0] full_op = SuperOp(mat_a).tensor(SuperOp(mat_b)).tensor(SuperOp(mat_c)) targ = np.dot(full_op.data, mat) self.assertEqual(op.compose(op3, qargs=[2, 1, 0]), SuperOp(targ)) + self.assertEqual(op @ op3([2, 1, 0]), SuperOp(targ)) # op2 qargs=[0, 1] full_op = iden.tensor(SuperOp(mat_b)).tensor(SuperOp(mat_a)) targ = np.dot(full_op.data, mat) self.assertEqual(op.compose(op2, qargs=[0, 1]), SuperOp(targ)) + self.assertEqual(op @ op2([0, 1]), SuperOp(targ)) # op2 qargs=[2, 0] full_op = SuperOp(mat_a).tensor(iden).tensor(SuperOp(mat_b)) targ = np.dot(full_op.data, mat) self.assertEqual(op.compose(op2, qargs=[2, 0]), SuperOp(targ)) + self.assertEqual(op @ op2([2, 0]), SuperOp(targ)) # op1 qargs=[0] full_op = iden.tensor(iden).tensor(SuperOp(mat_a)) targ = np.dot(full_op.data, mat) self.assertEqual(op.compose(op1, qargs=[0]), SuperOp(targ)) + self.assertEqual(op @ op1([0]), SuperOp(targ)) # op1 qargs=[1] full_op = iden.tensor(SuperOp(mat_a)).tensor(iden) targ = np.dot(full_op.data, mat) self.assertEqual(op.compose(op1, qargs=[1]), SuperOp(targ)) + self.assertEqual(op @ op1([1]), SuperOp(targ)) # op1 qargs=[2] full_op = SuperOp(mat_a).tensor(iden).tensor(iden) targ = np.dot(full_op.data, mat) self.assertEqual(op.compose(op1, qargs=[2]), SuperOp(targ)) + self.assertEqual(op @ op1([2]), SuperOp(targ)) def test_dot_subsystem(self): """Test subsystem dot method.""" @@ -375,34 +397,40 @@ def test_dot_subsystem(self): full_op = SuperOp(mat_c).tensor(SuperOp(mat_b)).tensor(SuperOp(mat_a)) targ = np.dot(mat, full_op.data) self.assertEqual(op.dot(op3, qargs=[0, 1, 2]), SuperOp(targ)) + self.assertEqual(op * op3([0, 1, 2]), SuperOp(targ)) # op3 qargs=[2, 1, 0] full_op = SuperOp(mat_a).tensor(SuperOp(mat_b)).tensor(SuperOp(mat_c)) targ = np.dot(mat, full_op.data) self.assertEqual(op.dot(op3, qargs=[2, 1, 0]), SuperOp(targ)) + self.assertEqual(op * op3([2, 1, 0]), SuperOp(targ)) # op2 qargs=[0, 1] full_op = iden.tensor(SuperOp(mat_b)).tensor(SuperOp(mat_a)) targ = np.dot(mat, full_op.data) self.assertEqual(op.dot(op2, qargs=[0, 1]), SuperOp(targ)) + self.assertEqual(op * op2([0, 1]), SuperOp(targ)) # op2 qargs=[2, 0] full_op = SuperOp(mat_a).tensor(iden).tensor(SuperOp(mat_b)) targ = np.dot(mat, full_op.data) self.assertEqual(op.dot(op2, qargs=[2, 0]), SuperOp(targ)) + self.assertEqual(op * op2([2, 0]), SuperOp(targ)) # op1 qargs=[0] full_op = iden.tensor(iden).tensor(SuperOp(mat_a)) targ = np.dot(mat, full_op.data) self.assertEqual(op.dot(op1, qargs=[0]), SuperOp(targ)) - + self.assertEqual(op * op1([0]), SuperOp(targ)) # op1 qargs=[1] full_op = iden.tensor(SuperOp(mat_a)).tensor(iden) targ = np.dot(mat, full_op.data) self.assertEqual(op.dot(op1, qargs=[1]), SuperOp(targ)) + self.assertEqual(op * op1([1]), SuperOp(targ)) # op1 qargs=[2] full_op = SuperOp(mat_a).tensor(iden).tensor(iden) targ = np.dot(mat, full_op.data) self.assertEqual(op.dot(op1, qargs=[2]), SuperOp(targ)) + self.assertEqual(op * op1([2]), SuperOp(targ)) def test_compose_front_subsystem(self): """Test subsystem front compose method.""" @@ -522,51 +550,35 @@ def test_add(self): """Test add method.""" mat1 = 0.5 * self.sopI mat2 = 0.5 * self.depol_sop(1) - targ = SuperOp(mat1 + mat2) - chan1 = SuperOp(mat1) chan2 = SuperOp(mat2) - self.assertEqual(chan1.add(chan2), targ) + targ = SuperOp(mat1 + mat2) + self.assertEqual(chan1._add(chan2), targ) self.assertEqual(chan1 + chan2, targ) - - def test_add_except(self): - """Test add method raises exceptions.""" - chan1 = SuperOp(self.sopI) - chan2 = SuperOp(np.eye(16)) - self.assertRaises(QiskitError, chan1.add, chan2) - self.assertRaises(QiskitError, chan1.add, 5) - - def test_subtract(self): - """Test subtract method.""" - mat1 = 0.5 * self.sopI - mat2 = 0.5 * self.depol_sop(1) targ = SuperOp(mat1 - mat2) - - chan1 = SuperOp(mat1) - chan2 = SuperOp(mat2) - self.assertEqual(chan1.subtract(chan2), targ) self.assertEqual(chan1 - chan2, targ) - def test_subtract_except(self): - """Test subtract method raises exceptions.""" + def test_add_except(self): + """Test add method raises exceptions.""" chan1 = SuperOp(self.sopI) chan2 = SuperOp(np.eye(16)) - self.assertRaises(QiskitError, chan1.subtract, chan2) - self.assertRaises(QiskitError, chan1.subtract, 5) + self.assertRaises(QiskitError, chan1._add, chan2) + self.assertRaises(QiskitError, chan1._add, 5) def test_multiply(self): """Test multiply method.""" chan = SuperOp(self.sopI) val = 0.5 targ = SuperOp(val * self.sopI) - self.assertEqual(chan.multiply(val), targ) + self.assertEqual(chan._multiply(val), targ) + self.assertEqual(val * chan, targ) def test_multiply_except(self): """Test multiply method raises exceptions.""" chan = SuperOp(self.sopI) - self.assertRaises(QiskitError, chan.multiply, 's') + self.assertRaises(QiskitError, chan._multiply, 's') self.assertRaises(QiskitError, chan.__rmul__, 's') - self.assertRaises(QiskitError, chan.multiply, chan) + self.assertRaises(QiskitError, chan._multiply, chan) self.assertRaises(QiskitError, chan.__rmul__, chan) def test_negate(self): diff --git a/test/python/quantum_info/operators/test_operator.py b/test/python/quantum_info/operators/test_operator.py index db40a34af5c7..dbf566f5e271 100644 --- a/test/python/quantum_info/operators/test_operator.py +++ b/test/python/quantum_info/operators/test_operator.py @@ -18,13 +18,14 @@ import unittest import logging +import copy import numpy as np from numpy.testing import assert_allclose import scipy.linalg as la from qiskit import QiskitError from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit -from qiskit.extensions.standard import HGate, CHGate, CnotGate +from qiskit.extensions.standard import HGate, CHGate, CXGate from qiskit.test import QiskitTestCase from qiskit.quantum_info.operators.operator import Operator from qiskit.quantum_info.operators.predicates import matrix_equal @@ -173,7 +174,7 @@ def test_circuit_init(self): def test_instruction_init(self): """Test initialization from a circuit.""" - gate = CnotGate() + gate = CXGate() op = Operator(gate).data target = gate.to_matrix() global_phase_equivalent = matrix_equal(op, target, ignore_phase=True) @@ -201,11 +202,6 @@ def test_equal(self): self.assertEqual(Operator(mat.tolist()), Operator(mat)) - def test_rep(self): - """Test Operator representation string property.""" - op = Operator(self.rand_matrix(2, 2)) - self.assertEqual(op.rep, 'Operator') - def test_data(self): """Test Operator representation string property.""" mat = self.rand_matrix(2, 2) @@ -244,24 +240,30 @@ def test_output_dims(self): self.assertEqual(op.output_dims(qargs=[2, 0]), (4, 2)) def test_reshape(self): - """Test Operator _reshape method.""" + """Test Operator reshape method.""" op = Operator(self.rand_matrix(8, 8)) + reshaped1 = op.reshape(input_dims=[8], output_dims=[8]) + reshaped2 = op.reshape(input_dims=[4, 2], output_dims=[2, 4]) self.assertEqual(op.output_dims(), (2, 2, 2)) self.assertEqual(op.input_dims(), (2, 2, 2)) - op._reshape(input_dims=[8], output_dims=[8]) - self.assertEqual(op.output_dims(), (8,)) - self.assertEqual(op.input_dims(), (8,)) - op._reshape(input_dims=[4, 2], output_dims=[2, 4]) - self.assertEqual(op.output_dims(), (2, 4)) - self.assertEqual(op.input_dims(), (4, 2)) + self.assertEqual(reshaped1.output_dims(), (8,)) + self.assertEqual(reshaped1.input_dims(), (8,)) + self.assertEqual(reshaped2.output_dims(), (2, 4)) + self.assertEqual(reshaped2.input_dims(), (4, 2)) def test_copy(self): """Test Operator copy method""" mat = np.eye(2) - orig = Operator(mat) - cpy = orig.copy() - cpy._data[0, 0] = 0.0 - self.assertFalse(cpy == orig) + with self.subTest("Deep copy"): + orig = Operator(mat) + cpy = orig.copy() + cpy._data[0, 0] = 0.0 + self.assertFalse(cpy == orig) + with self.subTest("Shallow copy"): + orig = Operator(mat) + clone = copy.copy(orig) + clone._data[0, 0] = 0.0 + self.assertTrue(clone == orig) def test_is_unitary(self): """Test is_unitary method.""" @@ -361,28 +363,34 @@ def test_compose_subsystem(self): # op3 qargs=[0, 1, 2] targ = np.dot(np.kron(mat_c, np.kron(mat_b, mat_a)), mat) self.assertEqual(op.compose(op3, qargs=[0, 1, 2]), Operator(targ)) + self.assertEqual(op.compose(op3([0, 1, 2])), Operator(targ)) + self.assertEqual(op @ op3([0, 1, 2]), Operator(targ)) # op3 qargs=[2, 1, 0] targ = np.dot(np.kron(mat_a, np.kron(mat_b, mat_c)), mat) self.assertEqual(op.compose(op3, qargs=[2, 1, 0]), Operator(targ)) + self.assertEqual(op @ op3([2, 1, 0]), Operator(targ)) # op2 qargs=[0, 1] targ = np.dot(np.kron(np.eye(2), np.kron(mat_b, mat_a)), mat) self.assertEqual(op.compose(op2, qargs=[0, 1]), Operator(targ)) + self.assertEqual(op @ op2([0, 1]), Operator(targ)) # op2 qargs=[2, 0] targ = np.dot(np.kron(mat_a, np.kron(np.eye(2), mat_b)), mat) self.assertEqual(op.compose(op2, qargs=[2, 0]), Operator(targ)) + self.assertEqual(op @ op2([2, 0]), Operator(targ)) # op1 qargs=[0] targ = np.dot(np.kron(np.eye(4), mat_a), mat) self.assertEqual(op.compose(op1, qargs=[0]), Operator(targ)) - + self.assertEqual(op @ op1([0]), Operator(targ)) # op1 qargs=[1] targ = np.dot(np.kron(np.eye(2), np.kron(mat_a, np.eye(2))), mat) self.assertEqual(op.compose(op1, qargs=[1]), Operator(targ)) - + self.assertEqual(op @ op1([1]), Operator(targ)) # op1 qargs=[2] targ = np.dot(np.kron(mat_a, np.eye(4)), mat) self.assertEqual(op.compose(op1, qargs=[2]), Operator(targ)) + self.assertEqual(op @ op1([2]), Operator(targ)) def test_dot_subsystem(self): """Test subsystem dot method.""" @@ -399,28 +407,33 @@ def test_dot_subsystem(self): # op3 qargs=[0, 1, 2] targ = np.dot(mat, np.kron(mat_c, np.kron(mat_b, mat_a))) self.assertEqual(op.dot(op3, qargs=[0, 1, 2]), Operator(targ)) + self.assertEqual(op * op3([0, 1, 2]), Operator(targ)) # op3 qargs=[2, 1, 0] targ = np.dot(mat, np.kron(mat_a, np.kron(mat_b, mat_c))) self.assertEqual(op.dot(op3, qargs=[2, 1, 0]), Operator(targ)) + self.assertEqual(op * op3([2, 1, 0]), Operator(targ)) # op2 qargs=[0, 1] targ = np.dot(mat, np.kron(np.eye(2), np.kron(mat_b, mat_a))) self.assertEqual(op.dot(op2, qargs=[0, 1]), Operator(targ)) + self.assertEqual(op * op2([0, 1]), Operator(targ)) # op2 qargs=[2, 0] targ = np.dot(mat, np.kron(mat_a, np.kron(np.eye(2), mat_b))) self.assertEqual(op.dot(op2, qargs=[2, 0]), Operator(targ)) + self.assertEqual(op * op2([2, 0]), Operator(targ)) # op1 qargs=[0] targ = np.dot(mat, np.kron(np.eye(4), mat_a)) self.assertEqual(op.dot(op1, qargs=[0]), Operator(targ)) - + self.assertEqual(op * op1([0]), Operator(targ)) # op1 qargs=[1] targ = np.dot(mat, np.kron(np.eye(2), np.kron(mat_a, np.eye(2)))) self.assertEqual(op.dot(op1, qargs=[1]), Operator(targ)) - + self.assertEqual(op * op1([1]), Operator(targ)) # op1 qargs=[2] targ = np.dot(mat, np.kron(mat_a, np.eye(4))) self.assertEqual(op.dot(op1, qargs=[2]), Operator(targ)) + self.assertEqual(op * op1([2]), Operator(targ)) def test_compose_front_subsystem(self): """Test subsystem front compose method.""" @@ -510,44 +523,30 @@ def test_add(self): mat2 = self.rand_matrix(4, 4) op1 = Operator(mat1) op2 = Operator(mat2) - self.assertEqual(op1.add(op2), Operator(mat1 + mat2)) + self.assertEqual(op1._add(op2), Operator(mat1 + mat2)) self.assertEqual(op1 + op2, Operator(mat1 + mat2)) + self.assertEqual(op1 - op2, Operator(mat1 - mat2)) def test_add_except(self): """Test add method raises exceptions.""" op1 = Operator(self.rand_matrix(2, 2)) op2 = Operator(self.rand_matrix(3, 3)) - self.assertRaises(QiskitError, op1.add, op2) - - def test_subtract(self): - """Test subtract method.""" - mat1 = self.rand_matrix(4, 4) - mat2 = self.rand_matrix(4, 4) - op1 = Operator(mat1) - op2 = Operator(mat2) - self.assertEqual(op1.subtract(op2), Operator(mat1 - mat2)) - self.assertEqual(op1 - op2, Operator(mat1 - mat2)) - - def test_subtract_except(self): - """Test subtract method raises exceptions.""" - op1 = Operator(self.rand_matrix(2, 2)) - op2 = Operator(self.rand_matrix(3, 3)) - self.assertRaises(QiskitError, op1.subtract, op2) + self.assertRaises(QiskitError, op1._add, op2) def test_multiply(self): """Test multiply method.""" mat = self.rand_matrix(4, 4) val = np.exp(5j) op = Operator(mat) - self.assertEqual(op.multiply(val), Operator(val * mat)) + self.assertEqual(op._multiply(val), Operator(val * mat)) self.assertEqual(val * op, Operator(val * mat)) def test_multiply_except(self): """Test multiply method raises exceptions.""" op = Operator(self.rand_matrix(2, 2)) - self.assertRaises(QiskitError, op.multiply, 's') + self.assertRaises(QiskitError, op._multiply, 's') self.assertRaises(QiskitError, op.__rmul__, 's') - self.assertRaises(QiskitError, op.multiply, op) + self.assertRaises(QiskitError, op._multiply, op) self.assertRaises(QiskitError, op.__rmul__, op) def test_negate(self): diff --git a/test/python/quantum_info/states/test_densitymatrix.py b/test/python/quantum_info/states/test_densitymatrix.py index 6c159e0cd114..528d0c7c0032 100644 --- a/test/python/quantum_info/states/test_densitymatrix.py +++ b/test/python/quantum_info/states/test_densitymatrix.py @@ -315,15 +315,8 @@ def test_subtract(self): rho1 = self.rand_rho(4) state0 = DensityMatrix(rho0) state1 = DensityMatrix(rho1) - self.assertEqual(state0.subtract(state1), DensityMatrix(rho0 - rho1)) self.assertEqual(state0 - state1, DensityMatrix(rho0 - rho1)) - def test_subtract_except(self): - """Test subtract method raises exceptions.""" - state1 = DensityMatrix(self.rand_rho(2)) - state2 = DensityMatrix(self.rand_rho(3)) - self.assertRaises(QiskitError, state1.subtract, state2) - def test_multiply(self): """Test multiply method.""" for _ in range(10): diff --git a/test/python/quantum_info/states/test_statevector.py b/test/python/quantum_info/states/test_statevector.py index 20d922adeecc..215daee2e4cd 100644 --- a/test/python/quantum_info/states/test_statevector.py +++ b/test/python/quantum_info/states/test_statevector.py @@ -306,15 +306,8 @@ def test_subtract(self): vec1 = self.rand_vec(4) state0 = Statevector(vec0) state1 = Statevector(vec1) - self.assertEqual(state0.subtract(state1), Statevector(vec0 - vec1)) self.assertEqual(state0 - state1, Statevector(vec0 - vec1)) - def test_subtract_except(self): - """Test subtract method raises exceptions.""" - state1 = Statevector(self.rand_vec(2)) - state2 = Statevector(self.rand_vec(3)) - self.assertRaises(QiskitError, state1.subtract, state2) - def test_multiply(self): """Test multiply method.""" for _ in range(10): diff --git a/test/python/quantum_info/test_synthesis.py b/test/python/quantum_info/test_synthesis.py index 8e729630e9a6..d7616b44a92b 100644 --- a/test/python/quantum_info/test_synthesis.py +++ b/test/python/quantum_info/test_synthesis.py @@ -21,8 +21,8 @@ from qiskit import execute from qiskit.circuit import QuantumCircuit, QuantumRegister from qiskit.extensions import UnitaryGate -from qiskit.extensions.standard import (HGate, IdGate, SdgGate, SGate, U3Gate, - XGate, YGate, ZGate, CnotGate) +from qiskit.extensions.standard import (HGate, IGate, SdgGate, SGate, U3Gate, + XGate, YGate, ZGate, CXGate) from qiskit.providers.basicaer import UnitarySimulatorPy from qiskit.quantum_info.operators import Operator, Pauli from qiskit.quantum_info.random import random_unitary @@ -38,9 +38,9 @@ def make_oneq_cliffords(): """Make as list of 1q Cliffords""" - ixyz_list = [g().to_matrix() for g in (IdGate, XGate, YGate, ZGate)] - ih_list = [g().to_matrix() for g in (IdGate, HGate)] - irs_list = [IdGate().to_matrix(), + ixyz_list = [g().to_matrix() for g in (IGate, XGate, YGate, ZGate)] + ih_list = [g().to_matrix() for g in (IGate, HGate)] + irs_list = [IGate().to_matrix(), SdgGate().to_matrix() @ HGate().to_matrix(), HGate().to_matrix() @ SGate().to_matrix()] oneq_cliffords = [Operator(ixyz @ ih @ irs) for ixyz in ixyz_list @@ -101,11 +101,11 @@ def test_euler_angles_1q_hard_thetas(self): for gate in HARD_THETA_ONEQS: self.check_one_qubit_euler_angles(Operator(gate)) - def test_euler_angles_1q_random(self, nsamples=100): + def test_euler_angles_1q_random(self, nsamples=100, seed=9000): """Verify euler_angles_1q produces correct Euler angles for random unitaries. """ - for _ in range(nsamples): - unitary = random_unitary(2) + for i in range(nsamples): + unitary = random_unitary(2, seed=seed+i) self.check_one_qubit_euler_angles(unitary) @@ -113,20 +113,20 @@ class TestOneQubitEulerDecomposer(QiskitTestCase): """Test OneQubitEulerDecomposer""" def check_one_qubit_euler_angles(self, operator, basis='U3', - tolerance=1e-12): + tolerance=1e-12, + phase_equal=False): """Check euler_angles_1q works for the given unitary""" decomposer = OneQubitEulerDecomposer(basis) with self.subTest(operator=operator): target_unitary = operator.data - decomp_unitary = Operator(decomposer(target_unitary)).data - # Add global phase to make special unitary - target_unitary *= la.det(target_unitary)**(-0.5) - decomp_unitary *= la.det(decomp_unitary)**(-0.5) + decomp_unitary = Operator(decomposer(operator)).data + if not phase_equal: + target_unitary *= la.det(target_unitary)**(-0.5) + decomp_unitary *= la.det(decomp_unitary)**(-0.5) maxdist = np.max(np.abs(target_unitary - decomp_unitary)) - if maxdist > 0.1: + if not phase_equal and maxdist > 0.1: maxdist = np.max(np.abs(target_unitary + decomp_unitary)) - self.assertTrue(np.abs(maxdist) < tolerance, - "Worst distance {}".format(maxdist)) + self.assertTrue(np.abs(maxdist) < tolerance, "Worst distance {}".format(maxdist)) # U3 basis def test_one_qubit_clifford_u3_basis(self): @@ -216,6 +216,23 @@ def test_one_qubit_random_xyx_basis(self, nsamples=50): unitary = random_unitary(2) self.check_one_qubit_euler_angles(unitary, 'XYX') + # R, R basis + def test_one_qubit_clifford_rr_basis(self): + """Verify for r, r basis and all Cliffords.""" + for clifford in ONEQ_CLIFFORDS: + self.check_one_qubit_euler_angles(clifford, 'RR') + + def test_one_qubit_hard_thetas_rr_basis(self): + """Verify for r, r basis and close-to-degenerate theta.""" + for gate in HARD_THETA_ONEQS: + self.check_one_qubit_euler_angles(Operator(gate), 'RR') + + def test_one_qubit_random_rr_basis(self, nsamples=50): + """Verify for r, r basis and random unitaries.""" + for _ in range(nsamples): + unitary = random_unitary(2) + self.check_one_qubit_euler_angles(unitary, 'RR') + # FIXME: streamline the set of test cases class TestTwoQubitWeylDecomposition(QiskitTestCase): @@ -401,7 +418,7 @@ def check_exact_decomposition(self, target_unitary, decomposer, tolerance=1.e-7) def test_cnot_rxx_decompose(self): """Verify CNOT decomposition into RXX gate is correct""" - cnot = Operator(CnotGate()) + cnot = Operator(CXGate()) decomps = [cnot_rxx_decompose(), cnot_rxx_decompose(plus_ry=True, plus_rxx=True), cnot_rxx_decompose(plus_ry=True, plus_rxx=False), diff --git a/test/python/test_dagcircuit.py b/test/python/test_dagcircuit.py index 76fb01ac7f0c..e86ec690a5dc 100644 --- a/test/python/test_dagcircuit.py +++ b/test/python/test_dagcircuit.py @@ -27,10 +27,10 @@ from qiskit.circuit import Measure from qiskit.circuit import Reset from qiskit.circuit import Gate, Instruction -from qiskit.extensions.standard.iden import IdGate +from qiskit.extensions.standard.i import IGate from qiskit.extensions.standard.h import HGate -from qiskit.extensions.standard.x import CnotGate -from qiskit.extensions.standard.z import CzGate +from qiskit.extensions.standard.x import CXGate +from qiskit.extensions.standard.z import CZGate from qiskit.extensions.standard.x import XGate from qiskit.extensions.standard.u1 import U1Gate from qiskit.extensions.standard.barrier import Barrier @@ -196,7 +196,7 @@ def setUp(self): def test_apply_operation_back(self): """The apply_operation_back() method.""" self.dag.apply_operation_back(HGate(), [self.qubit0], [], condition=None) - self.dag.apply_operation_back(CnotGate(), [self.qubit0, self.qubit1], [], condition=None) + self.dag.apply_operation_back(CXGate(), [self.qubit0, self.qubit1], [], condition=None) self.dag.apply_operation_back(Measure(), [self.qubit1, self.clbit1], [], condition=None) self.dag.apply_operation_back(XGate(), [self.qubit1], [], condition=self.condition) self.dag.apply_operation_back(Measure(), [self.qubit0, self.clbit0], [], condition=None) @@ -335,7 +335,7 @@ def test_apply_operation_front(self): def test_get_op_nodes_all(self): """The method dag.op_nodes() returns all op nodes""" self.dag.apply_operation_back(HGate(), [self.qubit0], []) - self.dag.apply_operation_back(CnotGate(), [self.qubit0, self.qubit1], []) + self.dag.apply_operation_back(CXGate(), [self.qubit0, self.qubit1], []) self.dag.apply_operation_back(Reset(), [self.qubit0], []) op_nodes = self.dag.op_nodes() @@ -350,7 +350,7 @@ def test_get_op_nodes_particular(self): self.dag.apply_operation_back(HGate(), [self.qubit1], []) self.dag.apply_operation_back(Reset(), [self.qubit0], []) - self.dag.apply_operation_back(CnotGate(), [self.qubit0, self.qubit1], []) + self.dag.apply_operation_back(CXGate(), [self.qubit0, self.qubit1], []) op_nodes = self.dag.op_nodes(op=HGate) self.assertEqual(len(op_nodes), 2) @@ -364,7 +364,7 @@ def test_get_op_nodes_particular(self): def test_quantum_successors(self): """The method dag.quantum_successors() returns successors connected by quantum edges""" self.dag.apply_operation_back(Measure(), [self.qubit1, self.clbit1], []) - self.dag.apply_operation_back(CnotGate(), [self.qubit0, self.qubit1], []) + self.dag.apply_operation_back(CXGate(), [self.qubit0, self.qubit1], []) self.dag.apply_operation_back(Reset(), [self.qubit0], []) successor_measure = self.dag.quantum_successors( @@ -373,7 +373,7 @@ def test_quantum_successors(self): with self.assertRaises(StopIteration): next(successor_measure) - self.assertIsInstance(cnot_node.op, CnotGate) + self.assertIsInstance(cnot_node.op, CXGate) successor_cnot = self.dag.quantum_successors(cnot_node) self.assertEqual(next(successor_cnot).type, 'out') @@ -384,7 +384,7 @@ def test_quantum_successors(self): def test_quantum_predecessors(self): """The method dag.quantum_predecessors() returns predecessors connected by quantum edges""" self.dag.apply_operation_back(Reset(), [self.qubit0], []) - self.dag.apply_operation_back(CnotGate(), [self.qubit0, self.qubit1], []) + self.dag.apply_operation_back(CXGate(), [self.qubit0, self.qubit1], []) self.dag.apply_operation_back(Measure(), [self.qubit1, self.clbit1], []) predecessor_measure = self.dag.quantum_predecessors( @@ -393,7 +393,7 @@ def test_quantum_predecessors(self): with self.assertRaises(StopIteration): next(predecessor_measure) - self.assertIsInstance(cnot_node.op, CnotGate) + self.assertIsInstance(cnot_node.op, CXGate) predecessor_cnot = self.dag.quantum_predecessors(cnot_node) self.assertIsInstance(next(predecessor_cnot).op, Reset) @@ -404,7 +404,7 @@ def test_quantum_predecessors(self): def test_get_gates_nodes(self): """The method dag.gate_nodes() returns all gate nodes""" self.dag.apply_operation_back(HGate(), [self.qubit0], []) - self.dag.apply_operation_back(CnotGate(), [self.qubit0, self.qubit1], []) + self.dag.apply_operation_back(CXGate(), [self.qubit0, self.qubit1], []) self.dag.apply_operation_back(Reset(), [self.qubit0], []) op_nodes = self.dag.gate_nodes() @@ -419,7 +419,7 @@ def test_get_gates_nodes(self): def test_two_q_gates(self): """The method dag.twoQ_gates() returns all 2Q gate nodes""" self.dag.apply_operation_back(HGate(), [self.qubit0], []) - self.dag.apply_operation_back(CnotGate(), [self.qubit0, self.qubit1], []) + self.dag.apply_operation_back(CXGate(), [self.qubit0, self.qubit1], []) self.dag.apply_operation_back(Barrier(2), [self.qubit0, self.qubit1], []) self.dag.apply_operation_back(Reset(), [self.qubit0], []) @@ -432,10 +432,10 @@ def test_two_q_gates(self): def test_get_named_nodes(self): """The get_named_nodes(AName) method returns all the nodes with name AName""" - self.dag.apply_operation_back(CnotGate(), [self.qubit0, self.qubit1], []) + self.dag.apply_operation_back(CXGate(), [self.qubit0, self.qubit1], []) self.dag.apply_operation_back(HGate(), [self.qubit0], []) - self.dag.apply_operation_back(CnotGate(), [self.qubit2, self.qubit1], []) - self.dag.apply_operation_back(CnotGate(), [self.qubit0, self.qubit2], []) + self.dag.apply_operation_back(CXGate(), [self.qubit2, self.qubit1], []) + self.dag.apply_operation_back(CXGate(), [self.qubit0, self.qubit2], []) self.dag.apply_operation_back(HGate(), [self.qubit2], []) # The ordering is not assured, so we only compare the output (unordered) sets. @@ -453,10 +453,10 @@ def test_get_named_nodes(self): def test_topological_nodes(self): """The topological_nodes() method""" - self.dag.apply_operation_back(CnotGate(), [self.qubit0, self.qubit1], []) + self.dag.apply_operation_back(CXGate(), [self.qubit0, self.qubit1], []) self.dag.apply_operation_back(HGate(), [self.qubit0], []) - self.dag.apply_operation_back(CnotGate(), [self.qubit2, self.qubit1], []) - self.dag.apply_operation_back(CnotGate(), [self.qubit0, self.qubit2], []) + self.dag.apply_operation_back(CXGate(), [self.qubit2, self.qubit1], []) + self.dag.apply_operation_back(CXGate(), [self.qubit0, self.qubit2], []) self.dag.apply_operation_back(HGate(), [self.qubit2], []) named_nodes = self.dag.topological_nodes() @@ -480,10 +480,10 @@ def test_topological_nodes(self): def test_topological_op_nodes(self): """The topological_op_nodes() method""" - self.dag.apply_operation_back(CnotGate(), [self.qubit0, self.qubit1], []) + self.dag.apply_operation_back(CXGate(), [self.qubit0, self.qubit1], []) self.dag.apply_operation_back(HGate(), [self.qubit0], []) - self.dag.apply_operation_back(CnotGate(), [self.qubit2, self.qubit1], []) - self.dag.apply_operation_back(CnotGate(), [self.qubit0, self.qubit2], []) + self.dag.apply_operation_back(CXGate(), [self.qubit2, self.qubit1], []) + self.dag.apply_operation_back(CXGate(), [self.qubit0, self.qubit2], []) self.dag.apply_operation_back(HGate(), [self.qubit2], []) named_nodes = self.dag.topological_op_nodes() @@ -497,7 +497,7 @@ def test_topological_op_nodes(self): def test_dag_nodes_on_wire(self): """Test that listing the gates on a qubit/classical bit gets the correct gates""" - self.dag.apply_operation_back(CnotGate(), [self.qubit0, self.qubit1], []) + self.dag.apply_operation_back(CXGate(), [self.qubit0, self.qubit1], []) self.dag.apply_operation_back(HGate(), [self.qubit0], []) qbit = self.dag.qubits()[0] @@ -524,9 +524,9 @@ def test_dag_nodes_on_wire_multiple_successors(self): Both the 2nd CX gate and the H gate follow the first CX gate in the DAG, so they both must be returned but in the correct order. """ - self.dag.apply_operation_back(CnotGate(), [self.qubit0, self.qubit1], []) + self.dag.apply_operation_back(CXGate(), [self.qubit0, self.qubit1], []) self.dag.apply_operation_back(HGate(), [self.qubit1], []) - self.dag.apply_operation_back(CnotGate(), [self.qubit0, self.qubit1], []) + self.dag.apply_operation_back(CXGate(), [self.qubit0, self.qubit1], []) nodes = self.dag.nodes_on_wire(self.dag.qubits()[1], only_ops=True) node_names = [nd.name for nd in nodes] @@ -545,10 +545,10 @@ def test_remove_op_node(self): def test_remove_op_node_longer(self): """Test remove_op_node method in a "longer" dag""" - self.dag.apply_operation_back(CnotGate(), [self.qubit0, self.qubit1]) + self.dag.apply_operation_back(CXGate(), [self.qubit0, self.qubit1]) self.dag.apply_operation_back(HGate(), [self.qubit0]) - self.dag.apply_operation_back(CnotGate(), [self.qubit2, self.qubit1]) - self.dag.apply_operation_back(CnotGate(), [self.qubit0, self.qubit2]) + self.dag.apply_operation_back(CXGate(), [self.qubit2, self.qubit1]) + self.dag.apply_operation_back(CXGate(), [self.qubit0, self.qubit2]) self.dag.apply_operation_back(HGate(), [self.qubit2]) op_nodes = list(self.dag.topological_op_nodes()) @@ -573,8 +573,8 @@ def test_dag_collect_runs(self): self.dag.apply_operation_back(U1Gate(3.14), [self.qubit0]) self.dag.apply_operation_back(U1Gate(3.14), [self.qubit0]) self.dag.apply_operation_back(U1Gate(3.14), [self.qubit0]) - self.dag.apply_operation_back(CnotGate(), [self.qubit2, self.qubit1]) - self.dag.apply_operation_back(CnotGate(), [self.qubit1, self.qubit2]) + self.dag.apply_operation_back(CXGate(), [self.qubit2, self.qubit1]) + self.dag.apply_operation_back(CXGate(), [self.qubit1, self.qubit2]) self.dag.apply_operation_back(HGate(), [self.qubit2]) collected_runs = self.dag.collect_runs(['u1', 'cx', 'h']) self.assertEqual(len(collected_runs), 3) @@ -643,7 +643,7 @@ def test_layers_basic(self): dag.add_qreg(qreg) dag.add_creg(creg) dag.apply_operation_back(HGate(), [qubit0], []) - dag.apply_operation_back(CnotGate(), [qubit0, qubit1], [], condition=None) + dag.apply_operation_back(CXGate(), [qubit0, qubit1], [], condition=None) dag.apply_operation_back(Measure(), [qubit1, clbit1], [], condition=None) dag.apply_operation_back(XGate(), [qubit1], [], condition=condition) dag.apply_operation_back(Measure(), [qubit0, clbit0], [], condition=None) @@ -680,7 +680,7 @@ def test_layers_maintains_order(self): qc.x(0) dag = circuit_to_dag(qc) dag1 = list(dag.layers())[0]['graph'] - dag1.apply_operation_back(IdGate(), [qr[0]], []) + dag1.apply_operation_back(IGate(), [qr[0]], []) comp = [(nd.type, nd.name, nd._node_id) for nd in dag1.topological_nodes()] self.assertEqual(comp, truth) @@ -833,19 +833,19 @@ def setUp(self): self.condition = (creg, 3) self.dag.apply_operation_back(HGate(), [self.qubit0], []) - self.dag.apply_operation_back(CnotGate(), [self.qubit0, self.qubit1], []) + self.dag.apply_operation_back(CXGate(), [self.qubit0, self.qubit1], []) self.dag.apply_operation_back(XGate(), [self.qubit1], []) def test_substitute_circuit_one_middle(self): """The method substitute_node_with_dag() replaces a in-the-middle node with a DAG.""" - cx_node = self.dag.op_nodes(op=CnotGate).pop() + cx_node = self.dag.op_nodes(op=CXGate).pop() flipped_cx_circuit = DAGCircuit() v = QuantumRegister(2, "v") flipped_cx_circuit.add_qreg(v) flipped_cx_circuit.apply_operation_back(HGate(), [v[0]], []) flipped_cx_circuit.apply_operation_back(HGate(), [v[1]], []) - flipped_cx_circuit.apply_operation_back(CnotGate(), [v[1], v[0]], []) + flipped_cx_circuit.apply_operation_back(CXGate(), [v[1], v[0]], []) flipped_cx_circuit.apply_operation_back(HGate(), [v[0]], []) flipped_cx_circuit.apply_operation_back(HGate(), [v[1]], []) @@ -897,7 +897,7 @@ def test_substituting_node_with_wrong_width_node_raises(self): dag = DAGCircuit() qr = QuantumRegister(2) dag.add_qreg(qr) - node_to_be_replaced = dag.apply_operation_back(CnotGate(), [qr[0], qr[1]]) + node_to_be_replaced = dag.apply_operation_back(CXGate(), [qr[0], qr[1]]) with self.assertRaises(DAGCircuitError) as _: dag.substitute_node(node_to_be_replaced, Measure()) @@ -923,12 +923,12 @@ def test_substituting_node_preserves_args_condition(self, inplace): dag.add_qreg(qr) dag.add_creg(cr) dag.apply_operation_back(HGate(), [qr[1]]) - node_to_be_replaced = dag.apply_operation_back(CnotGate(), [qr[1], qr[0]], + node_to_be_replaced = dag.apply_operation_back(CXGate(), [qr[1], qr[0]], condition=(cr, 1)) dag.apply_operation_back(HGate(), [qr[1]]) - replacement_node = dag.substitute_node(node_to_be_replaced, CzGate(), + replacement_node = dag.substitute_node(node_to_be_replaced, CZGate(), inplace=inplace) raise_if_dagcircuit_invalid(dag) diff --git a/test/python/test_qasm_parser.py b/test/python/test_qasm_parser.py index 8be70a1b5f8b..761a386555e0 100644 --- a/test/python/test_qasm_parser.py +++ b/test/python/test_qasm_parser.py @@ -48,10 +48,20 @@ def test_parser(self): res = parse(self.qasm_file_path) self.log.info(res) # TODO: For now only some basic checks. - self.assertEqual(len(res), 2358) - self.assertEqual(res[:12], "OPENQASM 2.0") - self.assertEqual(res[14:41], "gate u3(theta,phi,lambda) q") - self.assertEqual(res[2342:2357], "measure r -> d;") + starts_expected = "OPENQASM 2.0;\ngate " + ends_expected = '\n'.join(['}', + 'qreg q[3];', + 'qreg r[3];', + 'h q;', + 'cx q,r;', + 'creg c[3];', + 'creg d[3];', + 'barrier q;', + 'measure q -> c;', + 'measure r -> d;', '']) + + self.assertEqual(res[:len(starts_expected)], starts_expected) + self.assertEqual(res[-len(ends_expected):], ends_expected) def test_parser_fail(self): """should fail a for a not valid circuit.""" @@ -61,6 +71,7 @@ def test_parser_fail(self): def test_all_valid_nodes(self): """Test that the tree contains only Node subclasses.""" + def inspect(node): """Inspect node children.""" for child in node.children: diff --git a/test/python/tools/jupyter/test_notebooks.py b/test/python/tools/jupyter/test_notebooks.py index daae2a83bfe4..070e408c34b3 100644 --- a/test/python/tools/jupyter/test_notebooks.py +++ b/test/python/tools/jupyter/test_notebooks.py @@ -12,6 +12,8 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. +# pylint: disable=bad-docstring-quotes + """Tests for the wrapper functionality.""" import os @@ -20,6 +22,7 @@ import nbformat from nbconvert.preprocessors import ExecutePreprocessor +import qiskit from qiskit.tools.visualization import HAS_MATPLOTLIB from qiskit.test import (Path, QiskitTestCase, online_test, slow_test) @@ -30,6 +33,8 @@ JUPYTER_KERNEL = 'python3' +@unittest.skipUnless(hasattr(qiskit, 'IBMQ'), + 'qiskit-ibmq-provider is required for these tests') class TestJupyter(QiskitTestCase): """Notebooks test case.""" def setUp(self): diff --git a/test/python/tools/test_parallel.py b/test/python/tools/test_parallel.py index 358a7e7860c9..6af21c3c1469 100644 --- a/test/python/tools/test_parallel.py +++ b/test/python/tools/test_parallel.py @@ -18,6 +18,7 @@ from qiskit.tools.parallel import parallel_map from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit +from qiskit.pulse import Schedule from qiskit.test import QiskitTestCase @@ -28,13 +29,17 @@ def _parfunc(x): return x -def _build_simple(_): +def _build_simple_circuit(_): qreg = QuantumRegister(2) creg = ClassicalRegister(2) qc = QuantumCircuit(qreg, creg) return qc +def _build_simple_schedule(_): + return Schedule() + + class TestParallel(QiskitTestCase): """A class for testing parallel_map functionality. """ @@ -50,6 +55,12 @@ def test_parallel(self): def test_parallel_circuit_names(self): """Verify unique circuit names in parallel""" - out_circs = parallel_map(_build_simple, list(range(10))) + out_circs = parallel_map(_build_simple_circuit, list(range(10))) names = [circ.name for circ in out_circs] self.assertEqual(len(names), len(set(names))) + + def test_parallel_schedule_names(self): + """Verify unique schedule names in parallel""" + out_schedules = parallel_map(_build_simple_schedule, list(range(10))) + names = [schedule.name for schedule in out_schedules] + self.assertEqual(len(names), len(set(names))) diff --git a/test/python/transpiler/test_collect_2q_blocks.py b/test/python/transpiler/test_collect_2q_blocks.py index 5dc3c619f6b9..fa4d07aea38a 100644 --- a/test/python/transpiler/test_collect_2q_blocks.py +++ b/test/python/transpiler/test_collect_2q_blocks.py @@ -67,21 +67,21 @@ def test_block_interrupted_by_gate(self): blocks : [['cx', 'id', 'id', 'id'], ['id', 'cx']] - ┌───┐┌────┐┌─┐ ┌────┐┌───┐ - q_0: |0>┤ X ├┤ Id ├┤M├──────┤ Id ├┤ X ├ - └─┬─┘├────┤└╥┘┌────┐└────┘└─┬─┘ - q_1: |0>──■──┤ Id ├─╫─┤ Id ├────────■── - └────┘ ║ └────┘ - c_0: 0 ════════════╩══════════════════ + ┌───┐┌───┐┌─┐ ┌───┐┌───┐ + q_0: |0>┤ X ├┤ I ├┤M├─────┤ I ├┤ X ├ + └─┬─┘├───┤└╥┘┌───┐└───┘└─┬─┘ + q_1: |0>──■──┤ I ├─╫─┤ I ├───────■── + └───┘ ║ └───┘ + c_0: 0 ═══════════╩════════════════ """ qc = QuantumCircuit(2, 1) qc.cx(1, 0) - qc.iden(0) - qc.iden(1) + qc.i(0) + qc.i(1) qc.measure(0, 0) - qc.iden(0) - qc.iden(1) + qc.i(0) + qc.i(1) qc.cx(1, 0) dag = circuit_to_dag(qc) @@ -89,7 +89,7 @@ def test_block_interrupted_by_gate(self): pass_.run(dag) # list from Collect2QBlocks of nodes that it should have put into blocks - good_names = ["cx", "u1", "u2", "u3", "id"] + good_names = ['cx', 'u1', 'u2', 'u3', 'id'] dag_nodes = [node for node in dag.topological_op_nodes() if node.name in good_names] # we have to convert them to sets as the ordering can be different diff --git a/test/python/transpiler/test_consolidate_blocks.py b/test/python/transpiler/test_consolidate_blocks.py index ea9df3c197d1..2f4639c71645 100644 --- a/test/python/transpiler/test_consolidate_blocks.py +++ b/test/python/transpiler/test_consolidate_blocks.py @@ -190,10 +190,10 @@ def test_node_added_before_block(self): c_0: 0 ══╩══════════════ """ qc = QuantumCircuit(2, 1) - qc.iden(0) + qc.i(0) qc.measure(1, 0) qc.cx(1, 0) - qc.iden(1) + qc.i(1) # can't just add all the nodes to one block as in other tests # as we are trying to test the block gets added in the correct place @@ -224,9 +224,9 @@ def test_node_added_after_block(self): """ qc = QuantumCircuit(3) qc.cx(1, 2) - qc.iden(1) + qc.i(1) qc.cx(0, 1) - qc.iden(2) + qc.i(2) pass_manager = PassManager() pass_manager.append(Collect2qBlocks()) @@ -255,13 +255,13 @@ def test_node_middle_of_blocks(self): qc = QuantumCircuit(4) qc.cx(0, 1) qc.cx(3, 2) - qc.iden(1) - qc.iden(2) + qc.i(1) + qc.i(2) qc.swap(1, 2) - qc.iden(1) - qc.iden(2) + qc.i(1) + qc.i(2) qc.cx(0, 1) qc.cx(3, 2) diff --git a/test/python/transpiler/test_csp_layout.py b/test/python/transpiler/test_csp_layout.py index cc4500941149..6deb23c126ee 100644 --- a/test/python/transpiler/test_csp_layout.py +++ b/test/python/transpiler/test_csp_layout.py @@ -24,15 +24,7 @@ from qiskit.test import QiskitTestCase from qiskit.test.mock import FakeTenerife, FakeRueschlikon, FakeTokyo -try: - import constraint # pylint: disable=unused-import, import-error - HAS_CONSTRAINT = True -except Exception: # pylint: disable=broad-except - HAS_CONSTRAINT = False - - -@unittest.skipIf(not HAS_CONSTRAINT, 'python-constraint not installed.') class TestCSPLayout(QiskitTestCase): """Tests the CSPLayout pass""" seed = 42 diff --git a/test/python/transpiler/test_decompose.py b/test/python/transpiler/test_decompose.py index c09952c514ad..7cd4ee0f997c 100644 --- a/test/python/transpiler/test_decompose.py +++ b/test/python/transpiler/test_decompose.py @@ -20,7 +20,7 @@ from qiskit.transpiler.passes import Decompose from qiskit.converters import circuit_to_dag from qiskit.extensions.standard import HGate -from qiskit.extensions.standard import ToffoliGate +from qiskit.extensions.standard import CCXGate from qiskit.test import QiskitTestCase @@ -63,7 +63,7 @@ def test_decompose_toffoli(self): circuit = QuantumCircuit(qr1, qr2) circuit.ccx(qr1[0], qr1[1], qr2[0]) dag = circuit_to_dag(circuit) - pass_ = Decompose(ToffoliGate) + pass_ = Decompose(CCXGate) after_dag = pass_.run(dag) op_nodes = after_dag.op_nodes() self.assertEqual(len(op_nodes), 15) diff --git a/test/python/transpiler/test_layout_transformation.py b/test/python/transpiler/test_layout_transformation.py new file mode 100644 index 000000000000..f36a47591f11 --- /dev/null +++ b/test/python/transpiler/test_layout_transformation.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2018. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Test the LayoutTransformation pass""" + +import unittest + +import numpy as np + +from qiskit import QuantumRegister, QuantumCircuit +from qiskit.converters import circuit_to_dag +from qiskit.test import QiskitTestCase +from qiskit.transpiler import CouplingMap, Layout +from qiskit.transpiler.passes import LayoutTransformation + + +class TestLayoutTransformation(QiskitTestCase): + """ + Tests the LayoutTransformation pass. + """ + + def test_three_qubit(self): + """Test if the permutation {0->2,1->0,2->1} is implemented correctly.""" + np.random.seed(0) + v = QuantumRegister(3, 'v') # virtual qubits + coupling = CouplingMap([[0, 1], [1, 2]]) + from_layout = Layout({v[0]: 0, v[1]: 1, v[2]: 2}) + to_layout = Layout({v[0]: 2, v[1]: 0, v[2]: 1}) + ltpass = LayoutTransformation(coupling_map=coupling, + from_layout=from_layout, + to_layout=to_layout) + qc = QuantumCircuit(3) # input (empty) physical circuit + dag = circuit_to_dag(qc) + q = dag.qubits() + output_dag = ltpass.run(dag) + # output_dag.draw() + # Check that only two swaps were performed + self.assertCountEqual(["swap"] * 2, [op.name for op in output_dag.topological_op_nodes()]) + # And check that the swaps were first performed on {q0,q1} then on {q1,q2}. + self.assertEqual([frozenset([q[0], q[1]]), frozenset([q[1], q[2]])], + [frozenset(op.qargs) for op in output_dag.topological_op_nodes()]) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/python/transpiler/test_optimize_1q_gates.py b/test/python/transpiler/test_optimize_1q_gates.py index d94689aea7e8..1922f522d779 100644 --- a/test/python/transpiler/test_optimize_1q_gates.py +++ b/test/python/transpiler/test_optimize_1q_gates.py @@ -39,8 +39,8 @@ def test_dont_optimize_id(self): """ qr = QuantumRegister(1, 'qr') circuit = QuantumCircuit(qr) - circuit.iden(qr) - circuit.iden(qr) + circuit.i(qr) + circuit.i(qr) dag = circuit_to_dag(circuit) pass_ = Optimize1qGates() diff --git a/test/python/transpiler/test_passmanager_run.py b/test/python/transpiler/test_passmanager_run.py index 8d2c9911ef18..b9e1b522d705 100644 --- a/test/python/transpiler/test_passmanager_run.py +++ b/test/python/transpiler/test_passmanager_run.py @@ -15,7 +15,7 @@ """Tests PassManager.run()""" from qiskit import QuantumRegister, QuantumCircuit -from qiskit.extensions.standard import CnotGate +from qiskit.extensions.standard import CXGate from qiskit.transpiler.preset_passmanagers import level_1_pass_manager from qiskit.test import QiskitTestCase from qiskit.test.mock import FakeMelbourne @@ -64,7 +64,7 @@ def test_default_pass_manager_single(self): new_circuit = pass_manager.run(circuit) for gate, qargs, _ in new_circuit.data: - if isinstance(gate, CnotGate): + if isinstance(gate, CXGate): self.assertIn([x.index for x in qargs], coupling_map) def test_default_pass_manager_two(self): @@ -111,5 +111,5 @@ def test_default_pass_manager_two(self): for new_circuit in new_circuits: for gate, qargs, _ in new_circuit.data: - if isinstance(gate, CnotGate): + if isinstance(gate, CXGate): self.assertIn([x.index for x in qargs], coupling_map) diff --git a/test/python/transpiler/test_preset_passmanagers.py b/test/python/transpiler/test_preset_passmanagers.py index bb6b10a9a6af..a31dc652107d 100644 --- a/test/python/transpiler/test_preset_passmanagers.py +++ b/test/python/transpiler/test_preset_passmanagers.py @@ -263,17 +263,63 @@ def test_layout_tokyo_2845(self, level): 13: ancilla[8], 14: ancilla[9], 15: ancilla[10], 16: ancilla[11], 17: ancilla[12], 18: ancilla[13], 19: ancilla[14]} - dense_layout = {2: qr1[0], 6: qr1[1], 1: qr1[2], 5: qr2[0], 0: qr2[1], 3: ancilla[0], + dense_layout = {0: qr2[1], 1: qr1[2], 2: qr1[0], 3: ancilla[0], 4: ancilla[1], 5: qr2[0], + 6: qr1[1], 7: ancilla[2], 8: ancilla[3], 9: ancilla[4], 10: ancilla[5], + 11: ancilla[6], 12: ancilla[7], 13: ancilla[8], 14: ancilla[9], + 15: ancilla[10], 16: ancilla[11], 17: ancilla[12], 18: ancilla[13], + 19: ancilla[14]} + + csp_layout = {0: qr1[1], 1: qr1[2], 2: qr2[0], 5: qr1[0], 3: qr2[1], 4: ancilla[0], + 6: ancilla[1], 7: ancilla[2], 8: ancilla[3], 9: ancilla[4], 10: ancilla[5], + 11: ancilla[6], 12: ancilla[7], 13: ancilla[8], 14: ancilla[9], + 15: ancilla[10], 16: ancilla[11], 17: ancilla[12], 18: ancilla[13], + 19: ancilla[14]} + + # Trivial layout + expected_layout_level0 = trivial_layout + # Dense layout + expected_layout_level1 = dense_layout + # CSP layout + expected_layout_level2 = csp_layout + expected_layout_level3 = csp_layout + + expected_layouts = [expected_layout_level0, + expected_layout_level1, + expected_layout_level2, + expected_layout_level3] + backend = FakeTokyo() + result = transpile(qc, backend, optimization_level=level, seed_transpiler=42) + self.assertEqual(result._layout._p2v, expected_layouts[level]) + + @data(0, 1, 2, 3) + def test_layout_tokyo_fully_connected_cx(self, level): + """Test that final layout in tokyo in a fully connected circuit + """ + qr = QuantumRegister(5, 'qr') + qc = QuantumCircuit(qr) + for qubit_target in qr: + for qubit_control in qr: + if qubit_control != qubit_target: + qc.cx(qubit_control, qubit_target) + + ancilla = QuantumRegister(15, 'ancilla') + trivial_layout = {0: qr[0], 1: qr[1], 2: qr[2], 3: qr[3], 4: qr[4], + 5: ancilla[0], 6: ancilla[1], 7: ancilla[2], 8: ancilla[3], + 9: ancilla[4], 10: ancilla[5], 11: ancilla[6], 12: ancilla[7], + 13: ancilla[8], 14: ancilla[9], 15: ancilla[10], 16: ancilla[11], + 17: ancilla[12], 18: ancilla[13], 19: ancilla[14]} + + dense_layout = {2: qr[0], 6: qr[1], 1: qr[2], 5: qr[3], 0: qr[4], 3: ancilla[0], 4: ancilla[1], 7: ancilla[2], 8: ancilla[3], 9: ancilla[4], 10: ancilla[5], 11: ancilla[6], 12: ancilla[7], 13: ancilla[8], 14: ancilla[9], 15: ancilla[10], 16: ancilla[11], 17: ancilla[12], 18: ancilla[13], 19: ancilla[14]} - noise_adaptive_layout = {6: qr1[0], 11: qr1[1], 5: qr1[2], 0: qr2[0], 1: qr2[1], - 2: ancilla[0], 3: ancilla[1], 4: ancilla[2], 7: ancilla[3], - 8: ancilla[4], 9: ancilla[5], 10: ancilla[6], 12: ancilla[7], - 13: ancilla[8], 14: ancilla[9], 15: ancilla[10], 16: ancilla[11], - 17: ancilla[12], 18: ancilla[13], 19: ancilla[14]} + noise_adaptive_layout = {6: qr[0], 11: qr[1], 5: qr[2], 10: qr[3], 15: qr[4], 0: ancilla[0], + 1: ancilla[1], 2: ancilla[2], 3: ancilla[3], 4: ancilla[4], + 7: ancilla[5], 8: ancilla[6], 9: ancilla[7], 12: ancilla[8], + 13: ancilla[9], 14: ancilla[10], 16: ancilla[11], 17: ancilla[12], + 18: ancilla[13], 19: ancilla[14]} # Trivial layout expected_layout_level0 = trivial_layout @@ -291,9 +337,9 @@ def test_layout_tokyo_2845(self, level): result = transpile(qc, backend, optimization_level=level, seed_transpiler=42) self.assertEqual(result._layout._p2v, expected_layouts[level]) - @data(0, 1, 2, 3) + @data(0, 1) def test_trivial_layout(self, level): - """Test that, when possible, trivial layout should be preferred in level 0 and 1 + """Test that trivial layout is preferred in level 0 and 1 See: https://github.com/Qiskit/qiskit-terra/pull/3657#pullrequestreview-342012465 """ qr = QuantumRegister(10, 'qr') @@ -316,28 +362,8 @@ def test_trivial_layout(self, level): 14: ancilla[4], 15: ancilla[5], 16: ancilla[6], 17: ancilla[7], 18: ancilla[8], 19: ancilla[9]} - dense_layout = {0: qr[9], 1: qr[8], 2: qr[6], 3: qr[1], 4: ancilla[0], 5: qr[7], 6: qr[4], - 7: qr[5], 8: qr[0], 9: ancilla[1], 10: qr[3], 11: qr[2], 12: ancilla[2], - 13: ancilla[3], 14: ancilla[4], 15: ancilla[5], 16: ancilla[6], - 17: ancilla[7], 18: ancilla[8], 19: ancilla[9]} + expected_layouts = [trivial_layout, trivial_layout] - noise_adaptive_layout = {0: qr[6], 1: qr[7], 2: ancilla[0], 3: ancilla[1], 4: ancilla[2], - 5: qr[5], 6: qr[0], 7: ancilla[3], 8: ancilla[4], 9: ancilla[5], - 10: ancilla[6], 11: qr[1], 12: ancilla[7], 13: qr[8], 14: qr[9], - 15: ancilla[8], 16: ancilla[9], 17: qr[2], 18: qr[3], 19: qr[4]} - - # Trivial layout - expected_layout_level0 = trivial_layout - expected_layout_level1 = trivial_layout - # Dense layout - expected_layout_level2 = dense_layout - # Noise adaptive layout - expected_layout_level3 = noise_adaptive_layout - - expected_layouts = [expected_layout_level0, - expected_layout_level1, - expected_layout_level2, - expected_layout_level3] backend = FakeTokyo() result = transpile(qc, backend, optimization_level=level, seed_transpiler=42) self.assertEqual(result._layout._p2v, expected_layouts[level]) diff --git a/test/python/transpiler/test_remove_diagonal_gates_before_measure.py b/test/python/transpiler/test_remove_diagonal_gates_before_measure.py index ef77cb7f8198..7d9c8b8d13bb 100644 --- a/test/python/transpiler/test_remove_diagonal_gates_before_measure.py +++ b/test/python/transpiler/test_remove_diagonal_gates_before_measure.py @@ -219,7 +219,7 @@ class TesRemoveDiagonalControlGatesBeforeMeasure(QiskitTestCase): """ Test remove diagonal control gates before measure. """ def test_optimize_1cz_2measure(self): - """ Remove a single CzGate + """ Remove a single CZGate qr0:--Z--m--- qr0:--m--- | | | qr1:--.--|-m- ==> qr1:--|-m- @@ -244,7 +244,7 @@ def test_optimize_1cz_2measure(self): self.assertEqual(circuit_to_dag(expected), after) def test_optimize_1crz_2measure(self): - """ Remove a single CrzGate + """ Remove a single CRZGate qr0:-RZ--m--- qr0:--m--- | | | qr1:--.--|-m- ==> qr1:--|-m- @@ -269,7 +269,7 @@ def test_optimize_1crz_2measure(self): self.assertEqual(circuit_to_dag(expected), after) def test_optimize_1cu1_2measure(self): - """ Remove a single Cu1Gate + """ Remove a single CU1Gate qr0:-CU1-m--- qr0:--m--- | | | qr1:--.--|-m- ==> qr1:--|-m- @@ -323,7 +323,7 @@ class TestRemoveDiagonalGatesBeforeMeasureOveroptimizations(QiskitTestCase): """ Test situations where remove_diagonal_gates_before_measure should not optimize """ def test_optimize_1cz_1measure(self): - """ Do not remove a CzGate because measure happens on only one of the wires + """ Do not remove a CZGate because measure happens on only one of the wires Compare with test_optimize_1cz_2measure. qr0:--Z--m--- diff --git a/test/python/transpiler/test_token_swapper.py b/test/python/transpiler/test_token_swapper.py new file mode 100644 index 000000000000..30194f75b54a --- /dev/null +++ b/test/python/transpiler/test_token_swapper.py @@ -0,0 +1,120 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +# Copyright 2019 Andrew M. Childs, Eddie Schoute, Cem M. Unsal +# +# 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. + +"""Test cases for the permutation.complete package""" + +import itertools + +import networkx as nx +from numpy import random +from qiskit.transpiler.passes.routing.algorithms import ApproximateTokenSwapper +from qiskit.transpiler.passes.routing.algorithms import util + +from qiskit.test import QiskitTestCase + + +class TestGeneral(QiskitTestCase): + """The test cases""" + + def setUp(self) -> None: + """Set up test cases.""" + random.seed(0) + + def test_simple(self) -> None: + """Test a simple permutation on a path graph of size 4.""" + graph = nx.path_graph(4) + permutation = {0: 0, 1: 3, 3: 1, 2: 2} + swapper = ApproximateTokenSwapper(graph) # type: ApproximateTokenSwapper[int] + + out = list(swapper.map(permutation)) + self.assertEqual(3, len(out)) + util.swap_permutation([out], permutation) + self.assertEqual({i: i for i in range(4)}, permutation) + + def test_small(self) -> None: + """Test an inverting permutation on a small path graph of size 8""" + graph = nx.path_graph(8) + permutation = {i: 7 - i for i in range(8)} + swapper = ApproximateTokenSwapper(graph) # type: ApproximateTokenSwapper[int] + + out = list(swapper.map(permutation)) + util.swap_permutation([out], permutation) + self.assertEqual({i: i for i in range(8)}, permutation) + + def test_bug1(self) -> None: + """Tests for a bug that occured in happy swap chains of length >2.""" + graph = nx.Graph() + graph.add_edges_from([(0, 1), (0, 2), (0, 3), (0, 4), + (1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4), (3, 6)]) + permutation = {0: 4, 1: 0, 2: 3, 3: 6, 4: 2, 6: 1} + swapper = ApproximateTokenSwapper(graph) # type: ApproximateTokenSwapper[int] + + out = list(swapper.map(permutation)) + util.swap_permutation([out], permutation) + self.assertEqual({i: i for i in permutation}, permutation) + + def test_partial_simple(self) -> None: + """Test a partial mapping on a small graph.""" + graph = nx.path_graph(4) + mapping = {0: 3} + swapper = ApproximateTokenSwapper(graph) # type: ApproximateTokenSwapper[int] + out = list(swapper.map(mapping)) + self.assertEqual(3, len(out)) + util.swap_permutation([out], mapping, allow_missing_keys=True) + self.assertEqual({3: 3}, mapping) + + def test_partial_small(self) -> None: + """Test an partial inverting permutation on a small path graph of size 5""" + graph = nx.path_graph(4) + permutation = {i: 3 - i for i in range(2)} + swapper = ApproximateTokenSwapper(graph) # type: ApproximateTokenSwapper[int] + + out = list(swapper.map(permutation)) + self.assertEqual(5, len(out)) + util.swap_permutation([out], permutation, allow_missing_keys=True) + self.assertEqual({i: i for i in permutation.values()}, permutation) + + def test_large_partial_random(self) -> None: + """Test a random (partial) mapping on a large randomly generated graph""" + size = 100 + # Note that graph may have "gaps" in the node counts, i.e. the numbering is noncontiguous. + graph = nx.dense_gnm_random_graph(size, size ** 2 // 10) + graph.remove_edges_from((i, i) for i in graph.nodes) # Remove self-loops. + # Make sure the graph is connected by adding C_n + nodes = list(graph.nodes) + graph.add_edges_from((node, nodes[(i + 1) % len(nodes)]) for i, node in enumerate(nodes)) + swapper = ApproximateTokenSwapper(graph) # type: ApproximateTokenSwapper[int] + + # Generate a randomized permutation. + rand_perm = random.permutation(graph.nodes()) + permutation = dict(zip(graph.nodes(), rand_perm)) + mapping = dict(itertools.islice(permutation.items(), 0, size, 2)) # Drop every 2nd element. + + out = list(swapper.map(mapping, trials=40)) + util.swap_permutation([out], mapping, allow_missing_keys=True) + self.assertEqual({i: i for i in mapping.values()}, mapping) diff --git a/test/python/transpiler/test_unroller.py b/test/python/transpiler/test_unroller.py index f4ec79eef7bf..5c1c4d883ce2 100644 --- a/test/python/transpiler/test_unroller.py +++ b/test/python/transpiler/test_unroller.py @@ -125,7 +125,7 @@ def test_unroll_all_instructions(self): circuit.cy(qr[1], qr[2]) circuit.cz(qr[2], qr[0]) circuit.h(qr[1]) - circuit.iden(qr[0]) + circuit.i(qr[0]) circuit.rx(0.1, qr[0]) circuit.ry(0.2, qr[1]) circuit.rz(0.3, qr[2]) @@ -220,7 +220,7 @@ def test_unroll_all_instructions(self): ref_circuit.u3(0, 0, pi/2, qr[2]) ref_circuit.cx(qr[2], qr[0]) ref_circuit.u3(pi/2, 0, pi, qr[0]) - ref_circuit.iden(qr[0]) + ref_circuit.i(qr[0]) ref_circuit.u3(0.1, -pi/2, pi/2, qr[0]) ref_circuit.cx(qr[1], qr[0]) ref_circuit.u3(0, 0, 0.6, qr[0]) diff --git a/test/python/visualization/references/circuit_text_ref.txt b/test/python/visualization/references/circuit_text_ref.txt index e2a89e2967bf..4edf26f864f6 100644 --- a/test/python/visualization/references/circuit_text_ref.txt +++ b/test/python/visualization/references/circuit_text_ref.txt @@ -1,13 +1,13 @@ - ┌───┐┌───┐┌───┐ ░ ┌───┐┌───┐┌─────┐┌───┐┌─────┐┌────┐ ┌────────┐┌────────┐┌────────┐┌────────┐┌───────────┐┌──────────────┐ ┌─┐ -q_0: |0>┤ X ├┤ Y ├┤ Z ├─░─┤ H ├┤ S ├┤ Sdg ├┤ T ├┤ Tdg ├┤ Id ├─|0>─┤ Rx(pi) ├┤ Ry(pi) ├┤ Rz(pi) ├┤ U1(pi) ├┤ U2(pi,pi) ├┤ U3(pi,pi,pi) ├─X───■────■───■───■───■──────────■────────────■─────────■─────────■───────■───■─┤M├────── - └─░─┘└───┘└───┘ ░ └───┘└───┘└─────┘└───┘└─────┘└────┘ └────────┘└────────┘└────────┘└────────┘└───────────┘└──────────────┘ │ ┌─┴─┐┌─┴─┐ │ ┌─┴─┐ │pi ┌──────┴───────┐┌───┴────┐┌───┴────┐┌───┴────┐ │ │ └╥┘┌─┐ -q_1: |0>──░─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────X─┤ X ├┤ Y ├─■─┤ H ├─■───┤ U3(pi,pi,pi) ├┤ Rz(pi) ├┤ Ry(pi) ├┤ Rx(pi) ├──■───X──╫─┤M├─── - ░ └───┘└───┘ └───┘ └──────────────┘└────────┘└────────┘└────────┘┌─┴─┐ │ ║ └╥┘┌─┐ -q_2: |0>──░────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ X ├─X──╫──╫─┤M├ - ░ └───┘ ║ ║ └╥┘ - c_0: 0 ════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╩══╬══╬═ - ║ ║ - c_1: 0 ═══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╩══╬═ - ║ - c_2: 0 ══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╩═ - \ No newline at end of file + ┌───┐┌───┐┌───┐ ░ ┌───┐┌───┐┌─────┐┌───┐┌─────┐┌───┐ ┌────────┐┌────────┐┌────────┐┌────────┐┌───────────┐┌──────────────┐ ┌─┐ +q_0: |0>┤ X ├┤ Y ├┤ Z ├─░─┤ H ├┤ S ├┤ Sdg ├┤ T ├┤ Tdg ├┤ I ├─|0>─┤ Rx(pi) ├┤ Ry(pi) ├┤ Rz(pi) ├┤ U1(pi) ├┤ U2(pi,pi) ├┤ U3(pi,pi,pi) ├─X───■────■───■───■───■──────────■────────────■─────────■─────────■───────■───■─┤M├────── + └─░─┘└───┘└───┘ ░ └───┘└───┘└─────┘└───┘└─────┘└───┘ └────────┘└────────┘└────────┘└────────┘└───────────┘└──────────────┘ │ ┌─┴─┐┌─┴─┐ │ ┌─┴─┐ │pi ┌──────┴───────┐┌───┴────┐┌───┴────┐┌───┴────┐ │ │ └╥┘┌─┐ +q_1: |0>──░────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────X─┤ X ├┤ Y ├─■─┤ H ├─■───┤ U3(pi,pi,pi) ├┤ Rz(pi) ├┤ Ry(pi) ├┤ Rx(pi) ├──■───X──╫─┤M├─── + ░ └───┘└───┘ └───┘ └──────────────┘└────────┘└────────┘└────────┘┌─┴─┐ │ ║ └╥┘┌─┐ +q_2: |0>──░───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ X ├─X──╫──╫─┤M├ + ░ └───┘ ║ ║ └╥┘ + c_0: 0 ═══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╩══╬══╬═ + ║ ║ + c_1: 0 ══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╩══╬═ + ║ + c_2: 0 ═════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╩═ + \ No newline at end of file diff --git a/test/python/visualization/references/circuit_text_ref_original.txt b/test/python/visualization/references/circuit_text_ref_original.txt new file mode 100644 index 000000000000..c4c693a71966 --- /dev/null +++ b/test/python/visualization/references/circuit_text_ref_original.txt @@ -0,0 +1,12 @@ + ┌───┐┌───┐┌───┐ ░ ┌───┐┌───┐┌─────┐┌───┐┌─────┐┌────┐ ┌────────┐┌────────┐┌────────┐┌────────┐┌───────────┐┌──────────────┐ ┌─┐ +q_0: |0>┤ X ├┤ Y ├┤ Z ├─░─┤ H ├┤ S ├┤ Sdg ├┤ T ├┤ Tdg ├┤ Id ├─|0>─┤ Rx(pi) ├┤ Ry(pi) ├┤ Rz(pi) ├┤ U1(pi) ├┤ U2(pi,pi) ├┤ U3(pi,pi,pi) ├─X───■────■───■───■───■──────────■────────────■─────────■─────────■───────■───■─┤M├────── + └─░─┘└───┘└───┘ ░ └───┘└───┘└─────┘└───┘└─────┘└────┘ └────────┘└────────┘└────────┘└────────┘└───────────┘└──────────────┘ │ ┌─┴─┐┌─┴─┐ │ ┌─┴─┐ │pi ┌──────┴───────┐┌───┴────┐┌───┴────┐┌───┴────┐ │ │ └╥┘┌─┐ +q_1: |0>──░─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────X─┤ X ├┤ Y ├─■─┤ H ├─■───┤ U3(pi,pi,pi) ├┤ Rz(pi) ├┤ Ry(pi) ├┤ Rx(pi) ├──■───X──╫─┤M├─── + ░ └───┘└───┘ └───┘ └──────────────┘└────────┘└────────┘└────────┘┌─┴─┐ │ ║ └╥┘┌─┐ +q_2: |0>──░────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ X ├─X──╫──╫─┤M├ + ░ └───┘ ║ ║ └╥┘ + c_0: 0 ════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╩══╬══╬═ + ║ ║ + c_1: 0 ═══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╩══╬═ + ║ + c_2: 0 ══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╩═ diff --git a/test/python/visualization/test_circuit_text_drawer.py b/test/python/visualization/test_circuit_text_drawer.py index 676379f65101..eda1cb68e424 100644 --- a/test/python/visualization/test_circuit_text_drawer.py +++ b/test/python/visualization/test_circuit_text_drawer.py @@ -29,7 +29,7 @@ from qiskit.transpiler import Layout from qiskit.visualization import text as elements from qiskit.visualization.circuit_visualization import _text_circuit_drawer -from qiskit.extensions import HGate, U2Gate, XGate, UnitaryGate, CzGate +from qiskit.extensions import HGate, U2Gate, XGate, UnitaryGate, CZGate class TestTextDrawerElement(QiskitTestCase): @@ -965,8 +965,8 @@ def test_label_turns_to_box_2286(self): " └───────────┘"]) qr = QuantumRegister(2, 'q') circ = QuantumCircuit(qr) - circ.append(CzGate(), [qr[0], qr[1]]) - circ.append(CzGate(label='cz label'), [qr[0], qr[1]]) + circ.append(CZGate(), [qr[0], qr[1]]) + circ.append(CZGate(label='cz label'), [qr[0], qr[1]]) self.assertEqual(str(_text_circuit_drawer(circ)), expected) diff --git a/test/python/visualization/test_circuit_visualization_output.py b/test/python/visualization/test_circuit_visualization_output.py index a255c7b0bbbe..7c9bce3e7423 100644 --- a/test/python/visualization/test_circuit_visualization_output.py +++ b/test/python/visualization/test_circuit_visualization_output.py @@ -52,7 +52,7 @@ def sample_circuit(self): circuit.sdg(qr[0]) circuit.t(qr[0]) circuit.tdg(qr[0]) - circuit.iden(qr[0]) + circuit.i(qr[0]) circuit.reset(qr[0]) circuit.rx(pi, qr[0]) circuit.ry(pi, qr[0]) @@ -100,7 +100,7 @@ def test_matplotlib_drawer(self): def test_text_drawer(self): filename = self._get_resource_path('current_textplot.txt') qc = self.sample_circuit() - output = circuit_drawer(qc, filename=filename, output="text", line_length=-1) + output = circuit_drawer(qc, filename=filename, output="text", fold=-1) self.assertFilesAreEqual(filename, self.text_reference) os.remove(filename) try: diff --git a/test/randomized/test_transpiler_equivalence.py b/test/randomized/test_transpiler_equivalence.py index 7b0d6cd1050f..c3f5dee31243 100644 --- a/test/randomized/test_transpiler_equivalence.py +++ b/test/randomized/test_transpiler_equivalence.py @@ -36,16 +36,16 @@ # pylint: disable=wildcard-import,unused-wildcard-import from qiskit.extensions.standard import * -oneQ_gates = [HGate, IdGate, SGate, SdgGate, TGate, TdgGate, XGate, YGate, ZGate, Reset] -twoQ_gates = [CnotGate, CyGate, CzGate, SwapGate, CHGate] -threeQ_gates = [ToffoliGate, FredkinGate] +oneQ_gates = [HGate, IGate, SGate, SdgGate, TGate, TdgGate, XGate, YGate, ZGate, Reset] +twoQ_gates = [CXGate, CYGate, CZGate, SwapGate, CHGate] +threeQ_gates = [CCXGate, CSwapGate] oneQ_oneP_gates = [U1Gate, RXGate, RYGate, RZGate] oneQ_twoP_gates = [U2Gate] oneQ_threeP_gates = [U3Gate] -twoQ_oneP_gates = [CrzGate, RZZGate, Cu1Gate] -twoQ_threeP_gates = [Cu3Gate] +twoQ_oneP_gates = [CRZGate, RZZGate, CU1Gate] +twoQ_threeP_gates = [CU3Gate] oneQ_oneC_gates = [Measure] variadic_gates = [Barrier]