From 068525aa208af23194c0741cc7d4e68a07e0416c Mon Sep 17 00:00:00 2001 From: Alexander Ivrii Date: Wed, 20 Apr 2022 14:38:53 +0300 Subject: [PATCH 01/27] Adding Clifford to a QuantumCircuit natively; adding some tests; adding a preliminary Clifford optimization pass --- qiskit/circuit/__init__.py | 2 + qiskit/circuit/instruction.py | 3 +- qiskit/circuit/instructionset.py | 6 +- qiskit/circuit/operation.py | 23 +++ qiskit/circuit/quantumcircuit.py | 25 +-- qiskit/quantum_info/operators/operator.py | 8 +- .../operators/symplectic/clifford.py | 38 ++++- .../passes/optimization/optimize_cliffords.py | 83 ++++++++++ test/python/circuit/test_instructions.py | 2 +- test/python/circuit/test_native_operations.py | 72 +++++++++ .../python/transpiler/test_clifford_passes.py | 152 ++++++++++++++++++ 11 files changed, 394 insertions(+), 20 deletions(-) create mode 100644 qiskit/transpiler/passes/optimization/optimize_cliffords.py create mode 100644 test/python/circuit/test_native_operations.py create mode 100644 test/python/transpiler/test_clifford_passes.py diff --git a/qiskit/circuit/__init__.py b/qiskit/circuit/__init__.py index 140415e7988f..c6fe21944f28 100644 --- a/qiskit/circuit/__init__.py +++ b/qiskit/circuit/__init__.py @@ -192,6 +192,7 @@ Reset Instruction InstructionSet + Operation EquivalenceLibrary Control Flow Operations @@ -234,6 +235,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/instruction.py b/qiskit/circuit/instruction.py index d1bd8e2cf474..8bce98a2678c 100644 --- a/qiskit/circuit/instruction.py +++ b/qiskit/circuit/instruction.py @@ -42,12 +42,13 @@ from qiskit.circuit.classicalregister import ClassicalRegister, Clbit from qiskit.qobj.qasm_qobj import QasmQobjInstruction from qiskit.circuit.parameter import ParameterExpression +from qiskit.circuit.operation import Operation from .tools import pi_check _CUTOFF_PRECISION = 1e-10 -class Instruction: +class Instruction(Operation): """Generic quantum instruction.""" # Class attribute to treat like barrier for transpiler, unroller, drawer diff --git a/qiskit/circuit/instructionset.py b/qiskit/circuit/instructionset.py index e294d430ff43..a09048e3b9d1 100644 --- a/qiskit/circuit/instructionset.py +++ b/qiskit/circuit/instructionset.py @@ -19,8 +19,8 @@ from typing import Callable, Optional, Tuple, Union from qiskit.circuit.exceptions import CircuitError -from .instruction import Instruction from .classicalregister import Clbit, ClassicalRegister +from .operation import Operation # ClassicalRegister is hashable, and generally the registers in a circuit are completely fixed after @@ -150,8 +150,8 @@ def __getitem__(self, i): def add(self, gate, qargs, cargs): """Add an instruction and its context (where it is attached).""" - if not isinstance(gate, Instruction): - raise CircuitError("attempt to add non-Instruction" + " to InstructionSet") + if not isinstance(gate, Operation): + raise CircuitError("attempt to add non-Operation" + " to InstructionSet") self.instructions.append(gate) self.qargs.append(qargs) self.cargs.append(cargs) diff --git a/qiskit/circuit/operation.py b/qiskit/circuit/operation.py index f2e898c069f9..da84923d94f1 100644 --- a/qiskit/circuit/operation.py +++ b/qiskit/circuit/operation.py @@ -59,3 +59,26 @@ def num_qubits(self): def num_clbits(self): """Number of classical bits.""" raise NotImplementedError + + @abstractmethod + def broadcast_arguments(self, qargs, cargs): + """Expanding (broadcasting) arguments.""" + raise NotImplementedError + + @property + @abstractmethod + def _directive(self): + """Class attribute to treat like barrier for transpiler, unroller, drawer.""" + raise NotImplementedError + + # @property + # @abstractmethod + # def condition(self): + # """Condition for when the instruction has a conditional if.""" + # raise NotImplementedError + + @property + @abstractmethod + def definition(self): + """Definition of the operation in terms of more basic gates.""" + raise NotImplementedError diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index 7951277be2e0..52943854da2f 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -52,6 +52,7 @@ from .parametertable import ParameterTable, ParameterView from .parametervector import ParameterVector, ParameterVectorElement from .instructionset import InstructionSet +from .operation import Operation from .register import Register from .bit import Bit from .quantumcircuitdata import QuantumCircuitData @@ -1160,7 +1161,7 @@ def _resolve_classical_resource(self, specifier): def append( self, - instruction: Instruction, + instruction: Operation, qargs: Optional[Sequence[QubitSpecifier]] = None, cargs: Optional[Sequence[ClbitSpecifier]] = None, ) -> InstructionSet: @@ -1180,20 +1181,20 @@ def append( CircuitError: if object passed is neither subclass nor an instance of Instruction """ # Convert input to instruction - if not isinstance(instruction, Instruction) and not hasattr(instruction, "to_instruction"): - if issubclass(instruction, Instruction): + if not isinstance(instruction, Operation) and not hasattr(instruction, "to_instruction"): + if issubclass(instruction, Operation): raise CircuitError( - "Object is a subclass of Instruction, please add () to " + "Object is a subclass of Operation, please add () to " "pass an instance of this object." ) raise CircuitError( - "Object to append must be an Instruction or have a to_instruction() method." + "Object to append must be an Operation or have a to_instruction() method." ) - if not isinstance(instruction, Instruction) and hasattr(instruction, "to_instruction"): + if not isinstance(instruction, Operation) and hasattr(instruction, "to_instruction"): instruction = instruction.to_instruction() - if not isinstance(instruction, Instruction): - raise CircuitError("object is not an Instruction.") + if not isinstance(instruction, Operation): + raise CircuitError("object is not an Operation.") # Make copy of parameterized gate instances if hasattr(instruction, "params"): @@ -1218,7 +1219,7 @@ def append( def _append( self, - instruction: Instruction, + instruction: Operation, qargs: Sequence[Qubit], cargs: Sequence[Clbit], ) -> Instruction: @@ -1261,7 +1262,11 @@ def _append( return instruction - def _update_parameter_table(self, instruction: Instruction) -> Instruction: + def _update_parameter_table(self, instruction: Operation) -> Operation: + # A generic Operation object at the moment does not require to have params. + if not hasattr(instruction, "params"): + return instruction + for param_index, param in enumerate(instruction.params): if isinstance(param, (ParameterExpression, QuantumCircuit)): # Scoped constructs like the control-flow ops use QuantumCircuit as a parameter. diff --git a/qiskit/quantum_info/operators/operator.py b/qiskit/quantum_info/operators/operator.py index c23b83560454..6ba029f3dac3 100644 --- a/qiskit/quantum_info/operators/operator.py +++ b/qiskit/quantum_info/operators/operator.py @@ -21,7 +21,7 @@ import numpy as np from qiskit.circuit.quantumcircuit import QuantumCircuit -from qiskit.circuit.instruction import Instruction +from qiskit.circuit.operation import Operation from qiskit.circuit.library.standard_gates 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 @@ -53,7 +53,7 @@ def __init__(self, data, input_dims=None, output_dims=None): Args: data (QuantumCircuit or - Instruction or + Operation or BaseOperator or matrix): data to initialize operator. input_dims (tuple): the input subsystem dimensions. @@ -75,7 +75,7 @@ def __init__(self, data, input_dims=None, output_dims=None): 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)): + elif isinstance(data, (QuantumCircuit, Operation)): # 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 @@ -514,7 +514,7 @@ def _init_instruction(cls, instruction): @classmethod def _instruction_to_matrix(cls, obj): """Return Operator for instruction if defined or None otherwise.""" - if not isinstance(obj, Instruction): + if not isinstance(obj, Operation): raise QiskitError("Input is not an instruction.") mat = None if hasattr(obj, "to_matrix"): diff --git a/qiskit/quantum_info/operators/symplectic/clifford.py b/qiskit/quantum_info/operators/symplectic/clifford.py index 90b0ec2d3503..129effafd280 100644 --- a/qiskit/quantum_info/operators/symplectic/clifford.py +++ b/qiskit/quantum_info/operators/symplectic/clifford.py @@ -23,11 +23,12 @@ from qiskit.quantum_info.operators.scalar_op import ScalarOp from qiskit.quantum_info.synthesis.clifford_decompose import decompose_clifford from qiskit.quantum_info.operators.mixins import generate_apidocs, AdjointMixin +from qiskit.circuit.operation import Operation from .stabilizer_table import StabilizerTable 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** @@ -101,6 +102,10 @@ class Clifford(BaseOperator, AdjointMixin): `arXiv:quant-ph/0406196 `_ """ + # Fields that are currently required to add an object as an Operation. + condition = None + _directive = False + def __array__(self, dtype=None): if dtype: return np.asarray(self.to_matrix(), dtype=dtype) @@ -134,6 +139,9 @@ def __init__(self, data, validate=True): "Invalid Clifford. Input StabilizerTable is not a valid symplectic matrix." ) + # When required, we will compute the QuantumCircuit for this Clifford. + self._definition = None + # Initialize BaseOperator super().__init__(num_qubits=self._table.num_qubits) @@ -540,6 +548,34 @@ def _pad_with_identity(self, clifford, qargs): return padded + def broadcast_arguments(self, qargs, cargs): + """ + Broadcasting of the arguments. + This code is currently copied from Instruction. + This will be cleaned up when broadcasting is moved + to a separate model. + + Args: + qargs (List): List of quantum bit arguments. + cargs (List): List of classical bit arguments. + + Yields: + Tuple(List, List): A tuple with single arguments. + """ + + # [[q[0], q[1]], [c[0], c[1]]] -> [q[0], c[0]], [q[1], c[1]] + flat_qargs = [qarg for sublist in qargs for qarg in sublist] + flat_cargs = [carg for sublist in cargs for carg in sublist] + yield flat_qargs, flat_cargs + + @property + def definition(self): + """Computes and returns the circuit for this Clifford.""" + if self._definition is None: + self._definition = self.to_circuit() + + return self._definition + # Update docstrings for API docs generate_apidocs(Clifford) diff --git a/qiskit/transpiler/passes/optimization/optimize_cliffords.py b/qiskit/transpiler/passes/optimization/optimize_cliffords.py new file mode 100644 index 000000000000..3870b8644800 --- /dev/null +++ b/qiskit/transpiler/passes/optimization/optimize_cliffords.py @@ -0,0 +1,83 @@ +# 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. + +"""Combine consecutive Cliffords over the same qubits.""" + +from qiskit.transpiler.basepasses import TransformationPass +from qiskit.quantum_info.operators import Clifford + + +class OptimizeCliffords(TransformationPass): + """Combine consecutive Cliffords over the same qubits. + For now, this only serves as an example of extra capabilities enabled by storing + Cliffords natively on the circuit. + """ + + def run(self, dag): + """Run the OptimizeCliffords pass on `dag`. + + Args: + dag (DAGCircuit): the DAG to be optimized. + + Returns: + DAGCircuit: the optimized DAG. + """ + + blocks = [] + prev_node = None + cur_block = [] + + # Iterate over all nodes and collect consecutive Cliffords over the + # same qubits. In this very first proof-of-concept implementation + # we require the same ordering of qubits, but this restriction will + # be shortly removed. + for node in dag.op_nodes(): + if isinstance(node.op, Clifford): + if prev_node is None: + blocks.append(cur_block) + cur_block = [node] + else: + if prev_node.qargs == node.qargs: + cur_block.append(node) + else: + blocks.append(cur_block) + cur_block = [node] + + prev_node = node + + else: + # not a clifford + blocks.append(cur_block) + prev_node = None + cur_block = [] + + blocks.append(cur_block) + + # Replace every discovered block of cliffords by a single clifford + # based on the Cliffords' compose function. + for cur_nodes in blocks: + # Create clifford functions only out of blocks with at least 2 gates + if len(cur_nodes) <= 1: + continue + + wire_pos_map = dict((qb, ix) for ix, qb in enumerate(cur_nodes[0].qargs)) + + # Construct a linear circuit + cliff = cur_nodes[0].op + for i, node in enumerate(cur_nodes): + if i > 0: + cliff = Clifford.compose(node.op, cliff, front=True) + + # Replace the block by the composed clifford + dag.replace_block_with_op(cur_nodes, cliff, wire_pos_map, cycle_check=False) + + return dag diff --git a/test/python/circuit/test_instructions.py b/test/python/circuit/test_instructions.py index e346e77d453f..276f98c1fbe8 100644 --- a/test/python/circuit/test_instructions.py +++ b/test/python/circuit/test_instructions.py @@ -400,7 +400,7 @@ def test_instance_of_instruction(self): qr = QuantumRegister(2) qc = QuantumCircuit(qr) - with self.assertRaisesRegex(CircuitError, r"Object is a subclass of Instruction"): + with self.assertRaisesRegex(CircuitError, r"Object is a subclass of Operation"): qc.append(HGate, qr[:], []) def test_repr_of_instructions(self): diff --git a/test/python/circuit/test_native_operations.py b/test/python/circuit/test_native_operations.py new file mode 100644 index 000000000000..69dba803eda8 --- /dev/null +++ b/test/python/circuit/test_native_operations.py @@ -0,0 +1,72 @@ +# 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. + +"""This is a temporary test file to experiment with natively adding operations +to a quantum circuit.""" + +from qiskit.circuit import QuantumCircuit +from qiskit.quantum_info import Clifford +from qiskit.compiler.transpiler import circuit_to_dag, dag_to_circuit +from qiskit.transpiler.passes import Unroll3qOrMore +from qiskit.dagcircuit.dagnode import DAGOpNode + + +def create_clifford(): + """Comment so that pylint does not complain.""" + qc = QuantumCircuit(3) + qc.cx(0, 1) + qc.h(0) + qc.s(1) + qc.swap(1, 2) + # print(qc) + cliff = Clifford(qc) + print(cliff) + return cliff + + +def show_circuit(circ): + """Comment so that pylint does not complain.""" + for inst, qargs, cargs in circ.data: + print(f" {type(inst)}, {qargs}, {cargs}") + + +def show_dag(dag): + """Comment so that pylint does not complain.""" + for node in dag.topological_nodes(): + print(f"-- {type(node)}") + if isinstance(node, DAGOpNode): + print(f"---{type(node.op)}, {node.op}") + + +def run_test(): + """Comment so that pylint does not complain.""" + print("Creating Clifford") + cliff = create_clifford() + qc0 = QuantumCircuit(4) + print("Appending Clifford") + qc0.append(cliff, [0, 1, 2]) + print(qc0) + show_circuit(qc0) + print("Converting to dag") + dag1 = circuit_to_dag(qc0) + print(dag1) + show_dag(dag1) + print("Running transpiler pass on dag") + dag2 = Unroll3qOrMore().run(dag1) + print(dag2) + print("Converting back to circuit") + qc3 = dag_to_circuit(dag2) + print(qc3) + + +if __name__ == "__main__": + run_test() diff --git a/test/python/transpiler/test_clifford_passes.py b/test/python/transpiler/test_clifford_passes.py new file mode 100644 index 000000000000..f30801097e2a --- /dev/null +++ b/test/python/transpiler/test_clifford_passes.py @@ -0,0 +1,152 @@ +# 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 transpiler passes that deal with Cliffords.""" + +import unittest + +from qiskit.circuit import QuantumCircuit, Gate +from qiskit.transpiler.passes.optimization.optimize_cliffords import OptimizeCliffords +from qiskit.test import QiskitTestCase +from qiskit.quantum_info.operators import Clifford +from qiskit.transpiler import PassManager +from qiskit.quantum_info import Operator + + +class TestCliffordPasses(QiskitTestCase): + """Tests to verify correctness of the transpiler passes that deal with Cliffords.""" + + def create_cliff1(self): + """Creates a simple Clifford.""" + qc = QuantumCircuit(3) + qc.h(0) + qc.cx(0, 1) + qc.cx(1, 2) + qc.s(2) + return Clifford(qc) + + def create_cliff2(self): + """Creates another simple Clifford.""" + qc = QuantumCircuit(3) + qc.cx(0, 1) + qc.h(0) + qc.h(1) + qc.h(2) + qc.cx(1, 2) + qc.s(2) + return Clifford(qc) + + def create_cliff3(self): + """Creates a third Clifford which is the composition of the previous two.""" + qc = QuantumCircuit(3) + qc.h(0) + qc.cx(0, 1) + qc.cx(1, 2) + qc.s(2) + qc.cx(0, 1) + qc.h(0) + qc.h(1) + qc.h(2) + qc.cx(1, 2) + qc.s(2) + return Clifford(qc) + + def test_circuit_with_cliffords(self): + """Test that Cliffords get stored natively on a QuantumCircuit, + and that QuantumCircuit's decompose() replaces Clifford with gates.""" + + # Create a circuit with 2 cliffords and four other gates + cliff1 = self.create_cliff1() + cliff2 = self.create_cliff2() + qc = QuantumCircuit(4) + qc.h(0) + qc.cx(2, 0) + qc.append(cliff1, [3, 0, 2]) + qc.swap(1, 3) + qc.append(cliff2, [1, 2, 3]) + qc.h(3) + # print(qc) + + # Check that there are indeed two Clifford objects in the circuit, + # and that these are not gates. + cliffords = [inst for inst, _, _ in qc.data if isinstance(inst, Clifford)] + gates = [inst for inst, _, _ in qc.data if isinstance(inst, Gate)] + self.assertEqual(len(cliffords), 2) + self.assertEqual(len(gates), 4) + + # Check that calling QuantumCircuit's decompose(), no Clifford objects remain + qc2 = qc.decompose() + # print(qc2) + cliffords2 = [inst for inst, _, _ in qc2.data if isinstance(inst, Clifford)] + self.assertEqual(len(cliffords2), 0) + + def test_can_construct_operator(self): + """Test that we can construct an Operator from a circuit that + contains a Clifford gate.""" + + cliff = self.create_cliff1() + qc = QuantumCircuit(4) + qc.append(cliff, [3, 1, 2]) + + # Create an operator from the decomposition of qc into gates + op1 = Operator(qc.decompose()) + + # Create an operator from qc directly + op2 = Operator(qc) + + # Check that the two operators are equal + self.assertTrue(op1.equiv(op2)) + + def test_can_combine_cliffords(self): + """Test that we can combine a pair of Cliffords over the same qubits + using OptimizeCliffords transpiler pass.""" + + cliff1 = self.create_cliff1() + cliff2 = self.create_cliff2() + cliff3 = self.create_cliff3() + + # Create a circuit with two consective cliffords + qc1 = QuantumCircuit(4) + qc1.append(cliff1, [3, 1, 2]) + qc1.append(cliff2, [3, 1, 2]) + self.assertEqual(qc1.count_ops()["clifford"], 2) + + # Run OptimizeCliffords pass, and check that only one Clifford remains + qc1opt = PassManager(OptimizeCliffords()).run(qc1) + self.assertEqual(qc1opt.count_ops()["clifford"], 1) + + # Create the expected circuit + qc2 = QuantumCircuit(4) + qc2.append(cliff3, [3, 1, 2]) + + # Check that all possible operators are equal + self.assertTrue(Operator(qc1).equiv(Operator(qc1.decompose()))) + self.assertTrue(Operator(qc1opt).equiv(Operator(qc1opt.decompose()))) + self.assertTrue(Operator(qc1).equiv(Operator(qc1opt))) + self.assertTrue(Operator(qc2).equiv(Operator(qc2.decompose()))) + self.assertTrue(Operator(qc1opt).equiv(Operator(qc2))) + + def test_cannot_combine(self): + """Test that currently we cannot combine a pair of Cliffords. + The result will be changed after pass is updated""" + + cliff1 = self.create_cliff1() + cliff2 = self.create_cliff2() + qc1 = QuantumCircuit(4) + qc1.append(cliff1, [3, 1, 2]) + qc1.append(cliff2, [3, 2, 1]) + qc1 = PassManager(OptimizeCliffords()).run(qc1) + self.assertEqual(qc1.count_ops()["clifford"], 2) + + +if __name__ == "__main__": + unittest.main() From 2ab3084df3051ae54a0eb540605e41072178e40b Mon Sep 17 00:00:00 2001 From: Alexander Ivrii Date: Wed, 27 Apr 2022 09:29:08 +0300 Subject: [PATCH 02/27] Making Instruction.condition into a property --- qiskit/circuit/instruction.py | 10 ++++++++++ qiskit/circuit/operation.py | 10 +++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/qiskit/circuit/instruction.py b/qiskit/circuit/instruction.py index 0727585989b7..1c25cf5b0863 100644 --- a/qiskit/circuit/instruction.py +++ b/qiskit/circuit/instruction.py @@ -547,6 +547,16 @@ def name(self, name): """Set the name.""" self._name = name + @property + def condition(self): + """Returns the condition.""" + return self._condition + + @condition.setter + def condition(self, condition): + """Set condition.""" + self._condition = condition + @property def num_qubits(self): """Return the number of qubits.""" diff --git a/qiskit/circuit/operation.py b/qiskit/circuit/operation.py index da84923d94f1..bf723f8c40a2 100644 --- a/qiskit/circuit/operation.py +++ b/qiskit/circuit/operation.py @@ -71,11 +71,11 @@ def _directive(self): """Class attribute to treat like barrier for transpiler, unroller, drawer.""" raise NotImplementedError - # @property - # @abstractmethod - # def condition(self): - # """Condition for when the instruction has a conditional if.""" - # raise NotImplementedError + @property + @abstractmethod + def condition(self): + """Condition for when the instruction has a conditional if.""" + raise NotImplementedError @property @abstractmethod From dffc618e6d5682a0bacc27da766253778bb9bce8 Mon Sep 17 00:00:00 2001 From: Alexander Ivrii Date: Wed, 27 Apr 2022 12:06:19 +0300 Subject: [PATCH 03/27] improving tests --- .../passes/optimization/optimize_cliffords.py | 6 +- test/python/circuit/test_native_operations.py | 72 --------------- test/python/circuit/test_operation.py | 11 ++- .../python/transpiler/test_clifford_passes.py | 88 ++++++++++++++++++- 4 files changed, 99 insertions(+), 78 deletions(-) delete mode 100644 test/python/circuit/test_native_operations.py diff --git a/qiskit/transpiler/passes/optimization/optimize_cliffords.py b/qiskit/transpiler/passes/optimization/optimize_cliffords.py index 3870b8644800..c17f36e785dd 100644 --- a/qiskit/transpiler/passes/optimization/optimize_cliffords.py +++ b/qiskit/transpiler/passes/optimization/optimize_cliffords.py @@ -18,7 +18,7 @@ class OptimizeCliffords(TransformationPass): """Combine consecutive Cliffords over the same qubits. - For now, this only serves as an example of extra capabilities enabled by storing + This serves as an example of extra capabilities enabled by storing Cliffords natively on the circuit. """ @@ -39,7 +39,9 @@ def run(self, dag): # Iterate over all nodes and collect consecutive Cliffords over the # same qubits. In this very first proof-of-concept implementation # we require the same ordering of qubits, but this restriction will - # be shortly removed. + # be shortly removed. An interesting question is whether we may also + # want to compose Cliffords over different sets of qubits, such as + # cliff1 over qubits [1, 2, 3] and cliff2 over [2, 3, 4]. for node in dag.op_nodes(): if isinstance(node.op, Clifford): if prev_node is None: diff --git a/test/python/circuit/test_native_operations.py b/test/python/circuit/test_native_operations.py deleted file mode 100644 index 69dba803eda8..000000000000 --- a/test/python/circuit/test_native_operations.py +++ /dev/null @@ -1,72 +0,0 @@ -# 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. - -"""This is a temporary test file to experiment with natively adding operations -to a quantum circuit.""" - -from qiskit.circuit import QuantumCircuit -from qiskit.quantum_info import Clifford -from qiskit.compiler.transpiler import circuit_to_dag, dag_to_circuit -from qiskit.transpiler.passes import Unroll3qOrMore -from qiskit.dagcircuit.dagnode import DAGOpNode - - -def create_clifford(): - """Comment so that pylint does not complain.""" - qc = QuantumCircuit(3) - qc.cx(0, 1) - qc.h(0) - qc.s(1) - qc.swap(1, 2) - # print(qc) - cliff = Clifford(qc) - print(cliff) - return cliff - - -def show_circuit(circ): - """Comment so that pylint does not complain.""" - for inst, qargs, cargs in circ.data: - print(f" {type(inst)}, {qargs}, {cargs}") - - -def show_dag(dag): - """Comment so that pylint does not complain.""" - for node in dag.topological_nodes(): - print(f"-- {type(node)}") - if isinstance(node, DAGOpNode): - print(f"---{type(node.op)}, {node.op}") - - -def run_test(): - """Comment so that pylint does not complain.""" - print("Creating Clifford") - cliff = create_clifford() - qc0 = QuantumCircuit(4) - print("Appending Clifford") - qc0.append(cliff, [0, 1, 2]) - print(qc0) - show_circuit(qc0) - print("Converting to dag") - dag1 = circuit_to_dag(qc0) - print(dag1) - show_dag(dag1) - print("Running transpiler pass on dag") - dag2 = Unroll3qOrMore().run(dag1) - print(dag2) - print("Converting back to circuit") - qc3 = dag_to_circuit(dag2) - print(qc3) - - -if __name__ == "__main__": - run_test() diff --git a/test/python/circuit/test_operation.py b/test/python/circuit/test_operation.py index 35f417a8aefd..86db2c0ac456 100644 --- a/test/python/circuit/test_operation.py +++ b/test/python/circuit/test_operation.py @@ -17,7 +17,7 @@ import numpy as np from qiskit.test import QiskitTestCase -from qiskit.circuit import QuantumCircuit, Barrier, Measure, Reset, Gate +from qiskit.circuit import QuantumCircuit, Barrier, Measure, Reset, Gate, Operation from qiskit.circuit.library import XGate, CXGate from qiskit.quantum_info.operators import Clifford, CNOTDihedral, Pauli from qiskit.extensions.quantum_initializer import Initialize, Isometry @@ -35,6 +35,7 @@ def test_measure_as_operation(self): self.assertTrue(op.name == "measure") self.assertTrue(op.num_qubits == 1) self.assertTrue(op.num_clbits == 1) + self.assertIsInstance(op, Operation) def test_reset_as_operation(self): """Test that we can instantiate an object of class @@ -45,6 +46,7 @@ def test_reset_as_operation(self): self.assertTrue(op.name == "reset") self.assertTrue(op.num_qubits == 1) self.assertTrue(op.num_clbits == 0) + self.assertIsInstance(op, Operation) def test_barrier_as_operation(self): """Test that we can instantiate an object of class @@ -56,6 +58,7 @@ def test_barrier_as_operation(self): self.assertTrue(op.name == "barrier") self.assertTrue(op.num_qubits == num_qubits) self.assertTrue(op.num_clbits == 0) + self.assertIsInstance(op, Operation) def test_clifford_as_operation(self): """Test that we can instantiate an object of class @@ -70,6 +73,7 @@ def test_clifford_as_operation(self): self.assertTrue(op.name == "clifford") self.assertTrue(op.num_qubits == num_qubits) self.assertTrue(op.num_clbits == 0) + self.assertIsInstance(op, Operation) def test_cnotdihedral_as_operation(self): """Test that we can instantiate an object of class @@ -106,6 +110,7 @@ def test_isometry_as_operation(self): self.assertTrue(op.name == "isometry") self.assertTrue(op.num_qubits == 7) self.assertTrue(op.num_clbits == 0) + self.assertIsInstance(op, Operation) def test_initialize_as_operation(self): """Test that we can instantiate an object of class @@ -117,6 +122,7 @@ def test_initialize_as_operation(self): self.assertTrue(op.name == "initialize") self.assertTrue(op.num_qubits == 2) self.assertTrue(op.num_clbits == 0) + self.assertIsInstance(op, Operation) def test_gate_as_operation(self): """Test that we can instantiate an object of class @@ -129,6 +135,7 @@ def test_gate_as_operation(self): self.assertTrue(op.name == name) self.assertTrue(op.num_qubits == num_qubits) self.assertTrue(op.num_clbits == 0) + self.assertIsInstance(op, Operation) def test_xgate_as_operation(self): """Test that we can instantiate an object of class @@ -139,6 +146,7 @@ def test_xgate_as_operation(self): self.assertTrue(op.name == "x") self.assertTrue(op.num_qubits == 1) self.assertTrue(op.num_clbits == 0) + self.assertIsInstance(op, Operation) def test_cxgate_as_operation(self): """Test that we can instantiate an object of class @@ -149,6 +157,7 @@ def test_cxgate_as_operation(self): self.assertTrue(op.name == "cx") self.assertTrue(op.num_qubits == 2) self.assertTrue(op.num_clbits == 0) + self.assertIsInstance(op, Operation) def test_can_append_to_quantum_circuit(self): """Test that we can add various objects with Operation interface to a Quantum Circuit.""" diff --git a/test/python/transpiler/test_clifford_passes.py b/test/python/transpiler/test_clifford_passes.py index f30801097e2a..bc6a4795be5a 100644 --- a/test/python/transpiler/test_clifford_passes.py +++ b/test/python/transpiler/test_clifford_passes.py @@ -10,20 +10,25 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""Test transpiler passes that deal with Cliffords.""" +"""Test transpiler passes and conversion methods that deal with Cliffords.""" import unittest +import numpy as np from qiskit.circuit import QuantumCircuit, Gate +from qiskit.converters import dag_to_circuit, circuit_to_dag +from qiskit.dagcircuit import DAGOpNode +from qiskit.transpiler.passes import Unroll3qOrMore from qiskit.transpiler.passes.optimization.optimize_cliffords import OptimizeCliffords from qiskit.test import QiskitTestCase from qiskit.quantum_info.operators import Clifford from qiskit.transpiler import PassManager -from qiskit.quantum_info import Operator +from qiskit.quantum_info import Operator, random_clifford class TestCliffordPasses(QiskitTestCase): - """Tests to verify correctness of the transpiler passes that deal with Cliffords.""" + """Tests to verify correctness of transpiler passes and + conversion methods that deal with Cliffords.""" def create_cliff1(self): """Creates a simple Clifford.""" @@ -147,6 +152,83 @@ def test_cannot_combine(self): qc1 = PassManager(OptimizeCliffords()).run(qc1) self.assertEqual(qc1.count_ops()["clifford"], 2) + def test_circuit_to_dag_conversion_and_back(self): + """Test that converting a circuit containing Clifford to a DAG + and back preserves the Clifford. + """ + # Create a Clifford + cliff_circ = QuantumCircuit(3) + cliff_circ.cx(0, 1) + cliff_circ.h(0) + cliff_circ.s(1) + cliff_circ.swap(1, 2) + cliff = Clifford(cliff_circ) + + # Add this Clifford to a Quantum Circuit, and check that it remains a Clifford + circ0 = QuantumCircuit(4) + circ0.append(cliff, [0, 1, 2]) + circ0_cliffords = [inst for inst, _, _ in circ0.data if isinstance(inst, Clifford)] + circ0_gates = [inst for inst, _, _ in circ0.data if isinstance(inst, Gate)] + self.assertEqual(len(circ0_cliffords), 1) + self.assertEqual(len(circ0_gates), 0) + + # Check that converting circuit to DAG preserves Clifford. + dag0 = circuit_to_dag(circ0) + dag0_cliffords = [ + node + for node in dag0.topological_nodes() + if isinstance(node, DAGOpNode) and isinstance(node.op, Clifford) + ] + self.assertEqual(len(dag0_cliffords), 1) + + # Check that converted DAG to a circuit also preserves Clifford. + circ1 = dag_to_circuit(dag0) + circ1_cliffords = [inst for inst, _, _ in circ1.data if isinstance(inst, Clifford)] + circ1_gates = [inst for inst, _, _ in circ1.data if isinstance(inst, Gate)] + self.assertEqual(len(circ1_cliffords), 1) + self.assertEqual(len(circ1_gates), 0) + + # However, test that running an unrolling pass on the DAG replaces Clifford + # by gates. + dag1 = Unroll3qOrMore().run(dag0) + dag1_cliffords = [ + node + for node in dag1.topological_nodes() + if isinstance(node, DAGOpNode) and isinstance(node.op, Clifford) + ] + self.assertEqual(len(dag1_cliffords), 0) + + def test_optimize_cliffords(self): + """Test OptimizeCliffords pass.""" + + rng = np.random.default_rng(1234) + for _ in range(20): + # Create several random Cliffords + cliffs = [random_clifford(3, rng) for _ in range(5)] + + # The first circuit contains these cliffords + qc1 = QuantumCircuit(5) + for cliff in cliffs: + qc1.append(cliff, [4, 0, 2]) + self.assertEqual(qc1.count_ops()["clifford"], 5) + + # The second circuit is obtained by running the OptimizeCliffords pass. + qc2 = PassManager(OptimizeCliffords()).run(qc1) + self.assertEqual(qc2.count_ops()["clifford"], 1) + + # The third circuit contains the decompositions of Cliffods. + qc3 = QuantumCircuit(5) + for cliff in cliffs: + qc3.append(cliff.definition, [4, 0, 2]) + self.assertNotIn("clifford", qc3.count_ops()) + + # Check that qc1, qc2 and qc3 and their decompositions are all equivalent. + self.assertTrue(Operator(qc1).equiv(Operator(qc1.decompose()))) + self.assertTrue(Operator(qc2).equiv(Operator(qc2.decompose()))) + self.assertTrue(Operator(qc3).equiv(Operator(qc3.decompose()))) + self.assertTrue(Operator(qc1).equiv(Operator(qc2))) + self.assertTrue(Operator(qc1).equiv(Operator(qc3))) + if __name__ == "__main__": unittest.main() From ea56a13679980497ae8eda7f3879fad76a2b4366 Mon Sep 17 00:00:00 2001 From: Alexander Ivrii Date: Wed, 27 Apr 2022 13:53:32 +0300 Subject: [PATCH 04/27] adding release notes --- ...tion-abstract-base-class-c5efe020aa9caf46.yaml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 releasenotes/notes/operation-abstract-base-class-c5efe020aa9caf46.yaml diff --git a/releasenotes/notes/operation-abstract-base-class-c5efe020aa9caf46.yaml b/releasenotes/notes/operation-abstract-base-class-c5efe020aa9caf46.yaml new file mode 100644 index 000000000000..c46d0179251d --- /dev/null +++ b/releasenotes/notes/operation-abstract-base-class-c5efe020aa9caf46.yaml @@ -0,0 +1,15 @@ +--- + +features: + - | + A new :class:`.Operation` base class for objects that can be added to a :class:`.QuantumCircuit`. + These objects include :class:`.Gate`, :class:`.Reset`, :class:`.Barrier`, :class:.Measure`, + and operators such as :class:`.Clifford`. + - | + A Clifford gate is now added to a quantum circuit as an :class:`.Operation`, without first + synthesizing a subcircuit implementing this Clifford gate. The actual synthesis is postponed + to a later transpilation pass. + - | + Added a new transpiler pass :class:`.OptimizeCliffords` that collects blocks of consecutive + Clifford gates in a circuit, and replaces each block with a single Clifford object. + From f3af0b6610a18f6ff9a40ce23a396004a0615412 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Tue, 10 May 2022 13:30:02 +0300 Subject: [PATCH 05/27] A few changes based on review --- qiskit/circuit/quantumcircuit.py | 20 +++++++++---------- qiskit/quantum_info/operators/operator.py | 10 +++++----- .../passes/optimization/optimize_cliffords.py | 6 ++++-- 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index 52943854da2f..16528fb59a97 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -1165,20 +1165,20 @@ def append( qargs: Optional[Sequence[QubitSpecifier]] = None, cargs: Optional[Sequence[ClbitSpecifier]] = None, ) -> InstructionSet: - """Append one or more instructions to the end of the circuit, modifying + """Append one or more operations to the end of the circuit, modifying the circuit in place. Expands qargs and cargs. Args: - instruction (qiskit.circuit.Instruction): Instruction instance to append + instruction (qiskit.circuit.Operation): Operation instance to append qargs (list(argument)): qubits to attach instruction to cargs (list(argument)): clbits to attach instruction to Returns: - qiskit.circuit.Instruction: a handle to the instruction that was just added + qiskit.circuit.InstructionSet: a handle to the instruction that was just added Raises: - CircuitError: if object passed is a subclass of Instruction - CircuitError: if object passed is neither subclass nor an instance of Instruction + CircuitError: if object passed is a subclass of Operation + CircuitError: if object passed is neither subclass nor an instance of Operation """ # Convert input to instruction if not isinstance(instruction, Operation) and not hasattr(instruction, "to_instruction"): @@ -1222,8 +1222,8 @@ def _append( instruction: Operation, qargs: Sequence[Qubit], cargs: Sequence[Clbit], - ) -> Instruction: - """Append an instruction to the end of the circuit, modifying the circuit in place. + ) -> Operation: + """Append an operation to the end of the circuit, modifying the circuit in place. .. warning:: @@ -1243,12 +1243,12 @@ def _append( constructs of the control-flow builder interface. Args: - instruction: Instruction instance to append + instruction: Operation instance to append qargs: Qubits to attach the instruction to. cargs: Clbits to attach the instruction to. Returns: - Instruction: a handle to the instruction that was just added + Operation: a handle to the instruction that was just added :meta public: """ @@ -1264,7 +1264,7 @@ def _append( def _update_parameter_table(self, instruction: Operation) -> Operation: # A generic Operation object at the moment does not require to have params. - if not hasattr(instruction, "params"): + if not isinstance(instruction, Instruction): return instruction for param_index, param in enumerate(instruction.params): diff --git a/qiskit/quantum_info/operators/operator.py b/qiskit/quantum_info/operators/operator.py index 6ba029f3dac3..f8dc1e5d98dc 100644 --- a/qiskit/quantum_info/operators/operator.py +++ b/qiskit/quantum_info/operators/operator.py @@ -76,7 +76,7 @@ def __init__(self, data, input_dims=None, output_dims=None): # Default initialization from list or numpy array matrix self._data = np.asarray(data, dtype=complex) elif isinstance(data, (QuantumCircuit, Operation)): - # If the input is a Terra QuantumCircuit or Instruction we + # If the input is a Terra QuantumCircuit or Operation we # perform a simulation to construct the unitary operator. # This will only work if the circuit or instruction can be # defined in terms of unitary gate instructions which have a @@ -498,7 +498,7 @@ def _einsum_matmul(cls, tensor, mat, indices, shift=0, right_mul=False): @classmethod def _init_instruction(cls, instruction): - """Convert a QuantumCircuit or Instruction to an Operator.""" + """Convert a QuantumCircuit or Operation to an Operator.""" # Initialize an identity operator of the correct size of the circuit if hasattr(instruction, "__array__"): return Operator(np.array(instruction, dtype=complex)) @@ -544,10 +544,10 @@ def _append_instruction(self, obj, qargs=None): # circuit decomposition definition if it exists, otherwise we # cannot compose this gate and raise an error. if obj.definition is None: - raise QiskitError(f"Cannot apply Instruction: {obj.name}") + raise QiskitError(f"Cannot apply Operation: {obj.name}") if not isinstance(obj.definition, QuantumCircuit): raise QiskitError( - 'Instruction "{}" ' + 'Operation "{}" ' "definition is {} but expected QuantumCircuit.".format( obj.name, type(obj.definition) ) @@ -569,7 +569,7 @@ def _append_instruction(self, obj, qargs=None): for instr, qregs, cregs in flat_instr: if cregs: raise QiskitError( - f"Cannot apply instruction with classical registers: {instr.name}" + f"Cannot apply operation with classical registers: {instr.name}" ) # Get the integer position of the flat register if qargs is None: diff --git a/qiskit/transpiler/passes/optimization/optimize_cliffords.py b/qiskit/transpiler/passes/optimization/optimize_cliffords.py index c17f36e785dd..7e91969c875a 100644 --- a/qiskit/transpiler/passes/optimization/optimize_cliffords.py +++ b/qiskit/transpiler/passes/optimization/optimize_cliffords.py @@ -58,11 +58,13 @@ def run(self, dag): else: # not a clifford - blocks.append(cur_block) + if cur_block: + blocks.append(cur_block) prev_node = None cur_block = [] - blocks.append(cur_block) + if cur_block: + blocks.append(cur_block) # Replace every discovered block of cliffords by a single clifford # based on the Cliffords' compose function. From 15ce409469412820f95926dfc3d267149dd5b63e Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Tue, 7 Jun 2022 10:47:19 +0300 Subject: [PATCH 06/27] Removing definition from Operation interface. Instead adding HighLevelSynthesis transpiler pass, and incorporating it into the preset pass managers --- qiskit/circuit/operation.py | 6 --- .../operators/symplectic/clifford.py | 11 ----- qiskit/transpiler/passes/__init__.py | 2 + qiskit/transpiler/passes/basis/decompose.py | 4 ++ .../transpiler/passes/synthesis/__init__.py | 1 + .../passes/synthesis/high_level_synthesis.py | 40 +++++++++++++++++++ .../transpiler/preset_passmanagers/level0.py | 3 ++ .../transpiler/preset_passmanagers/level1.py | 3 ++ .../transpiler/preset_passmanagers/level2.py | 3 ++ .../transpiler/preset_passmanagers/level3.py | 3 ++ .../python/transpiler/test_clifford_passes.py | 6 +-- 11 files changed, 62 insertions(+), 20 deletions(-) create mode 100644 qiskit/transpiler/passes/synthesis/high_level_synthesis.py diff --git a/qiskit/circuit/operation.py b/qiskit/circuit/operation.py index bf723f8c40a2..2801a514dd45 100644 --- a/qiskit/circuit/operation.py +++ b/qiskit/circuit/operation.py @@ -76,9 +76,3 @@ def _directive(self): def condition(self): """Condition for when the instruction has a conditional if.""" raise NotImplementedError - - @property - @abstractmethod - def definition(self): - """Definition of the operation in terms of more basic gates.""" - raise NotImplementedError diff --git a/qiskit/quantum_info/operators/symplectic/clifford.py b/qiskit/quantum_info/operators/symplectic/clifford.py index 78fb0c58512a..844a8ddbb506 100644 --- a/qiskit/quantum_info/operators/symplectic/clifford.py +++ b/qiskit/quantum_info/operators/symplectic/clifford.py @@ -141,9 +141,6 @@ def __init__(self, data, validate=True): "Invalid Clifford. Input StabilizerTable is not a valid symplectic matrix." ) - # When required, we will compute the QuantumCircuit for this Clifford. - self._definition = None - # Initialize BaseOperator super().__init__(num_qubits=self._table.num_qubits) @@ -570,14 +567,6 @@ def broadcast_arguments(self, qargs, cargs): flat_cargs = [carg for sublist in cargs for carg in sublist] yield flat_qargs, flat_cargs - @property - def definition(self): - """Computes and returns the circuit for this Clifford.""" - if self._definition is None: - self._definition = self.to_circuit() - - return self._definition - # Update docstrings for API docs generate_apidocs(Clifford) diff --git a/qiskit/transpiler/passes/__init__.py b/qiskit/transpiler/passes/__init__.py index e55d7bdfccde..1dce453dc891 100644 --- a/qiskit/transpiler/passes/__init__.py +++ b/qiskit/transpiler/passes/__init__.py @@ -138,6 +138,7 @@ UnitarySynthesis LinearFunctionsSynthesis LinearFunctionsToPermutations + HighLevelSynthesis Post Layout (Post transpile qubit selection) ============================================ @@ -234,6 +235,7 @@ from .synthesis import unitary_synthesis_plugin_names from .synthesis import LinearFunctionsSynthesis from .synthesis import LinearFunctionsToPermutations +from .synthesis import HighLevelSynthesis # calibration from .calibration import PulseGates diff --git a/qiskit/transpiler/passes/basis/decompose.py b/qiskit/transpiler/passes/basis/decompose.py index ffbb80152be1..12b7d6c44915 100644 --- a/qiskit/transpiler/passes/basis/decompose.py +++ b/qiskit/transpiler/passes/basis/decompose.py @@ -20,6 +20,7 @@ from qiskit.converters.circuit_to_dag import circuit_to_dag from qiskit.circuit.gate import Gate from qiskit.utils.deprecation import deprecate_arguments +from qiskit.transpiler.passes.synthesis import HighLevelSynthesis class Decompose(TransformationPass): @@ -84,6 +85,9 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: Returns: output dag where ``gate`` was expanded. """ + # We start by decomposing high-level objects (such as Cliffords) + dag = HighLevelSynthesis().run(dag) + # Walk through the DAG and expand each non-basis node for node in dag.op_nodes(): if self._should_decompose(node): diff --git a/qiskit/transpiler/passes/synthesis/__init__.py b/qiskit/transpiler/passes/synthesis/__init__.py index f32a51674d3c..8869f403c61e 100644 --- a/qiskit/transpiler/passes/synthesis/__init__.py +++ b/qiskit/transpiler/passes/synthesis/__init__.py @@ -15,3 +15,4 @@ from .unitary_synthesis import UnitarySynthesis from .plugin import unitary_synthesis_plugin_names from .linear_functions_synthesis import LinearFunctionsSynthesis, LinearFunctionsToPermutations +from .high_level_synthesis import HighLevelSynthesis diff --git a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py new file mode 100644 index 000000000000..cef24cc20538 --- /dev/null +++ b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py @@ -0,0 +1,40 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 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. + + +"""Synthesize high-level objects.""" + +from qiskit.converters import circuit_to_dag +from qiskit.transpiler.basepasses import TransformationPass +from qiskit.dagcircuit.dagcircuit import DAGCircuit +from qiskit.quantum_info.synthesis.clifford_decompose import decompose_clifford + + +class HighLevelSynthesis(TransformationPass): + """Synthesize high-level objects by choosing the appropriate synthesis method based on + the object's name. + """ + + def run(self, dag: DAGCircuit) -> DAGCircuit: + """Run the HighLevelSynthesis pass on `dag`. + Args: + dag: input dag. + Returns: + Output dag with high level objects synthesized. + """ + + for node in dag.named_nodes("clifford"): + decomposition = circuit_to_dag(decompose_clifford(node.op)) + dag.substitute_node_with_dag(node, decomposition) + + return dag + diff --git a/qiskit/transpiler/preset_passmanagers/level0.py b/qiskit/transpiler/preset_passmanagers/level0.py index 6d3934575717..d50605fd79ed 100644 --- a/qiskit/transpiler/preset_passmanagers/level0.py +++ b/qiskit/transpiler/preset_passmanagers/level0.py @@ -43,6 +43,7 @@ from qiskit.transpiler.passes import Collect1qRuns from qiskit.transpiler.passes import ConsolidateBlocks from qiskit.transpiler.passes import UnitarySynthesis +from qiskit.transpiler.passes import HighLevelSynthesis from qiskit.transpiler.passes import TimeUnitConversion from qiskit.transpiler.passes import ALAPScheduleAnalysis from qiskit.transpiler.passes import ASAPScheduleAnalysis @@ -108,6 +109,7 @@ def level_0_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager: plugin_config=unitary_synthesis_plugin_config, target=target, ), + HighLevelSynthesis(), Unroll3qOrMore(target=target, basis_gates=basis_gates), ] @@ -190,6 +192,7 @@ def _swap_condition(property_set): plugin_config=unitary_synthesis_plugin_config, target=target, ), + HighLevelSynthesis(), Unroll3qOrMore(target=target, basis_gates=basis_gates), Collect2qBlocks(), Collect1qRuns(), diff --git a/qiskit/transpiler/preset_passmanagers/level1.py b/qiskit/transpiler/preset_passmanagers/level1.py index 846e343753c2..5e7cd762ffb9 100644 --- a/qiskit/transpiler/preset_passmanagers/level1.py +++ b/qiskit/transpiler/preset_passmanagers/level1.py @@ -51,6 +51,7 @@ from qiskit.transpiler.passes import Collect2qBlocks from qiskit.transpiler.passes import ConsolidateBlocks from qiskit.transpiler.passes import UnitarySynthesis +from qiskit.transpiler.passes import HighLevelSynthesis from qiskit.transpiler.passes import TimeUnitConversion from qiskit.transpiler.passes import ALAPScheduleAnalysis from qiskit.transpiler.passes import ASAPScheduleAnalysis @@ -172,6 +173,7 @@ def _vf2_match_not_found(property_set): plugin_config=unitary_synthesis_plugin_config, target=target, ), + HighLevelSynthesis(), Unroll3qOrMore(target=target, basis_gates=basis_gates), ] @@ -252,6 +254,7 @@ def _swap_condition(property_set): min_qubits=3, target=target, ), + HighLevelSynthesis(), Unroll3qOrMore(target=target, basis_gates=basis_gates), Collect2qBlocks(), ConsolidateBlocks(basis_gates=basis_gates, target=target), diff --git a/qiskit/transpiler/preset_passmanagers/level2.py b/qiskit/transpiler/preset_passmanagers/level2.py index 47db4205a2de..98eae2400be5 100644 --- a/qiskit/transpiler/preset_passmanagers/level2.py +++ b/qiskit/transpiler/preset_passmanagers/level2.py @@ -51,6 +51,7 @@ from qiskit.transpiler.passes import Collect2qBlocks from qiskit.transpiler.passes import ConsolidateBlocks from qiskit.transpiler.passes import UnitarySynthesis +from qiskit.transpiler.passes import HighLevelSynthesis from qiskit.transpiler.passes import TimeUnitConversion from qiskit.transpiler.passes import ALAPScheduleAnalysis from qiskit.transpiler.passes import ASAPScheduleAnalysis @@ -122,6 +123,7 @@ def level_2_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager: plugin_config=unitary_synthesis_plugin_config, target=target, ), + HighLevelSynthesis(), Unroll3qOrMore(target=target, basis_gates=basis_gates), ] @@ -237,6 +239,7 @@ def _swap_condition(property_set): min_qubits=3, target=target, ), + HighLevelSynthesis(), Unroll3qOrMore(target=target, basis_gates=basis_gates), Collect2qBlocks(), ConsolidateBlocks(basis_gates=basis_gates, target=target), diff --git a/qiskit/transpiler/preset_passmanagers/level3.py b/qiskit/transpiler/preset_passmanagers/level3.py index 61831359bc9a..5fbfeaa79b92 100644 --- a/qiskit/transpiler/preset_passmanagers/level3.py +++ b/qiskit/transpiler/preset_passmanagers/level3.py @@ -52,6 +52,7 @@ from qiskit.transpiler.passes import Collect2qBlocks from qiskit.transpiler.passes import ConsolidateBlocks from qiskit.transpiler.passes import UnitarySynthesis +from qiskit.transpiler.passes import HighLevelSynthesis from qiskit.transpiler.passes import ApplyLayout from qiskit.transpiler.passes import CheckGateDirection from qiskit.transpiler.passes import TimeUnitConversion @@ -125,6 +126,7 @@ def level_3_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager: min_qubits=3, target=target, ), + HighLevelSynthesis(), Unroll3qOrMore(target=target, basis_gates=basis_gates), ] @@ -235,6 +237,7 @@ def _swap_condition(property_set): min_qubits=3, target=target, ), + HighLevelSynthesis(), Unroll3qOrMore(target=target, basis_gates=basis_gates), Collect2qBlocks(), ConsolidateBlocks(basis_gates=basis_gates, target=target), diff --git a/test/python/transpiler/test_clifford_passes.py b/test/python/transpiler/test_clifford_passes.py index bc6a4795be5a..9ff5f31249b0 100644 --- a/test/python/transpiler/test_clifford_passes.py +++ b/test/python/transpiler/test_clifford_passes.py @@ -18,7 +18,7 @@ from qiskit.circuit import QuantumCircuit, Gate from qiskit.converters import dag_to_circuit, circuit_to_dag from qiskit.dagcircuit import DAGOpNode -from qiskit.transpiler.passes import Unroll3qOrMore +from qiskit.transpiler.passes import HighLevelSynthesis from qiskit.transpiler.passes.optimization.optimize_cliffords import OptimizeCliffords from qiskit.test import QiskitTestCase from qiskit.quantum_info.operators import Clifford @@ -190,7 +190,7 @@ def test_circuit_to_dag_conversion_and_back(self): # However, test that running an unrolling pass on the DAG replaces Clifford # by gates. - dag1 = Unroll3qOrMore().run(dag0) + dag1 = HighLevelSynthesis().run(dag0) dag1_cliffords = [ node for node in dag1.topological_nodes() @@ -219,7 +219,7 @@ def test_optimize_cliffords(self): # The third circuit contains the decompositions of Cliffods. qc3 = QuantumCircuit(5) for cliff in cliffs: - qc3.append(cliff.definition, [4, 0, 2]) + qc3.append(cliff.to_circuit(), [4, 0, 2]) self.assertNotIn("clifford", qc3.count_ops()) # Check that qc1, qc2 and qc3 and their decompositions are all equivalent. From d59dd7e43edb0ffeba8fad4fd00dda6464a87c59 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Tue, 7 Jun 2022 11:05:48 +0300 Subject: [PATCH 07/27] minor lint fix --- qiskit/transpiler/passes/synthesis/high_level_synthesis.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py index cef24cc20538..de36ae6291d6 100644 --- a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py @@ -37,4 +37,3 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: dag.substitute_node_with_dag(node, decomposition) return dag - From 51aff6d0e5ca868ad760a5e3c2572f9a95438d0d Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Tue, 7 Jun 2022 15:40:40 +0300 Subject: [PATCH 08/27] moving all of broadcast functionality to a separate file; removing broadcast from Operation interface and in particular from Clifford --- qiskit/circuit/barrier.py | 3 - qiskit/circuit/broadcast.py | 222 ++++++++++++++++++ qiskit/circuit/delay.py | 3 - qiskit/circuit/gate.py | 98 +------- qiskit/circuit/instruction.py | 26 -- .../data_preparation/state_preparation.py | 11 - qiskit/circuit/measure.py | 14 -- qiskit/circuit/operation.py | 11 +- qiskit/circuit/quantumcircuit.py | 3 +- qiskit/circuit/quantumcircuitdata.py | 3 +- qiskit/circuit/reset.py | 4 - .../quantum_initializer/initializer.py | 3 - .../operators/symplectic/clifford.py | 20 -- test/python/circuit/test_broadcast.py | 202 ++++++++++++++++ 14 files changed, 433 insertions(+), 190 deletions(-) create mode 100644 qiskit/circuit/broadcast.py create mode 100644 test/python/circuit/test_broadcast.py diff --git a/qiskit/circuit/barrier.py b/qiskit/circuit/barrier.py index c10b32069595..d4dfe8c498c9 100644 --- a/qiskit/circuit/barrier.py +++ b/qiskit/circuit/barrier.py @@ -29,8 +29,5 @@ def inverse(self): """Special case. Return self.""" return Barrier(self.num_qubits) - def broadcast_arguments(self, qargs, cargs): - yield [qarg for sublist in qargs for qarg in sublist], [] - def c_if(self, classical, val): raise QiskitError("Barriers are compiler directives and cannot be conditional.") diff --git a/qiskit/circuit/broadcast.py b/qiskit/circuit/broadcast.py new file mode 100644 index 000000000000..6af784e55ae2 --- /dev/null +++ b/qiskit/circuit/broadcast.py @@ -0,0 +1,222 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 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. + + +"""Broadcasting functionality.""" + +from typing import List, Tuple +from qiskit.circuit.operation import Operation +from qiskit.circuit.gate import Gate +from qiskit.circuit.exceptions import CircuitError, QiskitError + + +def broadcast_barrier(operation, qargs, cargs): # pylint: disable=unused-argument + """Broadcasting for barriers.""" + yield [qarg for sublist in qargs for qarg in sublist], [] + + +def broadcast_measure(operation, qargs, cargs): # pylint: disable=unused-argument + """Broadcasting for measures.""" + qarg = qargs[0] + carg = cargs[0] + + if len(carg) == len(qarg): + for qarg, carg in zip(qarg, carg): + yield [qarg], [carg] + elif len(qarg) == 1 and carg: + for each_carg in carg: + yield qarg, [each_carg] + else: + raise CircuitError("register size error") + + +def broadcast_reset(operation, qargs, cargs): # pylint: disable=unused-argument + """Broadcasting for resets.""" + for qarg in qargs[0]: + yield [qarg], [] + + +def broadcast_delay(operation, qargs, cargs): # pylint: disable=unused-argument + """Broadcasting for delays.""" + yield [qarg for sublist in qargs for qarg in sublist], [] + + +def _broadcast_single_argument_gate(qarg: List) -> List: + """Expands a single argument. + + For example: [q[0], q[1]] -> [q[0]], [q[1]] + """ + # [q[0], q[1]] -> [q[0]] + # -> [q[1]] + for arg0 in qarg: + yield [arg0], [] + + +def _broadcast_2_arguments_gate(qarg0: List, qarg1: List) -> List: + if len(qarg0) == len(qarg1): + # [[q[0], q[1]], [r[0], r[1]]] -> [q[0], r[0]] + # -> [q[1], r[1]] + for arg0, arg1 in zip(qarg0, qarg1): + yield [arg0, arg1], [] + elif len(qarg0) == 1: + # [[q[0]], [r[0], r[1]]] -> [q[0], r[0]] + # -> [q[0], r[1]] + for arg1 in qarg1: + yield [qarg0[0], arg1], [] + elif len(qarg1) == 1: + # [[q[0], q[1]], [r[0]]] -> [q[0], r[0]] + # -> [q[1], r[0]] + for arg0 in qarg0: + yield [arg0, qarg1[0]], [] + else: + raise CircuitError( + f"Not sure how to combine these two-qubit arguments:\n {qarg0}\n {qarg1}" + ) + + +def _broadcast_3_or_more_args_gate(qargs: List) -> List: + if all(len(qarg) == len(qargs[0]) for qarg in qargs): + for arg in zip(*qargs): + yield list(arg), [] + else: + raise CircuitError("Not sure how to combine these qubit arguments:\n %s\n" % qargs) + + +def broadcast_gate(operation: Gate, qargs: List, cargs: List) -> Tuple[List, List]: + """Validation and handling of the arguments and its relationship. + + For example, ``cx([q[0],q[1]], q[2])`` means ``cx(q[0], q[2]); cx(q[1], q[2])``. This + method yields the arguments in the right grouping. In the given example:: + + in: [[q[0],q[1]], q[2]],[] + outs: [q[0], q[2]], [] + [q[1], q[2]], [] + + The general broadcasting rules are: + + * If len(qargs) == 1:: + + [q[0], q[1]] -> [q[0]],[q[1]] + + * If len(qargs) == 2:: + + [[q[0], q[1]], [r[0], r[1]]] -> [q[0], r[0]], [q[1], r[1]] + [[q[0]], [r[0], r[1]]] -> [q[0], r[0]], [q[0], r[1]] + [[q[0], q[1]], [r[0]]] -> [q[0], r[0]], [q[1], r[0]] + + * If len(qargs) >= 3:: + + [q[0], q[1]], [r[0], r[1]], ...] -> [q[0], r[0], ...], [q[1], r[1], ...] + + Args: + operation: gate being broadcasted + qargs: List of quantum bit arguments. + cargs: List of classical bit arguments. + + Yields: + A tuple with single arguments. + + Raises: + CircuitError: If the input is not valid. For example, the number of + arguments does not match the gate expectation. + """ + if len(qargs) != operation.num_qubits or cargs: + raise CircuitError( + f"The amount of qubit({len(qargs)})/clbit({len(cargs)}) arguments does" + f" not match the gate expectation ({operation.num_qubits})." + ) + + if any(not qarg for qarg in qargs): + raise CircuitError("One or more of the arguments are empty") + + if len(qargs) == 1: + yield from _broadcast_single_argument_gate(qargs[0]) + elif len(qargs) == 2: + yield from _broadcast_2_arguments_gate(qargs[0], qargs[1]) + elif len(qargs) >= 3: + yield from _broadcast_3_or_more_args_gate(qargs) + else: + raise CircuitError("This gate cannot handle %i arguments" % len(qargs)) + + +def broadcast_generic(operation: Operation, qargs, cargs): + """ + Validation of the arguments. + + Args: + operation: Operation to broadcast + qargs (List): List of quantum bit arguments. + cargs (List): List of classical bit arguments. + + Yields: + Tuple(List, List): A tuple with single arguments. + + Raises: + CircuitError: If the input is not valid. For example, the number of + arguments does not match the gate expectation. + """ + if len(qargs) != operation.num_qubits: + raise CircuitError( + f"The amount of qubit arguments {len(qargs)} does not match" + f" the operation expectation ({operation.num_qubits})." + ) + + # [[q[0], q[1]], [c[0], c[1]]] -> [q[0], c[0]], [q[1], c[1]] + flat_qargs = [qarg for sublist in qargs for qarg in sublist] + flat_cargs = [carg for sublist in cargs for carg in sublist] + yield flat_qargs, flat_cargs + + +def broadcast_initialize(operation, qargs, cargs): + """Broadcasting for Initializers.""" + yield from broadcast_arguments(operation._stateprep, qargs, cargs) + + +def broadcast_state_preparation(operation, qargs, cargs): # pylint: disable=unused-argument + """Broadcasting for StatePreparations.""" + flat_qargs = [qarg for sublist in qargs for qarg in sublist] + + if operation.num_qubits != len(flat_qargs): + raise QiskitError( + "StatePreparation parameter vector has %d elements, therefore expects %s " + "qubits. However, %s were provided." + % (2**operation.num_qubits, operation.num_qubits, len(flat_qargs)) + ) + yield flat_qargs, [] + + +# Special implementations +broadcast_implementations = { + "barrier": broadcast_barrier, + "measure": broadcast_measure, + "reset": broadcast_reset, + "delay": broadcast_delay, + "initialize": broadcast_initialize, + "state_preparation": broadcast_state_preparation, + "state_preparation_dg": broadcast_state_preparation, +} + + +def broadcast_arguments(operation: Operation, qargs, cargs): + """The main function to broadcast arguments based on the operation.""" + + # print(f"broadcast_arguments: {operation = }, {qargs = }, {cargs = }") + if operation.name in broadcast_implementations.keys(): + # Use a custom broadcast implementation when available + broadcaster = broadcast_implementations[operation.name] + yield from broadcaster(operation, qargs, cargs) + elif isinstance(operation, Gate): + # Use implementation for gates, if the operation is a gate + yield from broadcast_gate(operation, qargs, cargs) + else: + # Use the generic implementation otherwise + yield from broadcast_generic(operation, qargs, cargs) diff --git a/qiskit/circuit/delay.py b/qiskit/circuit/delay.py index 97bc81099e75..61af763bb390 100644 --- a/qiskit/circuit/delay.py +++ b/qiskit/circuit/delay.py @@ -33,9 +33,6 @@ def inverse(self): """Special case. Return self.""" return self - def broadcast_arguments(self, qargs, cargs): - yield [qarg for sublist in qargs for qarg in sublist], [] - def c_if(self, classical, val): raise CircuitError("Conditional Delay is not yet implemented.") diff --git a/qiskit/circuit/gate.py b/qiskit/circuit/gate.py index b271aa161c82..ed6a5ebbe1bc 100644 --- a/qiskit/circuit/gate.py +++ b/qiskit/circuit/gate.py @@ -12,7 +12,7 @@ """Unitary gate.""" -from typing import List, Optional, Union, Tuple +from typing import List, Optional, Union import numpy as np from qiskit.circuit.parameterexpression import ParameterExpression @@ -117,102 +117,6 @@ def control( return add_control(self, num_ctrl_qubits, label, ctrl_state) - @staticmethod - def _broadcast_single_argument(qarg: List) -> List: - """Expands a single argument. - - For example: [q[0], q[1]] -> [q[0]], [q[1]] - """ - # [q[0], q[1]] -> [q[0]] - # -> [q[1]] - for arg0 in qarg: - yield [arg0], [] - - @staticmethod - def _broadcast_2_arguments(qarg0: List, qarg1: List) -> List: - if len(qarg0) == len(qarg1): - # [[q[0], q[1]], [r[0], r[1]]] -> [q[0], r[0]] - # -> [q[1], r[1]] - for arg0, arg1 in zip(qarg0, qarg1): - yield [arg0, arg1], [] - elif len(qarg0) == 1: - # [[q[0]], [r[0], r[1]]] -> [q[0], r[0]] - # -> [q[0], r[1]] - for arg1 in qarg1: - yield [qarg0[0], arg1], [] - elif len(qarg1) == 1: - # [[q[0], q[1]], [r[0]]] -> [q[0], r[0]] - # -> [q[1], r[0]] - for arg0 in qarg0: - yield [arg0, qarg1[0]], [] - else: - raise CircuitError( - f"Not sure how to combine these two-qubit arguments:\n {qarg0}\n {qarg1}" - ) - - @staticmethod - def _broadcast_3_or_more_args(qargs: List) -> List: - if all(len(qarg) == len(qargs[0]) for qarg in qargs): - for arg in zip(*qargs): - yield list(arg), [] - else: - raise CircuitError("Not sure how to combine these qubit arguments:\n %s\n" % qargs) - - def broadcast_arguments(self, qargs: List, cargs: List) -> Tuple[List, List]: - """Validation and handling of the arguments and its relationship. - - For example, ``cx([q[0],q[1]], q[2])`` means ``cx(q[0], q[2]); cx(q[1], q[2])``. This - method yields the arguments in the right grouping. In the given example:: - - in: [[q[0],q[1]], q[2]],[] - outs: [q[0], q[2]], [] - [q[1], q[2]], [] - - The general broadcasting rules are: - - * If len(qargs) == 1:: - - [q[0], q[1]] -> [q[0]],[q[1]] - - * If len(qargs) == 2:: - - [[q[0], q[1]], [r[0], r[1]]] -> [q[0], r[0]], [q[1], r[1]] - [[q[0]], [r[0], r[1]]] -> [q[0], r[0]], [q[0], r[1]] - [[q[0], q[1]], [r[0]]] -> [q[0], r[0]], [q[1], r[0]] - - * If len(qargs) >= 3:: - - [q[0], q[1]], [r[0], r[1]], ...] -> [q[0], r[0], ...], [q[1], r[1], ...] - - Args: - qargs: List of quantum bit arguments. - cargs: List of classical bit arguments. - - Returns: - A tuple with single arguments. - - Raises: - CircuitError: If the input is not valid. For example, the number of - arguments does not match the gate expectation. - """ - if len(qargs) != self.num_qubits or cargs: - raise CircuitError( - f"The amount of qubit({len(qargs)})/clbit({len(cargs)}) arguments does" - f" not match the gate expectation ({self.num_qubits})." - ) - - if any(not qarg for qarg in qargs): - raise CircuitError("One or more of the arguments are empty") - - if len(qargs) == 1: - return Gate._broadcast_single_argument(qargs[0]) - elif len(qargs) == 2: - return Gate._broadcast_2_arguments(qargs[0], qargs[1]) - elif len(qargs) >= 3: - return Gate._broadcast_3_or_more_args(qargs) - else: - raise CircuitError("This gate cannot handle %i arguments" % len(qargs)) - def validate_parameter(self, parameter): """Gate parameters should be int, float, or ParameterExpression""" if isinstance(parameter, ParameterExpression): diff --git a/qiskit/circuit/instruction.py b/qiskit/circuit/instruction.py index 1c25cf5b0863..7747035015c8 100644 --- a/qiskit/circuit/instruction.py +++ b/qiskit/circuit/instruction.py @@ -459,32 +459,6 @@ def qasm(self): return self._qasmif(name_param) - def broadcast_arguments(self, qargs, cargs): - """ - Validation of the arguments. - - Args: - qargs (List): List of quantum bit arguments. - cargs (List): List of classical bit arguments. - - Yields: - Tuple(List, List): A tuple with single arguments. - - Raises: - CircuitError: If the input is not valid. For example, the number of - arguments does not match the gate expectation. - """ - if len(qargs) != self.num_qubits: - raise CircuitError( - f"The amount of qubit arguments {len(qargs)} does not match" - f" the instruction expectation ({self.num_qubits})." - ) - - # [[q[0], q[1]], [c[0], c[1]]] -> [q[0], c[0]], [q[1], c[1]] - flat_qargs = [qarg for sublist in qargs for qarg in sublist] - flat_cargs = [carg for sublist in cargs for carg in sublist] - yield flat_qargs, flat_cargs - def _return_repeat(self, exponent): return Instruction( name=f"{self.name}*{exponent}", diff --git a/qiskit/circuit/library/data_preparation/state_preparation.py b/qiskit/circuit/library/data_preparation/state_preparation.py index 43592514da1f..bc245b202f6e 100644 --- a/qiskit/circuit/library/data_preparation/state_preparation.py +++ b/qiskit/circuit/library/data_preparation/state_preparation.py @@ -213,17 +213,6 @@ def inverse(self): return StatePreparation(self._params_arg, inverse=not self._inverse, label=label) - def broadcast_arguments(self, qargs, cargs): - flat_qargs = [qarg for sublist in qargs for qarg in sublist] - - if self.num_qubits != len(flat_qargs): - raise QiskitError( - "StatePreparation parameter vector has %d elements, therefore expects %s " - "qubits. However, %s were provided." - % (2**self.num_qubits, self.num_qubits, len(flat_qargs)) - ) - yield flat_qargs, [] - def validate_parameter(self, parameter): """StatePreparation instruction parameter can be str, int, float, and complex.""" diff --git a/qiskit/circuit/measure.py b/qiskit/circuit/measure.py index 8da0db586c0d..33268289dd63 100644 --- a/qiskit/circuit/measure.py +++ b/qiskit/circuit/measure.py @@ -17,7 +17,6 @@ import warnings from qiskit.circuit.instruction import Instruction -from qiskit.circuit.exceptions import CircuitError class Measure(Instruction): @@ -27,19 +26,6 @@ def __init__(self): """Create new measurement instruction.""" super().__init__("measure", 1, 1, []) - def broadcast_arguments(self, qargs, cargs): - qarg = qargs[0] - carg = cargs[0] - - if len(carg) == len(qarg): - for qarg, carg in zip(qarg, carg): - yield [qarg], [carg] - elif len(qarg) == 1 and carg: - for each_carg in carg: - yield qarg, [each_carg] - else: - raise CircuitError("register size error") - def measure(circuit, qubit, clbit): """Measure a quantum bit into classical bit. diff --git a/qiskit/circuit/operation.py b/qiskit/circuit/operation.py index 2801a514dd45..e409715bb8f7 100644 --- a/qiskit/circuit/operation.py +++ b/qiskit/circuit/operation.py @@ -60,19 +60,16 @@ def num_clbits(self): """Number of classical bits.""" raise NotImplementedError - @abstractmethod - def broadcast_arguments(self, qargs, cargs): - """Expanding (broadcasting) arguments.""" - raise NotImplementedError - @property @abstractmethod def _directive(self): - """Class attribute to treat like barrier for transpiler, unroller, drawer.""" + """Class attribute to treat like barrier for transpiler, unroller, drawer. + This is temporary, please do not use.""" raise NotImplementedError @property @abstractmethod def condition(self): - """Condition for when the instruction has a conditional if.""" + """Condition for when the instruction has a conditional if. + This is temporary, please do not use.""" raise NotImplementedError diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index c364ebbac617..9ef1c02b13a5 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -53,6 +53,7 @@ from .parametervector import ParameterVector, ParameterVectorElement from .instructionset import InstructionSet from .operation import Operation +from .broadcast import broadcast_arguments from .register import Register from .bit import Bit from .quantumcircuitdata import QuantumCircuitData @@ -1226,7 +1227,7 @@ def append( appender = self._append requester = self._resolve_classical_resource instructions = InstructionSet(resource_requester=requester) - for qarg, carg in instruction.broadcast_arguments(expanded_qargs, expanded_cargs): + for qarg, carg in broadcast_arguments(instruction, expanded_qargs, expanded_cargs): self._check_dups(qarg) instructions.add(appender(instruction, qarg, carg), qarg, carg) return instructions diff --git a/qiskit/circuit/quantumcircuitdata.py b/qiskit/circuit/quantumcircuitdata.py index af2198c7c6ac..498f2f478211 100644 --- a/qiskit/circuit/quantumcircuitdata.py +++ b/qiskit/circuit/quantumcircuitdata.py @@ -17,6 +17,7 @@ from qiskit.circuit.exceptions import CircuitError from qiskit.circuit.instruction import Instruction +from qiskit.circuit.broadcast import broadcast_arguments class QuantumCircuitData(MutableSequence): @@ -40,7 +41,7 @@ def __setitem__(self, key, value): expanded_qargs = [self._circuit.qbit_argument_conversion(qarg) for qarg in qargs or []] expanded_cargs = [self._circuit.cbit_argument_conversion(carg) for carg in cargs or []] - broadcast_args = list(instruction.broadcast_arguments(expanded_qargs, expanded_cargs)) + broadcast_args = list(broadcast_arguments(instruction, expanded_qargs, expanded_cargs)) if len(broadcast_args) > 1: raise CircuitError( diff --git a/qiskit/circuit/reset.py b/qiskit/circuit/reset.py index e986c1ba3390..1b6087095587 100644 --- a/qiskit/circuit/reset.py +++ b/qiskit/circuit/reset.py @@ -26,10 +26,6 @@ def __init__(self): """Create new reset instruction.""" super().__init__("reset", 1, 0, []) - def broadcast_arguments(self, qargs, cargs): - for qarg in qargs[0]: - yield [qarg], [] - def reset(circuit, qubit): """Reset a quantum bit on a circuit. diff --git a/qiskit/extensions/quantum_initializer/initializer.py b/qiskit/extensions/quantum_initializer/initializer.py index 8382fa62f846..84bcbf09974a 100644 --- a/qiskit/extensions/quantum_initializer/initializer.py +++ b/qiskit/extensions/quantum_initializer/initializer.py @@ -82,9 +82,6 @@ def params(self, parameters): """Set initialize params.""" self._stateprep.params = parameters - def broadcast_arguments(self, qargs, cargs): - return self._stateprep.broadcast_arguments(qargs, cargs) - def initialize(self, params, qubits=None): r"""Initialize qubits in a specific state. diff --git a/qiskit/quantum_info/operators/symplectic/clifford.py b/qiskit/quantum_info/operators/symplectic/clifford.py index 844a8ddbb506..6852445f5f0a 100644 --- a/qiskit/quantum_info/operators/symplectic/clifford.py +++ b/qiskit/quantum_info/operators/symplectic/clifford.py @@ -547,26 +547,6 @@ def _pad_with_identity(self, clifford, qargs): return padded - def broadcast_arguments(self, qargs, cargs): - """ - Broadcasting of the arguments. - This code is currently copied from Instruction. - This will be cleaned up when broadcasting is moved - to a separate model. - - Args: - qargs (List): List of quantum bit arguments. - cargs (List): List of classical bit arguments. - - Yields: - Tuple(List, List): A tuple with single arguments. - """ - - # [[q[0], q[1]], [c[0], c[1]]] -> [q[0], c[0]], [q[1], c[1]] - flat_qargs = [qarg for sublist in qargs for qarg in sublist] - flat_cargs = [carg for sublist in cargs for carg in sublist] - yield flat_qargs, flat_cargs - # Update docstrings for API docs generate_apidocs(Clifford) diff --git a/test/python/circuit/test_broadcast.py b/test/python/circuit/test_broadcast.py new file mode 100644 index 000000000000..a8512a34af27 --- /dev/null +++ b/test/python/circuit/test_broadcast.py @@ -0,0 +1,202 @@ +# 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 Arguments Broadcaster.""" + +import unittest +import math + +from qiskit.test import QiskitTestCase +from qiskit.circuit.library import StatePreparation +from qiskit.circuit import QuantumCircuit, QuantumRegister, ClassicalRegister +from qiskit.quantum_info.operators import Clifford + + +class TestBroadcast(QiskitTestCase): + """Testing qiskit.circuit.broadcast""" + + def test_barrier(self): + """Test adding a barrier to a quantum circuit.""" + qc = QuantumCircuit(3) + qc.h(0) + qc.barrier([0, 1, 2]) + qc.s(1) + ops = qc.count_ops() + self.assertIn("barrier", ops.keys()) + self.assertEqual(ops["barrier"], 1) + + def test_delay(self): + """Test adding a delay to a quantum circuit.""" + qc = QuantumCircuit(3) + qc.h(0) + qc.delay(1) + qc.s(1) + ops = qc.count_ops() + self.assertIn("delay", ops.keys()) + self.assertEqual(ops["delay"], 3) + + def test_gates_1q_1(self): + """Test the basic way of adding single-qubit gates to a quantum circuit.""" + qc = QuantumCircuit(3) + qc.h(0) + qc.s(1) + qc.h(2) + ops = qc.count_ops() + self.assertIn("h", ops.keys()) + self.assertEqual(ops["h"], 2) + self.assertIn("s", ops.keys()) + self.assertEqual(ops["s"], 1) + + def test_gates_1q_2(self): + """Test the alternative way of adding single-qubit gates to a quantum circuit.""" + qc = QuantumCircuit(3) + qc.h([0, 1]) + qc.s(range(3)) + qc.sdg([0, 2]) + ops = qc.count_ops() + self.assertIn("h", ops.keys()) + self.assertEqual(ops["h"], 2) + self.assertIn("s", ops.keys()) + self.assertEqual(ops["s"], 3) + self.assertIn("sdg", ops.keys()) + self.assertEqual(ops["sdg"], 2) + + def test_gates_2q_1(self): + """Test the basic way of adding two-qubit gates to a quantum circuit.""" + qc = QuantumCircuit(3) + qc.cx(0, 1) + qc.swap(1, 2) + ops = qc.count_ops() + self.assertIn("cx", ops.keys()) + self.assertEqual(ops["cx"], 1) + self.assertIn("swap", ops.keys()) + self.assertEqual(ops["swap"], 1) + + def test_gates_2q_2(self): + """Test an alternative way of adding two-qubit gates to a quantum circuit.""" + qc = QuantumCircuit(4) + qc.cx(0, [1, 2]) + qc.swap([0, 1, 2], 3) + qc.cz(3, range(3)) + qc.cz(range(3), 3) + ops = qc.count_ops() + self.assertIn("cx", ops.keys()) + self.assertEqual(ops["cx"], 2) + self.assertIn("swap", ops.keys()) + self.assertEqual(ops["swap"], 3) + self.assertIn("cz", ops.keys()) + self.assertEqual(ops["cz"], 6) + + def test_gates_2q_3(self): + """Test another alternative way of adding two-qubit gates to a quantum circuit.""" + qc = QuantumCircuit(4) + qc.cx([0, 0, 0], [1, 1, 1]) + ops = qc.count_ops() + self.assertIn("cx", ops.keys()) + self.assertEqual(ops["cx"], 3) + + def test_gates_3q_1(self): + """Test the basic way of adding three-qubit+ gates to a quantum circuit.""" + qc = QuantumCircuit(3) + qc.ccx(0, 1, 2) + ops = qc.count_ops() + self.assertIn("ccx", ops.keys()) + self.assertEqual(ops["ccx"], 1) + + def test_gates_3q_2(self): + """Test an alternative way of adding three-qubit+ gates to a quantum circuit.""" + qc = QuantumCircuit(4) + qc.ccx([0, 1, 2, 3], [1, 2, 3, 0], [2, 3, 1, 2]) + ops = qc.count_ops() + self.assertIn("ccx", ops.keys()) + self.assertEqual(ops["ccx"], 4) + + def test_measure_1(self): + """Test adding a measure to a quantum circuit.""" + qc = QuantumCircuit(3, 3) + qc.h(0) + qc.s(1) + qc.cx(0, 2) + qc.measure([0, 1, 2], [0, 1, 2]) + ops = qc.count_ops() + self.assertIn("measure", ops.keys()) + self.assertEqual(ops["measure"], 3) + + def test_measure_2(self): + """Test an alternative way of adding a measure to a quantum circuit.""" + qc = QuantumCircuit(3, 3) + qc.h(0) + qc.s(1) + qc.cx(0, 2) + qc.measure(0, [0, 1, 2]) + qc.measure([1], [0, 1, 2]) + ops = qc.count_ops() + self.assertIn("measure", ops.keys()) + self.assertEqual(ops["measure"], 6) + + def test_initializer(self): + """Test adding an initializer to a quantum circuit.""" + desired_vector_1 = [1.0 / math.sqrt(2), 1.0 / math.sqrt(2)] + qr = QuantumRegister(1, "qr") + cr = ClassicalRegister(1, "cr") + qc = QuantumCircuit(qr, cr) + qc.initialize(desired_vector_1, [qr[0]]) + ops = qc.count_ops() + self.assertIn("initialize", ops.keys()) + self.assertEqual(ops["initialize"], 1) + + def test_reset(self): + """Test adding a reset to a quantum circuit.""" + qc = QuantumCircuit(3) + qc.cx(0, 1) + qc.reset(0) + qc.cx(0, 2) + ops = qc.count_ops() + self.assertIn("reset", ops.keys()) + self.assertEqual(ops["reset"], 1) + + def test_quantum_circuit(self): + """Test adding a quantum circuit to a quantum circuit.""" + qc1 = QuantumCircuit(2) + qc1.cx(0, 1) + qc1.h(1) + qc = QuantumCircuit(3) + qc.append(qc1, [1, 2]) + ops = qc.count_ops() + self.assertEqual(len(ops), 1) + + def test_clifford(self): + """Test adding a clifford to a quantum circuit.""" + qc1 = QuantumCircuit(3) + qc1.h(0) + qc1.s(1) + qc1.cx(0, 2) + cliff1 = Clifford(qc1) + qc = QuantumCircuit(5) + qc.append(cliff1, [1, 3, 4]) + ops = qc.count_ops() + self.assertIn("clifford", ops.keys()) + self.assertEqual(ops["clifford"], 1) + + def test_state_preparation(self): + """Test adding a state preparation to a quantum circuit.""" + qc = QuantumCircuit(2) + stateprep = StatePreparation([1 / math.sqrt(2), 0, 0, 1 / math.sqrt(2)]) + qc.append(stateprep.inverse().inverse(), [0, 1]) + ops = qc.count_ops() + self.assertIn("state_preparation", ops.keys()) + self.assertEqual(ops["state_preparation"], 1) + + +if __name__ == "__main__": + unittest.main() From 2c3b908dbf99dc0de973a04483907959ecbb227b Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Tue, 7 Jun 2022 19:00:38 +0300 Subject: [PATCH 09/27] moving HighLevelSynthesis pass from Decompose to QuantumCircuit.decompose; slightly changing Decompose pass to skip nodes with definition attribute --- qiskit/circuit/quantumcircuit.py | 6 ++++-- qiskit/transpiler/passes/basis/decompose.py | 15 ++++++--------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index 9ef1c02b13a5..62bb64a100ba 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -1511,11 +1511,13 @@ def decompose( """ # pylint: disable=cyclic-import from qiskit.transpiler.passes.basis.decompose import Decompose + from qiskit.transpiler.passes.synthesis import HighLevelSynthesis from qiskit.converters.circuit_to_dag import circuit_to_dag from qiskit.converters.dag_to_circuit import dag_to_circuit - pass_ = Decompose(gates_to_decompose=gates_to_decompose) - decomposed_dag = pass_.run(circuit_to_dag(self)) + dag = circuit_to_dag(self) + hls_dag = HighLevelSynthesis().run(dag) + decomposed_dag = Decompose(gates_to_decompose=gates_to_decompose).run(hls_dag) return dag_to_circuit(decomposed_dag) def _check_compatible_regs(self, rhs: "QuantumCircuit") -> None: diff --git a/qiskit/transpiler/passes/basis/decompose.py b/qiskit/transpiler/passes/basis/decompose.py index 12b7d6c44915..af323893711d 100644 --- a/qiskit/transpiler/passes/basis/decompose.py +++ b/qiskit/transpiler/passes/basis/decompose.py @@ -20,7 +20,6 @@ from qiskit.converters.circuit_to_dag import circuit_to_dag from qiskit.circuit.gate import Gate from qiskit.utils.deprecation import deprecate_arguments -from qiskit.transpiler.passes.synthesis import HighLevelSynthesis class Decompose(TransformationPass): @@ -85,22 +84,20 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: Returns: output dag where ``gate`` was expanded. """ - # We start by decomposing high-level objects (such as Cliffords) - dag = HighLevelSynthesis().run(dag) - # Walk through the DAG and expand each non-basis node for node in dag.op_nodes(): if self._should_decompose(node): - if node.op.definition is None: + node_definition = getattr(node.op, "definition", None) + if node_definition is None: continue # TODO: allow choosing among multiple decomposition rules - rule = node.op.definition.data + rule = node_definition.data if len(rule) == 1 and len(node.qargs) == len(rule[0][1]) == 1: - if node.op.definition.global_phase: - dag.global_phase += node.op.definition.global_phase + if node_definition.global_phase: + dag.global_phase += node_definition.global_phase dag.substitute_node(node, rule[0][0], inplace=True) else: - decomposition = circuit_to_dag(node.op.definition) + decomposition = circuit_to_dag(node_definition) dag.substitute_node_with_dag(node, decomposition) return dag From 14ddc7bcbf9c24aa3b144c775ee088667a45fc63 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Sat, 2 Jul 2022 22:51:03 +0300 Subject: [PATCH 10/27] Fixing broadcasting for Cliffords --- qiskit/circuit/instruction.py | 10 +++++++++- qiskit/circuit/quantumcircuit.py | 27 ++++++++++++++++++++------- qiskit/circuit/quantumcircuitdata.py | 7 ++++++- 3 files changed, 35 insertions(+), 9 deletions(-) diff --git a/qiskit/circuit/instruction.py b/qiskit/circuit/instruction.py index 1f9d09419a63..27ff11d2409a 100644 --- a/qiskit/circuit/instruction.py +++ b/qiskit/circuit/instruction.py @@ -93,7 +93,7 @@ def __init__(self, name, num_qubits, num_clbits, params, duration=None, unit="dt self._label = label # tuple (ClassicalRegister, int), tuple (Clbit, bool) or tuple (Clbit, int) # when the instruction has a conditional ("if") - self.condition = None + self._condition = None # list of instructions (and their contexts) that this instruction is composed of # empty definition means opaque or fundamental instruction self._definition = None @@ -103,6 +103,14 @@ def __init__(self, name, num_qubits, num_clbits, params, duration=None, unit="dt self.params = params # must be at last (other properties may be required for validation) + @property + def condition(self): + return self._condition + + @condition.setter + def condition(self, condition): + self._condition = condition + def __eq__(self, other): """Two instructions are the same if they have the same name, same dimensions, and same params. diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index 29de76a2d17e..43672739bf2c 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -1284,11 +1284,21 @@ def append( appender = self._append requester = self._resolve_classical_resource instructions = InstructionSet(resource_requester=requester) - for qarg, carg in operation.broadcast_arguments(expanded_qargs, expanded_cargs): - self._check_dups(qarg) - instruction = CircuitInstruction(operation, qarg, carg) - appender(instruction) - instructions.add(instruction) + if isinstance(operation, Instruction): + for qarg, carg in operation.broadcast_arguments(expanded_qargs, expanded_cargs): + self._check_dups(qarg) + instruction = CircuitInstruction(operation, qarg, carg) + appender(instruction) + instructions.add(instruction) + else: + # For Operations that are non-Instructions, we use the Instruction's default method + for qarg, carg in Instruction.broadcast_arguments( + operation, expanded_qargs, expanded_cargs + ): + self._check_dups(qarg) + instruction = CircuitInstruction(operation, qarg, carg) + appender(instruction) + instructions.add(instruction) return instructions # Preferred new style. @@ -1342,7 +1352,9 @@ def _append(self, instruction, qargs=None, cargs=None): if old_style: instruction = CircuitInstruction(instruction, qargs, cargs) self._data.append(instruction) - self._update_parameter_table(instruction) + if isinstance(instruction.operation, Instruction): + self._update_parameter_table(instruction) + # mark as normal circuit if a new instruction is added self.duration = None self.unit = "dt" @@ -4204,7 +4216,8 @@ def _pop_previous_instruction_in_scope(self) -> CircuitInstruction: if not self._data: raise CircuitError("This circuit contains no instructions.") instruction = self._data.pop() - self._update_parameter_table_on_instruction_removal(instruction) + if isinstance(instruction.operation, Instruction): + self._update_parameter_table_on_instruction_removal(instruction) return instruction def _update_parameter_table_on_instruction_removal(self, instruction: CircuitInstruction): diff --git a/qiskit/circuit/quantumcircuitdata.py b/qiskit/circuit/quantumcircuitdata.py index 41e7b4e26f94..614ecd5e8ddc 100644 --- a/qiskit/circuit/quantumcircuitdata.py +++ b/qiskit/circuit/quantumcircuitdata.py @@ -154,7 +154,12 @@ def _resolve_legacy_value(self, operation, qargs, cargs) -> CircuitInstruction: expanded_qargs = [self._circuit.qbit_argument_conversion(qarg) for qarg in qargs or []] expanded_cargs = [self._circuit.cbit_argument_conversion(carg) for carg in cargs or []] - broadcast_args = list(operation.broadcast_arguments(expanded_qargs, expanded_cargs)) + if isinstance(operation, Instruction): + broadcast_args = list(operation.broadcast_arguments(expanded_qargs, expanded_cargs)) + else: + broadcast_args = list( + Instruction.broadcast_arguments(operation, expanded_qargs, expanded_cargs) + ) if len(broadcast_args) > 1: raise CircuitError( From 4949a1d744dd19e418ca13a607613ec8a2d3def3 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Sat, 2 Jul 2022 23:56:27 +0300 Subject: [PATCH 11/27] making sure that transpile decomposes cliffords --- .../transpiler/preset_passmanagers/common.py | 5 +++ .../python/transpiler/test_clifford_passes.py | 41 +++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/qiskit/transpiler/preset_passmanagers/common.py b/qiskit/transpiler/preset_passmanagers/common.py index ce01dce6a26e..dbe230f889f2 100644 --- a/qiskit/transpiler/preset_passmanagers/common.py +++ b/qiskit/transpiler/preset_passmanagers/common.py @@ -25,6 +25,7 @@ from qiskit.transpiler.passes import Collect1qRuns from qiskit.transpiler.passes import ConsolidateBlocks from qiskit.transpiler.passes import UnitarySynthesis +from qiskit.transpiler.passes import HighLevelSynthesis from qiskit.transpiler.passes import CheckMap from qiskit.transpiler.passes import GateDirection from qiskit.transpiler.passes import BarrierBeforeFinalMeasurements @@ -82,6 +83,7 @@ def generate_unroll_3q( target=target, ) ) + unroll_3q.append(HighLevelSynthesis()) unroll_3q.append(Unroll3qOrMore(target=target, basis_gates=basis_gates)) return unroll_3q @@ -275,6 +277,7 @@ def generate_translation_passmanager( method=unitary_synthesis_method, target=target, ), + HighLevelSynthesis(), UnrollCustomDefinitions(sel, basis_gates), BasisTranslator(sel, basis_gates, target), ] @@ -292,6 +295,7 @@ def generate_translation_passmanager( min_qubits=3, target=target, ), + HighLevelSynthesis(), Unroll3qOrMore(target=target, basis_gates=basis_gates), Collect2qBlocks(), Collect1qRuns(), @@ -305,6 +309,7 @@ def generate_translation_passmanager( method=unitary_synthesis_method, target=target, ), + HighLevelSynthesis() ] else: raise TranspilerError("Invalid translation method %s." % method) diff --git a/test/python/transpiler/test_clifford_passes.py b/test/python/transpiler/test_clifford_passes.py index 9ff5f31249b0..47b1bd257038 100644 --- a/test/python/transpiler/test_clifford_passes.py +++ b/test/python/transpiler/test_clifford_passes.py @@ -24,6 +24,7 @@ from qiskit.quantum_info.operators import Clifford from qiskit.transpiler import PassManager from qiskit.quantum_info import Operator, random_clifford +from qiskit.compiler.transpiler import transpile class TestCliffordPasses(QiskitTestCase): @@ -229,6 +230,46 @@ def test_optimize_cliffords(self): self.assertTrue(Operator(qc1).equiv(Operator(qc2))) self.assertTrue(Operator(qc1).equiv(Operator(qc3))) + def test_transpile_level_0(self): + """Make sure that transpile with optimization_level=0 transpiles + the Clifford.""" + cliff1 = self.create_cliff1() + qc = QuantumCircuit(3) + qc.append(cliff1, [0, 1, 2]) + self.assertIn("clifford", qc.count_ops()) + qc2 = transpile(qc, optimization_level=0) + self.assertNotIn("clifford", qc2.count_ops()) + + def test_transpile_level_1(self): + """Make sure that transpile with optimization_level=1 transpiles + the Clifford.""" + cliff1 = self.create_cliff1() + qc = QuantumCircuit(3) + qc.append(cliff1, [0, 1, 2]) + self.assertIn("clifford", qc.count_ops()) + qc2 = transpile(qc, optimization_level=1) + self.assertNotIn("clifford", qc2.count_ops()) + + def test_transpile_level_2(self): + """Make sure that transpile with optimization_level=2 transpiles + the Clifford.""" + cliff1 = self.create_cliff1() + qc = QuantumCircuit(3) + qc.append(cliff1, [0, 1, 2]) + self.assertIn("clifford", qc.count_ops()) + qc2 = transpile(qc, optimization_level=2) + self.assertNotIn("clifford", qc2.count_ops()) + + def test_transpile_level_3(self): + """Make sure that transpile with optimization_level=2 transpiles + the Clifford.""" + cliff1 = self.create_cliff1() + qc = QuantumCircuit(3) + qc.append(cliff1, [0, 1, 2]) + self.assertIn("clifford", qc.count_ops()) + qc2 = transpile(qc, optimization_level=3) + self.assertNotIn("clifford", qc2.count_ops()) + if __name__ == "__main__": unittest.main() From c1ab8600cb4931c2678aeb69ee5518b62f1f3961 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Sun, 3 Jul 2022 01:10:27 +0300 Subject: [PATCH 12/27] lint --- qiskit/transpiler/preset_passmanagers/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/transpiler/preset_passmanagers/common.py b/qiskit/transpiler/preset_passmanagers/common.py index dbe230f889f2..d66037e616b9 100644 --- a/qiskit/transpiler/preset_passmanagers/common.py +++ b/qiskit/transpiler/preset_passmanagers/common.py @@ -309,7 +309,7 @@ def generate_translation_passmanager( method=unitary_synthesis_method, target=target, ), - HighLevelSynthesis() + HighLevelSynthesis(), ] else: raise TranspilerError("Invalid translation method %s." % method) From aeef8943af73e9c042db9677cf4f63676a0e9eef Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Sun, 3 Jul 2022 08:24:00 +0300 Subject: [PATCH 13/27] As per code review, replacing x._direction by getattr(x, '_direction', False) --- qiskit/circuit/operation.py | 6 ------ qiskit/circuit/quantumcircuit.py | 16 ++++++++++++---- qiskit/dagcircuit/dagcircuit.py | 8 +++++--- qiskit/dagcircuit/dagdependency.py | 6 ++++-- .../passes/basis/unroll_custom_definitions.py | 2 +- qiskit/transpiler/passes/basis/unroller.py | 2 +- .../passes/optimization/commutation_analysis.py | 2 +- qiskit/visualization/dag_visualization.py | 2 +- qiskit/visualization/latex.py | 2 +- qiskit/visualization/matplotlib.py | 12 ++++++++---- qiskit/visualization/text.py | 4 ++-- 11 files changed, 36 insertions(+), 26 deletions(-) diff --git a/qiskit/circuit/operation.py b/qiskit/circuit/operation.py index 7feacfba4b06..6a64459741e1 100644 --- a/qiskit/circuit/operation.py +++ b/qiskit/circuit/operation.py @@ -60,12 +60,6 @@ def num_clbits(self): """Number of classical bits.""" raise NotImplementedError - @property - def _directive(self): - """Class attribute to treat like barrier for transpiler, unroller, drawer. - This is temporary, please do not use.""" - return False - @property def condition(self): """Condition for when the instruction has a conditional if. diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index 43672739bf2c..ec94831080e2 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -1946,7 +1946,10 @@ def draw( ) def size( - self, filter_function: Optional[callable] = lambda x: not x.operation._directive + self, + filter_function: Optional[callable] = lambda x: not getattr( + x.operation, "_directive", False + ), ) -> int: """Returns total number of instructions in circuit. @@ -1961,7 +1964,10 @@ def size( return sum(map(filter_function, self._data)) def depth( - self, filter_function: Optional[callable] = lambda x: not x.operation._directive + self, + filter_function: Optional[callable] = lambda x: not getattr( + x.operation, "_directive", False + ), ) -> int: """Return circuit depth (i.e., length of critical path). @@ -2074,7 +2080,9 @@ def num_nonlocal_gates(self) -> int: """ multi_qubit_gates = 0 for instruction in self._data: - if instruction.operation.num_qubits > 1 and not instruction.operation._directive: + if instruction.operation.num_qubits > 1 and not getattr( + instruction.operation, "_directive", False + ): multi_qubit_gates += 1 return multi_qubit_gates @@ -2117,7 +2125,7 @@ def num_connected_components(self, unitary_only: bool = False) -> int: args = instruction.qubits + instruction.clbits num_qargs = len(args) + (1 if instruction.operation.condition else 0) - if num_qargs >= 2 and not instruction.operation._directive: + if num_qargs >= 2 and not getattr(instruction.operation, "_directive", False): graphs_touched = [] num_touched = 0 # Controls necessarily join all the cbits in the diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index 60f6085a9241..1ff045c84941 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -1379,7 +1379,7 @@ def op_nodes(self, op=None, include_directives=True): nodes = [] for node in self._multi_graph.nodes(): if isinstance(node, DAGOpNode): - if not include_directives and node.op._directive: + if not include_directives and getattr(node.op, "_directive", False): continue if op is None or isinstance(node.op, op): nodes.append(node) @@ -1584,7 +1584,9 @@ def layers(self): # The quantum registers that have an operation in this layer. support_list = [ - op_node.qargs for op_node in new_layer.op_nodes() if not op_node.op._directive + op_node.qargs + for op_node in new_layer.op_nodes() + if not getattr(op_node.op, "_directive", False) ] yield {"graph": new_layer, "partition": support_list} @@ -1610,7 +1612,7 @@ def serial_layers(self): # Add node to new_layer new_layer.apply_operation_back(op, qargs, cargs) # Add operation to partition - if not next_node.op._directive: + if not getattr(next_node.op, "_directive", False): support_list.append(list(qargs)) l_dict = {"graph": new_layer, "partition": support_list} yield l_dict diff --git a/qiskit/dagcircuit/dagdependency.py b/qiskit/dagcircuit/dagdependency.py index ceaa9df6b845..a186621540da 100644 --- a/qiskit/dagcircuit/dagdependency.py +++ b/qiskit/dagcircuit/dagdependency.py @@ -388,7 +388,7 @@ def add_op_node(self, operation, qargs, cargs): cargs (list[Clbit]): list of classical wires to attach to. """ directives = ["measure"] - if not operation._directive and operation.name not in directives: + if not getattr(operation, "_directive", False) and operation.name not in directives: qindices_list = [] for elem in qargs: qindices_list.append(self.qubits.index(elem)) @@ -601,7 +601,9 @@ def _does_commute(node1, node2): non_unitaries = ["measure", "reset", "initialize", "delay"] def _unknown_commutator(n): - return n.op._directive or n.name in non_unitaries or n.op.is_parameterized() + return ( + getattr(n.op, "_directive", False) or n.name in non_unitaries or n.op.is_parameterized() + ) if _unknown_commutator(node1) or _unknown_commutator(node2): intersection_q = set(qarg1).intersection(set(qarg2)) diff --git a/qiskit/transpiler/passes/basis/unroll_custom_definitions.py b/qiskit/transpiler/passes/basis/unroll_custom_definitions.py index 8f8248e93fd3..185460ab9506 100644 --- a/qiskit/transpiler/passes/basis/unroll_custom_definitions.py +++ b/qiskit/transpiler/passes/basis/unroll_custom_definitions.py @@ -57,7 +57,7 @@ def run(self, dag): for node in dag.op_nodes(): - if node.op._directive: + if getattr(node.op, "_directive", False): continue if dag.has_calibration_for(node): diff --git a/qiskit/transpiler/passes/basis/unroller.py b/qiskit/transpiler/passes/basis/unroller.py index d4bb93134fca..035f87455783 100644 --- a/qiskit/transpiler/passes/basis/unroller.py +++ b/qiskit/transpiler/passes/basis/unroller.py @@ -54,7 +54,7 @@ def run(self, dag): # Walk through the DAG and expand each non-basis node basic_insts = ["measure", "reset", "barrier", "snapshot", "delay"] for node in dag.op_nodes(): - if node.op._directive: + if getattr(node.op, "_directive", False): continue if node.name in basic_insts: diff --git a/qiskit/transpiler/passes/optimization/commutation_analysis.py b/qiskit/transpiler/passes/optimization/commutation_analysis.py index 2226438f9ef7..2657fea5cef1 100644 --- a/qiskit/transpiler/passes/optimization/commutation_analysis.py +++ b/qiskit/transpiler/passes/optimization/commutation_analysis.py @@ -118,7 +118,7 @@ def _commute(node1, node2, cache): if not isinstance(node1, DAGOpNode) or not isinstance(node2, DAGOpNode): return False for nd in [node1, node2]: - if nd.op._directive or nd.name in {"measure", "reset", "delay"}: + if getattr(nd.op, "_directive", False) or nd.name in {"measure", "reset", "delay"}: return False if node1.op.condition or node2.op.condition: return False diff --git a/qiskit/visualization/dag_visualization.py b/qiskit/visualization/dag_visualization.py index c016e8b292dc..e7873b202717 100644 --- a/qiskit/visualization/dag_visualization.py +++ b/qiskit/visualization/dag_visualization.py @@ -89,7 +89,7 @@ def node_attr_func(node): n["color"] = "black" n["style"] = "filled" n["fillcolor"] = "green" - if node.op._directive: + if getattr(node.op, "_directive", False): n["color"] = "black" n["style"] = "filled" n["fillcolor"] = "red" diff --git a/qiskit/visualization/latex.py b/qiskit/visualization/latex.py index 64dcb91e2007..0a0ac777c0aa 100644 --- a/qiskit/visualization/latex.py +++ b/qiskit/visualization/latex.py @@ -429,7 +429,7 @@ def _build_latex_array(self): if isinstance(op, Measure): self._build_measure(node, column) - elif op._directive: # barrier, snapshot, etc. + elif getattr(op, "_directive", False): # barrier, snapshot, etc. self._build_barrier(node, column) else: diff --git a/qiskit/visualization/matplotlib.py b/qiskit/visualization/matplotlib.py index 5d9d49ab0f03..192cae99eb3a 100644 --- a/qiskit/visualization/matplotlib.py +++ b/qiskit/visualization/matplotlib.py @@ -414,7 +414,7 @@ def _get_layer_widths(self): self._data[node] = {} self._data[node]["width"] = WID num_ctrl_qubits = 0 if not hasattr(op, "num_ctrl_qubits") else op.num_ctrl_qubits - if op._directive or isinstance(op, Measure): + if getattr(op, "_directive", False) or isinstance(op, Measure): self._data[node]["raw_gate_text"] = op.name continue @@ -604,7 +604,9 @@ def _get_coords(self, n_lines): barrier_offset = 0 if not self._plot_barriers: # only adjust if everything in the layer wasn't plotted - barrier_offset = -1 if all(nd.op._directive for nd in layer) else 0 + barrier_offset = ( + -1 if all(getattr(nd.op, "_directive", False) for nd in layer) else 0 + ) prev_x_index = anc_x_index + layer_width + barrier_offset - 1 return prev_x_index + 1 @@ -809,7 +811,7 @@ def _draw_ops(self, verbose=False): self._measure(node) # draw barriers, snapshots, etc. - elif op._directive: + elif getattr(op, "_directive", False): if self._plot_barriers: self._barrier(node) @@ -829,7 +831,9 @@ def _draw_ops(self, verbose=False): barrier_offset = 0 if not self._plot_barriers: # only adjust if everything in the layer wasn't plotted - barrier_offset = -1 if all(nd.op._directive for nd in layer) else 0 + barrier_offset = ( + -1 if all(getattr(nd.op, "_directive", False) for nd in layer) else 0 + ) prev_x_index = anc_x_index + layer_width + barrier_offset - 1 diff --git a/qiskit/visualization/text.py b/qiskit/visualization/text.py index 7a6dec09afdb..88e44987aade 100644 --- a/qiskit/visualization/text.py +++ b/qiskit/visualization/text.py @@ -1055,7 +1055,7 @@ def _node_to_gate(self, node, layer): base_gate = getattr(op, "base_gate", None) params = get_param_str(op, "text", ndigits=5) - if not isinstance(op, (Measure, SwapGate, Reset)) and not op._directive: + if not isinstance(op, (Measure, SwapGate, Reset)) and not getattr(op, "_directive", False): gate_text, ctrl_text, _ = get_gate_ctrl_text(op, "text") gate_text = TextDrawing.special_label(op) or gate_text gate_text = gate_text + params @@ -1087,7 +1087,7 @@ def add_connected_gate(node, gates, layer, current_cons): else: layer.set_clbit(node.cargs[0], MeasureTo()) - elif op._directive: + elif getattr(op, "_directive", False): # barrier if not self.plotbarriers: return layer, current_cons, connection_label From 9f019c2f525a044871b2b783bad6f266a8140d57 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Tue, 5 Jul 2022 15:01:32 +0300 Subject: [PATCH 14/27] Removing condition from Operation API. Removing previously added getter and setter to instruction.py. As per code review, starting to replace condition getters and setters. For now, only dagcircuit and circuit_to_instruction. --- qiskit/circuit/instruction.py | 10 +--- qiskit/circuit/operation.py | 6 --- qiskit/converters/circuit_to_instruction.py | 2 +- qiskit/dagcircuit/dagcircuit.py | 55 ++++++++++++++------- 4 files changed, 38 insertions(+), 35 deletions(-) diff --git a/qiskit/circuit/instruction.py b/qiskit/circuit/instruction.py index 27ff11d2409a..1f9d09419a63 100644 --- a/qiskit/circuit/instruction.py +++ b/qiskit/circuit/instruction.py @@ -93,7 +93,7 @@ def __init__(self, name, num_qubits, num_clbits, params, duration=None, unit="dt self._label = label # tuple (ClassicalRegister, int), tuple (Clbit, bool) or tuple (Clbit, int) # when the instruction has a conditional ("if") - self._condition = None + self.condition = None # list of instructions (and their contexts) that this instruction is composed of # empty definition means opaque or fundamental instruction self._definition = None @@ -103,14 +103,6 @@ def __init__(self, name, num_qubits, num_clbits, params, duration=None, unit="dt self.params = params # must be at last (other properties may be required for validation) - @property - def condition(self): - return self._condition - - @condition.setter - def condition(self, condition): - self._condition = condition - def __eq__(self, other): """Two instructions are the same if they have the same name, same dimensions, and same params. diff --git a/qiskit/circuit/operation.py b/qiskit/circuit/operation.py index 6a64459741e1..f2e898c069f9 100644 --- a/qiskit/circuit/operation.py +++ b/qiskit/circuit/operation.py @@ -59,9 +59,3 @@ def num_qubits(self): def num_clbits(self): """Number of classical bits.""" raise NotImplementedError - - @property - def condition(self): - """Condition for when the instruction has a conditional if. - This is temporary, please do not use.""" - return None diff --git a/qiskit/converters/circuit_to_instruction.py b/qiskit/converters/circuit_to_instruction.py index a30a3576503b..a655c5141b3d 100644 --- a/qiskit/converters/circuit_to_instruction.py +++ b/qiskit/converters/circuit_to_instruction.py @@ -110,7 +110,7 @@ def circuit_to_instruction(circuit, parameter_map=None, equivalence_library=None # fix condition for rule in definition: - condition = rule.operation.condition + condition = getattr(rule.operation, "condition", None) if condition: reg, val = condition if isinstance(reg, Clbit): diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index 1ff045c84941..3f0fa48be8dc 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -32,6 +32,7 @@ from qiskit.circuit.quantumregister import QuantumRegister, Qubit from qiskit.circuit.classicalregister import ClassicalRegister, Clbit from qiskit.circuit.gate import Gate +from qiskit.circuit.instruction import Instruction from qiskit.circuit.parameterexpression import ParameterExpression from qiskit.dagcircuit.exceptions import DAGCircuitError from qiskit.dagcircuit.dagnode import DAGNode, DAGOpNode, DAGInNode, DAGOutNode @@ -554,10 +555,10 @@ def apply_operation_back(self, op, qargs=(), cargs=()): qargs = tuple(qargs) if qargs is not None else () cargs = tuple(cargs) if cargs is not None else () - all_cbits = self._bits_in_condition(op.condition) + all_cbits = self._bits_in_condition(getattr(op, "condition", None)) all_cbits = set(all_cbits).union(cargs) - self._check_condition(op.name, op.condition) + self._check_condition(op.name, getattr(op, "condition", None)) self._check_bits(qargs, self.output_map) self._check_bits(all_cbits, self.output_map) @@ -586,10 +587,10 @@ def apply_operation_front(self, op, qargs=(), cargs=()): Raises: DAGCircuitError: if initial nodes connected to multiple out edges """ - all_cbits = self._bits_in_condition(op.condition) + all_cbits = self._bits_in_condition(getattr(op, "condition", None)) all_cbits.extend(cargs) - self._check_condition(op.name, op.condition) + self._check_condition(op.name, getattr(op, "condition", None)) self._check_bits(qargs, self.input_map) self._check_bits(all_cbits, self.input_map) node_index = self._add_op_node(op, qargs, cargs) @@ -835,11 +836,15 @@ def compose(self, other, qubits=None, clbits=None, front=False, inplace=True): # ignore output nodes pass elif isinstance(nd, DAGOpNode): - condition = dag._map_condition(edge_map, nd.op.condition, dag.cregs.values()) + condition = dag._map_condition( + edge_map, getattr(nd.op, "condition", None), dag.cregs.values() + ) dag._check_condition(nd.op.name, condition) m_qargs = list(map(lambda x: edge_map.get(x, x), nd.qargs)) m_cargs = list(map(lambda x: edge_map.get(x, x), nd.cargs)) op = nd.op.copy() + if condition and not isinstance(op, Instruction): + raise DAGCircuitError("Cannot add a condition on a generic Operation.") op.condition = condition dag.apply_operation_back(op, m_qargs, m_cargs) else: @@ -954,8 +959,8 @@ def _check_wires_list(self, wires, node): raise DAGCircuitError("duplicate wires") wire_tot = len(node.qargs) + len(node.cargs) - if node.op.condition is not None: - wire_tot += node.op.condition[0].size + if getattr(node.op, "condition", None) is not None: + wire_tot += getattr(node.op, "condition", None)[0].size if len(wires) != wire_tot: raise DAGCircuitError("expected %d wires, got %d" % (wire_tot, len(wires))) @@ -1083,7 +1088,7 @@ def replace_block_with_op(self, node_block, op, wire_pos_map, cycle_check=True): for nd in node_block: block_qargs |= set(nd.qargs) - if isinstance(nd, DAGOpNode) and nd.op.condition: + if isinstance(nd, DAGOpNode) and getattr(nd.op, "condition", None): block_cargs |= set(nd.cargs) # Create replacement node @@ -1128,13 +1133,17 @@ def substitute_node_with_dag(self, node, input_dag, wires=None): # the dag must be amended if used in a # conditional context. delete the op nodes and replay # them with the condition. - if node.op.condition: + if getattr(node.op, "condition", None): in_dag = copy.deepcopy(input_dag) - in_dag.add_creg(node.op.condition[0]) + in_dag.add_creg(getattr(node.op, "condition", None)[0]) to_replay = [] for sorted_node in in_dag.topological_nodes(): if isinstance(sorted_node, DAGOpNode): - sorted_node.op.condition = node.op.condition + if getattr(node.op, "condition", None) and not isinstance( + sorted_node.op, Instruction + ): + raise DAGCircuitError("Cannot add a condition on a generic Operation.") + sorted_node.op.condition = getattr(node.op, "condition", None) to_replay.append(sorted_node) for input_node in in_dag.op_nodes(): in_dag.remove_op_node(input_node) @@ -1166,7 +1175,7 @@ def substitute_node_with_dag(self, node, input_dag, wires=None): if not isinstance(node, DAGOpNode): raise DAGCircuitError("expected node DAGOpNode, got %s" % type(node)) - condition_bit_list = self._bits_in_condition(node.op.condition) + condition_bit_list = self._bits_in_condition(getattr(node.op, "condition", None)) new_wires = list(node.qargs) + list(node.cargs) + list(condition_bit_list) @@ -1258,11 +1267,15 @@ def edge_weight_map(wire): for old_node_index, new_node_index in node_map.items(): # update node attributes old_node = in_dag._multi_graph[old_node_index] - condition = self._map_condition(wire_map, old_node.op.condition, self.cregs.values()) + condition = self._map_condition( + wire_map, getattr(old_node.op, "condition", None), self.cregs.values() + ) m_qargs = [wire_map.get(x, x) for x in old_node.qargs] m_cargs = [wire_map.get(x, x) for x in old_node.cargs] new_node = DAGOpNode(old_node.op, qargs=m_qargs, cargs=m_cargs) new_node._node_id = new_node_index + if condition and not isinstance(new_node.op, Instruction): + raise DAGCircuitError("Cannot add a condition on a generic Operation.") new_node.op.condition = condition self._multi_graph[new_node_index] = new_node self._increment_op(new_node.op) @@ -1306,14 +1319,18 @@ def substitute_node(self, node, op, inplace=False): if op.name != node.op.name: self._increment_op(op) self._decrement_op(node.op) - save_condition = node.op.condition + save_condition = getattr(node.op, "condition", None) node.op = op + if save_condition and not isinstance(op, Instruction): + raise DAGCircuitError("Cannot add a condition on a generic Operation.") node.op.condition = save_condition return node new_node = copy.copy(node) - save_condition = new_node.op.condition + save_condition = getattr(new_node.op, "condition", None) new_node.op = op + if save_condition and not isinstance(new_node.op, Instruction): + raise DAGCircuitError("Cannot add a condition on a generic Operation.") new_node.op.condition = save_condition self._multi_graph[node._node_id] = new_node if op.name != node.op.name: @@ -1606,7 +1623,7 @@ def serial_layers(self): op = copy.copy(next_node.op) qargs = copy.copy(next_node.qargs) cargs = copy.copy(next_node.cargs) - condition = copy.copy(next_node.op.condition) + condition = copy.copy(getattr(next_node.op, "condition", None)) _ = self._bits_in_condition(condition) # Add node to new_layer @@ -1639,7 +1656,7 @@ def filter_fn(node): return ( isinstance(node, DAGOpNode) and node.op.name in namelist - and node.op.condition is None + and getattr(node.op, "condition", None) is None ) group_list = rx.collect_runs(self._multi_graph, filter_fn) @@ -1653,7 +1670,7 @@ def filter_fn(node): isinstance(node, DAGOpNode) and len(node.qargs) == 1 and len(node.cargs) == 0 - and node.op.condition is None + and getattr(node.op, "condition", None) is None and not node.op.is_parameterized() and isinstance(node.op, Gate) and hasattr(node.op, "__array__") @@ -1673,7 +1690,7 @@ def filter_fn(node): return ( isinstance(node.op, Gate) and len(node.qargs) <= 2 - and not node.op.condition + and not getattr(node.op, "condition", None) and not node.op.is_parameterized() ) else: From c80efa906bb8ee47623a223a36a473742d89813d Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Tue, 5 Jul 2022 15:56:45 +0300 Subject: [PATCH 15/27] pass over condition in transpiler code --- qiskit/transpiler/passes/basis/basis_translator.py | 2 +- .../passes/optimization/collect_linear_functions.py | 2 +- .../passes/optimization/collect_multiqubit_blocks.py | 4 ++-- .../passes/optimization/commutation_analysis.py | 2 +- .../passes/optimization/commutative_cancellation.py | 2 +- .../transpiler/passes/optimization/consolidate_blocks.py | 4 ++-- .../transpiler/passes/optimization/optimize_1q_gates.py | 2 +- .../passes/optimization/optimize_swap_before_measure.py | 2 +- .../optimization/template_matching/backward_match.py | 9 ++++++--- .../optimization/template_matching/forward_match.py | 9 ++++++--- 10 files changed, 22 insertions(+), 16 deletions(-) diff --git a/qiskit/transpiler/passes/basis/basis_translator.py b/qiskit/transpiler/passes/basis/basis_translator.py index 93dc08067889..67131298f1b7 100644 --- a/qiskit/transpiler/passes/basis/basis_translator.py +++ b/qiskit/transpiler/passes/basis/basis_translator.py @@ -277,7 +277,7 @@ def _replace_node(self, dag, node, instr_map): dag_op = bound_target_dag.op_nodes()[0].op # dag_op may be the same instance as other ops in the dag, # so if there is a condition, need to copy - if node.op.condition: + if getattr(node.op, "condition", None): dag_op = dag_op.copy() dag.substitute_node(node, dag_op, inplace=True) diff --git a/qiskit/transpiler/passes/optimization/collect_linear_functions.py b/qiskit/transpiler/passes/optimization/collect_linear_functions.py index 0a1303a2325e..455115511bbc 100644 --- a/qiskit/transpiler/passes/optimization/collect_linear_functions.py +++ b/qiskit/transpiler/passes/optimization/collect_linear_functions.py @@ -30,7 +30,7 @@ def collect_linear_blocks(dag): pending_non_linear_ops = deque() def is_linear(op): - return op.name in ("cx", "swap") and op.condition is None + return op.name in ("cx", "swap") and getattr(op, "condition", None) is None def process_node(node): for suc in dag.successors(node): diff --git a/qiskit/transpiler/passes/optimization/collect_multiqubit_blocks.py b/qiskit/transpiler/passes/optimization/collect_multiqubit_blocks.py index 527cdbfd087e..e7afc7788510 100644 --- a/qiskit/transpiler/passes/optimization/collect_multiqubit_blocks.py +++ b/qiskit/transpiler/passes/optimization/collect_multiqubit_blocks.py @@ -120,7 +120,7 @@ def collect_key(x): if not isinstance(x, DAGOpNode): return "d" if isinstance(x.op, Gate): - if x.op.is_parameterized() or x.op.condition is not None: + if x.op.is_parameterized() or getattr(x.op, "condition", None) is not None: return "c" return "b" + chr(ord("a") + len(x.qargs)) return "d" @@ -134,7 +134,7 @@ def collect_key(x): # check if the node is a gate and if it is parameterized if ( - nd.op.condition is not None + getattr(nd.op, "condition", None) is not None or nd.op.is_parameterized() or not isinstance(nd.op, Gate) ): diff --git a/qiskit/transpiler/passes/optimization/commutation_analysis.py b/qiskit/transpiler/passes/optimization/commutation_analysis.py index 2657fea5cef1..c9302e375af6 100644 --- a/qiskit/transpiler/passes/optimization/commutation_analysis.py +++ b/qiskit/transpiler/passes/optimization/commutation_analysis.py @@ -120,7 +120,7 @@ def _commute(node1, node2, cache): for nd in [node1, node2]: if getattr(nd.op, "_directive", False) or nd.name in {"measure", "reset", "delay"}: return False - if node1.op.condition or node2.op.condition: + if getattr(node1.op, "condition", None) or getattr(node2.op, "condition", None): return False if node1.op.is_parameterized() or node2.op.is_parameterized(): return False diff --git a/qiskit/transpiler/passes/optimization/commutative_cancellation.py b/qiskit/transpiler/passes/optimization/commutative_cancellation.py index c3e956a9bbb8..7db4d37fc769 100644 --- a/qiskit/transpiler/passes/optimization/commutative_cancellation.py +++ b/qiskit/transpiler/passes/optimization/commutative_cancellation.py @@ -136,7 +136,7 @@ def run(self, dag): total_phase = 0.0 for current_node in run: if ( - current_node.op.condition is not None + getattr(current_node.op, "condition", None) is not None or len(current_node.qargs) != 1 or current_node.qargs[0] != run_qarg ): diff --git a/qiskit/transpiler/passes/optimization/consolidate_blocks.py b/qiskit/transpiler/passes/optimization/consolidate_blocks.py index c9f2d540b60e..4168ed17f0e1 100644 --- a/qiskit/transpiler/passes/optimization/consolidate_blocks.py +++ b/qiskit/transpiler/passes/optimization/consolidate_blocks.py @@ -87,8 +87,8 @@ def run(self, dag): block_cargs = set() for nd in block: block_qargs |= set(nd.qargs) - if isinstance(nd, DAGOpNode) and nd.op.condition: - block_cargs |= set(nd.op.condition[0]) + if isinstance(nd, DAGOpNode) and getattr(nd.op, "condition", None): + block_cargs |= set(getattr(nd.op, "condition", None)[0]) all_block_gates.add(nd) q = QuantumRegister(len(block_qargs)) qc = QuantumCircuit(q) diff --git a/qiskit/transpiler/passes/optimization/optimize_1q_gates.py b/qiskit/transpiler/passes/optimization/optimize_1q_gates.py index 61fb78aa9ec1..f702621d1d7d 100644 --- a/qiskit/transpiler/passes/optimization/optimize_1q_gates.py +++ b/qiskit/transpiler/passes/optimization/optimize_1q_gates.py @@ -72,7 +72,7 @@ def run(self, dag): for current_node in run: left_name = current_node.name if ( - current_node.op.condition is not None + getattr(current_node.op, "condition", None) is not None or len(current_node.qargs) != 1 or left_name not in ["p", "u1", "u2", "u3", "u", "id"] ): diff --git a/qiskit/transpiler/passes/optimization/optimize_swap_before_measure.py b/qiskit/transpiler/passes/optimization/optimize_swap_before_measure.py index a736b9438639..f4bc30d0cdb3 100644 --- a/qiskit/transpiler/passes/optimization/optimize_swap_before_measure.py +++ b/qiskit/transpiler/passes/optimization/optimize_swap_before_measure.py @@ -37,7 +37,7 @@ def run(self, dag): """ swaps = dag.op_nodes(SwapGate) for swap in swaps[::-1]: - if swap.op.condition is not None: + if getattr(swap.op, "condition", None) is not None: continue final_successor = [] for successor in dag.successors(swap): diff --git a/qiskit/transpiler/passes/optimization/template_matching/backward_match.py b/qiskit/transpiler/passes/optimization/template_matching/backward_match.py index df817c701f92..a4b11a33de2d 100644 --- a/qiskit/transpiler/passes/optimization/template_matching/backward_match.py +++ b/qiskit/transpiler/passes/optimization/template_matching/backward_match.py @@ -304,13 +304,16 @@ def _is_same_c_conf(self, node_circuit, node_template, carg_circuit): """ if ( node_circuit.type == "op" - and node_circuit.op.condition + and getattr(node_circuit.op, "condition", None) and node_template.type == "op" - and node_template.op.condition + and getattr(node_template.op, "condition", None) ): if set(carg_circuit) != set(node_template.cindices): return False - if node_circuit.op.condition[1] != node_template.op.conditon[1]: + if ( + getattr(node_circuit.op, "condition", None)[1] + != getattr(node_template.op, "condition", None)[1] + ): return False return True diff --git a/qiskit/transpiler/passes/optimization/template_matching/forward_match.py b/qiskit/transpiler/passes/optimization/template_matching/forward_match.py index 3e996a572004..6c5380539701 100644 --- a/qiskit/transpiler/passes/optimization/template_matching/forward_match.py +++ b/qiskit/transpiler/passes/optimization/template_matching/forward_match.py @@ -313,13 +313,16 @@ def _is_same_c_conf(self, node_circuit, node_template): """ if ( node_circuit.type == "op" - and node_circuit.op.condition + and getattr(node_circuit.op, "condition", None) and node_template.type == "op" - and node_template.op.conditon + and getattr(node_template.op, "condition", None) ): if set(self.carg_indices) != set(node_template.cindices): return False - if node_circuit.op.condition[1] != node_template.op.conditon[1]: + if ( + getattr(node_circuit.op, "condition", None)[1] + != getattr(node_template.op, "condition", None)[1] + ): return False return True From 4b1a246c21d3734473f89910b3716ddfbeda4fe9 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Tue, 5 Jul 2022 17:02:32 +0300 Subject: [PATCH 16/27] more refactoring of condition --- qiskit/circuit/quantumcircuit.py | 8 +++++--- qiskit/dagcircuit/dagdependency.py | 4 ++-- qiskit/dagcircuit/dagdepnode.py | 6 ++++-- qiskit/dagcircuit/dagnode.py | 2 +- qiskit/visualization/dag_visualization.py | 2 +- qiskit/visualization/latex.py | 14 ++++++++++---- qiskit/visualization/matplotlib.py | 2 +- qiskit/visualization/text.py | 4 ++-- qiskit/visualization/utils.py | 4 ++-- 9 files changed, 28 insertions(+), 18 deletions(-) diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index ec94831080e2..dcd125dafb86 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -948,7 +948,7 @@ def compose( n_cargs = [edge_map[carg] for carg in instr.clbits] n_instr = instr.operation.copy() - if instr.operation.condition is not None: + if getattr(instr.operation, "condition", None) is not None: from qiskit.dagcircuit import DAGCircuit # pylint: disable=cyclic-import n_instr.condition = DAGCircuit._map_condition( @@ -2016,7 +2016,7 @@ def depth( levels.append(op_stack[reg_ints[ind]]) # Assuming here that there is no conditional # snapshots or barriers ever. - if instruction.operation.condition: + if getattr(instruction.operation, "condition", None): # Controls operate over all bits of a classical register # or over a single bit if isinstance(instruction.operation.condition[0], Clbit): @@ -2123,7 +2123,9 @@ def num_connected_components(self, unitary_only: bool = False) -> int: num_qargs = len(args) else: args = instruction.qubits + instruction.clbits - num_qargs = len(args) + (1 if instruction.operation.condition else 0) + num_qargs = len(args) + ( + 1 if getattr(instruction.operation, "condition", None) else 0 + ) if num_qargs >= 2 and not getattr(instruction.operation, "_directive", False): graphs_touched = [] diff --git a/qiskit/dagcircuit/dagdependency.py b/qiskit/dagcircuit/dagdependency.py index a186621540da..281a84e0a75e 100644 --- a/qiskit/dagcircuit/dagdependency.py +++ b/qiskit/dagcircuit/dagdependency.py @@ -392,7 +392,7 @@ def add_op_node(self, operation, qargs, cargs): qindices_list = [] for elem in qargs: qindices_list.append(self.qubits.index(elem)) - if operation.condition: + if getattr(operation, "condition", None): for clbit in self.clbits: if clbit in operation.condition[0]: initial = self.clbits.index(clbit) @@ -591,7 +591,7 @@ def _does_commute(node1, node2): # TODO: qubits can be the same if conditions are identical and # the non-conditional gates commute. if node1.type == "op" and node2.type == "op": - if node1.op.condition or node2.op.condition: + if getattr(node1.op, "condition", None) or getattr(node2.op, "condition", None): intersection = set(qarg1).intersection(set(qarg2)) return not intersection diff --git a/qiskit/dagcircuit/dagdepnode.py b/qiskit/dagcircuit/dagdepnode.py index 3c370aeaa02b..c4038db24f8c 100644 --- a/qiskit/dagcircuit/dagdepnode.py +++ b/qiskit/dagcircuit/dagdepnode.py @@ -111,7 +111,7 @@ def condition(self): DeprecationWarning, 2, ) - return self._op.condition + return getattr(self._op, "condition", None) @condition.setter def condition(self, new_condition): @@ -162,7 +162,9 @@ def semantic_eq(node1, node2): if node1._qargs == node2._qargs: if node1.cargs == node2.cargs: if node1.type == "op": - if node1._op.condition != node2._op.condition: + if getattr(node1._op, "condition", None) != getattr( + node2._op, "condition", None + ): return False return True return False diff --git a/qiskit/dagcircuit/dagnode.py b/qiskit/dagcircuit/dagnode.py index b0ead7b4cbd8..ed6cfe15da24 100644 --- a/qiskit/dagcircuit/dagnode.py +++ b/qiskit/dagcircuit/dagnode.py @@ -79,7 +79,7 @@ def semantic_eq(node1, node2, bit_indices1=None, bit_indices2=None): if node1_qargs == node2_qargs: if node1_cargs == node2_cargs: - if node1.op.condition == node2.op.condition: + if getattr(node1.op, "condition", None) == getattr(node2.op, "condition", None): if node1.op == node2.op: return True elif (isinstance(node1, DAGInNode) and isinstance(node2, DAGInNode)) or ( diff --git a/qiskit/visualization/dag_visualization.py b/qiskit/visualization/dag_visualization.py index e7873b202717..3c513ba73dd9 100644 --- a/qiskit/visualization/dag_visualization.py +++ b/qiskit/visualization/dag_visualization.py @@ -93,7 +93,7 @@ def node_attr_func(node): n["color"] = "black" n["style"] = "filled" n["fillcolor"] = "red" - if node.op.condition: + if getattr(node.op, "condition", None): n["label"] = str(node.node_id) + ": " + str(node.name) + " (conditional)" n["color"] = "black" n["style"] = "filled" diff --git a/qiskit/visualization/latex.py b/qiskit/visualization/latex.py index 0a0ac777c0aa..989aae058034 100644 --- a/qiskit/visualization/latex.py +++ b/qiskit/visualization/latex.py @@ -288,7 +288,10 @@ def _get_image_depth(self): if self._cregbundle and ( self._nodes and self._nodes[0] - and (self._nodes[0][0].op.name == "measure" or self._nodes[0][0].op.condition) + and ( + self._nodes[0][0].op.name == "measure" + or getattr(self._nodes[0][0].op, "condition", None) + ) ): columns += 1 @@ -412,7 +415,10 @@ def _build_latex_array(self): if self._cregbundle and ( self._nodes and self._nodes[0] - and (self._nodes[0][0].op.name == "measure" or self._nodes[0][0].op.condition) + and ( + self._nodes[0][0].op.name == "measure" + or getattr(self._nodes[0][0].op, "condition", None) + ) ): column += 1 @@ -423,7 +429,7 @@ def _build_latex_array(self): op = node.op num_cols_op = 1 wire_list = [self._wire_map[qarg] for qarg in node.qargs] - if op.condition: + if getattr(op, "condition", None): self._add_condition(op, wire_list, column) if isinstance(op, Measure): @@ -563,7 +569,7 @@ def _build_measure(self, node, col): self._latex[wire1][col] = "\\meter" idx_str = "" - cond_offset = 1.5 if node.op.condition else 0.0 + cond_offset = 1.5 if getattr(node.op, "condition", None) else 0.0 if self._cregbundle: register = get_bit_register(self._circuit, node.cargs[0]) if register is not None: diff --git a/qiskit/visualization/matplotlib.py b/qiskit/visualization/matplotlib.py index 192cae99eb3a..a9501c2b59e2 100644 --- a/qiskit/visualization/matplotlib.py +++ b/qiskit/visualization/matplotlib.py @@ -795,7 +795,7 @@ def _draw_ops(self, verbose=False): print(op) # add conditional - if op.condition: + if getattr(op, "condition", None): cond_xy = [ self._c_anchors[ii].plot_coord(anc_x_index, layer_width, self._x_offset) for ii in self._clbits_dict diff --git a/qiskit/visualization/text.py b/qiskit/visualization/text.py index 88e44987aade..4cd3eea37352 100644 --- a/qiskit/visualization/text.py +++ b/qiskit/visualization/text.py @@ -1037,7 +1037,7 @@ def _set_ctrl_state(self, node, conditional, ctrl_text, bottom): for i in range(len(ctrl_qubits)): # For sidetext gate alignment, need to set every Bullet with # conditional on if there's a condition. - if op.condition is not None: + if getattr(op, "condition", None) is not None: conditional = True if cstate[i] == "1": gates.append(Bullet(conditional=conditional, label=ctrl_text, bottom=bottom)) @@ -1060,7 +1060,7 @@ def _node_to_gate(self, node, layer): gate_text = TextDrawing.special_label(op) or gate_text gate_text = gate_text + params - if op.condition is not None: + if getattr(op, "condition", None) is not None: # conditional layer.set_cl_multibox(op.condition, top_connect="╨") conditional = True diff --git a/qiskit/visualization/utils.py b/qiskit/visualization/utils.py index 159271d8db14..533c42370984 100644 --- a/qiskit/visualization/utils.py +++ b/qiskit/visualization/utils.py @@ -453,7 +453,7 @@ def _get_gate_span(qubits, node, reverse_bits): if index > max_index: max_index = index - if node.cargs or node.op.condition: + if node.cargs or getattr(node.op, "condition", None): if reverse_bits: return qubits[: max_index + 1] else: @@ -532,7 +532,7 @@ def slide_from_left(self, node, index): curr_index = index last_insertable_index = -1 index_stop = -1 - if node.op.condition: + if getattr(node.op, "condition", None): if isinstance(node.op.condition[0], Clbit): cond_bit = [clbit for clbit in self.clbits if node.op.condition[0] == clbit] index_stop = self.measure_map[cond_bit[0]] From 5ae37a5787f35eeebda5fd807c6aa86588aae853 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Tue, 5 Jul 2022 18:00:53 +0300 Subject: [PATCH 17/27] finishing condition pass --- qiskit/assembler/assemble_circuits.py | 4 +++- qiskit/circuit/controlflow/builder.py | 2 +- qiskit/qpy/binary_io/circuits.py | 2 +- test/python/dagcircuit/test_dagcircuit.py | 4 +++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/qiskit/assembler/assemble_circuits.py b/qiskit/assembler/assemble_circuits.py index bf2261453147..d1c90d5cc026 100644 --- a/qiskit/assembler/assemble_circuits.py +++ b/qiskit/assembler/assemble_circuits.py @@ -111,7 +111,9 @@ def _assemble_circuit( # their clbit_index, create a new register slot for every conditional gate # and add a bfunc to map the creg=val mask onto the gating register bit. - is_conditional_experiment = any(instruction.operation.condition for instruction in circuit.data) + is_conditional_experiment = any( + getattr(instruction.operation, "condition", None) for instruction in circuit.data + ) max_conditional_idx = 0 instructions = [] diff --git a/qiskit/circuit/controlflow/builder.py b/qiskit/circuit/controlflow/builder.py index 9c58df51d459..ee1ee58e2f6e 100644 --- a/qiskit/circuit/controlflow/builder.py +++ b/qiskit/circuit/controlflow/builder.py @@ -426,7 +426,7 @@ def build( # a register is already present, so we use our own tracking. self.add_register(register) out.add_register(register) - if instruction.operation.condition is not None: + if getattr(instruction.operation, "condition", None) is not None: for register in condition_registers(instruction.operation.condition): if register not in self.registers: self.add_register(register) diff --git a/qiskit/qpy/binary_io/circuits.py b/qiskit/qpy/binary_io/circuits.py index f851679179d8..ff30f00e21c4 100644 --- a/qiskit/qpy/binary_io/circuits.py +++ b/qiskit/qpy/binary_io/circuits.py @@ -513,7 +513,7 @@ def _write_instruction(file_obj, instruction, custom_operations, index_map): has_condition = False condition_register = b"" condition_value = 0 - if instruction.operation.condition: + if getattr(instruction.operation, "condition", None): has_condition = True if isinstance(instruction.operation.condition[0], Clbit): bit_index = index_map["c"][instruction.operation.condition[0]] diff --git a/test/python/dagcircuit/test_dagcircuit.py b/test/python/dagcircuit/test_dagcircuit.py index 5d6e1b71bf9a..8885dcf31a40 100644 --- a/test/python/dagcircuit/test_dagcircuit.py +++ b/test/python/dagcircuit/test_dagcircuit.py @@ -120,7 +120,9 @@ def raise_if_dagcircuit_invalid(dag): in_wires = {data for src, dest, data in in_edges} out_wires = {data for src, dest, data in out_edges} - node_cond_bits = set(node.op.condition[0][:] if node.op.condition is not None else []) + node_cond_bits = set( + node.op.condition[0][:] if getattr(node.op, "condition", None) is not None else [] + ) node_qubits = set(node.qargs) node_clbits = set(node.cargs) From 7a7b0f19250983b557db4786c137dec7748375de Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Fri, 5 Aug 2022 07:54:36 +0300 Subject: [PATCH 18/27] minor fixes --- qiskit/visualization/utils.py | 2 +- test/python/transpiler/test_clifford_passes.py | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/qiskit/visualization/utils.py b/qiskit/visualization/utils.py index ab271984fc01..0ae413981a20 100644 --- a/qiskit/visualization/utils.py +++ b/qiskit/visualization/utils.py @@ -488,7 +488,7 @@ def _get_gate_span(qubits, node): if index > max_index: max_index = index - if node.cargs or node.op.condition: + if node.cargs or getattr(node.op, "condition", None): return qubits[min_index : len(qubits)] return qubits[min_index : max_index + 1] diff --git a/test/python/transpiler/test_clifford_passes.py b/test/python/transpiler/test_clifford_passes.py index 47b1bd257038..8d362e090f0b 100644 --- a/test/python/transpiler/test_clifford_passes.py +++ b/test/python/transpiler/test_clifford_passes.py @@ -80,7 +80,6 @@ def test_circuit_with_cliffords(self): qc.swap(1, 3) qc.append(cliff2, [1, 2, 3]) qc.h(3) - # print(qc) # Check that there are indeed two Clifford objects in the circuit, # and that these are not gates. @@ -91,7 +90,6 @@ def test_circuit_with_cliffords(self): # Check that calling QuantumCircuit's decompose(), no Clifford objects remain qc2 = qc.decompose() - # print(qc2) cliffords2 = [inst for inst, _, _ in qc2.data if isinstance(inst, Clifford)] self.assertEqual(len(cliffords2), 0) From 64c8de337156c1d3bad4d417d6ce1f7f6428d1e1 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Sat, 6 Aug 2022 11:00:01 +0300 Subject: [PATCH 19/27] adding OptimizeClifford pass to __init__ --- qiskit/transpiler/passes/__init__.py | 2 ++ qiskit/transpiler/passes/optimization/__init__.py | 1 + test/python/transpiler/test_clifford_passes.py | 2 +- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/qiskit/transpiler/passes/__init__.py b/qiskit/transpiler/passes/__init__.py index 1dce453dc891..212327be50d2 100644 --- a/qiskit/transpiler/passes/__init__.py +++ b/qiskit/transpiler/passes/__init__.py @@ -84,6 +84,7 @@ HoareOptimizer TemplateOptimization EchoRZXWeylDecomposition + OptimizeCliffords Calibration ============= @@ -219,6 +220,7 @@ from .optimization import InverseCancellation from .optimization import EchoRZXWeylDecomposition from .optimization import CollectLinearFunctions +from .optimization import OptimizeCliffords # circuit analysis from .analysis import ResourceEstimation diff --git a/qiskit/transpiler/passes/optimization/__init__.py b/qiskit/transpiler/passes/optimization/__init__.py index 492224d5476c..ced0befe2cfa 100644 --- a/qiskit/transpiler/passes/optimization/__init__.py +++ b/qiskit/transpiler/passes/optimization/__init__.py @@ -31,3 +31,4 @@ from .collect_1q_runs import Collect1qRuns from .echo_rzx_weyl_decomposition import EchoRZXWeylDecomposition from .collect_linear_functions import CollectLinearFunctions +from .optimize_cliffords import OptimizeCliffords diff --git a/test/python/transpiler/test_clifford_passes.py b/test/python/transpiler/test_clifford_passes.py index 8d362e090f0b..cda359031eb9 100644 --- a/test/python/transpiler/test_clifford_passes.py +++ b/test/python/transpiler/test_clifford_passes.py @@ -19,7 +19,7 @@ from qiskit.converters import dag_to_circuit, circuit_to_dag from qiskit.dagcircuit import DAGOpNode from qiskit.transpiler.passes import HighLevelSynthesis -from qiskit.transpiler.passes.optimization.optimize_cliffords import OptimizeCliffords +from qiskit.transpiler.passes import OptimizeCliffords from qiskit.test import QiskitTestCase from qiskit.quantum_info.operators import Clifford from qiskit.transpiler import PassManager From b2f972ac57045bdc87108c3981d3da8bcd430620 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Sat, 6 Aug 2022 11:00:28 +0300 Subject: [PATCH 20/27] Improving release notes --- ...-abstract-base-class-c5efe020aa9caf46.yaml | 95 ++++++++++++++++++- 1 file changed, 90 insertions(+), 5 deletions(-) diff --git a/releasenotes/notes/operation-abstract-base-class-c5efe020aa9caf46.yaml b/releasenotes/notes/operation-abstract-base-class-c5efe020aa9caf46.yaml index c46d0179251d..81b0edf57cd8 100644 --- a/releasenotes/notes/operation-abstract-base-class-c5efe020aa9caf46.yaml +++ b/releasenotes/notes/operation-abstract-base-class-c5efe020aa9caf46.yaml @@ -2,14 +2,99 @@ features: - | - A new :class:`.Operation` base class for objects that can be added to a :class:`.QuantumCircuit`. - These objects include :class:`.Gate`, :class:`.Reset`, :class:`.Barrier`, :class:.Measure`, - and operators such as :class:`.Clifford`. + Currently, only subclasses of :class:`.Instruction` can be put on :class:`.QuantumCircuit`, + but this interface has become unwieldy and includes too many methods and attributes for + general-purpose objects. + + A new :class:`.Operation` base class provides a lightweight abstract interface + for objects that can be put on :class:`.QuantumCircuit`. This allows to store "higher-level" + objects directly on a circuit (for instance, :class:`.Clifford`s), to directly combine such objects + (for instance, to compose several consecutive :class:`.Clifford`s over the same qubits), and + to synthesize such objects at run time (for instance, to synthesize :class:`.Clifford` in + a way that optimizes depth and/or exploits device connectivity). + + The new :class:`.Operation` interface includes ``name``, ``num_qubits`` and ``num_clbits`` + (in the future this may be slightly adjusted), but importantly does not include ``definition`` + or ``_define`` (and thus does not tie synthesis to the object), does not include ``condition`` + (this should be part of separate classical control flow), and does not include ``duration`` and + ``unit`` (as these are properties of the output of the transpiler). + + As of now, :class:`.Operation` includes :class:`.Gate`, :class:`.Reset`, :class:`.Barrier`, + :class:.Measure`, and "higher-level" objects such as :class:`.Clifford`. This list of + "higher-level" objects will grow in the future. + - | - A Clifford gate is now added to a quantum circuit as an :class:`.Operation`, without first + A :class:`.Clifford` is now added to a quantum circuit as an :class:`.Operation`, without first synthesizing a subcircuit implementing this Clifford gate. The actual synthesis is postponed to a later transpilation pass. + + For example, the following code:: + + from qiskit import QuantumCircuit + from qiskit.quantum_info import random_clifford + + qc = QuantumCircuit(3) + cliff = random_clifford(2) + qc.append(cliff, [0, 1]) + + no longer converts ``cliff`` to :class:`.Instruction` when it is appended to ``qc``. + - | Added a new transpiler pass :class:`.OptimizeCliffords` that collects blocks of consecutive - Clifford gates in a circuit, and replaces each block with a single Clifford object. + :class:`.Clifford` objects in a circuit, and replaces each block with a single :class:`.Clifford`. + + For example, the following code:: + + from qiskit import QuantumCircuit + from qiskit.quantum_info import random_clifford + from qiskit.transpiler.passes import OptimizeCliffords + from qiskit.transpiler import PassManager + + qc = QuantumCircuit(3) + cliff1 = random_clifford(2) + cliff2 = random_clifford(2) + qc.append(cliff1, [2, 1]) + qc.append(cliff2, [2, 1]) + qc_optimized = PassManager(OptimizeCliffords()).run(qc) + + first stores the two Cliffords ``cliff1`` and ``cliff2`` on ``qc`` as "higher-level" objects, + and then the transpiler pass :class:`.OptimizeCliffords` optimizes the circuit by composing + these two Cliffords into a single Clifford. Note that the resulting Clifford is still stored + on ``qc`` as a higher-level object. This pass is not yet included in any of preset pass + managers. + + - | + Added a new transpiler pass :class:`.HighLevelSynthesis` that synthesizes higher-level objects + (for instance, :class:`.Clifford`s). + + As of now, :class:`.HighLevelSynthesis` is only limited to :class:`.Clifford`s, but it will be + expanded to cover other higher-level objects (as more higher-level objects will become available). + In addition, the plan is to make :class:`.HighLevelSynthesis` "pluggable", so that the users + can "plug in" their own synthesis methods for higher-level objects at transpilation run time. + + For example, the following code:: + + from qiskit import QuantumCircuit + from qiskit.quantum_info import random_clifford + from qiskit.transpiler import PassManager + from qiskit.transpiler.passes import HighLevelSynthesis + + qc = QuantumCircuit(3) + qc.h(0) + cliff = random_clifford(2) + qc.append(cliff, [0, 1]) + + qc_synthesized = PassManager(HighLevelSynthesis()).run(qc) + + will synthesize the higher-level Clifford stored in ``qc`` using the default + :func:`~qiskit.quantum_info.decompose_clifford` function. + + This new transpiler pass :class:`.HighLevelSynthesis` is integrated into the preset pass managers, + running right after :class:`.UnitarySynthesis` pass. Thus, ``qiskit.compiler.transpile()`` will + synthesize all higher-level Cliffords present in the circuit. + It is important to note that the work done to store :class:`.Clifford` objects as "higher-level" + objects and to transpile these objects using :class:`.HighLevelSynthesis` pass should be completely + transparent to the users, absolutely no code changes are required. However, as explained before, + the users are now able to opcimize consecutive Cliffords using the new :class:`.OptimizeCliffords` + pass, and in the future the users will be able to plug in their own synthesis methods for Cliffords. From 0d583edceab35c35e63c6b1d29aa81e0f742f3f5 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Sat, 6 Aug 2022 11:23:20 +0300 Subject: [PATCH 21/27] considering DAG nodes in topological order; adding a simple test to see this --- .../passes/optimization/optimize_cliffords.py | 2 +- test/python/transpiler/test_clifford_passes.py | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/qiskit/transpiler/passes/optimization/optimize_cliffords.py b/qiskit/transpiler/passes/optimization/optimize_cliffords.py index 7e91969c875a..c81539b60637 100644 --- a/qiskit/transpiler/passes/optimization/optimize_cliffords.py +++ b/qiskit/transpiler/passes/optimization/optimize_cliffords.py @@ -42,7 +42,7 @@ def run(self, dag): # be shortly removed. An interesting question is whether we may also # want to compose Cliffords over different sets of qubits, such as # cliff1 over qubits [1, 2, 3] and cliff2 over [2, 3, 4]. - for node in dag.op_nodes(): + for node in dag.topological_op_nodes(): if isinstance(node.op, Clifford): if prev_node is None: blocks.append(cur_block) diff --git a/test/python/transpiler/test_clifford_passes.py b/test/python/transpiler/test_clifford_passes.py index cda359031eb9..faf0db738008 100644 --- a/test/python/transpiler/test_clifford_passes.py +++ b/test/python/transpiler/test_clifford_passes.py @@ -228,6 +228,22 @@ def test_optimize_cliffords(self): self.assertTrue(Operator(qc1).equiv(Operator(qc2))) self.assertTrue(Operator(qc1).equiv(Operator(qc3))) + def test_topological_ordering(self): + """Test that Clifford optimization pass optimizes Cliffords across a gate + on a different qubit.""" + + cliff1 = self.create_cliff1() + cliff2 = self.create_cliff1() + + qc1 = QuantumCircuit(5) + qc1.append(cliff1, [0, 1, 2]) + qc1.h(4) + qc1.append(cliff2, [0, 1, 2]) + + # The second circuit is obtained by running the OptimizeCliffords pass. + qc2 = PassManager(OptimizeCliffords()).run(qc1) + self.assertEqual(qc2.count_ops()["clifford"], 1) + def test_transpile_level_0(self): """Make sure that transpile with optimization_level=0 transpiles the Clifford.""" From bdf9325c3fab23b9676d19220dd5fc68295b7b68 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Sat, 6 Aug 2022 11:26:52 +0300 Subject: [PATCH 22/27] typo --- .../notes/operation-abstract-base-class-c5efe020aa9caf46.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/releasenotes/notes/operation-abstract-base-class-c5efe020aa9caf46.yaml b/releasenotes/notes/operation-abstract-base-class-c5efe020aa9caf46.yaml index 81b0edf57cd8..d4e09f2a5b0f 100644 --- a/releasenotes/notes/operation-abstract-base-class-c5efe020aa9caf46.yaml +++ b/releasenotes/notes/operation-abstract-base-class-c5efe020aa9caf46.yaml @@ -96,5 +96,5 @@ features: It is important to note that the work done to store :class:`.Clifford` objects as "higher-level" objects and to transpile these objects using :class:`.HighLevelSynthesis` pass should be completely transparent to the users, absolutely no code changes are required. However, as explained before, - the users are now able to opcimize consecutive Cliffords using the new :class:`.OptimizeCliffords` + the users are now able to optimize consecutive Cliffords using the new :class:`.OptimizeCliffords` pass, and in the future the users will be able to plug in their own synthesis methods for Cliffords. From 49bc43626129f3eac54e0a27225ea97f92e16562 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Sat, 6 Aug 2022 18:55:44 +0300 Subject: [PATCH 23/27] release notes fixes --- ...ion-abstract-base-class-c5efe020aa9caf46.yaml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/releasenotes/notes/operation-abstract-base-class-c5efe020aa9caf46.yaml b/releasenotes/notes/operation-abstract-base-class-c5efe020aa9caf46.yaml index d4e09f2a5b0f..066255b8c4e6 100644 --- a/releasenotes/notes/operation-abstract-base-class-c5efe020aa9caf46.yaml +++ b/releasenotes/notes/operation-abstract-base-class-c5efe020aa9caf46.yaml @@ -8,8 +8,8 @@ features: A new :class:`.Operation` base class provides a lightweight abstract interface for objects that can be put on :class:`.QuantumCircuit`. This allows to store "higher-level" - objects directly on a circuit (for instance, :class:`.Clifford`s), to directly combine such objects - (for instance, to compose several consecutive :class:`.Clifford`s over the same qubits), and + objects directly on a circuit (for instance, :class:`.Clifford` objects), to directly combine such objects + (for instance, to compose several consecutive :class:`.Clifford` objects over the same qubits), and to synthesize such objects at run time (for instance, to synthesize :class:`.Clifford` in a way that optimizes depth and/or exploits device connectivity). @@ -20,13 +20,13 @@ features: ``unit`` (as these are properties of the output of the transpiler). As of now, :class:`.Operation` includes :class:`.Gate`, :class:`.Reset`, :class:`.Barrier`, - :class:.Measure`, and "higher-level" objects such as :class:`.Clifford`. This list of + :class:`.Measure`, and "higher-level" objects such as :class:`.Clifford`. This list of "higher-level" objects will grow in the future. - | A :class:`.Clifford` is now added to a quantum circuit as an :class:`.Operation`, without first - synthesizing a subcircuit implementing this Clifford gate. The actual synthesis is postponed - to a later transpilation pass. + synthesizing a subcircuit implementing this Clifford. The actual synthesis is postponed + to a later :class:`.HighLevelSynthesis` transpilation pass. For example, the following code:: @@ -65,9 +65,9 @@ features: - | Added a new transpiler pass :class:`.HighLevelSynthesis` that synthesizes higher-level objects - (for instance, :class:`.Clifford`s). + (for instance, :class:`.Clifford` objects). - As of now, :class:`.HighLevelSynthesis` is only limited to :class:`.Clifford`s, but it will be + As of now, :class:`.HighLevelSynthesis` is only limited to :class:`.Clifford` objects, but it will be expanded to cover other higher-level objects (as more higher-level objects will become available). In addition, the plan is to make :class:`.HighLevelSynthesis` "pluggable", so that the users can "plug in" their own synthesis methods for higher-level objects at transpilation run time. @@ -97,4 +97,4 @@ features: objects and to transpile these objects using :class:`.HighLevelSynthesis` pass should be completely transparent to the users, absolutely no code changes are required. However, as explained before, the users are now able to optimize consecutive Cliffords using the new :class:`.OptimizeCliffords` - pass, and in the future the users will be able to plug in their own synthesis methods for Cliffords. + pass, and in the future would be able to plug in their own synthesis methods for Cliffords. From 7f525d23a82fd2a6ee29c2ea23e302a2b91535ca Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Sat, 6 Aug 2022 19:17:28 +0300 Subject: [PATCH 24/27] Adding TODO comment to HighLevelSynthesis pass --- qiskit/transpiler/passes/synthesis/high_level_synthesis.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py index de36ae6291d6..c1b937842112 100644 --- a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py @@ -24,6 +24,12 @@ class HighLevelSynthesis(TransformationPass): the object's name. """ + # TODO: Currently, this class only contains the minimal functionality required to transpile + # Cliffords. In the near future, this class will be expanded to cover other higher-level + # objects (as these become available). Additionally, the plan is to make HighLevelSynthesis + # "pluggable", so that the users would be able to "plug in" their own synthesis methods + # for higher-level objects (which would be called during transpilation). + def run(self, dag: DAGCircuit) -> DAGCircuit: """Run the HighLevelSynthesis pass on `dag`. Args: From d82e2e0efef1fddbde20dd87602f4afd0273b99d Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Sun, 7 Aug 2022 00:15:31 +0300 Subject: [PATCH 25/27] another attempt to fix docs build --- .../operation-abstract-base-class-c5efe020aa9caf46.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/releasenotes/notes/operation-abstract-base-class-c5efe020aa9caf46.yaml b/releasenotes/notes/operation-abstract-base-class-c5efe020aa9caf46.yaml index 066255b8c4e6..ef5e619684d7 100644 --- a/releasenotes/notes/operation-abstract-base-class-c5efe020aa9caf46.yaml +++ b/releasenotes/notes/operation-abstract-base-class-c5efe020aa9caf46.yaml @@ -2,9 +2,9 @@ features: - | - Currently, only subclasses of :class:`.Instruction` can be put on :class:`.QuantumCircuit`, - but this interface has become unwieldy and includes too many methods and attributes for - general-purpose objects. + Currently, only subclasses of :class:`qiskit.circuit.Instruction` can be put on + :class:`.QuantumCircuit`, but this interface has become unwieldy and includes too many methods + and attributes for general-purpose objects. A new :class:`.Operation` base class provides a lightweight abstract interface for objects that can be put on :class:`.QuantumCircuit`. This allows to store "higher-level" @@ -37,7 +37,7 @@ features: cliff = random_clifford(2) qc.append(cliff, [0, 1]) - no longer converts ``cliff`` to :class:`.Instruction` when it is appended to ``qc``. + no longer converts ``cliff`` to :class:`qiskit.circuit.Instruction` when it is appended to ``qc``. - | Added a new transpiler pass :class:`.OptimizeCliffords` that collects blocks of consecutive From 07733d600159a086aba001528ae36a4ef021d7a9 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Tue, 9 Aug 2022 23:15:35 +0300 Subject: [PATCH 26/27] Fix based on code review --- qiskit/quantum_info/operators/operator.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/qiskit/quantum_info/operators/operator.py b/qiskit/quantum_info/operators/operator.py index 5c2584e65119..dd5b340d8240 100644 --- a/qiskit/quantum_info/operators/operator.py +++ b/qiskit/quantum_info/operators/operator.py @@ -514,8 +514,11 @@ def _init_instruction(cls, instruction): @classmethod def _instruction_to_matrix(cls, obj): """Return Operator for instruction if defined or None otherwise.""" - if not isinstance(obj, Operation): - raise QiskitError("Input is not an instruction.") + # Note: to_matrix() is not a required method for Operations, thus we raise + # an error for opaque instructions without a matrix representation. + # On the other hand, we do use to_matrix() when it's available. + if not isinstance(obj, Operation) and not hasattr(obj, "to_matrix"): + raise QiskitError("Input is not an Operation and does not have 'to_matrix()' method.") mat = None if hasattr(obj, "to_matrix"): # If instruction is a gate first we see if it has a From ab213f2e5fdc829100a9a92bfac6f681d0f82645 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Wed, 10 Aug 2022 18:53:57 +0300 Subject: [PATCH 27/27] Only construction Operator from Instruction (as before) and from Clifford (for backward compatibility) --- qiskit/quantum_info/operators/operator.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/qiskit/quantum_info/operators/operator.py b/qiskit/quantum_info/operators/operator.py index dd5b340d8240..413731e6fe9f 100644 --- a/qiskit/quantum_info/operators/operator.py +++ b/qiskit/quantum_info/operators/operator.py @@ -21,6 +21,7 @@ import numpy as np from qiskit.circuit.quantumcircuit import QuantumCircuit +from qiskit.circuit.instruction import Instruction from qiskit.circuit.operation import Operation from qiskit.circuit.library.standard_gates import IGate, XGate, YGate, ZGate, HGate, SGate, TGate from qiskit.exceptions import QiskitError @@ -514,11 +515,16 @@ def _init_instruction(cls, instruction): @classmethod def _instruction_to_matrix(cls, obj): """Return Operator for instruction if defined or None otherwise.""" - # Note: to_matrix() is not a required method for Operations, thus we raise - # an error for opaque instructions without a matrix representation. - # On the other hand, we do use to_matrix() when it's available. - if not isinstance(obj, Operation) and not hasattr(obj, "to_matrix"): - raise QiskitError("Input is not an Operation and does not have 'to_matrix()' method.") + # Note: to_matrix() is not a required method for Operations, so for now + # we do not allow constructing matrices for general Operations. + # However, for backward compatibility we need to support constructing matrices + # for Cliffords, which happen to have a to_matrix() method. + + # pylint: disable=cyclic-import + from qiskit.quantum_info import Clifford + + if not isinstance(obj, (Instruction, Clifford)): + raise QiskitError("Input is neither an Instruction nor Clifford.") mat = None if hasattr(obj, "to_matrix"): # If instruction is a gate first we see if it has a