From 53a8adfdbd206fd623c8242bef205a18a9c5d5cd Mon Sep 17 00:00:00 2001 From: MOYARD Romain Date: Tue, 3 Dec 2019 17:10:39 +0100 Subject: [PATCH 01/27] Add DAGcanonical form and the converter circuit to canonical --- qiskit/converters/circuit_to_canonical.py | 42 +++ qiskit/dagcircuit/dagcanonical.py | 335 ++++++++++++++++++++++ 2 files changed, 377 insertions(+) create mode 100644 qiskit/converters/circuit_to_canonical.py create mode 100644 qiskit/dagcircuit/dagcanonical.py diff --git a/qiskit/converters/circuit_to_canonical.py b/qiskit/converters/circuit_to_canonical.py new file mode 100644 index 000000000000..ac6543df9e9d --- /dev/null +++ b/qiskit/converters/circuit_to_canonical.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +''' +Convert a QuantumCircuit object to the dag canonical form +''' +from qiskit.dagcircuit.dagcanonical import DAGcanonical + +def circuit_to_canonical(circuit): + """Build a ``DAGCanonical`` object from a ``QuantumCircuit``. + + Args: + circuit (QuantumCircuit): the input circuits. + + Return: + DAGcanonical: the DAG representing the input circuit as in the canonical form. + """ + dagcircuit = DAGcanonical() + dagcircuit.name = circuit.name + + for register in circuit.qregs: + dagcircuit.add_qreg(register) + + for operation, qargs, cargs in circuit.data: + if operation.name not in {"barrier", "snapshot", "measure", "reset", "copy"}: + dagcircuit.add_node(operation, qargs, cargs) + dagcircuit.add_edge() + + dagcircuit.add_successors() + + return dagcircuit diff --git a/qiskit/dagcircuit/dagcanonical.py b/qiskit/dagcircuit/dagcanonical.py new file mode 100644 index 000000000000..c50a13ca9072 --- /dev/null +++ b/qiskit/dagcircuit/dagcanonical.py @@ -0,0 +1,335 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" +Object to represent a quantum circuit as a directed acyclic graph in the canonical form. + +The nodes in the graph are operation represented by quantum gates. +The edges correspond to non-commutation between two operations. A directed edge +from node A to node B means that operation A does not commute with operation B. +The object's methods allow circuits to be constructed. + +e.g. Bell circuit with no measurement + + ┌───┐ +qr_0: |0>┤ H ├──■── + └───┘┌─┴─┐ +qr_1: |0>─────┤ X ├ + └───┘ + +In the dag canonical form the circuit is representede by two nodes (1 and 2): +the first one corresponds to Hamdamard gate, the second one to the CNot gate +as the gates do not commute there is an edge between the wo noodes. + +The attributes are 'label' 'operation', 'successors', 'predecessors' +In the Bell circuit, the network takes the following form: + +[(1, {'label': 1, 'operation': , + 'successors': [2], 'predecessors': [], 'reachable': False}), +(2, {'label': 2, 'operation': , +'successors': [], 'predecessors': [1]})] + +The reference paper is https://arxiv.org/abs/1909.05270v1 + +""" + +from collections import OrderedDict +import copy +import heapq +import networkx as nx +import numpy as np +import nxpd + +from qiskit.circuit.quantumregister import QuantumRegister +from qiskit.quantum_info.operators import Operator +from .exceptions import DAGCircuitError +from .dagnode import DAGNode + + +class DAGcanonical: + ''' + Object to represent a quantum circuit as as + ''' + def __init__(self): + ''' + Create an empty directed acyclis graph (canonical form) + ''' + # Circuit name + self.name = None + + # Directed multigraph whose nodes are operations(gates) and edges + # represent non-commutativity between two gates. + self._multi_graph = nx.MultiDiGraph() + + # Map of qreg name to QuantumRegister object + self.qregs = OrderedDict() + + # Index of the last node added + self._max_node_id = 0 + + # Intern list of nodes + self._id_to_node = {} + + def to_networkx(self): + """Returns a copy of the DAGCircuit in networkx format.""" + return copy.deepcopy(self._multi_graph) + + def qubits(self): + """Return a list of qubits (as a list of Qubit instances).""" + return [qubit for qreg in self.qregs.values() for qubit in qreg] + + def node_counter(self): + """ Returns the number of nodes in the dag """ + return len(self._multi_graph) + + def add_qreg(self, qreg): + """Add qubits in a quantum register.""" + if not isinstance(qreg, QuantumRegister): + raise DAGCircuitError("not a QuantumRegister instance.") + if qreg.name in self.qregs: + raise DAGCircuitError("duplicate register %s" % qreg.name) + self.qregs[qreg.name] = qreg + + def add_node(self, operation, qargs, cargs, condition=None): + '''Add a node to the graph. + + Args: + op: operation as a quantum gate. + qargs: list of qubits on which the operation acts + + Return: + Add a node to the DAG. + ''' + node_properties = { + "type": "op", + "op": operation, + "name": operation.name, + "qargs": qargs, + "cargs": cargs, + "condition": condition + } + + # Add a new operation node to the graph + self._max_node_id += 1 + new_node = DAGNode(data_dict=node_properties, nid=self._max_node_id) + self._multi_graph.add_node(self._max_node_id, label=self._max_node_id, operation=new_node, + successors=[], predecessors=[]) + self._id_to_node[self._max_node_id] = new_node + + def _gather_pred(self, node_id, direct_pred): + ''' + Function set an attribute predecessors and gather multiple lists + of direct predecessors into a single one + + :param node_id: label of the considered node in the DAG + :param direct_pred: list of direct successors for the given node + :return: a multigraph with update of the attribute ['predecessors'] + the lists of direct successors are put into a single one + ''' + gather = self._multi_graph + gather.nodes[node_id]['predecessors'] = [] + for d_pred in direct_pred: + gather.nodes[node_id]['predecessors'].append([d_pred]) + pred = self._multi_graph.nodes[d_pred]['predecessors'] + gather.nodes[node_id]['predecessors'].append(pred) + return gather + + + def _gather_succ(self, node_id, direct_succ): + ''' + Function set an attribute successors and gather multiple lists + of direct successors into a single one. + :param node_id: label of the considered node in the DAG + :param direct_pred: list of direct successors for the given node + :return: a multigraph with update of the attribute ['predecessors'] + the lists of direct successors are put into a single one + ''' + gather = self._multi_graph + for d_succ in direct_succ: + gather.nodes[node_id]['successors'].append([d_succ]) + succ = gather.nodes[d_succ]['successors'] + gather.nodes[node_id]['successors'].append(succ) + return gather + + def _list_pred(self, node_id): + ''' + Use _gather_pred function and merge_no_duplicates to construct + the list of predecessors for a given node. + :param node_id: label of the considered node + :return: multi graph updated + ''' + direct_pred = sorted(list(self._multi_graph.predecessors(node_id))) + self._multi_graph = self._gather_pred(node_id, direct_pred) + self._multi_graph.nodes[node_id]['predecessors'] = list( + merge_no_duplicates(*(self._multi_graph.nodes[node_id]['predecessors']))) + + def add_edge(self): + ''' + Function to verify the commutation relation and reachability + for predecessors, the nodes do not commute and + if the predecessor is reachable. + :return: update the DAGcanonical by introducing edges and predecessors(attribute) + ''' + node = self._id_to_node[self._max_node_id] + max_id = self._max_node_id + for current_node in range(1, max_id): + self._multi_graph.nodes[current_node]['reachable'] = True + # Check the commutation relation with reachable node, it adds edges if it does not commute + for prev_node in range(max_id - 1, 0, -1): + if self._multi_graph.nodes[prev_node]['reachable'] and not commute( + self._multi_graph.nodes[prev_node]['operation'], node): + self._multi_graph.add_edge(prev_node, max_id) + self._list_pred(max_id) + list_predecessors = self._multi_graph.nodes[max_id]['predecessors'] + for pred in list_predecessors: + self._multi_graph.nodes[pred]['reachable'] = False + + def add_successors(self): + ''' + Use _gather_succ and merge_no_duplicates to create the list of successors for each node. + :return: Update DAGcanonical with attributes successors + ''' + for node_id in range(len(self._multi_graph), 0, -1): + + direct_successors = sorted(list(self._multi_graph.successors(node_id))) + + self._multi_graph = self._gather_succ(node_id, direct_successors) + + self._multi_graph.nodes[node_id]['successors'] = list( + merge_no_duplicates(*(self._multi_graph.nodes[node_id]['successors']))) + + def node(self, node_id): + ''' + :param node_id: label of considered node + :return: the nodes corresponding to the label + ''' + return self._multi_graph.nodes[node_id] + + def nodes(self): + ''' + :return: Generator of all nodes (label, DAGnode) + ''' + for node in self._multi_graph.nodes(data='operation'): + yield node + + def edges(self): + ''' + :return: Generator of all edges + ''' + for edge in self._multi_graph.edges(data=True): + yield edge + + def in_edge(self, node_id): + ''' + :return: Incoming edge (directed graph) + ''' + return self._multi_graph.in_edges(node_id) + + def out_edge(self, node_id): + ''' + :return: Outgoing edge (directed graph) + ''' + return self._multi_graph.out_edges(node_id) + + def direct_successors(self, node_id): + """Returns label of direct successors of a node as sorted list """ + return sorted(list(self.to_networkx().successors(node_id))) + + def direct_predecessors(self, node_id): + """Returns label of direct predecessors of a node as sorted list """ + return sorted(list(self.to_networkx().predecessors(node_id))) + + def successors(self, node_id): + """Returns set of the ancestors of a node as DAGNodes.""" + return self.to_networkx().nodes[node_id]['successors'] + + def predecessors(self, node_id): + """Returns set of the descendants of a node as DAGNodes.""" + return self.to_networkx().nodes[node_id]['predecessors'] + + def draw(self): + ''' + :return: Drawing of the DAG canonical + ''' + # To be enhanced + canonical = self.to_networkx() + canonical.graph['dpi'] = 100 * 0.7 + nxpd.draw(canonical, filename=None, show=True) + +def merge_no_duplicates(*iterables): + ''' + Merge K list without duplicate using python heapq ordered merging + :param iterables: A list of k sorted lists + :return: List from the merging of the k ones (without duplicates + ''' + last = object() + for val in heapq.merge(*iterables): + if val != last: + last = val + yield val + +def commute(node1, node2): + '''Function to verify commutation relation between two nodes in the DAG + + Args: + node1: first node operation (attribute ['operation'] in the DAG) + node2: second node operation + + Return: + True if the gates commute and false if it is not the case + ''' + + # Identity always commute + if node1.name == 'id' or node2.name == 'id': + return True + + # Create set of qubits on which the operation acts + qarg1 = [node1.qargs[i].index for i in range(0, len(node1.qargs))] + qarg2 = [node2.qargs[i].index for i in range(0, len(node2.qargs))] + + # If the sets are disjoint return false + if set(qarg1).intersection(set(qarg2)) == set(): + return True + + # if the operation are the same return true + if qarg1 == qarg2 and node1.op == node2.op: + return True + + # List of non commuting gates (TO DO: add more elements) + non_commute_list = [set(['x', 'y']), set(['x', 'z'])] + + if qarg1 == qarg2 and (set([node1.name, node2.name]) in non_commute_list): + return False + + # List of commuting gates (TO DO: add more elements) + commute_list = [set([])] + + if qarg1 == qarg2 and (set([node1.name, node2.name]) in commute_list): + return True + + # Create matrices to check commutation relation + qarg = list(set(node1.qargs + node2.qargs)) + qbit_num = len(qarg) + + qarg1 = [qarg.index(q) for q in node1.qargs] + qarg2 = [qarg.index(q) for q in node2.qargs] + + id_op = Operator(np.eye(2 ** qbit_num)) + + op12 = id_op.compose(node1.op, qargs=qarg1).compose(node2.op, qargs=qarg2) + op21 = id_op.compose(node2.op, qargs=qarg2).compose(node1.op, qargs=qarg1) + + if_commute = (op12 == op21) + + return if_commute From cb3d3fc80e711f496eb3f4eae2cab91b4c93afbf Mon Sep 17 00:00:00 2001 From: MOYARD Romain Date: Mon, 9 Dec 2019 02:41:54 +0100 Subject: [PATCH 02/27] Updated version of dag canonical and its converters, also include a test. --- ...anonical.py => circuit_to_dagcanonical.py} | 14 +++--- qiskit/converters/dagcanonical_to_circuit.py | 39 ++++++++++++++++ .../test_circuit_to_dag_canonical.py | 46 +++++++++++++++++++ 3 files changed, 93 insertions(+), 6 deletions(-) rename qiskit/converters/{circuit_to_canonical.py => circuit_to_dagcanonical.py} (83%) create mode 100644 qiskit/converters/dagcanonical_to_circuit.py create mode 100644 test/python/converters/test_circuit_to_dag_canonical.py diff --git a/qiskit/converters/circuit_to_canonical.py b/qiskit/converters/circuit_to_dagcanonical.py similarity index 83% rename from qiskit/converters/circuit_to_canonical.py rename to qiskit/converters/circuit_to_dagcanonical.py index ac6543df9e9d..d714219f0880 100644 --- a/qiskit/converters/circuit_to_canonical.py +++ b/qiskit/converters/circuit_to_dagcanonical.py @@ -2,7 +2,7 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2017. +# (C) Copyright IBM 2019. # # This code is licensed under the Apache License, Version 2.0. You may # obtain a copy of this license in the LICENSE.txt file in the root directory @@ -12,12 +12,11 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -''' -Convert a QuantumCircuit object to the dag canonical form -''' +'''Helper function for converting a circuit to a dag canonical''' from qiskit.dagcircuit.dagcanonical import DAGcanonical -def circuit_to_canonical(circuit): + +def circuit_to_dagcanonical(circuit): """Build a ``DAGCanonical`` object from a ``QuantumCircuit``. Args: @@ -31,12 +30,15 @@ def circuit_to_canonical(circuit): for register in circuit.qregs: dagcircuit.add_qreg(register) + + for register in circuit.cregs: + dagcircuit.add_creg(register) for operation, qargs, cargs in circuit.data: - if operation.name not in {"barrier", "snapshot", "measure", "reset", "copy"}: dagcircuit.add_node(operation, qargs, cargs) dagcircuit.add_edge() dagcircuit.add_successors() return dagcircuit + diff --git a/qiskit/converters/dagcanonical_to_circuit.py b/qiskit/converters/dagcanonical_to_circuit.py new file mode 100644 index 000000000000..810bc8e1f35d --- /dev/null +++ b/qiskit/converters/dagcanonical_to_circuit.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Helper function for converting a dag canonical to a circuit""" +from qiskit.circuit import QuantumCircuit + + +def dagcanonical_to_circuit(dagcanonical): + """Build a ``QuantumCircuit`` object from a ``DAGCanonical``. + + Args: + dag (DAGCcanonical): the input dag. + + Return: + QuantumCircuit: the circuit representing the input dag. + """ + + name = dagcanonical.name or None + circuit = QuantumCircuit(*dagcanonical.qregs.values(), *dagcanonical.cregs.values(), name=name) + + for node in list(dagcanonical.nodes()): + node_op=node[1] + # Get arguments for classical control (if any) + inst = node_op.op.copy() + inst.condition = node_op.condition + circuit._append(inst, node_op.qargs, node_op.cargs) + + return circuit diff --git a/test/python/converters/test_circuit_to_dag_canonical.py b/test/python/converters/test_circuit_to_dag_canonical.py new file mode 100644 index 000000000000..2f9cccc1b9c7 --- /dev/null +++ b/test/python/converters/test_circuit_to_dag_canonical.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2018. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Tests for the converters.""" + +import unittest + +from qiskit.converters import dagcanonical_to_circuit, circuit_to_dagcanonical +from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit +from qiskit.test import QiskitTestCase + + +class TestCircuitToDagCanonical(QiskitTestCase): + """Test Circuit to DAGcanonical.""" + + def test_circuit_and_dag_canonical(self): + """Check convert to dag canonical and back""" + qr = QuantumRegister(3) + cr = ClassicalRegister(3) + circuit_in = QuantumCircuit(qr, cr) + circuit_in.h(qr[0]) + circuit_in.h(qr[1]) + circuit_in.measure(qr[0], cr[0]) + circuit_in.measure(qr[1], cr[1]) + circuit_in.x(qr[0]).c_if(cr, 0x3) + circuit_in.measure(qr[0], cr[0]) + circuit_in.measure(qr[1], cr[1]) + circuit_in.measure(qr[2], cr[2]) + dag_canonical = circuit_to_dagcanonical(circuit_in) + circuit_out = dagcanonical_to_circuit(dag_canonical) + self.assertEqual(circuit_out, circuit_in) + + +if __name__ == '__main__': + unittest.main(verbosity=2) From 27fc7b93fa4de1c5b11351689ccbe6d41d52fbec Mon Sep 17 00:00:00 2001 From: MOYARD Romain Date: Mon, 9 Dec 2019 16:52:34 +0100 Subject: [PATCH 03/27] Updated dag_visualization.py to include DAG canonical and updated docs --- qiskit/converters/__init__.py | 4 + qiskit/converters/circuit_to_dagcanonical.py | 3 +- qiskit/converters/dagcanonical_to_circuit.py | 2 +- qiskit/dagcircuit/__init__.py | 2 + qiskit/dagcircuit/dagcanonical.py | 92 +++++++++++++++---- qiskit/visualization/dag_visualization.py | 84 +++++++++++------ .../Add-canonical-form-47e0466ed57640f3.yaml | 38 ++++++++ .../test_circuit_to_dag_canonical.py | 5 +- 8 files changed, 179 insertions(+), 51 deletions(-) create mode 100644 releasenotes/notes/Add-canonical-form-47e0466ed57640f3.yaml diff --git a/qiskit/converters/__init__.py b/qiskit/converters/__init__.py index e31cd0dc6238..f82d7e69cd71 100644 --- a/qiskit/converters/__init__.py +++ b/qiskit/converters/__init__.py @@ -27,6 +27,8 @@ circuit_to_instruction circuit_to_gate ast_to_dag + dagcanonical_to_circuit + circuit_to_dagcanonical """ from .circuit_to_dag import circuit_to_dag @@ -34,6 +36,8 @@ from .circuit_to_instruction import circuit_to_instruction from .circuit_to_gate import circuit_to_gate from .ast_to_dag import ast_to_dag +from .circuit_to_dagcanonical import circuit_to_dagcanonical +from .dagcanonical_to_circuit import dagcanonical_to_circuit def isinstanceint(obj): diff --git a/qiskit/converters/circuit_to_dagcanonical.py b/qiskit/converters/circuit_to_dagcanonical.py index d714219f0880..f8cc2dfbab4c 100644 --- a/qiskit/converters/circuit_to_dagcanonical.py +++ b/qiskit/converters/circuit_to_dagcanonical.py @@ -13,6 +13,7 @@ # that they have been altered from the originals. '''Helper function for converting a circuit to a dag canonical''' + from qiskit.dagcircuit.dagcanonical import DAGcanonical @@ -23,7 +24,7 @@ def circuit_to_dagcanonical(circuit): circuit (QuantumCircuit): the input circuits. Return: - DAGcanonical: the DAG representing the input circuit as in the canonical form. + DAGcanonical: the DAG representing the input circuit as a dag canonical. """ dagcircuit = DAGcanonical() dagcircuit.name = circuit.name diff --git a/qiskit/converters/dagcanonical_to_circuit.py b/qiskit/converters/dagcanonical_to_circuit.py index 810bc8e1f35d..3b9a5c78f011 100644 --- a/qiskit/converters/dagcanonical_to_circuit.py +++ b/qiskit/converters/dagcanonical_to_circuit.py @@ -23,7 +23,7 @@ def dagcanonical_to_circuit(dagcanonical): dag (DAGCcanonical): the input dag. Return: - QuantumCircuit: the circuit representing the input dag. + QuantumCircuit: the circuit representing the input dag canonical. """ name = dagcanonical.name or None diff --git a/qiskit/dagcircuit/__init__.py b/qiskit/dagcircuit/__init__.py index c8e937c47683..5b7a40094648 100644 --- a/qiskit/dagcircuit/__init__.py +++ b/qiskit/dagcircuit/__init__.py @@ -27,6 +27,7 @@ DAGCircuit DAGNode + DAGcanonical Exceptions ========== @@ -39,3 +40,4 @@ from .dagcircuit import DAGCircuit from .dagnode import DAGNode from .exceptions import DAGCircuitError +from .dagcanonical import DAGcanonical diff --git a/qiskit/dagcircuit/dagcanonical.py b/qiskit/dagcircuit/dagcanonical.py index c50a13ca9072..c4f6576d67cb 100644 --- a/qiskit/dagcircuit/dagcanonical.py +++ b/qiskit/dagcircuit/dagcanonical.py @@ -2,7 +2,7 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2017. +# (C) Copyright IBM 2019. # # This code is licensed under the Apache License, Version 2.0. You may # obtain a copy of this license in the LICENSE.txt file in the root directory @@ -49,9 +49,9 @@ import heapq import networkx as nx import numpy as np -import nxpd from qiskit.circuit.quantumregister import QuantumRegister +from qiskit.circuit.classicalregister import ClassicalRegister from qiskit.quantum_info.operators import Operator from .exceptions import DAGCircuitError from .dagnode import DAGNode @@ -74,7 +74,9 @@ def __init__(self): # Map of qreg name to QuantumRegister object self.qregs = OrderedDict() - + + # Map of creg name to ClassicalRegister object + self.cregs = OrderedDict() # Index of the last node added self._max_node_id = 0 @@ -100,8 +102,16 @@ def add_qreg(self, qreg): if qreg.name in self.qregs: raise DAGCircuitError("duplicate register %s" % qreg.name) self.qregs[qreg.name] = qreg - - def add_node(self, operation, qargs, cargs, condition=None): + + def add_creg(self, creg): + """Add all wires in a classical register.""" + if not isinstance(creg, ClassicalRegister): + raise DAGCircuitError("not a ClassicalRegister instance.") + if creg.name in self.cregs: + raise DAGCircuitError("duplicate register %s" % creg.name) + self.cregs[creg.name] = creg + + def add_node(self, operation, qargs, cargs): '''Add a node to the graph. Args: @@ -117,7 +127,7 @@ def add_node(self, operation, qargs, cargs, condition=None): "name": operation.name, "qargs": qargs, "cargs": cargs, - "condition": condition + "condition": operation.condition } # Add a new operation node to the graph @@ -257,15 +267,26 @@ def successors(self, node_id): def predecessors(self, node_id): """Returns set of the descendants of a node as DAGNodes.""" return self.to_networkx().nodes[node_id]['predecessors'] + + def draw(self, scale=0.7, filename=None, style='color'): + """ + Draws the dag circuit. - def draw(self): - ''' - :return: Drawing of the DAG canonical - ''' - # To be enhanced - canonical = self.to_networkx() - canonical.graph['dpi'] = 100 * 0.7 - nxpd.draw(canonical, filename=None, show=True) + This function needs `pydot `, which in turn needs + Graphviz ` to be installed. + + Args: + scale (float): scaling factor + filename (str): file path to save image to (format inferred from name) + style (str): 'plain': B&W graph + 'color' (default): color input/output/op nodes + + Returns: + Ipython.display.Image: if in Jupyter notebook and not saving to file, + otherwise None. + """ + from qiskit.visualization.dag_visualization import dag_drawer + return dag_drawer(self, scale=scale, filename=filename, style=style, type='canonical') def merge_no_duplicates(*iterables): ''' @@ -287,17 +308,48 @@ def commute(node1, node2): node2: second node operation Return: - True if the gates commute and false if it is not the case + True if the gates commute and false if it is not the case ''' - # Identity always commute - if node1.name == 'id' or node2.name == 'id': - return True - # Create set of qubits on which the operation acts qarg1 = [node1.qargs[i].index for i in range(0, len(node1.qargs))] qarg2 = [node2.qargs[i].index for i in range(0, len(node2.qargs))] + # Create set of cbits on which the operation acts + carg1 = [node1.qargs[i].index for i in range(0, len(node1.cargs))] + carg2 = [node2.qargs[i].index for i in range(0, len(node2.cargs))] + + # Commutation for classical conditional gates + if node1.condition or node2.condition: + if len(set(qarg1).intersection(set(qarg2))) > 0: + return False + elif len(set(carg1)) > 0 or len(set(carg2)) > 0: + return False + else: + return True + + # Commutation for measurement + if node1.name == 'measure' or node2.name == 'measure': + if len(set(qarg1).intersection(set(qarg2))) > 0: + return False + else: + return True + + # Commutation for barrier + if node1.name == 'barrier' or node2.name == 'barrier': + if len(set(qarg1).intersection(set(qarg2))) > 0: + return False + else: + return True + + # Commutation for snapshot + if node1.name == 'snapshot' or node2.name == 'snapshot': + return False + + # Identity always commute + if node1.name == 'id' or node2.name == 'id': + return True + # If the sets are disjoint return false if set(qarg1).intersection(set(qarg2)) == set(): return True @@ -318,7 +370,7 @@ def commute(node1, node2): if qarg1 == qarg2 and (set([node1.name, node2.name]) in commute_list): return True - # Create matrices to check commutation relation + # Create matrices to check commutation relation if no other criteria are matched qarg = list(set(node1.qargs + node2.qargs)) qbit_num = len(qarg) diff --git a/qiskit/visualization/dag_visualization.py b/qiskit/visualization/dag_visualization.py index c9112920b017..29128beb7773 100644 --- a/qiskit/visualization/dag_visualization.py +++ b/qiskit/visualization/dag_visualization.py @@ -33,7 +33,7 @@ HAS_PIL = False -def dag_drawer(dag, scale=0.7, filename=None, style='color'): +def dag_drawer(dag, scale=0.7, filename=None, style='color', type=None): """Plot the directed acyclic graph (dag) to represent operation dependencies in a quantum circuit. @@ -86,32 +86,62 @@ def dag_drawer(dag, scale=0.7, filename=None, style='color'): except ImportError: raise ImportError("dag_drawer requires pydot. " "Run 'pip install pydot'.") - - G = dag.to_networkx() - G.graph['dpi'] = 100 * scale - - if style == 'plain': - pass - elif style == 'color': - for node in G.nodes: - n = G.nodes[node] - n['label'] = node.name - if node.type == 'op': - n['color'] = 'blue' - n['style'] = 'filled' - n['fillcolor'] = 'lightblue' - if node.type == 'in': - n['color'] = 'black' - n['style'] = 'filled' - n['fillcolor'] = 'green' - if node.type == 'out': - n['color'] = 'black' - n['style'] = 'filled' - n['fillcolor'] = 'red' - for e in G.edges(data=True): - e[2]['label'] = e[2]['name'] - else: - raise VisualizationError("Unrecognized style for the dag_drawer.") + if type==None: + G = dag.to_networkx() + G.graph['dpi'] = 100 * scale + + if style == 'plain': + pass + elif style == 'color': + for node in G.nodes: + n = G.nodes[node] + n['label'] = node.name + if node.type == 'op': + n['color'] = 'blue' + n['style'] = 'filled' + n['fillcolor'] = 'lightblue' + if node.type == 'in': + n['color'] = 'black' + n['style'] = 'filled' + n['fillcolor'] = 'green' + if node.type == 'out': + n['color'] = 'black' + n['style'] = 'filled' + n['fillcolor'] = 'red' + for e in G.edges(data=True): + e[2]['label'] = e[2]['name'] + else: + raise VisualizationError("Unrecognized style for the dag_drawer.") + + elif type=='canonical': + G = dag.to_networkx() + G.graph['dpi'] = 100 * scale + + if style == 'plain': + pass + elif style == 'color': + for node in G.nodes(data='operation'): + n = G.nodes[node[0]] + n['label'] = str(node[0])+ ': ' + node[1].name + if node[1].name == 'measure': + n['color'] = 'blue' + n['style'] = 'filled' + n['fillcolor'] = 'lightblue' + if node[1].name == 'barrier': + n['color'] = 'black' + n['style'] = 'filled' + n['fillcolor'] = 'green' + if node[1].name == 'snapshot': + n['color'] = 'black' + n['style'] = 'filled' + n['fillcolor'] = 'red' + if node[1].condition: + n['label'] = str(node[0]) + ': ' + node[1].name + ' (conditional)' + n['color'] = 'black' + n['style'] = 'filled' + n['fillcolor'] = 'lightgreen' + else: + raise VisualizationError("Unrecognized style for the dag_drawer.") dot = to_pydot(G) diff --git a/releasenotes/notes/Add-canonical-form-47e0466ed57640f3.yaml b/releasenotes/notes/Add-canonical-form-47e0466ed57640f3.yaml new file mode 100644 index 000000000000..49e1e4b89594 --- /dev/null +++ b/releasenotes/notes/Add-canonical-form-47e0466ed57640f3.yaml @@ -0,0 +1,38 @@ +--- + +prelude: > + Added a new DAG to qiskit; the canonical form. In this DAG, the node are + operation (gates, measure, barrier, etc...). The edges corresponds to + non-commutation between two operations. +features: + - | + The ''qiskit.dagcircuit.dagcanonical'' introduces the DAGcanonical class. + It allows to create an empty DAG canonical. For exmaple: + + from qiskit.dagcircuit.dagcanonical import DAGcanonical + + canonical=DAGcanonical() + + - | + The ''qiskit.converters.circuit_to_dagcanonical'' introduces a converter + from a QuantumCircuit() object to a DAGcanonical() object. + The ''qiskit.converters.dagconical_to_ciruit'' introduces a converter + from a DAGcanonical() object to aQuantumCircuit() object. + For exmaple: + + from qiskit.converters.dagcanonical_to_circuit import dagcanonical_to_circuit + from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit + + circuit_in = QuantumCircuit(2) + circuit_in.h(qr[0]) + circuit_in.h(qr[1]) + + dag_canonical = circuit_to_dagcanonical(circuit_in) + circuit_out = dagcanonical_to_circuit(dag_canonical) + + + +issues: + - | + In ''qiskit.dagcircuit.dagcanonical'' the function commute could be + improved. diff --git a/test/python/converters/test_circuit_to_dag_canonical.py b/test/python/converters/test_circuit_to_dag_canonical.py index 2f9cccc1b9c7..8e93a272e15c 100644 --- a/test/python/converters/test_circuit_to_dag_canonical.py +++ b/test/python/converters/test_circuit_to_dag_canonical.py @@ -12,11 +12,12 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""Tests for the converters.""" +"""Test for the converter dag canonical to ciruit and circuit to dag canonical.""" import unittest -from qiskit.converters import dagcanonical_to_circuit, circuit_to_dagcanonical +from qiskit.converters.dagcanonical_to_circuit import dagcanonical_to_circuit +from qiskit.converters.circuit_to_dagcanonical import circuit_to_dagcanonical from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit from qiskit.test import QiskitTestCase From d9267c0d7a1e9e5257eaf3f2ebd31a4d909351e5 Mon Sep 17 00:00:00 2001 From: MOYARD Romain Date: Mon, 9 Dec 2019 16:54:06 +0100 Subject: [PATCH 04/27] Release notes and raise error for type in dag_visualization --- qiskit/visualization/dag_visualization.py | 2 ++ releasenotes/notes/Add-canonical-form-47e0466ed57640f3.yaml | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/qiskit/visualization/dag_visualization.py b/qiskit/visualization/dag_visualization.py index 29128beb7773..dc8255cb11a1 100644 --- a/qiskit/visualization/dag_visualization.py +++ b/qiskit/visualization/dag_visualization.py @@ -142,6 +142,8 @@ def dag_drawer(dag, scale=0.7, filename=None, style='color', type=None): n['fillcolor'] = 'lightgreen' else: raise VisualizationError("Unrecognized style for the dag_drawer.") + else: + raise VisualizationError("Unrecognized of DAG") dot = to_pydot(G) diff --git a/releasenotes/notes/Add-canonical-form-47e0466ed57640f3.yaml b/releasenotes/notes/Add-canonical-form-47e0466ed57640f3.yaml index 49e1e4b89594..1a5d8a893220 100644 --- a/releasenotes/notes/Add-canonical-form-47e0466ed57640f3.yaml +++ b/releasenotes/notes/Add-canonical-form-47e0466ed57640f3.yaml @@ -13,7 +13,7 @@ features: canonical=DAGcanonical() - - | +- | The ''qiskit.converters.circuit_to_dagcanonical'' introduces a converter from a QuantumCircuit() object to a DAGcanonical() object. The ''qiskit.converters.dagconical_to_ciruit'' introduces a converter @@ -30,6 +30,9 @@ features: dag_canonical = circuit_to_dagcanonical(circuit_in) circuit_out = dagcanonical_to_circuit(dag_canonical) +- | + The ''qiskit/visualization/dag_visualization'' has been updated such that + it can draw DAG canonical forms. It takes the argument type issues: From cfcd380bf6856b8760938d834a4b562cde1b6d0b Mon Sep 17 00:00:00 2001 From: MOYARD Romain Date: Mon, 9 Dec 2019 20:45:09 +0100 Subject: [PATCH 05/27] Update with linting --- qiskit/converters/circuit_to_dagcanonical.py | 7 +- qiskit/converters/dagcanonical_to_circuit.py | 4 +- qiskit/dagcircuit/dagcanonical.py | 182 ++++++++++-------- qiskit/visualization/dag_visualization.py | 11 +- .../Add-canonical-form-47e0466ed57640f3.yaml | 4 +- 5 files changed, 119 insertions(+), 89 deletions(-) diff --git a/qiskit/converters/circuit_to_dagcanonical.py b/qiskit/converters/circuit_to_dagcanonical.py index f8cc2dfbab4c..c9b8fae601b6 100644 --- a/qiskit/converters/circuit_to_dagcanonical.py +++ b/qiskit/converters/circuit_to_dagcanonical.py @@ -31,15 +31,14 @@ def circuit_to_dagcanonical(circuit): for register in circuit.qregs: dagcircuit.add_qreg(register) - + for register in circuit.cregs: dagcircuit.add_creg(register) for operation, qargs, cargs in circuit.data: - dagcircuit.add_node(operation, qargs, cargs) - dagcircuit.add_edge() + dagcircuit.add_node(operation, qargs, cargs) + dagcircuit.add_edge() dagcircuit.add_successors() return dagcircuit - diff --git a/qiskit/converters/dagcanonical_to_circuit.py b/qiskit/converters/dagcanonical_to_circuit.py index 3b9a5c78f011..a38b2c01114f 100644 --- a/qiskit/converters/dagcanonical_to_circuit.py +++ b/qiskit/converters/dagcanonical_to_circuit.py @@ -20,7 +20,7 @@ def dagcanonical_to_circuit(dagcanonical): """Build a ``QuantumCircuit`` object from a ``DAGCanonical``. Args: - dag (DAGCcanonical): the input dag. + dagcanonical (DAGCcanonical): the input dag. Return: QuantumCircuit: the circuit representing the input dag canonical. @@ -30,7 +30,7 @@ def dagcanonical_to_circuit(dagcanonical): circuit = QuantumCircuit(*dagcanonical.qregs.values(), *dagcanonical.cregs.values(), name=name) for node in list(dagcanonical.nodes()): - node_op=node[1] + node_op = node[1] # Get arguments for classical control (if any) inst = node_op.op.copy() inst.condition = node_op.condition diff --git a/qiskit/dagcircuit/dagcanonical.py b/qiskit/dagcircuit/dagcanonical.py index c4f6576d67cb..cfb53dfbfd85 100644 --- a/qiskit/dagcircuit/dagcanonical.py +++ b/qiskit/dagcircuit/dagcanonical.py @@ -58,13 +58,13 @@ class DAGcanonical: - ''' - Object to represent a quantum circuit as as - ''' + """ + Object to represent a quantum circuit in the DAG canonical. + """ def __init__(self): - ''' + """ Create an empty directed acyclis graph (canonical form) - ''' + """ # Circuit name self.name = None @@ -74,7 +74,7 @@ def __init__(self): # Map of qreg name to QuantumRegister object self.qregs = OrderedDict() - + # Map of creg name to ClassicalRegister object self.cregs = OrderedDict() # Index of the last node added @@ -102,7 +102,7 @@ def add_qreg(self, qreg): if qreg.name in self.qregs: raise DAGCircuitError("duplicate register %s" % qreg.name) self.qregs[qreg.name] = qreg - + def add_creg(self, creg): """Add all wires in a classical register.""" if not isinstance(creg, ClassicalRegister): @@ -112,15 +112,13 @@ def add_creg(self, creg): self.cregs[creg.name] = creg def add_node(self, operation, qargs, cargs): - '''Add a node to the graph. + """Add a DAGnode to the graph. Args: - op: operation as a quantum gate. - qargs: list of qubits on which the operation acts - - Return: - Add a node to the DAG. - ''' + operation (Instruction): operation as a quantum gate. + qargs (list[Qubit]): list of qubits on which the operation acts + cargs (list[Clbit]): list of classical wires to attach to. + """ node_properties = { "type": "op", "op": operation, @@ -138,15 +136,17 @@ def add_node(self, operation, qargs, cargs): self._id_to_node[self._max_node_id] = new_node def _gather_pred(self, node_id, direct_pred): - ''' - Function set an attribute predecessors and gather multiple lists - of direct predecessors into a single one - - :param node_id: label of the considered node in the DAG - :param direct_pred: list of direct successors for the given node - :return: a multigraph with update of the attribute ['predecessors'] - the lists of direct successors are put into a single one - ''' + """Function set an attribute predecessors and gather multiple lists + of direct predecessors into a single one. + + Args: + node_id (int): label of the considered node in the DAG + direct_pred (list): list of direct successors for the given node + + Returns: + DAGcanonical: A multigraph with update of the attribute ['predecessors'] + the lists of direct successors are put into a single one + """ gather = self._multi_graph gather.nodes[node_id]['predecessors'] = [] for d_pred in direct_pred: @@ -155,16 +155,19 @@ def _gather_pred(self, node_id, direct_pred): gather.nodes[node_id]['predecessors'].append(pred) return gather - def _gather_succ(self, node_id, direct_succ): - ''' + """ Function set an attribute successors and gather multiple lists of direct successors into a single one. - :param node_id: label of the considered node in the DAG - :param direct_pred: list of direct successors for the given node - :return: a multigraph with update of the attribute ['predecessors'] - the lists of direct successors are put into a single one - ''' + + Args: + node_id (int): label of the considered node in the DAG + direct_succ (lis): list of direct successors for the given node + + Returns: + MultiDiGraph: with update of the attribute ['predecessors'] + the lists of direct successors are put into a single one + """ gather = self._multi_graph for d_succ in direct_succ: gather.nodes[node_id]['successors'].append([d_succ]) @@ -173,24 +176,25 @@ def _gather_succ(self, node_id, direct_succ): return gather def _list_pred(self, node_id): - ''' + """ Use _gather_pred function and merge_no_duplicates to construct the list of predecessors for a given node. - :param node_id: label of the considered node - :return: multi graph updated - ''' + + Args: + node_id (int): label of the considered node + """ direct_pred = sorted(list(self._multi_graph.predecessors(node_id))) self._multi_graph = self._gather_pred(node_id, direct_pred) self._multi_graph.nodes[node_id]['predecessors'] = list( merge_no_duplicates(*(self._multi_graph.nodes[node_id]['predecessors']))) def add_edge(self): - ''' + """ Function to verify the commutation relation and reachability for predecessors, the nodes do not commute and - if the predecessor is reachable. - :return: update the DAGcanonical by introducing edges and predecessors(attribute) - ''' + if the predecessor is reachable. Update the DAGcanonical by + introducing edges and predecessors(attribute) + """ node = self._id_to_node[self._max_node_id] max_id = self._max_node_id for current_node in range(1, max_id): @@ -206,10 +210,10 @@ def add_edge(self): self._multi_graph.nodes[pred]['reachable'] = False def add_successors(self): - ''' - Use _gather_succ and merge_no_duplicates to create the list of successors for each node. - :return: Update DAGcanonical with attributes successors - ''' + """ + Use _gather_succ and merge_no_duplicates to create the list of successors + for each node. Update DAGcanonical with attributes successors. + """ for node_id in range(len(self._multi_graph), 0, -1): direct_successors = sorted(list(self._multi_graph.successors(node_id))) @@ -220,36 +224,53 @@ def add_successors(self): merge_no_duplicates(*(self._multi_graph.nodes[node_id]['successors']))) def node(self, node_id): - ''' - :param node_id: label of considered node - :return: the nodes corresponding to the label - ''' + """ + Args: + node_id (int): label of considered node. + + Returns: + Node: corresponding to the label. + """ return self._multi_graph.nodes[node_id] def nodes(self): - ''' - :return: Generator of all nodes (label, DAGnode) - ''' + """Function to return all nodes + + Yields: + Iterator: generate all nodes (label, DAGnode). + """ for node in self._multi_graph.nodes(data='operation'): yield node def edges(self): - ''' - :return: Generator of all edges - ''' + """Function to yield all edges. + + Yields: + Iterator: generate all edges. + """ for edge in self._multi_graph.edges(data=True): yield edge def in_edge(self, node_id): - ''' - :return: Incoming edge (directed graph) - ''' + """ Get the list of incoming nodes for a given node_id. + + Args: + node_id (int): id of the corresponding node + + Returns: + list[In_edges()]: List of all incoming edges. + """ return self._multi_graph.in_edges(node_id) def out_edge(self, node_id): - ''' - :return: Outgoing edge (directed graph) - ''' + """List of all outgoing edges for the given node id. + + Args: + node_id (int): id of the corresponding node. + + Returns: + list[out_edges()]: List of all incoming edges. + """ return self._multi_graph.out_edges(node_id) def direct_successors(self, node_id): @@ -267,10 +288,10 @@ def successors(self, node_id): def predecessors(self, node_id): """Returns set of the descendants of a node as DAGNodes.""" return self.to_networkx().nodes[node_id]['predecessors'] - - def draw(self, scale=0.7, filename=None, style='color'): + + def draw(self, scale=0.7, filename=None, style='color', type='canonical'): """ - Draws the dag circuit. + Draws the dag circuit (canonical). This function needs `pydot `, which in turn needs Graphviz ` to be installed. @@ -280,36 +301,42 @@ def draw(self, scale=0.7, filename=None, style='color'): filename (str): file path to save image to (format inferred from name) style (str): 'plain': B&W graph 'color' (default): color input/output/op nodes + type(str): 'canonical' Other type of DAG Returns: Ipython.display.Image: if in Jupyter notebook and not saving to file, otherwise None. """ from qiskit.visualization.dag_visualization import dag_drawer - return dag_drawer(self, scale=scale, filename=filename, style=style, type='canonical') + return dag_drawer(self, scale=scale, filename=filename, style=style, type=type) + def merge_no_duplicates(*iterables): - ''' - Merge K list without duplicate using python heapq ordered merging - :param iterables: A list of k sorted lists - :return: List from the merging of the k ones (without duplicates - ''' + """Merge K list without duplicate using python heapq ordered merging + + Args: + *iterables: A list of k sorted lists + + Yields: + Iteraor: List from the merging of the k ones (without duplicates + """ last = object() for val in heapq.merge(*iterables): if val != last: last = val yield val + def commute(node1, node2): - '''Function to verify commutation relation between two nodes in the DAG + """Function to verify commutation relation between two nodes in the DAG Args: - node1: first node operation (attribute ['operation'] in the DAG) - node2: second node operation + node1 (DAGnode): first node operation (attribute ['operation'] in the DAG) + node2 (DAGnode): second node operation Return: - True if the gates commute and false if it is not the case - ''' + if_commute (Boolean): True if the gates commute and false if it is not the case. + """ # Create set of qubits on which the operation acts qarg1 = [node1.qargs[i].index for i in range(0, len(node1.qargs))] @@ -321,23 +348,26 @@ def commute(node1, node2): # Commutation for classical conditional gates if node1.condition or node2.condition: - if len(set(qarg1).intersection(set(qarg2))) > 0: + intersection = set(qarg1).intersection(set(qarg2)) + if intersection: return False - elif len(set(carg1)) > 0 or len(set(carg2)) > 0: + elif carg1 or carg2: return False else: return True # Commutation for measurement if node1.name == 'measure' or node2.name == 'measure': - if len(set(qarg1).intersection(set(qarg2))) > 0: + intersection = set(qarg1).intersection(set(qarg2)) + if intersection: return False else: return True # Commutation for barrier if node1.name == 'barrier' or node2.name == 'barrier': - if len(set(qarg1).intersection(set(qarg2))) > 0: + intersection = set(qarg1).intersection(set(qarg2)) + if intersection: return False else: return True diff --git a/qiskit/visualization/dag_visualization.py b/qiskit/visualization/dag_visualization.py index dc8255cb11a1..a1563939b9a1 100644 --- a/qiskit/visualization/dag_visualization.py +++ b/qiskit/visualization/dag_visualization.py @@ -52,6 +52,7 @@ def dag_drawer(dag, scale=0.7, filename=None, style='color', type=None): filename (str): file path to save image to (format inferred from name) style (str): 'plain': B&W graph 'color' (default): color input/output/op nodes + type (str): 'canonical' for drawing DAG canonical Returns: PIL.Image: if in Jupyter notebook and not saving to file, @@ -86,7 +87,7 @@ def dag_drawer(dag, scale=0.7, filename=None, style='color', type=None): except ImportError: raise ImportError("dag_drawer requires pydot. " "Run 'pip install pydot'.") - if type==None: + if type is None: G = dag.to_networkx() G.graph['dpi'] = 100 * scale @@ -112,11 +113,11 @@ def dag_drawer(dag, scale=0.7, filename=None, style='color', type=None): e[2]['label'] = e[2]['name'] else: raise VisualizationError("Unrecognized style for the dag_drawer.") - - elif type=='canonical': + + elif type == 'canonical': G = dag.to_networkx() G.graph['dpi'] = 100 * scale - + if style == 'plain': pass elif style == 'color': @@ -143,7 +144,7 @@ def dag_drawer(dag, scale=0.7, filename=None, style='color', type=None): else: raise VisualizationError("Unrecognized style for the dag_drawer.") else: - raise VisualizationError("Unrecognized of DAG") + raise VisualizationError("Unrecognized type of DAG") dot = to_pydot(G) diff --git a/releasenotes/notes/Add-canonical-form-47e0466ed57640f3.yaml b/releasenotes/notes/Add-canonical-form-47e0466ed57640f3.yaml index 1a5d8a893220..d575c60c9781 100644 --- a/releasenotes/notes/Add-canonical-form-47e0466ed57640f3.yaml +++ b/releasenotes/notes/Add-canonical-form-47e0466ed57640f3.yaml @@ -32,10 +32,10 @@ features: - | The ''qiskit/visualization/dag_visualization'' has been updated such that - it can draw DAG canonical forms. It takes the argument type + it can draw DAG canonical forms. It takes the new argument 'type'. issues: - | In ''qiskit.dagcircuit.dagcanonical'' the function commute could be - improved. + improved. (Too many returns) From 16e3bcabd915095d8b06ebd526a24240ced67ddf Mon Sep 17 00:00:00 2001 From: MOYARD Romain Date: Mon, 9 Dec 2019 22:42:09 +0100 Subject: [PATCH 06/27] Linting --- qiskit/visualization/dag_visualization.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/visualization/dag_visualization.py b/qiskit/visualization/dag_visualization.py index a1563939b9a1..f221aa847a49 100644 --- a/qiskit/visualization/dag_visualization.py +++ b/qiskit/visualization/dag_visualization.py @@ -123,7 +123,7 @@ def dag_drawer(dag, scale=0.7, filename=None, style='color', type=None): elif style == 'color': for node in G.nodes(data='operation'): n = G.nodes[node[0]] - n['label'] = str(node[0])+ ': ' + node[1].name + n['label'] = str(node[0]) + ': ' + node[1].name if node[1].name == 'measure': n['color'] = 'blue' n['style'] = 'filled' From a18d020ce235b44ca368778ca3a24bcc593304c1 Mon Sep 17 00:00:00 2001 From: MOYARD Romain Date: Mon, 9 Dec 2019 22:45:59 +0100 Subject: [PATCH 07/27] Linting --- qiskit/dagcircuit/dagcanonical.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/dagcircuit/dagcanonical.py b/qiskit/dagcircuit/dagcanonical.py index cfb53dfbfd85..0df2242b2413 100644 --- a/qiskit/dagcircuit/dagcanonical.py +++ b/qiskit/dagcircuit/dagcanonical.py @@ -59,7 +59,7 @@ class DAGcanonical: """ - Object to represent a quantum circuit in the DAG canonical. + Object to represent a quantum circuit as a DAG in the canonical form. """ def __init__(self): """ From cad0c1e71838f85a829ac918afd58c2ff2db41ec Mon Sep 17 00:00:00 2001 From: MOYARD Romain Date: Tue, 10 Dec 2019 12:57:58 +0100 Subject: [PATCH 08/27] Update release note --- .../Add-canonical-form-47e0466ed57640f3.yaml | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/releasenotes/notes/Add-canonical-form-47e0466ed57640f3.yaml b/releasenotes/notes/Add-canonical-form-47e0466ed57640f3.yaml index d575c60c9781..7d5cdab0488f 100644 --- a/releasenotes/notes/Add-canonical-form-47e0466ed57640f3.yaml +++ b/releasenotes/notes/Add-canonical-form-47e0466ed57640f3.yaml @@ -1,19 +1,17 @@ --- - -prelude: > +features: + - | Added a new DAG to qiskit; the canonical form. In this DAG, the node are operation (gates, measure, barrier, etc...). The edges corresponds to non-commutation between two operations. -features: - - | The ''qiskit.dagcircuit.dagcanonical'' introduces the DAGcanonical class. It allows to create an empty DAG canonical. For exmaple: from qiskit.dagcircuit.dagcanonical import DAGcanonical canonical=DAGcanonical() - -- | + + - | The ''qiskit.converters.circuit_to_dagcanonical'' introduces a converter from a QuantumCircuit() object to a DAGcanonical() object. The ''qiskit.converters.dagconical_to_ciruit'' introduces a converter @@ -30,9 +28,9 @@ features: dag_canonical = circuit_to_dagcanonical(circuit_in) circuit_out = dagcanonical_to_circuit(dag_canonical) -- | - The ''qiskit/visualization/dag_visualization'' has been updated such that - it can draw DAG canonical forms. It takes the new argument 'type'. + - | + The ''qiskit/visualization/dag_visualization'' has been updated such that + it can draw DAG canonical forms. It takes the new argument 'type'. issues: From 0fce65ddfccf739b83481dd54abb2dd9258072b9 Mon Sep 17 00:00:00 2001 From: MOYARD Romain Date: Tue, 10 Dec 2019 14:45:50 +0100 Subject: [PATCH 09/27] More linting for dagcanonical and dag_visualization --- qiskit/dagcircuit/dagcanonical.py | 8 ++++---- qiskit/visualization/dag_visualization.py | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/qiskit/dagcircuit/dagcanonical.py b/qiskit/dagcircuit/dagcanonical.py index 0df2242b2413..f876d09a6110 100644 --- a/qiskit/dagcircuit/dagcanonical.py +++ b/qiskit/dagcircuit/dagcanonical.py @@ -289,9 +289,9 @@ def predecessors(self, node_id): """Returns set of the descendants of a node as DAGNodes.""" return self.to_networkx().nodes[node_id]['predecessors'] - def draw(self, scale=0.7, filename=None, style='color', type='canonical'): + def draw(self, scale=0.7, filename=None, style='color', category='canonical'): """ - Draws the dag circuit (canonical). + Draws the DAG canonical circuit. This function needs `pydot `, which in turn needs Graphviz ` to be installed. @@ -301,14 +301,14 @@ def draw(self, scale=0.7, filename=None, style='color', type='canonical'): filename (str): file path to save image to (format inferred from name) style (str): 'plain': B&W graph 'color' (default): color input/output/op nodes - type(str): 'canonical' Other type of DAG + category(str): 'canonical' Other type of DAG Returns: Ipython.display.Image: if in Jupyter notebook and not saving to file, otherwise None. """ from qiskit.visualization.dag_visualization import dag_drawer - return dag_drawer(self, scale=scale, filename=filename, style=style, type=type) + return dag_drawer(dag=self, scale=scale, filename=filename, style=style, category=category) def merge_no_duplicates(*iterables): diff --git a/qiskit/visualization/dag_visualization.py b/qiskit/visualization/dag_visualization.py index f221aa847a49..4b0b789371d7 100644 --- a/qiskit/visualization/dag_visualization.py +++ b/qiskit/visualization/dag_visualization.py @@ -33,7 +33,7 @@ HAS_PIL = False -def dag_drawer(dag, scale=0.7, filename=None, style='color', type=None): +def dag_drawer(dag, scale=0.7, filename=None, style='color', category=None): """Plot the directed acyclic graph (dag) to represent operation dependencies in a quantum circuit. @@ -52,7 +52,7 @@ def dag_drawer(dag, scale=0.7, filename=None, style='color', type=None): filename (str): file path to save image to (format inferred from name) style (str): 'plain': B&W graph 'color' (default): color input/output/op nodes - type (str): 'canonical' for drawing DAG canonical + category (str): 'canonical' for drawing DAG canonical Returns: PIL.Image: if in Jupyter notebook and not saving to file, @@ -87,7 +87,7 @@ def dag_drawer(dag, scale=0.7, filename=None, style='color', type=None): except ImportError: raise ImportError("dag_drawer requires pydot. " "Run 'pip install pydot'.") - if type is None: + if category is None: G = dag.to_networkx() G.graph['dpi'] = 100 * scale @@ -114,7 +114,7 @@ def dag_drawer(dag, scale=0.7, filename=None, style='color', type=None): else: raise VisualizationError("Unrecognized style for the dag_drawer.") - elif type == 'canonical': + elif category == 'canonical': G = dag.to_networkx() G.graph['dpi'] = 100 * scale @@ -144,7 +144,7 @@ def dag_drawer(dag, scale=0.7, filename=None, style='color', type=None): else: raise VisualizationError("Unrecognized style for the dag_drawer.") else: - raise VisualizationError("Unrecognized type of DAG") + raise VisualizationError("Unrecognized category of DAG") dot = to_pydot(G) From 3cd2463b1f1d17b47278c521c1a3f5e705ab1186 Mon Sep 17 00:00:00 2001 From: MOYARD Romain Date: Tue, 10 Dec 2019 15:19:17 +0100 Subject: [PATCH 10/27] Linting for dagcanonical --- qiskit/dagcircuit/dagcanonical.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/dagcircuit/dagcanonical.py b/qiskit/dagcircuit/dagcanonical.py index f876d09a6110..6f35e39556b4 100644 --- a/qiskit/dagcircuit/dagcanonical.py +++ b/qiskit/dagcircuit/dagcanonical.py @@ -335,7 +335,7 @@ def commute(node1, node2): node2 (DAGnode): second node operation Return: - if_commute (Boolean): True if the gates commute and false if it is not the case. + bool: True if the gates commute and false if it is not the case. """ # Create set of qubits on which the operation acts From ec1240d4e5a3a91f3f1d2e1c9cf0a64c999e858f Mon Sep 17 00:00:00 2001 From: MOYARD Romain Date: Tue, 10 Dec 2019 16:14:42 +0100 Subject: [PATCH 11/27] Less returns in the commute function --- qiskit/dagcircuit/dagcanonical.py | 41 +++++++++---------------------- 1 file changed, 12 insertions(+), 29 deletions(-) diff --git a/qiskit/dagcircuit/dagcanonical.py b/qiskit/dagcircuit/dagcanonical.py index 6f35e39556b4..293ade7296c6 100644 --- a/qiskit/dagcircuit/dagcanonical.py +++ b/qiskit/dagcircuit/dagcanonical.py @@ -201,7 +201,7 @@ def add_edge(self): self._multi_graph.nodes[current_node]['reachable'] = True # Check the commutation relation with reachable node, it adds edges if it does not commute for prev_node in range(max_id - 1, 0, -1): - if self._multi_graph.nodes[prev_node]['reachable'] and not commute( + if self._multi_graph.nodes[prev_node]['reachable'] and not _commute( self._multi_graph.nodes[prev_node]['operation'], node): self._multi_graph.add_edge(prev_node, max_id) self._list_pred(max_id) @@ -327,7 +327,7 @@ def merge_no_duplicates(*iterables): yield val -def commute(node1, node2): +def _commute(node1, node2): """Function to verify commutation relation between two nodes in the DAG Args: @@ -349,57 +349,40 @@ def commute(node1, node2): # Commutation for classical conditional gates if node1.condition or node2.condition: intersection = set(qarg1).intersection(set(qarg2)) - if intersection: - return False - elif carg1 or carg2: - return False + if intersection or carg1 or carg2: + commute_condition = False else: - return True + commute_condition = True + return commute_condition # Commutation for measurement if node1.name == 'measure' or node2.name == 'measure': intersection = set(qarg1).intersection(set(qarg2)) if intersection: - return False + commute_measurement = False else: - return True + commute_measurement = True + return commute_measurement # Commutation for barrier if node1.name == 'barrier' or node2.name == 'barrier': intersection = set(qarg1).intersection(set(qarg2)) if intersection: - return False + commute_barrier = False else: - return True + commute_barrier = True + return commute_barrier # Commutation for snapshot if node1.name == 'snapshot' or node2.name == 'snapshot': return False - # Identity always commute - if node1.name == 'id' or node2.name == 'id': - return True - - # If the sets are disjoint return false - if set(qarg1).intersection(set(qarg2)) == set(): - return True - - # if the operation are the same return true - if qarg1 == qarg2 and node1.op == node2.op: - return True - # List of non commuting gates (TO DO: add more elements) non_commute_list = [set(['x', 'y']), set(['x', 'z'])] if qarg1 == qarg2 and (set([node1.name, node2.name]) in non_commute_list): return False - # List of commuting gates (TO DO: add more elements) - commute_list = [set([])] - - if qarg1 == qarg2 and (set([node1.name, node2.name]) in commute_list): - return True - # Create matrices to check commutation relation if no other criteria are matched qarg = list(set(node1.qargs + node2.qargs)) qbit_num = len(qarg) From e21e6afc8fac84c3ea24cfe74dc0716b0873e6e0 Mon Sep 17 00:00:00 2001 From: MOYARD Romain Date: Tue, 10 Dec 2019 17:57:00 +0100 Subject: [PATCH 12/27] Update due to cyclic importation --- qiskit/transpiler/passes/decompose.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/transpiler/passes/decompose.py b/qiskit/transpiler/passes/decompose.py index 4dbc2fb81734..244cc31810a8 100644 --- a/qiskit/transpiler/passes/decompose.py +++ b/qiskit/transpiler/passes/decompose.py @@ -15,7 +15,7 @@ """Expand a gate in a circuit using its decomposition rules.""" from qiskit.transpiler.basepasses import TransformationPass -from qiskit.dagcircuit import DAGCircuit +from qiskit.dagcircuit.dagcircuit import DAGCircuit class Decompose(TransformationPass): From 1c44fd27da9d0341242d7530efd2c0451b514e24 Mon Sep 17 00:00:00 2001 From: MOYARD Romain Date: Mon, 27 Jan 2020 15:36:31 +0100 Subject: [PATCH 13/27] Update with two converters from DAGcircuit() to DAGcanonical() and DAGcanonical() to DAGcircuit() --- qiskit/converters/dag_to_dagcanonical.py | 50 ++++++++++++++++++ qiskit/converters/dagcanonical_to_dag.py | 49 ++++++++++++++++++ .../Add-canonical-form-47e0466ed57640f3.yaml | 9 +++- .../converters/test_dag_to_dag_canonical.py | 51 +++++++++++++++++++ 4 files changed, 157 insertions(+), 2 deletions(-) create mode 100644 qiskit/converters/dag_to_dagcanonical.py create mode 100644 qiskit/converters/dagcanonical_to_dag.py create mode 100644 test/python/converters/test_dag_to_dag_canonical.py diff --git a/qiskit/converters/dag_to_dagcanonical.py b/qiskit/converters/dag_to_dagcanonical.py new file mode 100644 index 000000000000..418e7ef29546 --- /dev/null +++ b/qiskit/converters/dag_to_dagcanonical.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2018. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Helper function for converting a dag to a dagcanonical""" +from qiskit.dagcircuit.dagcanonical import DAGcanonical + + +def dag_to_dagcanonical(dag): + """Build a ``QuantumCircuit`` object from a ``DAGCircuit``. + + Args: + dag (DAGCircuit): the input dag. + + Return: + DAGcanonical: the DAG representing the input circuit as a dag canonical. + """ + + dagcanonical = DAGcanonical() + DAGcanonical.name = dag.name + + qregs = list(dag.qregs.values()) + cregs = list(dag.cregs.values()) + + for register in qregs: + dagcanonical.add_qreg(register) + + for register in cregs: + dagcanonical.add_creg(register) + + for node in dag.topological_op_nodes(): + # Get arguments for classical control (if any) + inst = node.op.copy() + inst.condition = node.condition + dagcanonical.add_node(inst, node.qargs, node.cargs) + dagcanonical.add_edge() + + dagcanonical.add_successors() + + return dagcanonical diff --git a/qiskit/converters/dagcanonical_to_dag.py b/qiskit/converters/dagcanonical_to_dag.py new file mode 100644 index 000000000000..cc8a59d1d9dc --- /dev/null +++ b/qiskit/converters/dagcanonical_to_dag.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Helper function for converting a dag canonical to a circuit""" +from qiskit.dagcircuit.dagcircuit import DAGCircuit + + +def dagcanonical_to_dag(dagcanonical): + """Build a ``QuantumCircuit`` object from a ``DAGCanonical``. + + Args: + dagcanonical (DAGCcanonical): the input dag. + + Return: + DAGCircuit: the DAG representing the input circuit. + """ + + dagcircuit = DAGCircuit() + dagcircuit.name = dagcanonical.name + + qregs = list(dagcanonical.qregs.values()) + cregs = list(dagcanonical.cregs.values()) + + for register in qregs: + dagcircuit.add_qreg(register) + + for register in cregs: + dagcircuit.add_creg(register) + + for node in list(dagcanonical.nodes()): + node_op = node[1] + # Get arguments for classical control (if any) + inst = node_op.op.copy() + inst.condition = node_op.condition + + dagcircuit.apply_operation_back(inst, node_op.qargs, node_op.cargs, inst.condition) + + return dagcircuit diff --git a/releasenotes/notes/Add-canonical-form-47e0466ed57640f3.yaml b/releasenotes/notes/Add-canonical-form-47e0466ed57640f3.yaml index 7d5cdab0488f..8460f0ffb8e1 100644 --- a/releasenotes/notes/Add-canonical-form-47e0466ed57640f3.yaml +++ b/releasenotes/notes/Add-canonical-form-47e0466ed57640f3.yaml @@ -15,12 +15,17 @@ features: The ''qiskit.converters.circuit_to_dagcanonical'' introduces a converter from a QuantumCircuit() object to a DAGcanonical() object. The ''qiskit.converters.dagconical_to_ciruit'' introduces a converter - from a DAGcanonical() object to aQuantumCircuit() object. + from a DAGcanonical() object to a QuantumCircuit() object. + The ''qiskit.converters.dag_to_dagcanonical'' introduces a converter + from a DAGCircuit() object to a DAGcanonical() object. + The ''qiskit.converters.dagconical_to_ciruit'' introduces a converter + from a DAGcanonical() object to a DAGCircuit() object. + For exmaple: from qiskit.converters.dagcanonical_to_circuit import dagcanonical_to_circuit from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit - + circuit_in = QuantumCircuit(2) circuit_in.h(qr[0]) circuit_in.h(qr[1]) diff --git a/test/python/converters/test_dag_to_dag_canonical.py b/test/python/converters/test_dag_to_dag_canonical.py new file mode 100644 index 000000000000..20c0c6f83946 --- /dev/null +++ b/test/python/converters/test_dag_to_dag_canonical.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2018. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Test for the converter dag canonical to dag and dag to dag canonical.""" + +import unittest + +from qiskit.converters.circuit_to_dag import circuit_to_dag +from qiskit.converters.dag_to_dagcanonical import dag_to_dagcanonical +from qiskit.converters.dagcanonical_to_dag import dagcanonical_to_dag +from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit +from qiskit.test import QiskitTestCase + + +class TestCircuitToDagCanonical(QiskitTestCase): + """Test DAG to DAGcanonical.""" + + def test_circuit_and_dag_canonical(self): + """Check convert to dag canonical and back""" + qr = QuantumRegister(3) + cr = ClassicalRegister(3) + circuit_in = QuantumCircuit(qr, cr) + circuit_in.h(qr[0]) + circuit_in.h(qr[1]) + circuit_in.measure(qr[0], cr[0]) + circuit_in.measure(qr[1], cr[1]) + circuit_in.x(qr[0]).c_if(cr, 0x3) + circuit_in.measure(qr[0], cr[0]) + circuit_in.measure(qr[1], cr[1]) + circuit_in.measure(qr[2], cr[2]) + dag_in = circuit_to_dag(circuit_in) + + dag_canonical = dag_to_dagcanonical(dag_in) + dag_out = dagcanonical_to_dag(dag_canonical) + + self.assertEqual(dag_out, dag_in) + + +if __name__ == '__main__': + unittest.main(verbosity=2) From c23391399fcdf233919377c284695d33533c34ac Mon Sep 17 00:00:00 2001 From: romain Date: Thu, 30 Apr 2020 07:36:22 +0200 Subject: [PATCH 14/27] Adaptation to retworkx, change of name (dagdependency) and add tests --- qiskit/converters/__init__.py | 12 +- ...nonical.py => circuit_to_dagdependency.py} | 28 +- ...agcanonical.py => dag_to_dagdependency.py} | 28 +- ...circuit.py => dagdependency_to_circuit.py} | 21 +- ...ical_to_dag.py => dagdependency_to_dag.py} | 20 +- qiskit/dagcircuit/DAGdependency.py | 0 qiskit/dagcircuit/__init__.py | 4 +- qiskit/dagcircuit/dagcanonical.py | 400 -------------- qiskit/dagcircuit/dagdependency.py | 505 ++++++++++++++++++ qiskit/dagcircuit/exceptions.py | 13 + qiskit/visualization/dag_visualization.py | 8 +- .../Add-canonical-form-47e0466ed57640f3.yaml | 40 +- ...al.py => test_circuit_to_dagdependency.py} | 17 +- ...onical.py => test_dag_to_dagdependency.py} | 21 +- test/python/dagcircuit/test_dagdependency.py | 249 +++++++++ 15 files changed, 870 insertions(+), 496 deletions(-) rename qiskit/converters/{circuit_to_dagcanonical.py => circuit_to_dagdependency.py} (51%) rename qiskit/converters/{dag_to_dagcanonical.py => dag_to_dagdependency.py} (57%) rename qiskit/converters/{dagcanonical_to_circuit.py => dagdependency_to_circuit.py} (56%) rename qiskit/converters/{dagcanonical_to_dag.py => dagdependency_to_dag.py} (68%) create mode 100644 qiskit/dagcircuit/DAGdependency.py delete mode 100644 qiskit/dagcircuit/dagcanonical.py create mode 100644 qiskit/dagcircuit/dagdependency.py rename test/python/converters/{test_circuit_to_dag_canonical.py => test_circuit_to_dagdependency.py} (71%) rename test/python/converters/{test_dag_to_dag_canonical.py => test_dag_to_dagdependency.py} (68%) create mode 100644 test/python/dagcircuit/test_dagdependency.py diff --git a/qiskit/converters/__init__.py b/qiskit/converters/__init__.py index f82d7e69cd71..9c035c349b09 100644 --- a/qiskit/converters/__init__.py +++ b/qiskit/converters/__init__.py @@ -27,8 +27,10 @@ circuit_to_instruction circuit_to_gate ast_to_dag - dagcanonical_to_circuit - circuit_to_dagcanonical + dagdependency_to_circuit + circuit_to_dagdependency + dag_to_dagdependency + dagdependency_to_dag """ from .circuit_to_dag import circuit_to_dag @@ -36,8 +38,10 @@ from .circuit_to_instruction import circuit_to_instruction from .circuit_to_gate import circuit_to_gate from .ast_to_dag import ast_to_dag -from .circuit_to_dagcanonical import circuit_to_dagcanonical -from .dagcanonical_to_circuit import dagcanonical_to_circuit +from .circuit_to_dagdependency import circuit_to_dagdependency +from .dagdependency_to_circuit import dagdependency_to_circuit +from .dag_to_dagdependency import dag_to_dagdependency +from .dagdependency_to_dag import dagdependency_to_dag def isinstanceint(obj): diff --git a/qiskit/converters/circuit_to_dagcanonical.py b/qiskit/converters/circuit_to_dagdependency.py similarity index 51% rename from qiskit/converters/circuit_to_dagcanonical.py rename to qiskit/converters/circuit_to_dagdependency.py index c9b8fae601b6..81635ec82b48 100644 --- a/qiskit/converters/circuit_to_dagcanonical.py +++ b/qiskit/converters/circuit_to_dagdependency.py @@ -2,7 +2,7 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2019. +# (C) Copyright IBM 2020. # # This code is licensed under the Apache License, Version 2.0. You may # obtain a copy of this license in the LICENSE.txt file in the root directory @@ -12,33 +12,33 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -'''Helper function for converting a circuit to a dag canonical''' +'''Helper function for converting a circuit to a dag dependency''' -from qiskit.dagcircuit.dagcanonical import DAGcanonical +from qiskit.dagcircuit.dagdependency import DAGDependency -def circuit_to_dagcanonical(circuit): - """Build a ``DAGCanonical`` object from a ``QuantumCircuit``. +def circuit_to_dagdependency(circuit): + """Build a ``DAGDependency`` object from a ``QuantumCircuit``. Args: circuit (QuantumCircuit): the input circuits. Return: - DAGcanonical: the DAG representing the input circuit as a dag canonical. + DAGDependency: the DAG representing the input circuit as a dag dependency. """ - dagcircuit = DAGcanonical() - dagcircuit.name = circuit.name + dagdependency = DAGDependency() + dagdependency.name = circuit.name for register in circuit.qregs: - dagcircuit.add_qreg(register) + dagdependency.add_qreg(register) for register in circuit.cregs: - dagcircuit.add_creg(register) + dagdependency.add_creg(register) for operation, qargs, cargs in circuit.data: - dagcircuit.add_node(operation, qargs, cargs) - dagcircuit.add_edge() + dagdependency.add_op_node(operation, qargs, cargs) + dagdependency.add_edge() - dagcircuit.add_successors() + dagdependency.add_successors() - return dagcircuit + return dagdependency diff --git a/qiskit/converters/dag_to_dagcanonical.py b/qiskit/converters/dag_to_dagdependency.py similarity index 57% rename from qiskit/converters/dag_to_dagcanonical.py rename to qiskit/converters/dag_to_dagdependency.py index 418e7ef29546..7173724a9ae7 100644 --- a/qiskit/converters/dag_to_dagcanonical.py +++ b/qiskit/converters/dag_to_dagdependency.py @@ -2,7 +2,7 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2017, 2018. +# (C) Copyright IBM 2020. # # This code is licensed under the Apache License, Version 2.0. You may # obtain a copy of this license in the LICENSE.txt file in the root directory @@ -12,39 +12,39 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""Helper function for converting a dag to a dagcanonical""" -from qiskit.dagcircuit.dagcanonical import DAGcanonical +"""Helper function for converting a dag circuit to a dag dependency""" +from qiskit.dagcircuit.dagdependency import DAGDependency -def dag_to_dagcanonical(dag): - """Build a ``QuantumCircuit`` object from a ``DAGCircuit``. +def dag_to_dagdependency(dag): + """Build a ``DAGDependency`` object from a ``DAGCircuit``. Args: dag (DAGCircuit): the input dag. Return: - DAGcanonical: the DAG representing the input circuit as a dag canonical. + DAGDependency: the DAG representing the input circuit as a dag dependency. """ - dagcanonical = DAGcanonical() - DAGcanonical.name = dag.name + dagdependency = DAGDependency() + DAGDependency.name = dag.name qregs = list(dag.qregs.values()) cregs = list(dag.cregs.values()) for register in qregs: - dagcanonical.add_qreg(register) + dagdependency.add_qreg(register) for register in cregs: - dagcanonical.add_creg(register) + dagdependency.add_creg(register) for node in dag.topological_op_nodes(): # Get arguments for classical control (if any) inst = node.op.copy() inst.condition = node.condition - dagcanonical.add_node(inst, node.qargs, node.cargs) - dagcanonical.add_edge() + dagdependency.add_op_node(inst, node.qargs, node.cargs) + dagdependency.add_edge() - dagcanonical.add_successors() + dagdependency.add_successors() - return dagcanonical + return dagdependency diff --git a/qiskit/converters/dagcanonical_to_circuit.py b/qiskit/converters/dagdependency_to_circuit.py similarity index 56% rename from qiskit/converters/dagcanonical_to_circuit.py rename to qiskit/converters/dagdependency_to_circuit.py index a38b2c01114f..b80c5f663c1b 100644 --- a/qiskit/converters/dagcanonical_to_circuit.py +++ b/qiskit/converters/dagdependency_to_circuit.py @@ -2,7 +2,7 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2019. +# (C) Copyright IBM 2020. # # This code is licensed under the Apache License, Version 2.0. You may # obtain a copy of this license in the LICENSE.txt file in the root directory @@ -12,25 +12,26 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""Helper function for converting a dag canonical to a circuit""" +"""Helper function for converting a dag dependency to a circuit""" from qiskit.circuit import QuantumCircuit -def dagcanonical_to_circuit(dagcanonical): - """Build a ``QuantumCircuit`` object from a ``DAGCanonical``. +def dagdependency_to_circuit(dagdependency): + """Build a ``QuantumCircuit`` object from a ``DAGDependency``. Args: - dagcanonical (DAGCcanonical): the input dag. + dagdependency (DAGDependency): the input dag. Return: - QuantumCircuit: the circuit representing the input dag canonical. + QuantumCircuit: the circuit representing the input dag dependency. """ - name = dagcanonical.name or None - circuit = QuantumCircuit(*dagcanonical.qregs.values(), *dagcanonical.cregs.values(), name=name) + name = dagdependency.name or None + circuit = QuantumCircuit(*dagdependency.qregs.values(), *dagdependency.cregs.values(), + name=name) - for node in list(dagcanonical.nodes()): - node_op = node[1] + for node in dagdependency.get_nodes(): + node_op = node['operation'] # Get arguments for classical control (if any) inst = node_op.op.copy() inst.condition = node_op.condition diff --git a/qiskit/converters/dagcanonical_to_dag.py b/qiskit/converters/dagdependency_to_dag.py similarity index 68% rename from qiskit/converters/dagcanonical_to_dag.py rename to qiskit/converters/dagdependency_to_dag.py index cc8a59d1d9dc..a84ce426f4eb 100644 --- a/qiskit/converters/dagcanonical_to_dag.py +++ b/qiskit/converters/dagdependency_to_dag.py @@ -2,7 +2,7 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2019. +# (C) Copyright IBM 2020. # # This code is licensed under the Apache License, Version 2.0. You may # obtain a copy of this license in the LICENSE.txt file in the root directory @@ -12,25 +12,25 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""Helper function for converting a dag canonical to a circuit""" +"""Helper function for converting a dag dependency to a dag circuit""" from qiskit.dagcircuit.dagcircuit import DAGCircuit -def dagcanonical_to_dag(dagcanonical): - """Build a ``QuantumCircuit`` object from a ``DAGCanonical``. +def dagdependency_to_dag(dagdependency): + """Build a ``DAGCircuit`` object from a ``DAGDependency``. Args: - dagcanonical (DAGCcanonical): the input dag. + dag dependency (DAGDependency): the input dag. Return: DAGCircuit: the DAG representing the input circuit. """ dagcircuit = DAGCircuit() - dagcircuit.name = dagcanonical.name + dagcircuit.name = dagdependency.name - qregs = list(dagcanonical.qregs.values()) - cregs = list(dagcanonical.cregs.values()) + qregs = list(dagdependency.qregs.values()) + cregs = list(dagdependency.cregs.values()) for register in qregs: dagcircuit.add_qreg(register) @@ -38,8 +38,8 @@ def dagcanonical_to_dag(dagcanonical): for register in cregs: dagcircuit.add_creg(register) - for node in list(dagcanonical.nodes()): - node_op = node[1] + for node in dagdependency.get_nodes(): + node_op = node['operation'] # Get arguments for classical control (if any) inst = node_op.op.copy() inst.condition = node_op.condition diff --git a/qiskit/dagcircuit/DAGdependency.py b/qiskit/dagcircuit/DAGdependency.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/qiskit/dagcircuit/__init__.py b/qiskit/dagcircuit/__init__.py index 5b7a40094648..facadf3b6ce5 100644 --- a/qiskit/dagcircuit/__init__.py +++ b/qiskit/dagcircuit/__init__.py @@ -27,7 +27,7 @@ DAGCircuit DAGNode - DAGcanonical + DAGDependency Exceptions ========== @@ -40,4 +40,4 @@ from .dagcircuit import DAGCircuit from .dagnode import DAGNode from .exceptions import DAGCircuitError -from .dagcanonical import DAGcanonical +from .dagdependency import DAGDependency diff --git a/qiskit/dagcircuit/dagcanonical.py b/qiskit/dagcircuit/dagcanonical.py deleted file mode 100644 index 293ade7296c6..000000000000 --- a/qiskit/dagcircuit/dagcanonical.py +++ /dev/null @@ -1,400 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -""" -Object to represent a quantum circuit as a directed acyclic graph in the canonical form. - -The nodes in the graph are operation represented by quantum gates. -The edges correspond to non-commutation between two operations. A directed edge -from node A to node B means that operation A does not commute with operation B. -The object's methods allow circuits to be constructed. - -e.g. Bell circuit with no measurement - - ┌───┐ -qr_0: |0>┤ H ├──■── - └───┘┌─┴─┐ -qr_1: |0>─────┤ X ├ - └───┘ - -In the dag canonical form the circuit is representede by two nodes (1 and 2): -the first one corresponds to Hamdamard gate, the second one to the CNot gate -as the gates do not commute there is an edge between the wo noodes. - -The attributes are 'label' 'operation', 'successors', 'predecessors' -In the Bell circuit, the network takes the following form: - -[(1, {'label': 1, 'operation': , - 'successors': [2], 'predecessors': [], 'reachable': False}), -(2, {'label': 2, 'operation': , -'successors': [], 'predecessors': [1]})] - -The reference paper is https://arxiv.org/abs/1909.05270v1 - -""" - -from collections import OrderedDict -import copy -import heapq -import networkx as nx -import numpy as np - -from qiskit.circuit.quantumregister import QuantumRegister -from qiskit.circuit.classicalregister import ClassicalRegister -from qiskit.quantum_info.operators import Operator -from .exceptions import DAGCircuitError -from .dagnode import DAGNode - - -class DAGcanonical: - """ - Object to represent a quantum circuit as a DAG in the canonical form. - """ - def __init__(self): - """ - Create an empty directed acyclis graph (canonical form) - """ - # Circuit name - self.name = None - - # Directed multigraph whose nodes are operations(gates) and edges - # represent non-commutativity between two gates. - self._multi_graph = nx.MultiDiGraph() - - # Map of qreg name to QuantumRegister object - self.qregs = OrderedDict() - - # Map of creg name to ClassicalRegister object - self.cregs = OrderedDict() - # Index of the last node added - self._max_node_id = 0 - - # Intern list of nodes - self._id_to_node = {} - - def to_networkx(self): - """Returns a copy of the DAGCircuit in networkx format.""" - return copy.deepcopy(self._multi_graph) - - def qubits(self): - """Return a list of qubits (as a list of Qubit instances).""" - return [qubit for qreg in self.qregs.values() for qubit in qreg] - - def node_counter(self): - """ Returns the number of nodes in the dag """ - return len(self._multi_graph) - - def add_qreg(self, qreg): - """Add qubits in a quantum register.""" - if not isinstance(qreg, QuantumRegister): - raise DAGCircuitError("not a QuantumRegister instance.") - if qreg.name in self.qregs: - raise DAGCircuitError("duplicate register %s" % qreg.name) - self.qregs[qreg.name] = qreg - - def add_creg(self, creg): - """Add all wires in a classical register.""" - if not isinstance(creg, ClassicalRegister): - raise DAGCircuitError("not a ClassicalRegister instance.") - if creg.name in self.cregs: - raise DAGCircuitError("duplicate register %s" % creg.name) - self.cregs[creg.name] = creg - - def add_node(self, operation, qargs, cargs): - """Add a DAGnode to the graph. - - Args: - operation (Instruction): operation as a quantum gate. - qargs (list[Qubit]): list of qubits on which the operation acts - cargs (list[Clbit]): list of classical wires to attach to. - """ - node_properties = { - "type": "op", - "op": operation, - "name": operation.name, - "qargs": qargs, - "cargs": cargs, - "condition": operation.condition - } - - # Add a new operation node to the graph - self._max_node_id += 1 - new_node = DAGNode(data_dict=node_properties, nid=self._max_node_id) - self._multi_graph.add_node(self._max_node_id, label=self._max_node_id, operation=new_node, - successors=[], predecessors=[]) - self._id_to_node[self._max_node_id] = new_node - - def _gather_pred(self, node_id, direct_pred): - """Function set an attribute predecessors and gather multiple lists - of direct predecessors into a single one. - - Args: - node_id (int): label of the considered node in the DAG - direct_pred (list): list of direct successors for the given node - - Returns: - DAGcanonical: A multigraph with update of the attribute ['predecessors'] - the lists of direct successors are put into a single one - """ - gather = self._multi_graph - gather.nodes[node_id]['predecessors'] = [] - for d_pred in direct_pred: - gather.nodes[node_id]['predecessors'].append([d_pred]) - pred = self._multi_graph.nodes[d_pred]['predecessors'] - gather.nodes[node_id]['predecessors'].append(pred) - return gather - - def _gather_succ(self, node_id, direct_succ): - """ - Function set an attribute successors and gather multiple lists - of direct successors into a single one. - - Args: - node_id (int): label of the considered node in the DAG - direct_succ (lis): list of direct successors for the given node - - Returns: - MultiDiGraph: with update of the attribute ['predecessors'] - the lists of direct successors are put into a single one - """ - gather = self._multi_graph - for d_succ in direct_succ: - gather.nodes[node_id]['successors'].append([d_succ]) - succ = gather.nodes[d_succ]['successors'] - gather.nodes[node_id]['successors'].append(succ) - return gather - - def _list_pred(self, node_id): - """ - Use _gather_pred function and merge_no_duplicates to construct - the list of predecessors for a given node. - - Args: - node_id (int): label of the considered node - """ - direct_pred = sorted(list(self._multi_graph.predecessors(node_id))) - self._multi_graph = self._gather_pred(node_id, direct_pred) - self._multi_graph.nodes[node_id]['predecessors'] = list( - merge_no_duplicates(*(self._multi_graph.nodes[node_id]['predecessors']))) - - def add_edge(self): - """ - Function to verify the commutation relation and reachability - for predecessors, the nodes do not commute and - if the predecessor is reachable. Update the DAGcanonical by - introducing edges and predecessors(attribute) - """ - node = self._id_to_node[self._max_node_id] - max_id = self._max_node_id - for current_node in range(1, max_id): - self._multi_graph.nodes[current_node]['reachable'] = True - # Check the commutation relation with reachable node, it adds edges if it does not commute - for prev_node in range(max_id - 1, 0, -1): - if self._multi_graph.nodes[prev_node]['reachable'] and not _commute( - self._multi_graph.nodes[prev_node]['operation'], node): - self._multi_graph.add_edge(prev_node, max_id) - self._list_pred(max_id) - list_predecessors = self._multi_graph.nodes[max_id]['predecessors'] - for pred in list_predecessors: - self._multi_graph.nodes[pred]['reachable'] = False - - def add_successors(self): - """ - Use _gather_succ and merge_no_duplicates to create the list of successors - for each node. Update DAGcanonical with attributes successors. - """ - for node_id in range(len(self._multi_graph), 0, -1): - - direct_successors = sorted(list(self._multi_graph.successors(node_id))) - - self._multi_graph = self._gather_succ(node_id, direct_successors) - - self._multi_graph.nodes[node_id]['successors'] = list( - merge_no_duplicates(*(self._multi_graph.nodes[node_id]['successors']))) - - def node(self, node_id): - """ - Args: - node_id (int): label of considered node. - - Returns: - Node: corresponding to the label. - """ - return self._multi_graph.nodes[node_id] - - def nodes(self): - """Function to return all nodes - - Yields: - Iterator: generate all nodes (label, DAGnode). - """ - for node in self._multi_graph.nodes(data='operation'): - yield node - - def edges(self): - """Function to yield all edges. - - Yields: - Iterator: generate all edges. - """ - for edge in self._multi_graph.edges(data=True): - yield edge - - def in_edge(self, node_id): - """ Get the list of incoming nodes for a given node_id. - - Args: - node_id (int): id of the corresponding node - - Returns: - list[In_edges()]: List of all incoming edges. - """ - return self._multi_graph.in_edges(node_id) - - def out_edge(self, node_id): - """List of all outgoing edges for the given node id. - - Args: - node_id (int): id of the corresponding node. - - Returns: - list[out_edges()]: List of all incoming edges. - """ - return self._multi_graph.out_edges(node_id) - - def direct_successors(self, node_id): - """Returns label of direct successors of a node as sorted list """ - return sorted(list(self.to_networkx().successors(node_id))) - - def direct_predecessors(self, node_id): - """Returns label of direct predecessors of a node as sorted list """ - return sorted(list(self.to_networkx().predecessors(node_id))) - - def successors(self, node_id): - """Returns set of the ancestors of a node as DAGNodes.""" - return self.to_networkx().nodes[node_id]['successors'] - - def predecessors(self, node_id): - """Returns set of the descendants of a node as DAGNodes.""" - return self.to_networkx().nodes[node_id]['predecessors'] - - def draw(self, scale=0.7, filename=None, style='color', category='canonical'): - """ - Draws the DAG canonical circuit. - - This function needs `pydot `, which in turn needs - Graphviz ` to be installed. - - Args: - scale (float): scaling factor - filename (str): file path to save image to (format inferred from name) - style (str): 'plain': B&W graph - 'color' (default): color input/output/op nodes - category(str): 'canonical' Other type of DAG - - Returns: - Ipython.display.Image: if in Jupyter notebook and not saving to file, - otherwise None. - """ - from qiskit.visualization.dag_visualization import dag_drawer - return dag_drawer(dag=self, scale=scale, filename=filename, style=style, category=category) - - -def merge_no_duplicates(*iterables): - """Merge K list without duplicate using python heapq ordered merging - - Args: - *iterables: A list of k sorted lists - - Yields: - Iteraor: List from the merging of the k ones (without duplicates - """ - last = object() - for val in heapq.merge(*iterables): - if val != last: - last = val - yield val - - -def _commute(node1, node2): - """Function to verify commutation relation between two nodes in the DAG - - Args: - node1 (DAGnode): first node operation (attribute ['operation'] in the DAG) - node2 (DAGnode): second node operation - - Return: - bool: True if the gates commute and false if it is not the case. - """ - - # Create set of qubits on which the operation acts - qarg1 = [node1.qargs[i].index for i in range(0, len(node1.qargs))] - qarg2 = [node2.qargs[i].index for i in range(0, len(node2.qargs))] - - # Create set of cbits on which the operation acts - carg1 = [node1.qargs[i].index for i in range(0, len(node1.cargs))] - carg2 = [node2.qargs[i].index for i in range(0, len(node2.cargs))] - - # Commutation for classical conditional gates - if node1.condition or node2.condition: - intersection = set(qarg1).intersection(set(qarg2)) - if intersection or carg1 or carg2: - commute_condition = False - else: - commute_condition = True - return commute_condition - - # Commutation for measurement - if node1.name == 'measure' or node2.name == 'measure': - intersection = set(qarg1).intersection(set(qarg2)) - if intersection: - commute_measurement = False - else: - commute_measurement = True - return commute_measurement - - # Commutation for barrier - if node1.name == 'barrier' or node2.name == 'barrier': - intersection = set(qarg1).intersection(set(qarg2)) - if intersection: - commute_barrier = False - else: - commute_barrier = True - return commute_barrier - - # Commutation for snapshot - if node1.name == 'snapshot' or node2.name == 'snapshot': - return False - - # List of non commuting gates (TO DO: add more elements) - non_commute_list = [set(['x', 'y']), set(['x', 'z'])] - - if qarg1 == qarg2 and (set([node1.name, node2.name]) in non_commute_list): - return False - - # Create matrices to check commutation relation if no other criteria are matched - qarg = list(set(node1.qargs + node2.qargs)) - qbit_num = len(qarg) - - qarg1 = [qarg.index(q) for q in node1.qargs] - qarg2 = [qarg.index(q) for q in node2.qargs] - - id_op = Operator(np.eye(2 ** qbit_num)) - - op12 = id_op.compose(node1.op, qargs=qarg1).compose(node2.op, qargs=qarg2) - op21 = id_op.compose(node2.op, qargs=qarg2).compose(node1.op, qargs=qarg1) - - if_commute = (op12 == op21) - - return if_commute diff --git a/qiskit/dagcircuit/dagdependency.py b/qiskit/dagcircuit/dagdependency.py new file mode 100644 index 000000000000..9ceed36d2a77 --- /dev/null +++ b/qiskit/dagcircuit/dagdependency.py @@ -0,0 +1,505 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" +Object to represent a quantum circuit as a directed acyclic graph +in the dependency form (commutation). + +The nodes in the graph are operation represented by quantum gates. +The edges correspond to non-commutation between two operations +(i.e. a dependency). A directed edge from node A to node B means that +operation A does not commute with operation B. +The object's methods allow circuits to be constructed. + +e.g. Bell circuit with no measurement + + ┌───┐ +qr_0: |0>┤ H ├──■── + └───┘┌─┴─┐ +qr_1: |0>─────┤ X ├ + └───┘ + +In the dag dependency form the circuit is represented by two nodes (1 and 2): +the first one corresponds to Hamdamard gate, the second one to the CNot gate +as the gates do not commute there is an edge between the wo noodes. + +The attributes are 'operation', 'successors', 'predecessors' +In the Bell circuit, the retwork PyDAG takes the following form: + +[{'operation': , + 'successors': [2], 'predecessors': []}), + { 'operation': , +'successors': [], 'predecessors': [1]}] + +The reference paper is https://arxiv.org/abs/1909.05270v1 + +""" + +import heapq +from collections import OrderedDict +import networkx as nx +import retworkx as rx +import numpy as np + +from qiskit.circuit.quantumregister import QuantumRegister +from qiskit.circuit.classicalregister import ClassicalRegister +from qiskit.dagcircuit.exceptions import DAGDependencyError +from qiskit.dagcircuit.dagnode import DAGNode +from qiskit.quantum_info.operators import Operator + + +class DAGDependency: + """ + Object to represent a quantum circuit as a DAG in the dependency form. + """ + + def __init__(self): + """ + Create an empty directed acyclis graph (dependency form) + """ + # Circuit name + self.name = None + + # Directed multigraph whose nodes are operations(gates) and edges + # represent non-commutativity between two gates. + self._multi_graph = rx.PyDAG() + + # Map of qreg name to QuantumRegister object + self.qregs = OrderedDict() + + # Map of creg name to ClassicalRegister object + self.cregs = OrderedDict() + + def to_networkx(self): + """Returns a copy of the DAGDependency in networkx format.""" + # For backwards compatibility, return networkx structure from terra 0.12 + # where DAGNodes instances are used as indexes on the networkx graph. + + dag_networkx = nx.MultiDiGraph() + + for node in self.get_nodes(): + dag_networkx.add_node(node['operation']._node_id, operation=node['operation'], + successors=node['successors'], predecessors=node['predecessors']) + for node in self.topological_nodes(): + for source_id, dest_id, edge in \ + self.get_in_edges(node['operation']._node_id): + dag_networkx.add_edge(source_id, dest_id, **edge) + return dag_networkx + + def to_retworkx(self): + """ Returns the DAGDependency in retworkx format.""" + return self._multi_graph + + def qubits(self): + """Return a list of qubits (as a list of Qubit instances).""" + return [qubit for qreg in self.qregs.values() for qubit in qreg] + + def clbits(self): + """Return a list of classical bits (as a list of Clbit instances).""" + return [clbit for creg in self.cregs.values() for clbit in creg] + + def size(self): + """ Returns the number of gates in the circuit""" + return len(self._multi_graph) + + def depth(self): + """Return the circuit depth. + Returns: + int: the circuit depth + """ + depth = rx.dag_longest_path_length(self._multi_graph) + return depth if depth >= 0 else 0 + + def add_qreg(self, qreg): + """Add qubits in a quantum register.""" + if not isinstance(qreg, QuantumRegister): + raise DAGDependencyError("not a QuantumRegister instance.") + if qreg.name in self.qregs: + raise DAGDependencyError("duplicate register %s" % qreg.name) + self.qregs[qreg.name] = qreg + + def add_creg(self, creg): + """Add all wires in a classical register.""" + if not isinstance(creg, ClassicalRegister): + raise DAGDependencyError("not a ClassicalRegister instance.") + if creg.name in self.cregs: + raise DAGDependencyError("duplicate register %s" % creg.name) + self.cregs[creg.name] = creg + + def _add_multi_graph_node(self, node): + """ + Args: + node (DAGNode): considered node. + + Returns: + node_id(int): corresponding label to the added node. + """ + node_id = self._multi_graph.add_node(node) + node['operation']._node_id = node_id + return node_id + + def get_nodes(self): + """ + Returns: + generator(dict): iterator over all the nodes. + """ + return iter(self._multi_graph.nodes()) + + def get_node(self, node_id): + """ + Args: + node_id (int): label of considered node. + + Returns: + node: corresponding to the label. + """ + return self._multi_graph.get_node_data(node_id) + + def _add_multi_graph_edge(self, src_id, dest_id, data): + """ + Function to add an edge from given data (dict) between two nodes. + + Args: + src_id (int): label of the first node. + dest_id (int): label of the second node. + data (dict): data contained on the edge. + + """ + self._multi_graph.add_edge(src_id, dest_id, data) + + def get_edges(self, src_id, dest_id): + """ + Edge enumeration between two nodes through method get_all_edge_data. + + Args: + src_id (int): label of the first node. + dest_id (int): label of the second node. + + Returns: + List: corresponding to all edges between the two nodes. + """ + return self._multi_graph.get_all_edge_data(src_id, dest_id) + + def get_all_edges(self): + """ + Enumaration of all edges. + + Returns: + List: corresponding to the label. + """ + + return [(src, dest, data) + for src_node in self._multi_graph.nodes() + for (src, dest, data) + in self._multi_graph.out_edges(src_node['operation']._node_id)] + + def get_in_edges(self, node_id): + """ + Enumeration of all incoming edges for a given node. + + Args: + node_id (int): label of considered node. + + Returns: + List: corresponding incoming edges data. + """ + return self._multi_graph.in_edges(node_id) + + def get_out_edges(self, node_id): + """ + Enumeration of all outgoing edges for a given node. + + Args: + node_id (int): label of considered node. + + Returns: + List: corresponding outgoing edges data. + """ + return self._multi_graph.out_edges(node_id) + + def direct_successors(self, node_id): + """ + Direct successors id of a given node as sorted list. + + Args: + node_id (int): label of considered node. + + Returns: + List: direct successors id as a sorted list + """ + return sorted(list(self._multi_graph.adj_direction(node_id, False).keys())) + + def direct_predecessors(self, node_id): + """ + Direct predecessors id of a given node as sorted list. + + Args: + node_id (int): label of considered node. + + Returns: + List: direct predecessors id as a sorted list + """ + return sorted(list(self._multi_graph.adj_direction(node_id, True).keys())) + + def successors(self, node_id): + """ + Successors id of a given node as sorted list. + + Args: + node_id (int): label of considered node. + + Returns: + List: all successors id as a sorted list + """ + return self._multi_graph.get_node_data(node_id)['successors'] + + def predecessors(self, node_id): + """ + Predecessors id of a given node as sorted list. + + Args: + node_id (int): label of considered node. + + Returns: + List: all predecessors id as a sorted list + """ + return self._multi_graph.get_node_data(node_id)['predecessors'] + + def topological_nodes(self): + """ + Yield nodes in topological order. + + Returns: + generator(DAGNode): node in topological order. + """ + + def _key(x): + return x['operation'].sort_key + + return iter(rx.lexicographical_topological_sort( + self._multi_graph, + key=_key)) + + def add_op_node(self, operation, qargs, cargs): + """Add a DAGnode to the graph. + + Args: + operation (Instruction): operation as a quantum gate. + qargs (list[Qubit]): list of qubits on which the operation acts + cargs (list[Clbit]): list of classical wires to attach to. + """ + new_node = DAGNode(type="op", op=operation, name=operation.name, qargs=qargs, + cargs=cargs, condition=operation.condition) + operation_node = {'operation': new_node, 'successors': [], 'predecessors': []} + self._add_multi_graph_node(operation_node) + + def _gather_pred(self, node_id, direct_pred): + """Function set an attribute predecessors and gather multiple lists + of direct predecessors into a single one. + + Args: + node_id (int): label of the considered node in the DAG + direct_pred (list): list of direct successors for the given node + + Returns: + DAGDependency: A multigraph with update of the attribute ['predecessors'] + the lists of direct successors are put into a single one + """ + gather = self._multi_graph + gather.get_node_data(node_id)['predecessors'] = [] + for d_pred in direct_pred: + gather.get_node_data(node_id)['predecessors'].append([d_pred]) + pred = self._multi_graph.get_node_data(d_pred)['predecessors'] + gather.get_node_data(node_id)['predecessors'].append(pred) + return gather + + def _gather_succ(self, node_id, direct_succ): + """ + Function set an attribute successors and gather multiple lists + of direct successors into a single one. + + Args: + node_id (int): label of the considered node in the DAG + direct_succ (list): list of direct successors for the given node + + Returns: + MultiDiGraph: with update of the attribute ['predecessors'] + the lists of direct successors are put into a single one + """ + gather = self._multi_graph + for d_succ in direct_succ: + gather.get_node_data(node_id)['successors'].append([d_succ]) + succ = gather.get_node_data(d_succ)['successors'] + gather.get_node_data(node_id)['successors'].append(succ) + return gather + + def _list_pred(self, node_id): + """ + Use _gather_pred function and merge_no_duplicates to construct + the list of predecessors for a given node. + + Args: + node_id (int): label of the considered node + """ + direct_pred = self.direct_predecessors(node_id) + self._multi_graph = self._gather_pred(node_id, direct_pred) + self._multi_graph.get_node_data(node_id)['predecessors'] = list( + merge_no_duplicates(*(self._multi_graph.get_node_data(node_id)['predecessors']))) + + def add_edge(self): + """ + Function to verify the commutation relation and reachability + for predecessors, the nodes do not commute and + if the predecessor is reachable. Update the DAGcanonical by + introducing edges and predecessors(attribute) + """ + max_node_id = len(self._multi_graph) - 1 + max_node = self._multi_graph.get_node_data(max_node_id)['operation'] + + for current_node_id in range(0, max_node_id): + self._multi_graph.get_node_data(current_node_id)['reachable'] = True + # Check the commutation relation with reachable node, it adds edges if it does not commute + for prev_node_id in range(max_node_id - 1, -1, -1): + if self._multi_graph.get_node_data(prev_node_id)['reachable'] and not _commute( + self._multi_graph.get_node_data(prev_node_id)['operation'], max_node): + self._multi_graph.add_edge(prev_node_id, max_node_id, {'commute': False}) + self._list_pred(max_node_id) + list_predecessors = self._multi_graph.get_node_data(max_node_id)['predecessors'] + for pred_id in list_predecessors: + self._multi_graph.get_node_data(pred_id)['reachable'] = False + + def add_successors(self): + """ + Use _gather_succ and merge_no_duplicates to create the list of successors + for each node. Update DAGcanonical with attributes successors. + """ + for node_id in range(len(self._multi_graph)-1, 0, -1): + + direct_successors = self.direct_successors(node_id) + + self._multi_graph = self._gather_succ(node_id, direct_successors) + + self._multi_graph.get_node_data(node_id)['successors'] = list( + merge_no_duplicates(*(self._multi_graph.get_node_data(node_id)['successors']))) + + def draw(self, scale=0.7, filename=None, style='color', category='dependency'): + """ + Draws the DAG canonical circuit. + + This function needs `pydot `, which in turn needs + Graphviz ` to be installed. + + Args: + scale (float): scaling factor + filename (str): file path to save image to (format inferred from name) + style (str): 'plain': B&W graph + 'color' (default): color input/output/op nodes + category(str): 'canonical' Other type of DAG + + Returns: + Ipython.display.Image: if in Jupyter notebook and not saving to file, + otherwise None. + """ + from qiskit.visualization.dag_visualization import dag_drawer + return dag_drawer(dag=self, scale=scale, filename=filename, style=style, category=category) + + +def merge_no_duplicates(*iterables): + """Merge K list without duplicate using python heapq ordered merging + + Args: + *iterables: A list of k sorted lists + + Yields: + Iterator: List from the merging of the k ones (without duplicates + """ + last = object() + for val in heapq.merge(*iterables): + if val != last: + last = val + yield val + + +def _commute(node1, node2): + """Function to verify commutation relation between two nodes in the DAG + + Args: + node1 (DAGnode): first node operation (attribute ['operation'] in the DAG) + node2 (DAGnode): second node operation + + Return: + bool: True if the gates commute and false if it is not the case. + """ + + # Create set of qubits on which the operation acts + qarg1 = [node1.qargs[i].index for i in range(0, len(node1.qargs))] + qarg2 = [node2.qargs[i].index for i in range(0, len(node2.qargs))] + + # Create set of cbits on which the operation acts + carg1 = [node1.qargs[i].index for i in range(0, len(node1.cargs))] + carg2 = [node2.qargs[i].index for i in range(0, len(node2.cargs))] + + # Commutation for classical conditional gates + if node1.condition or node2.condition: + intersection = set(qarg1).intersection(set(qarg2)) + if intersection or carg1 or carg2: + commute_condition = False + else: + commute_condition = True + return commute_condition + + # Commutation for measurement + if node1.name == 'measure' or node2.name == 'measure': + intersection_q = set(qarg1).intersection(set(qarg2)) + intersection_c = set(carg1).intersection(set(carg2)) + if intersection_q or intersection_c: + commute_measurement = False + else: + commute_measurement = True + return commute_measurement + + # Commutation for barrier + if node1.name == 'barrier' or node2.name == 'barrier': + intersection = set(qarg1).intersection(set(qarg2)) + if intersection: + commute_barrier = False + else: + commute_barrier = True + return commute_barrier + + # Commutation for snapshot + if node1.name == 'snapshot' or node2.name == 'snapshot': + return False + + # List of non commuting gates (TO DO: add more elements) + non_commute_list = [set(['x', 'y']), set(['x', 'z'])] + + if qarg1 == qarg2 and (set([node1.name, node2.name]) in non_commute_list): + return False + + # Create matrices to check commutation relation if no other criteria are matched + qarg = list(set(node1.qargs + node2.qargs)) + qbit_num = len(qarg) + + qarg1 = [qarg.index(q) for q in node1.qargs] + qarg2 = [qarg.index(q) for q in node2.qargs] + + id_op = Operator(np.eye(2 ** qbit_num)) + + op12 = id_op.compose(node1.op, qargs=qarg1).compose(node2.op, qargs=qarg2) + op21 = id_op.compose(node2.op, qargs=qarg2).compose(node1.op, qargs=qarg1) + + if_commute = (op12 == op21) + + return if_commute diff --git a/qiskit/dagcircuit/exceptions.py b/qiskit/dagcircuit/exceptions.py index 0d2c2cb04cc7..9bcd97e89018 100644 --- a/qiskit/dagcircuit/exceptions.py +++ b/qiskit/dagcircuit/exceptions.py @@ -29,3 +29,16 @@ def __init__(self, *msg): def __str__(self): """Return the message.""" return repr(self.msg) + + +class DAGDependencyError(QiskitError): + """Base class for errors raised by the DAGDependency object.""" + + def __init__(self, *msg): + """Set the error message.""" + super().__init__(*msg) + self.msg = ' '.join(msg) + + def __str__(self): + """Return the message.""" + return repr(self.msg) diff --git a/qiskit/visualization/dag_visualization.py b/qiskit/visualization/dag_visualization.py index 4b0b789371d7..3d8892942008 100644 --- a/qiskit/visualization/dag_visualization.py +++ b/qiskit/visualization/dag_visualization.py @@ -2,7 +2,7 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2017, 2018. +# (C) Copyright IBM 2017, 2018, 2020. # # This code is licensed under the Apache License, Version 2.0. You may # obtain a copy of this license in the LICENSE.txt file in the root directory @@ -52,7 +52,7 @@ def dag_drawer(dag, scale=0.7, filename=None, style='color', category=None): filename (str): file path to save image to (format inferred from name) style (str): 'plain': B&W graph 'color' (default): color input/output/op nodes - category (str): 'canonical' for drawing DAG canonical + category (str): 'dependency' for drawing DAG dependency Returns: PIL.Image: if in Jupyter notebook and not saving to file, @@ -114,7 +114,7 @@ def dag_drawer(dag, scale=0.7, filename=None, style='color', category=None): else: raise VisualizationError("Unrecognized style for the dag_drawer.") - elif category == 'canonical': + elif category == 'dependency': G = dag.to_networkx() G.graph['dpi'] = 100 * scale @@ -123,7 +123,7 @@ def dag_drawer(dag, scale=0.7, filename=None, style='color', category=None): elif style == 'color': for node in G.nodes(data='operation'): n = G.nodes[node[0]] - n['label'] = str(node[0]) + ': ' + node[1].name + n['label'] = str(node[0]+1) + ': ' + node[1].name if node[1].name == 'measure': n['color'] = 'blue' n['style'] = 'filled' diff --git a/releasenotes/notes/Add-canonical-form-47e0466ed57640f3.yaml b/releasenotes/notes/Add-canonical-form-47e0466ed57640f3.yaml index 8460f0ffb8e1..8f209440267b 100644 --- a/releasenotes/notes/Add-canonical-form-47e0466ed57640f3.yaml +++ b/releasenotes/notes/Add-canonical-form-47e0466ed57640f3.yaml @@ -1,44 +1,44 @@ --- features: - | - Added a new DAG to qiskit; the canonical form. In this DAG, the node are + Added a new DAG to qiskit; the dependency form. In this DAG, the node are operation (gates, measure, barrier, etc...). The edges corresponds to non-commutation between two operations. - The ''qiskit.dagcircuit.dagcanonical'' introduces the DAGcanonical class. - It allows to create an empty DAG canonical. For exmaple: + The ''qiskit.dagcircuit.dagdependency'' introduces the DAGDependency class. + It allows to create an empty DAG dependency. For exmaple: - from qiskit.dagcircuit.dagcanonical import DAGcanonical + from qiskit.dagcircuit.dependency import DAGDependency - canonical=DAGcanonical() + dag_dependency = DAGDependency() - | - The ''qiskit.converters.circuit_to_dagcanonical'' introduces a converter - from a QuantumCircuit() object to a DAGcanonical() object. - The ''qiskit.converters.dagconical_to_ciruit'' introduces a converter - from a DAGcanonical() object to a QuantumCircuit() object. - The ''qiskit.converters.dag_to_dagcanonical'' introduces a converter - from a DAGCircuit() object to a DAGcanonical() object. - The ''qiskit.converters.dagconical_to_ciruit'' introduces a converter - from a DAGcanonical() object to a DAGCircuit() object. + The ''qiskit.converters.circuit_to_dagdependency'' introduces a converter + from a QuantumCircuit() object to a DAGDependency() object. + The ''qiskit.converters.dagdependency_to_ciruit'' introduces a converter + from a DAGDependency() object to a QuantumCircuit() object. + The ''qiskit.converters.dag_to_dagdepency'' introduces a converter + from a DAGCircuit() object to a DAGDependency() object. + The ''qiskit.converters.dagdependency_to_ciruit'' introduces a converter + from a DAGDependcy() object to a DAGCircuit() object. - For exmaple: + For example: - from qiskit.converters.dagcanonical_to_circuit import dagcanonical_to_circuit + from qiskit.converters.dagdependency_to_circuit import dagdependency_to_circuit from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit circuit_in = QuantumCircuit(2) circuit_in.h(qr[0]) circuit_in.h(qr[1]) - dag_canonical = circuit_to_dagcanonical(circuit_in) - circuit_out = dagcanonical_to_circuit(dag_canonical) + dag_dependency = circuit_to_dagdependency(circuit_in) + circuit_out = dagdepency_to_circuit(dag_dependency) - | The ''qiskit/visualization/dag_visualization'' has been updated such that - it can draw DAG canonical forms. It takes the new argument 'type'. + it can draw DAG dependency forms. It takes the new argument 'type'. issues: - | - In ''qiskit.dagcircuit.dagcanonical'' the function commute could be - improved. (Too many returns) + In ''qiskit.dagcircuit.dagdependency' the function commute could be + improved. diff --git a/test/python/converters/test_circuit_to_dag_canonical.py b/test/python/converters/test_circuit_to_dagdependency.py similarity index 71% rename from test/python/converters/test_circuit_to_dag_canonical.py rename to test/python/converters/test_circuit_to_dagdependency.py index 8e93a272e15c..16b07fd844dd 100644 --- a/test/python/converters/test_circuit_to_dag_canonical.py +++ b/test/python/converters/test_circuit_to_dagdependency.py @@ -2,7 +2,7 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2017, 2018. +# (C) Copyright IBM 2020. # # This code is licensed under the Apache License, Version 2.0. You may # obtain a copy of this license in the LICENSE.txt file in the root directory @@ -12,21 +12,22 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""Test for the converter dag canonical to ciruit and circuit to dag canonical.""" +"""Test for the converter dag dependency to circuit and circuit to dag +dependency.""" import unittest -from qiskit.converters.dagcanonical_to_circuit import dagcanonical_to_circuit -from qiskit.converters.circuit_to_dagcanonical import circuit_to_dagcanonical +from qiskit.converters.dagdependency_to_circuit import dagdependency_to_circuit +from qiskit.converters.circuit_to_dagdependency import circuit_to_dagdependency from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit from qiskit.test import QiskitTestCase class TestCircuitToDagCanonical(QiskitTestCase): - """Test Circuit to DAGcanonical.""" + """Test QuantumCircuit to DAGDependency.""" def test_circuit_and_dag_canonical(self): - """Check convert to dag canonical and back""" + """Check convert to dag dependency and back""" qr = QuantumRegister(3) cr = ClassicalRegister(3) circuit_in = QuantumCircuit(qr, cr) @@ -38,8 +39,8 @@ def test_circuit_and_dag_canonical(self): circuit_in.measure(qr[0], cr[0]) circuit_in.measure(qr[1], cr[1]) circuit_in.measure(qr[2], cr[2]) - dag_canonical = circuit_to_dagcanonical(circuit_in) - circuit_out = dagcanonical_to_circuit(dag_canonical) + dag_dependency = circuit_to_dagdependency(circuit_in) + circuit_out = dagdependency_to_circuit(dag_dependency) self.assertEqual(circuit_out, circuit_in) diff --git a/test/python/converters/test_dag_to_dag_canonical.py b/test/python/converters/test_dag_to_dagdependency.py similarity index 68% rename from test/python/converters/test_dag_to_dag_canonical.py rename to test/python/converters/test_dag_to_dagdependency.py index 20c0c6f83946..5e48e11824c1 100644 --- a/test/python/converters/test_dag_to_dag_canonical.py +++ b/test/python/converters/test_dag_to_dagdependency.py @@ -2,7 +2,7 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2017, 2018. +# (C) Copyright IBM 2020. # # This code is licensed under the Apache License, Version 2.0. You may # obtain a copy of this license in the LICENSE.txt file in the root directory @@ -12,22 +12,23 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""Test for the converter dag canonical to dag and dag to dag canonical.""" +"""Test for the converter dag dependency to dag circuit and +dag circuit to dag dependency.""" import unittest from qiskit.converters.circuit_to_dag import circuit_to_dag -from qiskit.converters.dag_to_dagcanonical import dag_to_dagcanonical -from qiskit.converters.dagcanonical_to_dag import dagcanonical_to_dag +from qiskit.converters.dag_to_dagdependency import dag_to_dagdependency +from qiskit.converters.dagdependency_to_dag import dagdependency_to_dag from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit from qiskit.test import QiskitTestCase -class TestCircuitToDagCanonical(QiskitTestCase): - """Test DAG to DAGcanonical.""" +class TestCircuitToDagDependency(QiskitTestCase): + """Test DAGCircuit to DAGDependency.""" - def test_circuit_and_dag_canonical(self): - """Check convert to dag canonical and back""" + def test_circuit_and_dag_dependency(self): + """Check convert to dag dependency and back""" qr = QuantumRegister(3) cr = ClassicalRegister(3) circuit_in = QuantumCircuit(qr, cr) @@ -41,8 +42,8 @@ def test_circuit_and_dag_canonical(self): circuit_in.measure(qr[2], cr[2]) dag_in = circuit_to_dag(circuit_in) - dag_canonical = dag_to_dagcanonical(dag_in) - dag_out = dagcanonical_to_dag(dag_canonical) + dag_dependency = dag_to_dagdependency(dag_in) + dag_out = dagdependency_to_dag(dag_dependency) self.assertEqual(dag_out, dag_in) diff --git a/test/python/dagcircuit/test_dagdependency.py b/test/python/dagcircuit/test_dagdependency.py new file mode 100644 index 000000000000..f87bb1ad8c47 --- /dev/null +++ b/test/python/dagcircuit/test_dagdependency.py @@ -0,0 +1,249 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Test for the DAGDependency object""" + +import unittest + +from qiskit.dagcircuit import DAGDependency +from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit +from qiskit.circuit import Measure +from qiskit.circuit import Instruction +from qiskit.extensions.standard.h import HGate +from qiskit.dagcircuit.exceptions import DAGDependencyError +from qiskit.converters import circuit_to_dagdependency +from qiskit.test import QiskitTestCase + +try: + import retworkx as rx +except ImportError: + pass + + +def raise_if_dagdependency_invalid(dag): + """Validates the internal consistency of a DAGDependency._multi_graph. + Intended for use in testing. + + Raises: + DAGDependencyError: if DAGDependency._multi_graph is inconsistent. + """ + + multi_graph = dag._multi_graph + + if not rx.is_directed_acyclic_graph(multi_graph): + raise DAGDependencyError('multi_graph is not a DAG.') + + # Every node should be of type op. + for node in dag.get_nodes(): + if node.type != 'op': + raise DAGDependencyError('Found node of unexpected type: {}'.format( + node.type)) + + +class TestDagRegisters(QiskitTestCase): + """Test qreg and creg inside the dag""" + + def test_add_qreg_creg(self): + """add_qreg() and add_creg() methods""" + dag = DAGDependency() + dag.add_qreg(QuantumRegister(2, 'qr')) + dag.add_creg(ClassicalRegister(1, 'cr')) + self.assertDictEqual(dag.qregs, {'qr': QuantumRegister(2, 'qr')}) + self.assertDictEqual(dag.cregs, {'cr': ClassicalRegister(1, 'cr')}) + + def test_dag_get_qubits(self): + """get_qubits() method """ + dag = DAGDependency() + dag.add_qreg(QuantumRegister(1, 'qr1')) + dag.add_qreg(QuantumRegister(1, 'qr10')) + dag.add_qreg(QuantumRegister(1, 'qr0')) + dag.add_qreg(QuantumRegister(1, 'qr3')) + dag.add_qreg(QuantumRegister(1, 'qr4')) + dag.add_qreg(QuantumRegister(1, 'qr6')) + self.assertListEqual(dag.qubits(), [QuantumRegister(1, 'qr1')[0], + QuantumRegister(1, 'qr10')[0], + QuantumRegister(1, 'qr0')[0], + QuantumRegister(1, 'qr3')[0], + QuantumRegister(1, 'qr4')[0], + QuantumRegister(1, 'qr6')[0]]) + + def test_add_reg_duplicate(self): + """add_qreg with the same register twice is not allowed.""" + dag = DAGDependency() + qr = QuantumRegister(2) + dag.add_qreg(qr) + self.assertRaises(DAGDependencyError, dag.add_qreg, qr) + + def test_add_reg_duplicate_name(self): + """Adding quantum registers with the same name is not allowed.""" + dag = DAGDependency() + qr1 = QuantumRegister(3, 'qr') + dag.add_qreg(qr1) + qr2 = QuantumRegister(2, 'qr') + self.assertRaises(DAGDependencyError, dag.add_qreg, qr2) + + def test_add_reg_bad_type(self): + """add_qreg with a classical register is not allowed.""" + dag = DAGDependency() + cr = ClassicalRegister(2) + self.assertRaises(DAGDependencyError, dag.add_qreg, cr) + + +class TestDagNodeEdge(QiskitTestCase): + """Test adding nodes and edges to a dag and related functions.""" + + def setUp(self): + self.dag = DAGDependency() + self.qreg = QuantumRegister(2, 'qr') + self.creg = ClassicalRegister(2, 'cr') + + self.dag.add_qreg(self.qreg) + self.dag.add_creg(self.creg) + + def test_node(self): + """Test the methods add_op_node(), get_node() and get_nodes()""" + circuit = QuantumCircuit(self.qreg, self.creg) + + circuit.h(self.qreg[0]) + self.dag.add_op_node(circuit.data[0][0], circuit.data[0][1], circuit.data[0][2]) + self.assertIsInstance(self.dag.get_node(0)['operation'].op, HGate) + + circuit.measure(self.qreg[0], self.creg[0]) + self.dag.add_op_node(circuit.data[1][0], circuit.data[1][1], circuit.data[1][2]) + self.assertIsInstance(self.dag.get_node(1)['operation'].op, Measure) + + nodes = list(self.dag.get_nodes()) + self.assertEqual(len(list(nodes)), 2) + + for node in nodes: + self.assertIsInstance(node['operation'].op, Instruction) + + node_1 = nodes.pop() + node_2 = nodes.pop() + + self.assertIsInstance(node_1['operation'].op, Measure) + self.assertIsInstance(node_2['operation'].op, HGate) + + def test_add_edge(self): + """Test that add_edge(), get_edges(), get_all_edges(), + get_in_edges() and get_out_edges(). """ + circuit = QuantumCircuit(self.qreg, self.creg) + circuit.h(self.qreg[0]) + circuit.x(self.qreg[1]) + circuit.cnot(self.qreg[1], self.qreg[0]) + circuit.measure(self.qreg[0], self.creg[0]) + + self.dag = circuit_to_dagdependency(circuit) + + second_edge = self.dag.get_edges(1, 2) + self.assertEqual(second_edge[0]['commute'], False) + + all_edges = self.dag.get_all_edges() + self.assertEqual(len(all_edges), 3) + + for edges in all_edges: + self.assertEqual(edges[2]['commute'], False) + + in_edges = self.dag.get_in_edges(2) + self.assertEqual(len(list(in_edges)), 2) + + out_edges = self.dag.get_out_edges(2) + self.assertEqual(len(list(out_edges)), 1) + + +class TestDagNodeSelection(QiskitTestCase): + """Test methods that select successors and predecessors""" + + def setUp(self): + self.dag = DAGDependency() + self.qreg = QuantumRegister(2, 'qr') + self.creg = ClassicalRegister(2, 'cr') + self.dag.add_qreg(self.qreg) + self.dag.add_creg(self.creg) + + def test_successors_predecessors(self): + """Test the method direct_successors.""" + + circuit = QuantumCircuit(self.qreg, self.creg) + circuit.h(self.qreg[0]) + circuit.x(self.qreg[0]) + circuit.h(self.qreg[0]) + circuit.x(self.qreg[1]) + circuit.h(self.qreg[0]) + circuit.measure(self.qreg[0], self.creg[0]) + + self.dag = circuit_to_dagdependency(circuit) + + dir_successors_second = self.dag.direct_successors(1) + self.assertEqual(dir_successors_second, [2, 4]) + + dir_successors_fourth = self.dag.direct_successors(3) + self.assertEqual(dir_successors_fourth, []) + + successors_second = self.dag.successors(1) + self.assertEqual(successors_second, [2, 4, 5]) + + successors_fourth = self.dag.successors(3) + self.assertEqual(successors_fourth, []) + + dir_predecessors_sixth = self.dag.direct_predecessors(5) + self.assertEqual(dir_predecessors_sixth, [2, 4]) + + dir_predecessors_fourth = self.dag.direct_predecessors(3) + self.assertEqual(dir_predecessors_fourth, []) + + predecessors_sixth = self.dag.predecessors(5) + self.assertEqual(predecessors_sixth, [0, 1, 2, 4]) + + predecessors_fourth = self.dag.predecessors(3) + self.assertEqual(predecessors_fourth, []) + + +class TestDagProperties(QiskitTestCase): + """Test the DAG properties. + """ + + def setUp(self): + qr1 = QuantumRegister(4) + qr2 = QuantumRegister(2) + circ = QuantumCircuit(qr1, qr2) + circ.h(qr1[0]) + circ.cx(qr1[2], qr1[3]) + circ.h(qr1[2]) + circ.t(qr1[2]) + circ.ch(qr1[2], qr1[1]) + circ.u2(0.1, 0.2, qr1[3]) + circ.ccx(qr2[0], qr2[1], qr1[0]) + + self.dag = circuit_to_dagdependency(circ) + + def test_size(self): + """Test total number of operations in dag.""" + self.assertEqual(self.dag.size(), 7) + + def test_dag_depth(self): + """Test dag depth.""" + self.assertEqual(self.dag.depth(), 2) + + def test_dag_depth_empty(self): + """Empty circuit DAG is zero depth + """ + q = QuantumRegister(5, 'q') + qc = QuantumCircuit(q) + dag = circuit_to_dagdependency(qc) + self.assertEqual(dag.depth(), 0) + + +if __name__ == '__main__': + unittest.main() From 2eeb76f3e7fde63c9ef1da4350ff4dfec241683c Mon Sep 17 00:00:00 2001 From: romain Date: Thu, 30 Apr 2020 07:53:04 +0200 Subject: [PATCH 15/27] Delete wrong file --- qiskit/dagcircuit/DAGdependency.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 qiskit/dagcircuit/DAGdependency.py diff --git a/qiskit/dagcircuit/DAGdependency.py b/qiskit/dagcircuit/DAGdependency.py deleted file mode 100644 index e69de29bb2d1..000000000000 From 345dafb739e26adfc678d4188b8912bc0b69ff42 Mon Sep 17 00:00:00 2001 From: romain Date: Thu, 30 Apr 2020 08:15:41 +0200 Subject: [PATCH 16/27] Correction test --- test/python/dagcircuit/test_dagdependency.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/python/dagcircuit/test_dagdependency.py b/test/python/dagcircuit/test_dagdependency.py index f87bb1ad8c47..8ff00fd598a2 100644 --- a/test/python/dagcircuit/test_dagdependency.py +++ b/test/python/dagcircuit/test_dagdependency.py @@ -141,7 +141,7 @@ def test_add_edge(self): circuit = QuantumCircuit(self.qreg, self.creg) circuit.h(self.qreg[0]) circuit.x(self.qreg[1]) - circuit.cnot(self.qreg[1], self.qreg[0]) + circuit.cx(self.qreg[1], self.qreg[0]) circuit.measure(self.qreg[0], self.creg[0]) self.dag = circuit_to_dagdependency(circuit) From a2adf200ac50cf8aca85941b5b28df2403e75ee9 Mon Sep 17 00:00:00 2001 From: romain Date: Thu, 30 Apr 2020 08:42:35 +0200 Subject: [PATCH 17/27] Linting --- qiskit/dagcircuit/dagdependency.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/dagcircuit/dagdependency.py b/qiskit/dagcircuit/dagdependency.py index 9ceed36d2a77..c9bdbb9414cb 100644 --- a/qiskit/dagcircuit/dagdependency.py +++ b/qiskit/dagcircuit/dagdependency.py @@ -295,7 +295,7 @@ def add_op_node(self, operation, qargs, cargs): """Add a DAGnode to the graph. Args: - operation (Instruction): operation as a quantum gate. + operation (qiskit.circuit.Instruction): operation as a quantum gate. qargs (list[Qubit]): list of qubits on which the operation acts cargs (list[Clbit]): list of classical wires to attach to. """ From 630487cdc07c1e1bb774ff5633b40044508eb02f Mon Sep 17 00:00:00 2001 From: romain Date: Mon, 4 May 2020 10:54:28 +0200 Subject: [PATCH 18/27] Correction method Add_successors() --- qiskit/dagcircuit/dagdependency.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/dagcircuit/dagdependency.py b/qiskit/dagcircuit/dagdependency.py index c9bdbb9414cb..35a21bf025dd 100644 --- a/qiskit/dagcircuit/dagdependency.py +++ b/qiskit/dagcircuit/dagdependency.py @@ -384,7 +384,7 @@ def add_successors(self): Use _gather_succ and merge_no_duplicates to create the list of successors for each node. Update DAGcanonical with attributes successors. """ - for node_id in range(len(self._multi_graph)-1, 0, -1): + for node_id in range(len(self._multi_graph)-1, -1, -1): direct_successors = self.direct_successors(node_id) From dc859be846f525c9d1958d3c2758960d643ab5be Mon Sep 17 00:00:00 2001 From: Ali Javadi Date: Sun, 17 May 2020 22:53:03 -0400 Subject: [PATCH 19/27] some leftover typos and doc cleanup --- qiskit/dagcircuit/dagdependency.py | 70 ++++++++++++++++-------------- 1 file changed, 37 insertions(+), 33 deletions(-) diff --git a/qiskit/dagcircuit/dagdependency.py b/qiskit/dagcircuit/dagdependency.py index 35a21bf025dd..bc9c9b89ac3b 100644 --- a/qiskit/dagcircuit/dagdependency.py +++ b/qiskit/dagcircuit/dagdependency.py @@ -12,38 +12,7 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Object to represent a quantum circuit as a directed acyclic graph -in the dependency form (commutation). - -The nodes in the graph are operation represented by quantum gates. -The edges correspond to non-commutation between two operations -(i.e. a dependency). A directed edge from node A to node B means that -operation A does not commute with operation B. -The object's methods allow circuits to be constructed. - -e.g. Bell circuit with no measurement - - ┌───┐ -qr_0: |0>┤ H ├──■── - └───┘┌─┴─┐ -qr_1: |0>─────┤ X ├ - └───┘ - -In the dag dependency form the circuit is represented by two nodes (1 and 2): -the first one corresponds to Hamdamard gate, the second one to the CNot gate -as the gates do not commute there is an edge between the wo noodes. - -The attributes are 'operation', 'successors', 'predecessors' -In the Bell circuit, the retwork PyDAG takes the following form: - -[{'operation': , - 'successors': [2], 'predecessors': []}), - { 'operation': , -'successors': [], 'predecessors': [1]}] - -The reference paper is https://arxiv.org/abs/1909.05270v1 - +"""DAGDependency class for representing non-commutativity in a circuit. """ import heapq @@ -61,7 +30,42 @@ class DAGDependency: """ - Object to represent a quantum circuit as a DAG in the dependency form. + Object to represent a quantum circuit as a directed acyclic graph + via operation dependencies (i.e. lack of commutation). + + The nodes in the graph are operations represented by quantum gates. + The edges correspond to non-commutation between two operations + (i.e. a dependency). A directed edge from node A to node B means that + operation A does not commute with operation B. + The object's methods allow circuits to be constructed. + + The attributes are 'operation', 'successors', 'predecessors'. + The retworkx PyDAG takes the following form: + + [{'operation': , + 'successors': [2], 'predecessors': []}), + { 'operation': , + 'successors': [], 'predecessors': [1]}] + + **Example:** + + Bell circuit with no measurement. + + ┌───┐ + qr_0: |0>┤ H ├──■── + └───┘┌─┴─┐ + qr_1: |0>─────┤ X ├ + └───┘ + + The dependency DAG for the above circuit is represented by two nodes (1 and 2): + the first one corresponds to Hadamard gate, the second one to the CNOT gate + as the gates do not commute there is an edge between the two nodes. + + **Reference:** + + [1] Iten, R., Sutter, D. and Woerner, S., 2019. + Efficient template matching in quantum circuits. + `arXiv:1909.05270 `_ """ def __init__(self): From 35890d06493cc5bfd4239e9377b5b23252118b1e Mon Sep 17 00:00:00 2001 From: Ali Javadi Date: Sun, 17 May 2020 23:14:43 -0400 Subject: [PATCH 20/27] doc --- qiskit/dagcircuit/dagdependency.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/qiskit/dagcircuit/dagdependency.py b/qiskit/dagcircuit/dagdependency.py index bc9c9b89ac3b..7103651c6201 100644 --- a/qiskit/dagcircuit/dagdependency.py +++ b/qiskit/dagcircuit/dagdependency.py @@ -29,8 +29,7 @@ class DAGDependency: - """ - Object to represent a quantum circuit as a directed acyclic graph + r"""Object to represent a quantum circuit as a directed acyclic graph via operation dependencies (i.e. lack of commutation). The nodes in the graph are operations represented by quantum gates. @@ -57,8 +56,8 @@ class DAGDependency: qr_1: |0>─────┤ X ├ └───┘ - The dependency DAG for the above circuit is represented by two nodes (1 and 2): - the first one corresponds to Hadamard gate, the second one to the CNOT gate + The dependency DAG for the above circuit is represented by two nodes. + The first one corresponds to Hadamard gate, the second one to the CNOT gate as the gates do not commute there is an edge between the two nodes. **Reference:** @@ -66,6 +65,7 @@ class DAGDependency: [1] Iten, R., Sutter, D. and Woerner, S., 2019. Efficient template matching in quantum circuits. `arXiv:1909.05270 `_ + """ def __init__(self): From ef39cefa78f0988937f5d319caf942089fe27dc4 Mon Sep 17 00:00:00 2001 From: Ali Javadi Date: Sun, 17 May 2020 23:41:38 -0400 Subject: [PATCH 21/27] snapshot should be like barrier --- qiskit/dagcircuit/dagdependency.py | 38 +++++++++++++++--------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/qiskit/dagcircuit/dagdependency.py b/qiskit/dagcircuit/dagdependency.py index 7103651c6201..622c5fe182c9 100644 --- a/qiskit/dagcircuit/dagdependency.py +++ b/qiskit/dagcircuit/dagdependency.py @@ -41,20 +41,24 @@ class DAGDependency: The attributes are 'operation', 'successors', 'predecessors'. The retworkx PyDAG takes the following form: - [{'operation': , - 'successors': [2], 'predecessors': []}), - { 'operation': , - 'successors': [], 'predecessors': [1]}] + .. parsed-literal:: + + [{'operation': , + 'successors': [2], 'predecessors': []}), + { 'operation': , + 'successors': [], 'predecessors': [1]}] **Example:** Bell circuit with no measurement. - ┌───┐ - qr_0: |0>┤ H ├──■── - └───┘┌─┴─┐ - qr_1: |0>─────┤ X ├ - └───┘ + .. parsed-literal:: + + ┌───┐ + qr_0: |0>┤ H ├──■── + └───┘┌─┴─┐ + qr_1: |0>─────┤ X ├ + └───┘ The dependency DAG for the above circuit is represented by two nodes. The first one corresponds to Hadamard gate, the second one to the CNOT gate @@ -65,7 +69,6 @@ class DAGDependency: [1] Iten, R., Sutter, D. and Woerner, S., 2019. Efficient template matching in quantum circuits. `arXiv:1909.05270 `_ - """ def __init__(self): @@ -473,18 +476,15 @@ def _commute(node1, node2): commute_measurement = True return commute_measurement - # Commutation for barrier - if node1.name == 'barrier' or node2.name == 'barrier': + # Commutation for barrier-like directives + directives = ['barrier', 'snapshot'] + if node1.name in directives or node2.name in directives: intersection = set(qarg1).intersection(set(qarg2)) if intersection: - commute_barrier = False + commute_directive = False else: - commute_barrier = True - return commute_barrier - - # Commutation for snapshot - if node1.name == 'snapshot' or node2.name == 'snapshot': - return False + commute_directive = True + return commute_directive # List of non commuting gates (TO DO: add more elements) non_commute_list = [set(['x', 'y']), set(['x', 'z'])] From a4cd90a618c67f4ab2575c5c52ac24c6ece05a30 Mon Sep 17 00:00:00 2001 From: Ali Javadi Date: Mon, 18 May 2020 00:12:51 -0400 Subject: [PATCH 22/27] draw method update --- qiskit/dagcircuit/dagdependency.py | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/qiskit/dagcircuit/dagdependency.py b/qiskit/dagcircuit/dagdependency.py index 622c5fe182c9..e1e5f85fdd1b 100644 --- a/qiskit/dagcircuit/dagdependency.py +++ b/qiskit/dagcircuit/dagdependency.py @@ -38,15 +38,8 @@ class DAGDependency: operation A does not commute with operation B. The object's methods allow circuits to be constructed. - The attributes are 'operation', 'successors', 'predecessors'. - The retworkx PyDAG takes the following form: - - .. parsed-literal:: - - [{'operation': , - 'successors': [2], 'predecessors': []}), - { 'operation': , - 'successors': [], 'predecessors': [1]}] + The nodes in the graph have the following attributes: + 'operation', 'successors', 'predecessors'. **Example:** @@ -73,7 +66,7 @@ class DAGDependency: def __init__(self): """ - Create an empty directed acyclis graph (dependency form) + Create an empty DAGDependency. """ # Circuit name self.name = None @@ -368,7 +361,7 @@ def add_edge(self): """ Function to verify the commutation relation and reachability for predecessors, the nodes do not commute and - if the predecessor is reachable. Update the DAGcanonical by + if the predecessor is reachable. Update the DAGDependency by introducing edges and predecessors(attribute) """ max_node_id = len(self._multi_graph) - 1 @@ -389,7 +382,7 @@ def add_edge(self): def add_successors(self): """ Use _gather_succ and merge_no_duplicates to create the list of successors - for each node. Update DAGcanonical with attributes successors. + for each node. Update DAGDependency with attributes successors. """ for node_id in range(len(self._multi_graph)-1, -1, -1): @@ -400,9 +393,9 @@ def add_successors(self): self._multi_graph.get_node_data(node_id)['successors'] = list( merge_no_duplicates(*(self._multi_graph.get_node_data(node_id)['successors']))) - def draw(self, scale=0.7, filename=None, style='color', category='dependency'): + def draw(self, scale=0.7, filename=None, style='color'): """ - Draws the DAG canonical circuit. + Draws the DAGDependency graph. This function needs `pydot `, which in turn needs Graphviz ` to be installed. @@ -412,14 +405,14 @@ def draw(self, scale=0.7, filename=None, style='color', category='dependency'): filename (str): file path to save image to (format inferred from name) style (str): 'plain': B&W graph 'color' (default): color input/output/op nodes - category(str): 'canonical' Other type of DAG Returns: Ipython.display.Image: if in Jupyter notebook and not saving to file, otherwise None. """ from qiskit.visualization.dag_visualization import dag_drawer - return dag_drawer(dag=self, scale=scale, filename=filename, style=style, category=category) + return dag_drawer(dag=self, scale=scale, filename=filename, + style=style, category='dependency') def merge_no_duplicates(*iterables): From 5b055fcbbd6af02be5d984ee6ea70dd0313b05ad Mon Sep 17 00:00:00 2001 From: Ali Javadi Date: Mon, 18 May 2020 00:31:53 -0400 Subject: [PATCH 23/27] tweak --- qiskit/dagcircuit/dagdependency.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/qiskit/dagcircuit/dagdependency.py b/qiskit/dagcircuit/dagdependency.py index e1e5f85fdd1b..7dd942183f97 100644 --- a/qiskit/dagcircuit/dagdependency.py +++ b/qiskit/dagcircuit/dagdependency.py @@ -47,11 +47,11 @@ class DAGDependency: .. parsed-literal:: - ┌───┐ - qr_0: |0>┤ H ├──■── - └───┘┌─┴─┐ - qr_1: |0>─────┤ X ├ - └───┘ + ┌───┐ + qr_0: ┤ H ├──■── + └───┘┌─┴─┐ + qr_1: ─────┤ X ├ + └───┘ The dependency DAG for the above circuit is represented by two nodes. The first one corresponds to Hadamard gate, the second one to the CNOT gate From 4b9485d454dab3c0ba64608d47b903b152c899b5 Mon Sep 17 00:00:00 2001 From: romain Date: Thu, 21 May 2020 00:37:21 +0200 Subject: [PATCH 24/27] Add a DagDepNode, change _update_edge and _add_successors. --- qiskit/converters/circuit_to_dagdependency.py | 3 +- qiskit/converters/dag_to_dagdependency.py | 3 +- qiskit/converters/dagdependency_to_circuit.py | 7 +- qiskit/converters/dagdependency_to_dag.py | 7 +- qiskit/dagcircuit/dagdependency.py | 96 ++++++++------ qiskit/dagcircuit/dagdepnode.py | 125 ++++++++++++++++++ qiskit/visualization/dag_visualization.py | 16 +-- test/python/dagcircuit/test_dagdependency.py | 10 +- 8 files changed, 203 insertions(+), 64 deletions(-) create mode 100644 qiskit/dagcircuit/dagdepnode.py diff --git a/qiskit/converters/circuit_to_dagdependency.py b/qiskit/converters/circuit_to_dagdependency.py index 81635ec82b48..1453a3d1d0be 100644 --- a/qiskit/converters/circuit_to_dagdependency.py +++ b/qiskit/converters/circuit_to_dagdependency.py @@ -37,8 +37,7 @@ def circuit_to_dagdependency(circuit): for operation, qargs, cargs in circuit.data: dagdependency.add_op_node(operation, qargs, cargs) - dagdependency.add_edge() - dagdependency.add_successors() + dagdependency._add_successors() return dagdependency diff --git a/qiskit/converters/dag_to_dagdependency.py b/qiskit/converters/dag_to_dagdependency.py index 7173724a9ae7..14b0ae47b941 100644 --- a/qiskit/converters/dag_to_dagdependency.py +++ b/qiskit/converters/dag_to_dagdependency.py @@ -43,8 +43,7 @@ def dag_to_dagdependency(dag): inst = node.op.copy() inst.condition = node.condition dagdependency.add_op_node(inst, node.qargs, node.cargs) - dagdependency.add_edge() - dagdependency.add_successors() + dagdependency._add_successors() return dagdependency diff --git a/qiskit/converters/dagdependency_to_circuit.py b/qiskit/converters/dagdependency_to_circuit.py index b80c5f663c1b..300641a35948 100644 --- a/qiskit/converters/dagdependency_to_circuit.py +++ b/qiskit/converters/dagdependency_to_circuit.py @@ -31,10 +31,9 @@ def dagdependency_to_circuit(dagdependency): name=name) for node in dagdependency.get_nodes(): - node_op = node['operation'] # Get arguments for classical control (if any) - inst = node_op.op.copy() - inst.condition = node_op.condition - circuit._append(inst, node_op.qargs, node_op.cargs) + inst = node.op.copy() + inst.condition = node.condition + circuit._append(inst, node.qargs, node.cargs) return circuit diff --git a/qiskit/converters/dagdependency_to_dag.py b/qiskit/converters/dagdependency_to_dag.py index a84ce426f4eb..567ebf93fa25 100644 --- a/qiskit/converters/dagdependency_to_dag.py +++ b/qiskit/converters/dagdependency_to_dag.py @@ -39,11 +39,10 @@ def dagdependency_to_dag(dagdependency): dagcircuit.add_creg(register) for node in dagdependency.get_nodes(): - node_op = node['operation'] # Get arguments for classical control (if any) - inst = node_op.op.copy() - inst.condition = node_op.condition + inst = node.op.copy() + inst.condition = node.condition - dagcircuit.apply_operation_back(inst, node_op.qargs, node_op.cargs, inst.condition) + dagcircuit.apply_operation_back(inst, node.qargs, node.cargs, inst.condition) return dagcircuit diff --git a/qiskit/dagcircuit/dagdependency.py b/qiskit/dagcircuit/dagdependency.py index 7dd942183f97..768aa06ab4ae 100644 --- a/qiskit/dagcircuit/dagdependency.py +++ b/qiskit/dagcircuit/dagdependency.py @@ -24,12 +24,12 @@ from qiskit.circuit.quantumregister import QuantumRegister from qiskit.circuit.classicalregister import ClassicalRegister from qiskit.dagcircuit.exceptions import DAGDependencyError -from qiskit.dagcircuit.dagnode import DAGNode +from qiskit.dagcircuit.dagdepnode import DAGDepNode from qiskit.quantum_info.operators import Operator class DAGDependency: - r"""Object to represent a quantum circuit as a directed acyclic graph + """Object to represent a quantum circuit as a directed acyclic graph via operation dependencies (i.e. lack of commutation). The nodes in the graph are operations represented by quantum gates. @@ -89,12 +89,11 @@ def to_networkx(self): dag_networkx = nx.MultiDiGraph() for node in self.get_nodes(): - dag_networkx.add_node(node['operation']._node_id, operation=node['operation'], - successors=node['successors'], predecessors=node['predecessors']) + dag_networkx.add_node(node) for node in self.topological_nodes(): for source_id, dest_id, edge in \ - self.get_in_edges(node['operation']._node_id): - dag_networkx.add_edge(source_id, dest_id, **edge) + self.get_in_edges(node.node_id): + dag_networkx.add_edge(self.get_node(source_id), self.get_node(dest_id), **edge) return dag_networkx def to_retworkx(self): @@ -140,13 +139,13 @@ def add_creg(self, creg): def _add_multi_graph_node(self, node): """ Args: - node (DAGNode): considered node. + node (DAGDepNode): considered node. Returns: node_id(int): corresponding label to the added node. """ node_id = self._multi_graph.add_node(node) - node['operation']._node_id = node_id + node.node_id = node_id return node_id def get_nodes(self): @@ -202,7 +201,7 @@ def get_all_edges(self): return [(src, dest, data) for src_node in self._multi_graph.nodes() for (src, dest, data) - in self._multi_graph.out_edges(src_node['operation']._node_id)] + in self._multi_graph.out_edges(src_node.node_id)] def get_in_edges(self, node_id): """ @@ -262,7 +261,7 @@ def successors(self, node_id): Returns: List: all successors id as a sorted list """ - return self._multi_graph.get_node_data(node_id)['successors'] + return self._multi_graph.get_node_data(node_id).successors def predecessors(self, node_id): """ @@ -274,7 +273,7 @@ def predecessors(self, node_id): Returns: List: all predecessors id as a sorted list """ - return self._multi_graph.get_node_data(node_id)['predecessors'] + return self._multi_graph.get_node_data(node_id).predecessors def topological_nodes(self): """ @@ -285,24 +284,25 @@ def topological_nodes(self): """ def _key(x): - return x['operation'].sort_key + return x.sort_key return iter(rx.lexicographical_topological_sort( self._multi_graph, key=_key)) def add_op_node(self, operation, qargs, cargs): - """Add a DAGnode to the graph. + """Add a DAGDepNode to the graph and update the edges. Args: operation (qiskit.circuit.Instruction): operation as a quantum gate. qargs (list[Qubit]): list of qubits on which the operation acts cargs (list[Clbit]): list of classical wires to attach to. """ - new_node = DAGNode(type="op", op=operation, name=operation.name, qargs=qargs, - cargs=cargs, condition=operation.condition) - operation_node = {'operation': new_node, 'successors': [], 'predecessors': []} - self._add_multi_graph_node(operation_node) + new_node = DAGDepNode(type="op", op=operation, name=operation.name, qargs=qargs, + cargs=cargs, condition=operation.condition, successors=[], + predecessors=[]) + self._add_multi_graph_node(new_node) + self._update_edges() def _gather_pred(self, node_id, direct_pred): """Function set an attribute predecessors and gather multiple lists @@ -317,11 +317,11 @@ def _gather_pred(self, node_id, direct_pred): the lists of direct successors are put into a single one """ gather = self._multi_graph - gather.get_node_data(node_id)['predecessors'] = [] + gather.get_node_data(node_id).predecessors = [] for d_pred in direct_pred: - gather.get_node_data(node_id)['predecessors'].append([d_pred]) - pred = self._multi_graph.get_node_data(d_pred)['predecessors'] - gather.get_node_data(node_id)['predecessors'].append(pred) + gather.get_node_data(node_id).predecessors.append([d_pred]) + pred = self._multi_graph.get_node_data(d_pred).predecessors + gather.get_node_data(node_id).predecessors.append(pred) return gather def _gather_succ(self, node_id, direct_succ): @@ -339,9 +339,9 @@ def _gather_succ(self, node_id, direct_succ): """ gather = self._multi_graph for d_succ in direct_succ: - gather.get_node_data(node_id)['successors'].append([d_succ]) - succ = gather.get_node_data(d_succ)['successors'] - gather.get_node_data(node_id)['successors'].append(succ) + gather.get_node_data(node_id).successors.append([d_succ]) + succ = gather.get_node_data(d_succ).successors + gather.get_node_data(node_id).successors.append(succ) return gather def _list_pred(self, node_id): @@ -354,10 +354,10 @@ def _list_pred(self, node_id): """ direct_pred = self.direct_predecessors(node_id) self._multi_graph = self._gather_pred(node_id, direct_pred) - self._multi_graph.get_node_data(node_id)['predecessors'] = list( - merge_no_duplicates(*(self._multi_graph.get_node_data(node_id)['predecessors']))) + self._multi_graph.get_node_data(node_id).predecessors = list( + merge_no_duplicates(*(self._multi_graph.get_node_data(node_id).predecessors))) - def add_edge(self): + def _update_edges(self): """ Function to verify the commutation relation and reachability for predecessors, the nodes do not commute and @@ -365,33 +365,51 @@ def add_edge(self): introducing edges and predecessors(attribute) """ max_node_id = len(self._multi_graph) - 1 - max_node = self._multi_graph.get_node_data(max_node_id)['operation'] + max_node = self._multi_graph.get_node_data(max_node_id) for current_node_id in range(0, max_node_id): - self._multi_graph.get_node_data(current_node_id)['reachable'] = True + self._multi_graph.get_node_data(current_node_id).reachable = True # Check the commutation relation with reachable node, it adds edges if it does not commute for prev_node_id in range(max_node_id - 1, -1, -1): - if self._multi_graph.get_node_data(prev_node_id)['reachable'] and not _commute( - self._multi_graph.get_node_data(prev_node_id)['operation'], max_node): + if self._multi_graph.get_node_data(prev_node_id).reachable and not _commute( + self._multi_graph.get_node_data(prev_node_id), max_node): self._multi_graph.add_edge(prev_node_id, max_node_id, {'commute': False}) self._list_pred(max_node_id) - list_predecessors = self._multi_graph.get_node_data(max_node_id)['predecessors'] + list_predecessors = self._multi_graph.get_node_data(max_node_id).predecessors for pred_id in list_predecessors: - self._multi_graph.get_node_data(pred_id)['reachable'] = False + self._multi_graph.get_node_data(pred_id).reachable = False - def add_successors(self): + def _add_successors(self): """ Use _gather_succ and merge_no_duplicates to create the list of successors - for each node. Update DAGDependency with attributes successors. + for each node. Update DAGDependency 'successors' attribute. It has to + be used when the DAGDependency() object is complete (i.e. converters). """ - for node_id in range(len(self._multi_graph)-1, -1, -1): - + for node_id in range(len(self._multi_graph) - 1, -1, -1): direct_successors = self.direct_successors(node_id) self._multi_graph = self._gather_succ(node_id, direct_successors) - self._multi_graph.get_node_data(node_id)['successors'] = list( - merge_no_duplicates(*(self._multi_graph.get_node_data(node_id)['successors']))) + self._multi_graph.get_node_data(node_id).successors = list( + merge_no_duplicates(*self._multi_graph.get_node_data(node_id).successors)) + + def copy(self): + """ + Function to copy a DAGDependency object. + Returns: + DAGDependency: a copy of a DAGDependency object. + """ + + dag = DAGDependency() + dag.name = self.name + dag.cregs = self.cregs.copy() + dag.qregs = self.qregs.copy() + + for node in self.get_nodes(): + dag._multi_graph.add_node(node.copy()) + for edges in self.get_all_edges(): + dag._multi_graph.add_edge(edges[0], edges[1], edges[2]) + return dag def draw(self, scale=0.7, filename=None, style='color'): """ diff --git a/qiskit/dagcircuit/dagdepnode.py b/qiskit/dagcircuit/dagdepnode.py new file mode 100644 index 000000000000..d20c7ec58b4d --- /dev/null +++ b/qiskit/dagcircuit/dagdepnode.py @@ -0,0 +1,125 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +# pylint: disable=redefined-builtin + +"""Object to represent the information at a node in the DAGCircuit.""" + +from qiskit.exceptions import QiskitError + + +class DAGDepNode: + """Object to represent the information at a node in the DAGDependency(). + + It is used as the return value from `*_nodes()` functions and can + be supplied to functions that take a node. + """ + + __slots__ = ['type', '_op', 'name', '_qargs', 'cargs', 'condition', + 'sort_key', 'node_id', 'successors', 'predecessors', + 'reachable', 'matchedwith', 'isblocked', 'successorstovisit'] + + def __init__(self, type=None, op=None, name=None, qargs=None, cargs=None, + condition=None, successors=None, predecessors=None, reachable=None, + matchedwith=None, successorstovisit=None, isblocked=None, nid=-1): + + self.type = type + self._op = op + self.name = name + self._qargs = qargs if qargs is not None else [] + self.cargs = cargs if cargs is not None else [] + self.condition = condition + self.node_id = nid + self.sort_key = str(self._qargs) + self.successors = successors if successors is not None else [] + self.predecessors = predecessors if predecessors is not None else [] + self.reachable = reachable + self.matchedwith = matchedwith if matchedwith is not None else [] + self.isblocked = isblocked + self.successorstovisit = successorstovisit if successorstovisit is not None else [] + + @property + def op(self): + """Returns the Instruction object corresponding to the op for the node, else None""" + if not self.type or self.type != 'op': + raise QiskitError("The node %s is not an op node" % (str(self))) + return self._op + + @op.setter + def op(self, data): + self._op = data + + @property + def qargs(self): + """ + Returns list of Qubit, else an empty list. + """ + return self._qargs + + @qargs.setter + def qargs(self, new_qargs): + """Sets the qargs to be the given list of qargs.""" + self._qargs = new_qargs + self.sort_key = str(new_qargs) + + @staticmethod + def semantic_eq(node1, node2): + """ + Check if DAG nodes are considered equivalent, e.g., as a node_match for nx.is_isomorphic. + + Args: + node1 (DAGDepNode): A node to compare. + node2 (DAGDepNode): The other node to compare. + + Return: + Bool: If node1 == node2 + """ + # For barriers, qarg order is not significant so compare as sets + if 'barrier' == node1.name == node2.name: + return set(node1._qargs) == set(node2._qargs) + result = False + if node1.type == node2.type: + if node1._op == node2._op: + if node1.name == node2.name: + if node1._qargs == node2._qargs: + if node1.cargs == node2.cargs: + if node1.condition == node2.condition: + result = True + return result + + def copy(self): + """ + Function to copy a DAGDepNode object. + Returns: + DAGDepNode: a copy of a DAGDepNode objectto. + """ + + dagdepnode = DAGDepNode() + + dagdepnode.type = self.type + dagdepnode._op = self.op + dagdepnode.name = self.name + dagdepnode._qargs = self._qargs + dagdepnode.cargs = self.cargs + dagdepnode.condition = self.condition + dagdepnode.node_id = self.node_id + dagdepnode.sort_key = self.sort_key + dagdepnode.successors = self.successors.copy() + dagdepnode.predecessors = self.predecessors.copy() + dagdepnode.reachable = self.reachable + dagdepnode.matchedwith = self.matchedwith.copy() + dagdepnode.isblocked = self.isblocked + dagdepnode.successorstovisit = self.successorstovisit.copy() + + return dagdepnode diff --git a/qiskit/visualization/dag_visualization.py b/qiskit/visualization/dag_visualization.py index 3d8892942008..34ce00fb60e7 100644 --- a/qiskit/visualization/dag_visualization.py +++ b/qiskit/visualization/dag_visualization.py @@ -121,23 +121,23 @@ def dag_drawer(dag, scale=0.7, filename=None, style='color', category=None): if style == 'plain': pass elif style == 'color': - for node in G.nodes(data='operation'): - n = G.nodes[node[0]] - n['label'] = str(node[0]+1) + ': ' + node[1].name - if node[1].name == 'measure': + for node in G.nodes: + n = G.nodes[node] + n['label'] = str(node.node_id) + ': ' + node.name + if node.name == 'measure': n['color'] = 'blue' n['style'] = 'filled' n['fillcolor'] = 'lightblue' - if node[1].name == 'barrier': + if node.name == 'barrier': n['color'] = 'black' n['style'] = 'filled' n['fillcolor'] = 'green' - if node[1].name == 'snapshot': + if node.name == 'snapshot': n['color'] = 'black' n['style'] = 'filled' n['fillcolor'] = 'red' - if node[1].condition: - n['label'] = str(node[0]) + ': ' + node[1].name + ' (conditional)' + if node.condition: + n['label'] = str(node.node_id) + ': ' + node.name + ' (conditional)' n['color'] = 'black' n['style'] = 'filled' n['fillcolor'] = 'lightgreen' diff --git a/test/python/dagcircuit/test_dagdependency.py b/test/python/dagcircuit/test_dagdependency.py index 8ff00fd598a2..c4d541bf46e5 100644 --- a/test/python/dagcircuit/test_dagdependency.py +++ b/test/python/dagcircuit/test_dagdependency.py @@ -117,23 +117,23 @@ def test_node(self): circuit.h(self.qreg[0]) self.dag.add_op_node(circuit.data[0][0], circuit.data[0][1], circuit.data[0][2]) - self.assertIsInstance(self.dag.get_node(0)['operation'].op, HGate) + self.assertIsInstance(self.dag.get_node(0).op, HGate) circuit.measure(self.qreg[0], self.creg[0]) self.dag.add_op_node(circuit.data[1][0], circuit.data[1][1], circuit.data[1][2]) - self.assertIsInstance(self.dag.get_node(1)['operation'].op, Measure) + self.assertIsInstance(self.dag.get_node(1).op, Measure) nodes = list(self.dag.get_nodes()) self.assertEqual(len(list(nodes)), 2) for node in nodes: - self.assertIsInstance(node['operation'].op, Instruction) + self.assertIsInstance(node.op, Instruction) node_1 = nodes.pop() node_2 = nodes.pop() - self.assertIsInstance(node_1['operation'].op, Measure) - self.assertIsInstance(node_2['operation'].op, HGate) + self.assertIsInstance(node_1.op, Measure) + self.assertIsInstance(node_2.op, HGate) def test_add_edge(self): """Test that add_edge(), get_edges(), get_all_edges(), From 9a6ad24987b58c6bba82fb7a5b0cdd06c45e84f3 Mon Sep 17 00:00:00 2001 From: romain Date: Sat, 23 May 2020 00:37:22 +0200 Subject: [PATCH 25/27] Add qindices and cindices attributes to the DAGRepNode --- qiskit/dagcircuit/dagdependency.py | 20 +++++++++++++++++++- qiskit/dagcircuit/dagdepnode.py | 10 ++++++++-- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/qiskit/dagcircuit/dagdependency.py b/qiskit/dagcircuit/dagdependency.py index 768aa06ab4ae..f5e4f1c6f323 100644 --- a/qiskit/dagcircuit/dagdependency.py +++ b/qiskit/dagcircuit/dagdependency.py @@ -298,9 +298,27 @@ def add_op_node(self, operation, qargs, cargs): qargs (list[Qubit]): list of qubits on which the operation acts cargs (list[Clbit]): list of classical wires to attach to. """ + all_qubits = self.qubits() + all_clbits = self.clbits() + + directives = ['measure', 'barrier', 'snapshot'] + if operation.name not in directives: + qindices_list = [] + for elem in qargs: + qindices_list.append(all_qubits.index(elem)) + if operation.condition: + for clbit in all_clbits: + if clbit.register == operation.condition[0]: + initial = all_clbits.index(clbit) + final = all_clbits.index(clbit) + clbit.register.size + cindices_list = range(initial, final) + break + else: + cindices_list = [] + new_node = DAGDepNode(type="op", op=operation, name=operation.name, qargs=qargs, cargs=cargs, condition=operation.condition, successors=[], - predecessors=[]) + predecessors=[], qindices=qindices_list, cindices=cindices_list) self._add_multi_graph_node(new_node) self._update_edges() diff --git a/qiskit/dagcircuit/dagdepnode.py b/qiskit/dagcircuit/dagdepnode.py index d20c7ec58b4d..e6090ddbec4c 100644 --- a/qiskit/dagcircuit/dagdepnode.py +++ b/qiskit/dagcircuit/dagdepnode.py @@ -28,11 +28,13 @@ class DAGDepNode: __slots__ = ['type', '_op', 'name', '_qargs', 'cargs', 'condition', 'sort_key', 'node_id', 'successors', 'predecessors', - 'reachable', 'matchedwith', 'isblocked', 'successorstovisit'] + 'reachable', 'matchedwith', 'isblocked', 'successorstovisit', + 'qindices', 'cindices'] def __init__(self, type=None, op=None, name=None, qargs=None, cargs=None, condition=None, successors=None, predecessors=None, reachable=None, - matchedwith=None, successorstovisit=None, isblocked=None, nid=-1): + matchedwith=None, successorstovisit=None, isblocked=None, qindices=None, + cindices=None, nid=-1): self.type = type self._op = op @@ -48,6 +50,8 @@ def __init__(self, type=None, op=None, name=None, qargs=None, cargs=None, self.matchedwith = matchedwith if matchedwith is not None else [] self.isblocked = isblocked self.successorstovisit = successorstovisit if successorstovisit is not None else [] + self.qindices = qindices if qindices is not None else [] + self.cindices = cindices if cindices is not None else [] @property def op(self): @@ -121,5 +125,7 @@ def copy(self): dagdepnode.matchedwith = self.matchedwith.copy() dagdepnode.isblocked = self.isblocked dagdepnode.successorstovisit = self.successorstovisit.copy() + dagdepnode.qindices = self.qindices.copy() + dagdepnode.cindices = self.cindices.copy() return dagdepnode From 6c40cf44935c63572cc723df81d417194c009634 Mon Sep 17 00:00:00 2001 From: romain Date: Sat, 23 May 2020 17:19:30 +0200 Subject: [PATCH 26/27] Add qindices and cindices attributes initialization in _add_op_node from DAGDependency(). --- qiskit/dagcircuit/dagdependency.py | 3 +++ qiskit/dagcircuit/dagdepnode.py | 12 ++++++------ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/qiskit/dagcircuit/dagdependency.py b/qiskit/dagcircuit/dagdependency.py index f5e4f1c6f323..b5767282aa7c 100644 --- a/qiskit/dagcircuit/dagdependency.py +++ b/qiskit/dagcircuit/dagdependency.py @@ -315,6 +315,9 @@ def add_op_node(self, operation, qargs, cargs): break else: cindices_list = [] + else: + qindices_list = [] + cindices_list = [] new_node = DAGDepNode(type="op", op=operation, name=operation.name, qargs=qargs, cargs=cargs, condition=operation.condition, successors=[], diff --git a/qiskit/dagcircuit/dagdepnode.py b/qiskit/dagcircuit/dagdepnode.py index e6090ddbec4c..5b7f3ca855de 100644 --- a/qiskit/dagcircuit/dagdepnode.py +++ b/qiskit/dagcircuit/dagdepnode.py @@ -119,13 +119,13 @@ def copy(self): dagdepnode.condition = self.condition dagdepnode.node_id = self.node_id dagdepnode.sort_key = self.sort_key - dagdepnode.successors = self.successors.copy() - dagdepnode.predecessors = self.predecessors.copy() + dagdepnode.successors = self.successors + dagdepnode.predecessors = self.predecessors dagdepnode.reachable = self.reachable - dagdepnode.matchedwith = self.matchedwith.copy() dagdepnode.isblocked = self.isblocked - dagdepnode.successorstovisit = self.successorstovisit.copy() - dagdepnode.qindices = self.qindices.copy() - dagdepnode.cindices = self.cindices.copy() + dagdepnode.successorstovisit = self.successorstovisit + dagdepnode.qindices = self.qindices + dagdepnode.cindices = self.cindices + dagdepnode.matchedwith = self.matchedwith.copy() return dagdepnode From afc033176389656cda0bcb1302c8f379a279dfb5 Mon Sep 17 00:00:00 2001 From: romain Date: Sat, 23 May 2020 17:29:50 +0200 Subject: [PATCH 27/27] Update __init__ for DAGDepNode --- qiskit/dagcircuit/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/qiskit/dagcircuit/__init__.py b/qiskit/dagcircuit/__init__.py index facadf3b6ce5..987face7870f 100644 --- a/qiskit/dagcircuit/__init__.py +++ b/qiskit/dagcircuit/__init__.py @@ -27,6 +27,7 @@ DAGCircuit DAGNode + DAGDepNode DAGDependency Exceptions @@ -39,5 +40,6 @@ """ from .dagcircuit import DAGCircuit from .dagnode import DAGNode +from .dagdepnode import DAGDepNode from .exceptions import DAGCircuitError from .dagdependency import DAGDependency