diff --git a/qiskit/circuit/__init__.py b/qiskit/circuit/__init__.py index 140415e7988f..3f9afacaef5b 100644 --- a/qiskit/circuit/__init__.py +++ b/qiskit/circuit/__init__.py @@ -234,6 +234,7 @@ from .controlledgate import ControlledGate from .instruction import Instruction from .instructionset import InstructionSet +from .operation import Operation from .barrier import Barrier from .delay import Delay from .measure import Measure diff --git a/qiskit/circuit/barrier.py b/qiskit/circuit/barrier.py index c10b32069595..08395d4d4d3c 100644 --- a/qiskit/circuit/barrier.py +++ b/qiskit/circuit/barrier.py @@ -14,9 +14,10 @@ from qiskit.exceptions import QiskitError from .instruction import Instruction +from .operation import Operation -class Barrier(Instruction): +class Barrier(Instruction, Operation): """Barrier instruction.""" _directive = True diff --git a/qiskit/circuit/gate.py b/qiskit/circuit/gate.py index a9bd6bf3a577..c3136a84a7ef 100644 --- a/qiskit/circuit/gate.py +++ b/qiskit/circuit/gate.py @@ -20,9 +20,10 @@ from qiskit.circuit.parameterexpression import ParameterExpression from qiskit.circuit.exceptions import CircuitError from .instruction import Instruction +from .operation import Operation -class Gate(Instruction): +class Gate(Instruction, Operation): """Unitary gate.""" def __init__( diff --git a/qiskit/circuit/instruction.py b/qiskit/circuit/instruction.py index 2a7f4d924d9a..7b8cbc4b3549 100644 --- a/qiskit/circuit/instruction.py +++ b/qiskit/circuit/instruction.py @@ -76,9 +76,9 @@ def __init__(self, name, num_qubits, num_clbits, params, duration=None, unit="dt raise CircuitError( "bad instruction dimensions: %d qubits, %d clbits." % num_qubits, num_clbits ) - self.name = name - self.num_qubits = num_qubits - self.num_clbits = num_clbits + self._name = name + self._num_qubits = num_qubits + self._num_clbits = num_clbits self._params = [] # a list of gate params stored # Custom instruction label @@ -546,3 +546,33 @@ def condition_bits(self) -> List[Clbit]: return [self.condition[0]] else: # ClassicalRegister return list(self.condition[0]) + + @property + def name(self): + """Return the name.""" + return self._name + + @name.setter + def name(self, name): + """Set the name.""" + self._name = name + + @property + def num_qubits(self): + """Return the number of qubits.""" + return self._num_qubits + + @num_qubits.setter + def num_qubits(self, num_qubits): + """Set num_qubits.""" + self._num_qubits = num_qubits + + @property + def num_clbits(self): + """Return the number of clbits.""" + return self._num_clbits + + @num_clbits.setter + def num_clbits(self, num_clbits): + """Set num_clbits.""" + self._num_clbits = num_clbits diff --git a/qiskit/circuit/measure.py b/qiskit/circuit/measure.py index 8da0db586c0d..57e4c5e4db12 100644 --- a/qiskit/circuit/measure.py +++ b/qiskit/circuit/measure.py @@ -17,10 +17,11 @@ import warnings from qiskit.circuit.instruction import Instruction +from qiskit.circuit.operation import Operation from qiskit.circuit.exceptions import CircuitError -class Measure(Instruction): +class Measure(Instruction, Operation): """Quantum measurement in the computational basis.""" def __init__(self): diff --git a/qiskit/circuit/operation.py b/qiskit/circuit/operation.py new file mode 100644 index 000000000000..f2e898c069f9 --- /dev/null +++ b/qiskit/circuit/operation.py @@ -0,0 +1,61 @@ +# 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. + +"""Quantum Operation Mixin.""" + +from abc import ABC, abstractmethod + + +class Operation(ABC): + """Quantum Operation Interface Class. + For objects that can be added to a :class:`~qiskit.circuit.QuantumCircuit`. + These objects include :class:`~qiskit.circuit.Gate`, :class:`~qiskit.circuit.Reset`, + :class:`~qiskit.circuit.Barrier`, :class:`~qiskit.circuit.Measure`, + and operators such as :class:`~qiskit.quantum_info.Clifford`. + The main purpose is to add an :class:`~qiskit.circuit.Operation` to a + :class:`~qiskit.circuit.QuantumCircuit` without synthesizing it before the transpilation. + + Example: + + Add a Clifford and a Toffoli gate to a QuantumCircuit. + + .. jupyter-execute:: + + from qiskit import QuantumCircuit + from qiskit.quantum_info import Clifford, random_clifford + + qc = QuantumCircuit(3) + cliff = random_clifford(2) + qc.append(cliff, [0, 1]) + qc.ccx(0, 1, 2) + qc.draw() + """ + + __slots__ = () + + @property + @abstractmethod + def name(self): + """Unique string identifier for operation type.""" + raise NotImplementedError + + @property + @abstractmethod + def num_qubits(self): + """Number of qubits.""" + raise NotImplementedError + + @property + @abstractmethod + def num_clbits(self): + """Number of classical bits.""" + raise NotImplementedError diff --git a/qiskit/circuit/reset.py b/qiskit/circuit/reset.py index e986c1ba3390..b5a36c51f7a1 100644 --- a/qiskit/circuit/reset.py +++ b/qiskit/circuit/reset.py @@ -17,9 +17,10 @@ import warnings from qiskit.circuit.instruction import Instruction +from qiskit.circuit.operation import Operation -class Reset(Instruction): +class Reset(Instruction, Operation): """Qubit reset.""" def __init__(self): diff --git a/qiskit/extensions/quantum_initializer/initializer.py b/qiskit/extensions/quantum_initializer/initializer.py index fd929b57ed1c..6488f4f7ef11 100644 --- a/qiskit/extensions/quantum_initializer/initializer.py +++ b/qiskit/extensions/quantum_initializer/initializer.py @@ -20,7 +20,7 @@ from qiskit.exceptions import QiskitError from qiskit.circuit import QuantumCircuit from qiskit.circuit import QuantumRegister -from qiskit.circuit import Instruction +from qiskit.circuit import Instruction, Operation from qiskit.circuit.exceptions import CircuitError from qiskit.circuit.library.standard_gates.x import CXGate, XGate from qiskit.circuit.library.standard_gates.h import HGate @@ -32,7 +32,7 @@ _EPS = 1e-10 # global variable used to chop very small numbers to zero -class Initialize(Instruction): +class Initialize(Instruction, Operation): """Complex amplitude initialization. Class that implements the (complex amplitude) initialization of some diff --git a/qiskit/extensions/quantum_initializer/isometry.py b/qiskit/extensions/quantum_initializer/isometry.py index 419c18df8881..121cef4ed4aa 100644 --- a/qiskit/extensions/quantum_initializer/isometry.py +++ b/qiskit/extensions/quantum_initializer/isometry.py @@ -25,6 +25,7 @@ from qiskit.circuit.exceptions import CircuitError from qiskit.circuit.instruction import Instruction +from qiskit.circuit.operation import Operation from qiskit.circuit.quantumcircuit import QuantumCircuit from qiskit.circuit.quantumregister import QuantumRegister from qiskit.exceptions import QiskitError @@ -35,7 +36,7 @@ _EPS = 1e-10 # global variable used to chop very small numbers to zero -class Isometry(Instruction): +class Isometry(Instruction, Operation): """ Decomposition of arbitrary isometries from m to n qubits. In particular, this allows to decompose unitaries (m=n) and to do state preparation (m=0). diff --git a/qiskit/quantum_info/operators/dihedral/dihedral.py b/qiskit/quantum_info/operators/dihedral/dihedral.py index 0d08851ec73b..3e2f0c32b975 100644 --- a/qiskit/quantum_info/operators/dihedral/dihedral.py +++ b/qiskit/quantum_info/operators/dihedral/dihedral.py @@ -23,12 +23,12 @@ from qiskit.quantum_info.operators.scalar_op import ScalarOp from qiskit.quantum_info.synthesis.cnotdihedral_decompose import decompose_cnotdihedral from qiskit.quantum_info.operators.mixins import generate_apidocs, AdjointMixin -from qiskit.circuit import QuantumCircuit, Instruction +from qiskit.circuit import QuantumCircuit, Instruction, Operation from .dihedral_circuits import _append_circuit from .polynomial import SpecialPolynomial -class CNOTDihedral(BaseOperator, AdjointMixin): +class CNOTDihedral(BaseOperator, AdjointMixin, Operation): """An N-qubit operator from the CNOT-Dihedral group. The CNOT-Dihedral group is generated by the quantum gates, @@ -160,6 +160,16 @@ def __init__(self, data=None, num_qubits=None, validate=True): if validate and not self._is_valid(): raise QiskitError("Invalid CNOTDihedral element.") + @property + def name(self): + """Unique string identifier for operation type.""" + return "cnotdihedral" + + @property + def num_clbits(self): + """Number of classical bits.""" + return 0 + def _z2matmul(self, left, right): """Compute product of two n x n z2 matrices.""" prod = np.mod(np.dot(left, right), 2) diff --git a/qiskit/quantum_info/operators/symplectic/clifford.py b/qiskit/quantum_info/operators/symplectic/clifford.py index 13dd35acec0d..cd4a48c88e6e 100644 --- a/qiskit/quantum_info/operators/symplectic/clifford.py +++ b/qiskit/quantum_info/operators/symplectic/clifford.py @@ -16,7 +16,7 @@ import numpy as np from qiskit.exceptions import QiskitError -from qiskit.circuit import QuantumCircuit, Instruction +from qiskit.circuit import QuantumCircuit, Instruction, Operation from qiskit.circuit.library.standard_gates import IGate, XGate, YGate, ZGate, HGate, SGate from qiskit.quantum_info.operators.base_operator import BaseOperator from qiskit.quantum_info.operators.operator import Operator @@ -27,7 +27,7 @@ from .clifford_circuits import _append_circuit -class Clifford(BaseOperator, AdjointMixin): +class Clifford(BaseOperator, AdjointMixin, Operation): """An N-qubit unitary operator from the Clifford group. **Representation** @@ -137,6 +137,21 @@ def __init__(self, data, validate=True): # Initialize BaseOperator super().__init__(num_qubits=self._table.num_qubits) + @property + def name(self): + """Unique string identifier for operation type.""" + return "clifford" + + @property + def num_qubits(self): + """Number of qubits.""" + return self._table.num_qubits + + @property + def num_clbits(self): + """Number of classical bits.""" + return 0 + def __repr__(self): return f"Clifford({repr(self.table)})" diff --git a/qiskit/quantum_info/operators/symplectic/pauli.py b/qiskit/quantum_info/operators/symplectic/pauli.py index 5776ab8da1d5..236a4cc89296 100644 --- a/qiskit/quantum_info/operators/symplectic/pauli.py +++ b/qiskit/quantum_info/operators/symplectic/pauli.py @@ -20,7 +20,7 @@ import numpy as np -from qiskit.circuit import Instruction, QuantumCircuit +from qiskit.circuit import Instruction, QuantumCircuit, Operation from qiskit.circuit.barrier import Barrier from qiskit.circuit.library.generalized_gates import PauliGate from qiskit.circuit.library.standard_gates import IGate, XGate, YGate, ZGate @@ -31,7 +31,7 @@ from qiskit.utils.deprecation import deprecate_function -class Pauli(BasePauli): +class Pauli(BasePauli, Operation): r"""N-qubit Pauli operator. This class represents an operator :math:`P` from the full :math:`n`-qubit @@ -202,6 +202,16 @@ def __init__(self, data=None, x=None, *, z=None, label=None): raise QiskitError("Input is not a single Pauli") super().__init__(base_z, base_x, base_phase) + @property + def name(self): + """Unique string identifier for operation type.""" + return "pauli" + + @property + def num_clbits(self): + """Number of classical bits.""" + return 0 + def __repr__(self): """Display representation.""" return f"Pauli('{self.__str__()}')" diff --git a/releasenotes/notes/Operation-abstract-base-class-99f46180c7ddaa6b.yaml b/releasenotes/notes/Operation-abstract-base-class-99f46180c7ddaa6b.yaml new file mode 100644 index 000000000000..b4d13418d7ad --- /dev/null +++ b/releasenotes/notes/Operation-abstract-base-class-99f46180c7ddaa6b.yaml @@ -0,0 +1,10 @@ +--- +features: + - | + A new :class:`~qiskit.circuit.Operation` base class for objects + that can be added to a :class:`~qiskit.circuit.QuantumCircuit`. + These objects include :class:`~qiskit.circuit.Gate`, :class:`~qiskit.circuit.Reset`, + :class:`~qiskit.circuit.Barrier`, :class:`~qiskit.circuit.Measure`, + and operators such as :class:`~qiskit.quantum_info.Clifford`. + The main purpose is to add an :class:`~qiskit.circuit.Operation` to a + :class:`~qiskit.circuit.QuantumCircuit` without synthesizing it before the transpilation. diff --git a/test/python/circuit/test_operation.py b/test/python/circuit/test_operation.py new file mode 100644 index 000000000000..35f417a8aefd --- /dev/null +++ b/test/python/circuit/test_operation.py @@ -0,0 +1,185 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2021. +# +# 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 Qiskit's Operation class.""" + +import unittest + +import numpy as np + +from qiskit.test import QiskitTestCase +from qiskit.circuit import QuantumCircuit, Barrier, Measure, Reset, Gate +from qiskit.circuit.library import XGate, CXGate +from qiskit.quantum_info.operators import Clifford, CNOTDihedral, Pauli +from qiskit.extensions.quantum_initializer import Initialize, Isometry + + +class TestOperationClass(QiskitTestCase): + """Testing qiskit.circuit.Operation""" + + def test_measure_as_operation(self): + """Test that we can instantiate an object of class + :class:`~qiskit.circuit.Measure` and that + it has the expected name, num_qubits and num_clbits. + """ + op = Measure() + self.assertTrue(op.name == "measure") + self.assertTrue(op.num_qubits == 1) + self.assertTrue(op.num_clbits == 1) + + def test_reset_as_operation(self): + """Test that we can instantiate an object of class + :class:`~qiskit.circuit.Reset` and that + it has the expected name, num_qubits and num_clbits. + """ + op = Reset() + self.assertTrue(op.name == "reset") + self.assertTrue(op.num_qubits == 1) + self.assertTrue(op.num_clbits == 0) + + def test_barrier_as_operation(self): + """Test that we can instantiate an object of class + :class:`~qiskit.circuit.Barrier` and that + it has the expected name, num_qubits and num_clbits. + """ + num_qubits = 4 + op = Barrier(num_qubits) + self.assertTrue(op.name == "barrier") + self.assertTrue(op.num_qubits == num_qubits) + self.assertTrue(op.num_clbits == 0) + + def test_clifford_as_operation(self): + """Test that we can instantiate an object of class + :class:`~qiskit.quantum_info.operators.Clifford` and that + it has the expected name, num_qubits and num_clbits. + """ + num_qubits = 4 + qc = QuantumCircuit(4, 0) + qc.h(2) + qc.cx(0, 1) + op = Clifford(qc) + self.assertTrue(op.name == "clifford") + self.assertTrue(op.num_qubits == num_qubits) + self.assertTrue(op.num_clbits == 0) + + def test_cnotdihedral_as_operation(self): + """Test that we can instantiate an object of class + :class:`~qiskit.quantum_info.operators.CNOTDihedral` and that + it has the expected name, num_qubits and num_clbits. + """ + num_qubits = 4 + qc = QuantumCircuit(4) + qc.t(0) + qc.x(0) + qc.t(0) + op = CNOTDihedral(qc) + self.assertTrue(op.name == "cnotdihedral") + self.assertTrue(op.num_qubits == num_qubits) + self.assertTrue(op.num_clbits == 0) + + def test_pauli_as_operation(self): + """Test that we can instantiate an object of class + :class:`~qiskit.quantum_info.operators.Pauli` and that + it has the expected name, num_qubits and num_clbits. + """ + num_qubits = 4 + op = Pauli("I" * num_qubits) + self.assertTrue(op.name == "pauli") + self.assertTrue(op.num_qubits == num_qubits) + self.assertTrue(op.num_clbits == 0) + + def test_isometry_as_operation(self): + """Test that we can instantiate an object of class + :class:`~qiskit.extensions.quantum_initializer.Isometry` and that + it has the expected name, num_qubits and num_clbits. + """ + op = Isometry(np.eye(4, 4), 3, 2) + self.assertTrue(op.name == "isometry") + self.assertTrue(op.num_qubits == 7) + self.assertTrue(op.num_clbits == 0) + + def test_initialize_as_operation(self): + """Test that we can instantiate an object of class + :class:`~qiskit.extensions.quantum_initializer.Initialize` and that + it has the expected name, num_qubits and num_clbits. + """ + desired_vector = [0.5, 0.5, 0.5, 0.5] + op = Initialize(desired_vector) + self.assertTrue(op.name == "initialize") + self.assertTrue(op.num_qubits == 2) + self.assertTrue(op.num_clbits == 0) + + def test_gate_as_operation(self): + """Test that we can instantiate an object of class + :class:`~qiskit.circuit.Gate` and that + it has the expected name, num_qubits and num_clbits. + """ + name = "test_gate_name" + num_qubits = 3 + op = Gate(name, num_qubits, []) + self.assertTrue(op.name == name) + self.assertTrue(op.num_qubits == num_qubits) + self.assertTrue(op.num_clbits == 0) + + def test_xgate_as_operation(self): + """Test that we can instantiate an object of class + :class:`~qiskit.circuit.library.XGate` and that + it has the expected name, num_qubits and num_clbits. + """ + op = XGate() + self.assertTrue(op.name == "x") + self.assertTrue(op.num_qubits == 1) + self.assertTrue(op.num_clbits == 0) + + def test_cxgate_as_operation(self): + """Test that we can instantiate an object of class + :class:`~qiskit.circuit.library.CXGate` and that + it has the expected name, num_qubits and num_clbits. + """ + op = CXGate() + self.assertTrue(op.name == "cx") + self.assertTrue(op.num_qubits == 2) + self.assertTrue(op.num_clbits == 0) + + def test_can_append_to_quantum_circuit(self): + """Test that we can add various objects with Operation interface to a Quantum Circuit.""" + qc = QuantumCircuit(6, 1) + qc.append(XGate(), [2]) + qc.append(Barrier(3), [1, 2, 4]) + qc.append(CXGate(), [0, 1]) + qc.append(Measure(), [1], [0]) + qc.append(Reset(), [0]) + qc.cx(3, 4) + qc.append(Gate("some_gate", 3, []), [1, 2, 3]) + qc.append(Initialize([0.5, 0.5, 0.5, 0.5]), [4, 5]) + qc.append(Isometry(np.eye(4, 4), 0, 0), [3, 4]) + qc.append(Pauli("II"), [0, 1]) + + # Appending Clifford + circ1 = QuantumCircuit(2) + circ1.h(1) + circ1.cx(0, 1) + qc.append(Clifford(circ1), [0, 1]) + + # Appending CNOTDihedral + circ2 = QuantumCircuit(2) + circ2.t(0) + circ2.x(0) + circ2.t(1) + qc.append(CNOTDihedral(circ2), [2, 3]) + + # If we got to here, we have successfully appended everything to qc + self.assertIsInstance(qc, QuantumCircuit) + + +if __name__ == "__main__": + unittest.main()