From a32fef8b2faa5aeb60bd864ca1c565a2f09d2266 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Sun, 25 Apr 2021 08:57:19 -0400 Subject: [PATCH 01/20] Use retworkx for substitute_node_with_dag This commit leverage the substitute_node_with_subgraph method being added Qiskit/retworkx#312 for the dagcircuit method substitute_node_with_dag. --- qiskit/dagcircuit/dagcircuit.py | 146 +++++++--------------- test/python/dagcircuit/test_dagcircuit.py | 32 ++++- tox.ini | 1 + 3 files changed, 78 insertions(+), 101 deletions(-) diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index 7ee8766f7110..496c70834193 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -825,63 +825,6 @@ def _check_wires_list(self, wires, node): raise DAGCircuitError("expected %d wires, got %d" % (wire_tot, len(wires))) - def _make_pred_succ_maps(self, node): - """Return predecessor and successor dictionaries. - - Args: - node (DAGNode): reference to multi_graph node - - Returns: - tuple(dict): tuple(predecessor_map, successor_map) - These map from wire (Register, int) to the node ids for the - predecessor (successor) nodes of the input node. - """ - - pred_map = {e[2]: e[0] for e in - self._multi_graph.in_edges(node._node_id)} - succ_map = {e[2]: e[1] for e in - self._multi_graph.out_edges(node._node_id)} - return pred_map, succ_map - - def _full_pred_succ_maps(self, pred_map, succ_map, input_circuit, - wire_map): - """Map all wires of the input circuit. - - Map all wires of the input circuit to predecessor and - successor nodes in self, keyed on wires in self. - - Args: - pred_map (dict): comes from _make_pred_succ_maps - succ_map (dict): comes from _make_pred_succ_maps - input_circuit (DAGCircuit): the input circuit - wire_map (dict): the map from wires of input_circuit to wires of self - - Returns: - tuple: full_pred_map, full_succ_map (dict, dict) - - Raises: - DAGCircuitError: if more than one predecessor for output nodes - """ - full_pred_map = {} - full_succ_map = {} - for w in input_circuit.input_map: - # If w is wire mapped, find the corresponding predecessor - # of the node - if w in wire_map: - full_pred_map[wire_map[w]] = pred_map[wire_map[w]] - full_succ_map[wire_map[w]] = succ_map[wire_map[w]] - else: - # Otherwise, use the corresponding output nodes of self - # and compute the predecessor. - full_succ_map[w] = self.output_map[w] - full_pred_map[w] = self._multi_graph.predecessors( - self.output_map[w])[0] - if len(self._multi_graph.predecessors(self.output_map[w])) != 1: - raise DAGCircuitError("too many predecessors for %s[%d] " - "output node" % (w.register, w.index)) - - return full_pred_map, full_succ_map - def __eq__(self, other): # Try to convert to float, but in case of unbound ParameterExpressions # a TypeError will be raise, fallback to normal equality in those @@ -991,7 +934,7 @@ def substitute_node_with_dag(self, node, input_dag, wires=None): if wires is None: wires = in_dag.wires - + wire_set = set(wires) self._check_wires_list(wires, node) # Create a proxy wire_map to identify fragments and duplicates @@ -1014,11 +957,11 @@ def substitute_node_with_dag(self, node, input_dag, wires=None): condition_bit_list = self._bits_in_condition(condition) - wire_map = dict(zip(wires, list(node.qargs) + list(node.cargs) + list(condition_bit_list))) + new_wires = list(node.qargs) + list(node.cargs) + list(condition_bit_list) + + wire_map = dict(zip(wires, new_wires)) + reverse_wire_map = dict(zip(new_wires, wires)) self._check_wiremap_validity(wire_map, wires, self.input_map) - pred_map, succ_map = self._make_pred_succ_maps(node) - full_pred_map, full_succ_map = self._full_pred_succ_maps(pred_map, succ_map, - in_dag, wire_map) if condition_bit_list: # If we are replacing a conditional node, map input dag through @@ -1033,48 +976,51 @@ def substitute_node_with_dag(self, node, input_dag, wires=None): raise DAGCircuitError('Mapped DAG would alter clbits ' 'on which it would be conditioned.') - # Now that we know the connections, delete node - self._multi_graph.remove_node(node._node_id) - - # Iterate over nodes of input_circuit - for sorted_node in in_dag.topological_op_nodes(): - # Insert a new node + def filter_fn(node): + if node.type != 'op': + return False + for qarg in node.qargs: + if qarg not in wire_set: + return False + return True + + def edge_map_fn(source, target, self_wire): + wire = reverse_wire_map[self_wire] + # successor edge + if source == node._node_id: + wire_id = in_dag.output_map[wire]._node_id + out_index = in_dag._multi_graph.predecessor_indices(wire_id)[0] + # predecessor edge + else: + wire_id = in_dag.input_map[wire]._node_id + out_index = in_dag._multi_graph.successor_indices(wire_id)[0] + return out_index + + def edge_weight_map(wire): + return wire_map[wire] + + node_map = self._multi_graph.substitute_node_with_subgraph( + node._node_id, in_dag._multi_graph, edge_map_fn, filter_fn, + edge_weight_map) + + # Iterate over nodes of input_circuit and update wires + for old_node_index in node_map: + # update node attributes + new_node_index = node_map[old_node_index] + old_node = in_dag._multi_graph[old_node_index] + new_node = copy.copy(old_node) condition = self._map_condition(wire_map, - sorted_node.op.condition, + old_node.op.condition, self.cregs.values()) m_qargs = list(map(lambda x: wire_map.get(x, x), - sorted_node.qargs)) + old_node.qargs)) m_cargs = list(map(lambda x: wire_map.get(x, x), - sorted_node.cargs)) - node_index = self._add_op_node(sorted_node.op, m_qargs, m_cargs) - - # Add edges from predecessor nodes to new node - # and update predecessor nodes that change - all_cbits = self._bits_in_condition(condition) - all_cbits.extend(m_cargs) - al = [m_qargs, all_cbits] - for q in itertools.chain(*al): - self._multi_graph.add_edge(full_pred_map[q], - node_index, - q) - full_pred_map[q] = node_index - - # Connect all predecessors and successors, and remove - # residual edges between input and output nodes - for w in full_pred_map: - self._multi_graph.add_edge(full_pred_map[w], - full_succ_map[w], - w) - o_pred = self._multi_graph.predecessors(self.output_map[w]._node_id) - if len(o_pred) > 1: - if len(o_pred) != 2: - raise DAGCircuitError("expected 2 predecessors here") - - p = [x for x in o_pred if x != full_pred_map[w]] - if len(p) != 1: - raise DAGCircuitError("expected 1 predecessor to pass filter") - - self._multi_graph.remove_edge(p[0], self.output_map[w]) + old_node.cargs)) + new_node.qargs = m_qargs + new_node.cargs = m_cargs + new_node._node_id = new_node_index + new_node.op.condition = condition + self._multi_graph[new_node_index] = new_node def substitute_node(self, node, op, inplace=False): """Replace a DAGNode with a single instruction. qargs, cargs and diff --git a/test/python/dagcircuit/test_dagcircuit.py b/test/python/dagcircuit/test_dagcircuit.py index 2f73f11c4168..d82f796cd5b1 100644 --- a/test/python/dagcircuit/test_dagcircuit.py +++ b/test/python/dagcircuit/test_dagcircuit.py @@ -1141,10 +1141,40 @@ def test_substitute_circuit_one_middle(self): self.dag.substitute_node_with_dag(cx_node, flipped_cx_circuit, wires=[v[0], v[1]]) self.assertEqual(self.dag.count_ops()['h'], 5) + expected = DAGCircuit() + qreg = QuantumRegister(3, 'qr') + creg = ClassicalRegister(2, 'cr') + expected.add_qreg(qreg) + expected.add_creg(creg) + expected.apply_operation_back(HGate(), [qreg[0]], []) + expected.apply_operation_back(HGate(), [qreg[0]], []) + expected.apply_operation_back(HGate(), [qreg[1]], []) + expected.apply_operation_back(CXGate(), [qreg[1], qreg[0]], []) + expected.apply_operation_back(HGate(), [qreg[0]], []) + expected.apply_operation_back(HGate(), [qreg[1]], []) + expected.apply_operation_back(XGate(), [qreg[1]], []) + self.assertEqual(self.dag, expected) def test_substitute_circuit_one_front(self): """The method substitute_node_with_dag() replaces a leaf-in-the-front node with a DAG.""" - pass + flipped_cx_circuit = DAGCircuit() + v = QuantumRegister(1, "v") + flipped_cx_circuit.add_qreg(v) + flipped_cx_circuit.apply_operation_back(HGate(), [v[0]], []) + flipped_cx_circuit.apply_operation_back(XGate(), [v[0]], []) + + self.dag.substitute_node_with_dag(self.dag.op_nodes()[0], + flipped_cx_circuit) + expected = DAGCircuit() + qreg = QuantumRegister(3, 'qr') + creg = ClassicalRegister(2, 'cr') + expected.add_qreg(qreg) + expected.add_creg(creg) + expected.apply_operation_back(HGate(), [qreg[0]], []) + expected.apply_operation_back(XGate(), [qreg[0]], []) + expected.apply_operation_back(CXGate(), [qreg[0], qreg[1]], []) + expected.apply_operation_back(XGate(), [qreg[1]], []) + self.assertEqual(self.dag, expected) def test_substitute_circuit_one_back(self): """The method substitute_node_with_dag() replaces a leaf-in-the-back node with a DAG.""" diff --git a/tox.ini b/tox.ini index ae0df73e786f..c13c77c4a4c3 100644 --- a/tox.ini +++ b/tox.ini @@ -15,6 +15,7 @@ setenv = QISKIT_TEST_CAPTURE_STREAMS=1 deps = -r{toxinidir}/requirements.txt -r{toxinidir}/requirements-dev.txt + git+https://github.com/mtreinish/retworkx@test-stuff commands = stestr run {posargs} From acee0aba9a088f654e393f59238d0b369e25ac12 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Mon, 26 Apr 2021 16:49:22 -0400 Subject: [PATCH 02/20] DNM: Add retworkx from PR branch to CI --- azure-pipelines.yml | 9 +++++++++ tox.ini | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index ddff03f217d2..c9148b350cc6 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -189,6 +189,7 @@ stages: virtualenv test-job source test-job/bin/activate pip install -U -r requirements.txt -r requirements-dev.txt -c constraints.txt + pip install -U -c constraints.txt git+https://github.com/mtreinish/retworkx@substitute-node python setup.py sdist pip install -U -c constraints.txt dist/qiskit-terra*.tar.gz pip install -U "qiskit-aer" "z3-solver" -c constraints.txt @@ -267,6 +268,7 @@ stages: virtualenv test-job source test-job/bin/activate pip install -U -r requirements.txt -r requirements-dev.txt -c constraints.txt + pip install -U -c constraints.txt git+https://github.com/mtreinish/retworkx@substitute-node pip install -U -c constraints.txt -e . pip install -U "qiskit-aer" -c constraints.txt python setup.py build_ext --inplace @@ -319,6 +321,7 @@ stages: virtualenv test-job source test-job/bin/activate pip install -U -r requirements.txt -r requirements-dev.txt -c constraints.txt + pip install -U -c constraints.txt git+https://github.com/mtreinish/retworkx@substitute-node pip install -U -c constraints.txt -e . pip install -U "qiskit-aer" -c constraints.txt python setup.py build_ext --inplace @@ -388,6 +391,7 @@ stages: virtualenv test-job source test-job/Scripts/activate pip install -r requirements.txt -r requirements-dev.txt -c constraints.txt + pip install -U -c constraints.txt git+https://github.com/mtreinish/retworkx@substitute-node pip install -c constraints.txt -e . pip install "z3-solver" -c constraints.txt python setup.py build_ext --inplace @@ -472,6 +476,7 @@ stages: virtualenv test-job source test-job/Scripts/activate pip install -r requirements.txt -r requirements-dev.txt -c constraints.txt + pip install -U -c constraints.txt git+https://github.com/mtreinish/retworkx@substitute-node pip install -c constraints.txt -e . pip install "z3-solver" -c constraints.txt python setup.py build_ext --inplace @@ -563,6 +568,7 @@ stages: virtualenv test-job source test-job/bin/activate pip install -U -r requirements.txt -r requirements-dev.txt -c constraints.txt + pip install -U -c constraints.txt git+https://github.com/mtreinish/retworkx@substitute-node pip install -U -c constraints.txt -e . pip install -U "qiskit-aer" "z3-solver" -c constraints.txt python setup.py build_ext --inplace @@ -647,6 +653,7 @@ stages: virtualenv test-job source test-job/bin/activate pip install -U -r requirements.txt -r requirements-dev.txt -c constraints.txt + pip install -U -c constraints.txt git+https://github.com/mtreinish/retworkx@substitute-node pip install -U -c constraints.txt -e . python setup.py build_ext --inplace pip check @@ -763,6 +770,7 @@ stages: git clone https://github.com/Qiskit/qiskit-tutorials --depth=1 python -m pip install --upgrade pip pip install -U -r requirements.txt -r requirements-dev.txt -c constraints.txt + pip install -U -c constraints.txt git+https://github.com/mtreinish/retworkx@substitute-node pip install -c constraints.txt -e . # TODO: Remove aqua and ignis from source after next aqua release pip install "qiskit-ibmq-provider" "qiskit-aer" "z3-solver" "git+https://github.com/Qiskit/qiskit-ignis" "git+https://github.com/Qiskit/qiskit-aqua" "pyscf<1.7.4" "matplotlib<3.3.0" sphinx nbsphinx sphinx_rtd_theme cvxpy -c constraints.txt @@ -815,6 +823,7 @@ stages: set -e python -m pip install --upgrade pip pip install -U -r requirements.txt -c constraints.txt + pip install -U -c constraints.txt git+https://github.com/mtreinish/retworkx@substitute-node pip install -U -c constraints.txt -e . pip install -U "matplotlib<3.3.0" pylatexenc pillow python setup.py build_ext --inplace diff --git a/tox.ini b/tox.ini index c13c77c4a4c3..ffeb20dfd1ce 100644 --- a/tox.ini +++ b/tox.ini @@ -15,7 +15,7 @@ setenv = QISKIT_TEST_CAPTURE_STREAMS=1 deps = -r{toxinidir}/requirements.txt -r{toxinidir}/requirements-dev.txt - git+https://github.com/mtreinish/retworkx@test-stuff + git+https://github.com/mtreinish/retworkx@substitute-node commands = stestr run {posargs} From 2ee32169811996b89d93f3d065d80ab15f3732c0 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 3 Jun 2021 07:19:44 -0400 Subject: [PATCH 03/20] Fix handling of input dag with direct edge from input to output --- qiskit/dagcircuit/dagcircuit.py | 36 ++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index 9bbdee67c9a1..afae04df7f5f 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -1024,8 +1024,22 @@ def substitute_node_with_dag(self, node, input_dag, wires=None): "Mapped DAG would alter clbits " "on which it would be conditioned." ) + # Add wire from pred to succ if no ops on wire + for wire in wires: + input_node = in_dag.input_map[wire] + output_node = in_dag.output_map[wire] + if in_dag._multi_graph.has_edge(input_node._node_id, output_node._node_id): + self_wire = wire_map[wire] + pred = self._multi_graph.find_predecessors_by_edge( + node._node_id, lambda edge: edge == self_wire + )[0] + succ = self._multi_graph.find_successors_by_edge( + node._node_id, lambda edge: edge == self_wire + )[0] + self._multi_graph.add_edge(pred._node_id, succ._node_id, self_wire) + def filter_fn(node): - if node.type != 'op': + if node.type != "op": return False for qarg in node.qargs: if qarg not in wire_set: @@ -1038,18 +1052,24 @@ def edge_map_fn(source, target, self_wire): if source == node._node_id: wire_id = in_dag.output_map[wire]._node_id out_index = in_dag._multi_graph.predecessor_indices(wire_id)[0] + # Edge from input to output don't map (handled already) + if in_dag._multi_graph[out_index].type != "op": + return None # predecessor edge else: wire_id = in_dag.input_map[wire]._node_id out_index = in_dag._multi_graph.successor_indices(wire_id)[0] + # Edge from input to output don't map (handled already) + if in_dag._multi_graph[out_index].type != "op": + return None return out_index def edge_weight_map(wire): return wire_map[wire] node_map = self._multi_graph.substitute_node_with_subgraph( - node._node_id, in_dag._multi_graph, edge_map_fn, filter_fn, - edge_weight_map) + node._node_id, in_dag._multi_graph, edge_map_fn, filter_fn, edge_weight_map + ) # Iterate over nodes of input_circuit and update wires for old_node_index in node_map: @@ -1057,13 +1077,9 @@ def edge_weight_map(wire): new_node_index = node_map[old_node_index] old_node = in_dag._multi_graph[old_node_index] new_node = copy.copy(old_node) - condition = self._map_condition(wire_map, - old_node.op.condition, - self.cregs.values()) - m_qargs = list(map(lambda x: wire_map.get(x, x), - old_node.qargs)) - m_cargs = list(map(lambda x: wire_map.get(x, x), - old_node.cargs)) + condition = self._map_condition(wire_map, old_node.op.condition, self.cregs.values()) + m_qargs = list(map(lambda x: wire_map.get(x, x), old_node.qargs)) + m_cargs = list(map(lambda x: wire_map.get(x, x), old_node.cargs)) new_node.qargs = m_qargs new_node.cargs = m_cargs new_node._node_id = new_node_index From baa8f910f574d80bed38b194f1fee2499dab0c1e Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 3 Jun 2021 07:25:23 -0400 Subject: [PATCH 04/20] Update requirements for testing --- azure-pipelines.yml | 9 --------- requirements.txt | 2 +- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 7aef657b8786..a9eff49dab0d 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -187,7 +187,6 @@ stages: virtualenv test-job source test-job/bin/activate pip install -U -r requirements.txt -r requirements-dev.txt -c constraints.txt - pip install -U -c constraints.txt git+https://github.com/mtreinish/retworkx@substitute-node python setup.py sdist pip install -U -c constraints.txt dist/qiskit-terra*.tar.gz pip install -U "qiskit-aer" "z3-solver" -c constraints.txt @@ -266,7 +265,6 @@ stages: virtualenv test-job source test-job/bin/activate pip install -U -r requirements.txt -r requirements-dev.txt -c constraints.txt - pip install -U -c constraints.txt git+https://github.com/mtreinish/retworkx@substitute-node pip install -U -c constraints.txt -e . pip install -U "qiskit-aer" -c constraints.txt python setup.py build_ext --inplace @@ -319,7 +317,6 @@ stages: virtualenv test-job source test-job/bin/activate pip install -U -r requirements.txt -r requirements-dev.txt -c constraints.txt - pip install -U -c constraints.txt git+https://github.com/mtreinish/retworkx@substitute-node pip install -U -c constraints.txt -e . pip install -U "qiskit-aer" -c constraints.txt python setup.py build_ext --inplace @@ -389,7 +386,6 @@ stages: virtualenv test-job source test-job/Scripts/activate pip install -r requirements.txt -r requirements-dev.txt -c constraints.txt - pip install -U -c constraints.txt git+https://github.com/mtreinish/retworkx@substitute-node pip install -c constraints.txt -e . pip install "z3-solver" -c constraints.txt python setup.py build_ext --inplace @@ -474,7 +470,6 @@ stages: virtualenv test-job source test-job/Scripts/activate pip install -r requirements.txt -r requirements-dev.txt -c constraints.txt - pip install -U -c constraints.txt git+https://github.com/mtreinish/retworkx@substitute-node pip install -c constraints.txt -e . pip install "z3-solver" -c constraints.txt python setup.py build_ext --inplace @@ -566,7 +561,6 @@ stages: virtualenv test-job source test-job/bin/activate pip install -U -r requirements.txt -r requirements-dev.txt -c constraints.txt - pip install -U -c constraints.txt git+https://github.com/mtreinish/retworkx@substitute-node pip install -U -c constraints.txt -e . pip install -U "qiskit-aer" "z3-solver" -c constraints.txt python setup.py build_ext --inplace @@ -651,7 +645,6 @@ stages: virtualenv test-job source test-job/bin/activate pip install -U -r requirements.txt -r requirements-dev.txt -c constraints.txt - pip install -U -c constraints.txt git+https://github.com/mtreinish/retworkx@substitute-node pip install -U -c constraints.txt -e . python setup.py build_ext --inplace pip check @@ -768,7 +761,6 @@ stages: git clone https://github.com/Qiskit/qiskit-tutorials --depth=1 python -m pip install --upgrade pip pip install -U -r requirements.txt -r requirements-dev.txt -c constraints.txt - pip install -U -c constraints.txt git+https://github.com/mtreinish/retworkx@substitute-node pip install -c constraints.txt -e . # TODO: Remove aqua and ignis from source after next aqua release pip install "qiskit-ibmq-provider" "qiskit-aer" "z3-solver" "git+https://github.com/Qiskit/qiskit-ignis" "git+https://github.com/Qiskit/qiskit-aqua" "pyscf<1.7.4" "matplotlib<3.3.0" sphinx nbsphinx sphinx_rtd_theme cvxpy -c constraints.txt @@ -821,7 +813,6 @@ stages: set -e python -m pip install --upgrade pip pip install -U -r requirements.txt -c constraints.txt - pip install -U -c constraints.txt git+https://github.com/mtreinish/retworkx@substitute-node pip install -U -c constraints.txt -e . pip install -U "matplotlib<3.3.0" pylatexenc pillow python setup.py build_ext --inplace diff --git a/requirements.txt b/requirements.txt index 964c637b770f..90fba591ec67 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ contextvars>=2.4;python_version<'3.7' jsonschema>=2.6 -retworkx>=0.8.0 +retworkx @ git+https://github.com/mtreinish/retworkx@substitute-node numpy>=1.17 ply>=3.10 psutil>=5 From d26cf7d176fd356c52c0a6d114538222d70f10c6 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 3 Jun 2021 09:03:52 -0400 Subject: [PATCH 05/20] Run black --- test/python/dagcircuit/test_dagcircuit.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/test/python/dagcircuit/test_dagcircuit.py b/test/python/dagcircuit/test_dagcircuit.py index e91865043c52..4170e3905ea0 100644 --- a/test/python/dagcircuit/test_dagcircuit.py +++ b/test/python/dagcircuit/test_dagcircuit.py @@ -1136,10 +1136,10 @@ def test_substitute_circuit_one_middle(self): self.dag.substitute_node_with_dag(cx_node, flipped_cx_circuit, wires=[v[0], v[1]]) - self.assertEqual(self.dag.count_ops()['h'], 5) + self.assertEqual(self.dag.count_ops()["h"], 5) expected = DAGCircuit() - qreg = QuantumRegister(3, 'qr') - creg = ClassicalRegister(2, 'cr') + qreg = QuantumRegister(3, "qr") + creg = ClassicalRegister(2, "cr") expected.add_qreg(qreg) expected.add_creg(creg) expected.apply_operation_back(HGate(), [qreg[0]], []) @@ -1159,11 +1159,10 @@ def test_substitute_circuit_one_front(self): flipped_cx_circuit.apply_operation_back(HGate(), [v[0]], []) flipped_cx_circuit.apply_operation_back(XGate(), [v[0]], []) - self.dag.substitute_node_with_dag(self.dag.op_nodes()[0], - flipped_cx_circuit) + self.dag.substitute_node_with_dag(self.dag.op_nodes()[0], flipped_cx_circuit) expected = DAGCircuit() - qreg = QuantumRegister(3, 'qr') - creg = ClassicalRegister(2, 'cr') + qreg = QuantumRegister(3, "qr") + creg = ClassicalRegister(2, "cr") expected.add_qreg(qreg) expected.add_creg(creg) expected.apply_operation_back(HGate(), [qreg[0]], []) From ad51c041b7d3d245f70ca4141a0e61d06282a22e Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 3 Jun 2021 09:47:36 -0400 Subject: [PATCH 06/20] Avoid node copy --- qiskit/dagcircuit/dagcircuit.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index afae04df7f5f..0e6ca9fcb879 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -1076,14 +1076,13 @@ def edge_weight_map(wire): # update node attributes new_node_index = node_map[old_node_index] old_node = in_dag._multi_graph[old_node_index] - new_node = copy.copy(old_node) condition = self._map_condition(wire_map, old_node.op.condition, self.cregs.values()) m_qargs = list(map(lambda x: wire_map.get(x, x), old_node.qargs)) m_cargs = list(map(lambda x: wire_map.get(x, x), old_node.cargs)) - new_node.qargs = m_qargs - new_node.cargs = m_cargs - new_node._node_id = new_node_index - new_node.op.condition = condition + new_node = DAGNode( + "op", op=old_node._op, qargs=m_qargs, cargs=m_cargs, nid=new_node_index + ) + new_node._op.condition = condition self._multi_graph[new_node_index] = new_node def substitute_node(self, node, op, inplace=False): From 037258330e8af375d399966773168f013bddab70 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 3 Jun 2021 09:54:57 -0400 Subject: [PATCH 07/20] Avoid op func call overhead --- qiskit/dagcircuit/dagcircuit.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index 0e6ca9fcb879..b68e4a8b096b 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -959,7 +959,7 @@ def substitute_node_with_dag(self, node, input_dag, wires=None): DAGCircuitError: if met with unexpected predecessor/successors """ in_dag = input_dag - condition = None if node.type != "op" else node.op.condition + condition = None if node.type != "op" else node._op.condition # the dag must be amended if used in a # conditional context. delete the op nodes and replay @@ -970,12 +970,12 @@ def substitute_node_with_dag(self, node, input_dag, wires=None): to_replay = [] for sorted_node in in_dag.topological_nodes(): if sorted_node.type == "op": - sorted_node.op.condition = condition + sorted_node._op.condition = condition to_replay.append(sorted_node) for input_node in in_dag.op_nodes(): in_dag.remove_op_node(input_node) for replay_node in to_replay: - in_dag.apply_operation_back(replay_node.op, replay_node.qargs, replay_node.cargs) + in_dag.apply_operation_back(replay_node._op, replay_node.qargs, replay_node.cargs) if in_dag.global_phase: self.global_phase += in_dag.global_phase @@ -1076,7 +1076,7 @@ def edge_weight_map(wire): # update node attributes new_node_index = node_map[old_node_index] old_node = in_dag._multi_graph[old_node_index] - condition = self._map_condition(wire_map, old_node.op.condition, self.cregs.values()) + condition = self._map_condition(wire_map, old_node._op.condition, self.cregs.values()) m_qargs = list(map(lambda x: wire_map.get(x, x), old_node.qargs)) m_cargs = list(map(lambda x: wire_map.get(x, x), old_node.cargs)) new_node = DAGNode( From ca4217835bd501a15072520552fc1150737c6a18 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 3 Jun 2021 11:23:18 -0400 Subject: [PATCH 08/20] Fix lint --- qiskit/dagcircuit/dagcircuit.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index b68e4a8b096b..6aac0e4f14c0 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -1031,10 +1031,10 @@ def substitute_node_with_dag(self, node, input_dag, wires=None): if in_dag._multi_graph.has_edge(input_node._node_id, output_node._node_id): self_wire = wire_map[wire] pred = self._multi_graph.find_predecessors_by_edge( - node._node_id, lambda edge: edge == self_wire + node._node_id, lambda edge, wire=self_wire: edge == wire )[0] succ = self._multi_graph.find_successors_by_edge( - node._node_id, lambda edge: edge == self_wire + node._node_id, lambda edge, wire=self_wire: edge == wire )[0] self._multi_graph.add_edge(pred._node_id, succ._node_id, self_wire) @@ -1046,7 +1046,7 @@ def filter_fn(node): return False return True - def edge_map_fn(source, target, self_wire): + def edge_map_fn(source, _target, self_wire): wire = reverse_wire_map[self_wire] # successor edge if source == node._node_id: From b4e2efa868b3c195c824f5c6d82d374467264c92 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 3 Jun 2021 13:41:24 -0400 Subject: [PATCH 09/20] Expand substitute tests --- test/python/dagcircuit/test_dagcircuit.py | 29 ++++++++++++++++++----- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/test/python/dagcircuit/test_dagcircuit.py b/test/python/dagcircuit/test_dagcircuit.py index 4170e3905ea0..96a877058032 100644 --- a/test/python/dagcircuit/test_dagcircuit.py +++ b/test/python/dagcircuit/test_dagcircuit.py @@ -1153,13 +1153,13 @@ def test_substitute_circuit_one_middle(self): def test_substitute_circuit_one_front(self): """The method substitute_node_with_dag() replaces a leaf-in-the-front node with a DAG.""" - flipped_cx_circuit = DAGCircuit() + circuit = DAGCircuit() v = QuantumRegister(1, "v") - flipped_cx_circuit.add_qreg(v) - flipped_cx_circuit.apply_operation_back(HGate(), [v[0]], []) - flipped_cx_circuit.apply_operation_back(XGate(), [v[0]], []) + circuit.add_qreg(v) + circuit.apply_operation_back(HGate(), [v[0]], []) + circuit.apply_operation_back(XGate(), [v[0]], []) - self.dag.substitute_node_with_dag(self.dag.op_nodes()[0], flipped_cx_circuit) + self.dag.substitute_node_with_dag(self.dag.op_nodes()[0], circuit) expected = DAGCircuit() qreg = QuantumRegister(3, "qr") creg = ClassicalRegister(2, "cr") @@ -1173,7 +1173,24 @@ def test_substitute_circuit_one_front(self): def test_substitute_circuit_one_back(self): """The method substitute_node_with_dag() replaces a leaf-in-the-back node with a DAG.""" - pass + circuit = DAGCircuit() + v = QuantumRegister(1, "v") + circuit.add_qreg(v) + circuit.apply_operation_back(HGate(), [v[0]], []) + circuit.apply_operation_back(XGate(), [v[0]], []) + + self.dag.substitute_node_with_dag(self.dag.op_nodes()[2], circuit) + expected = DAGCircuit() + qreg = QuantumRegister(3, "qr") + creg = ClassicalRegister(2, "cr") + expected.add_qreg(qreg) + expected.add_creg(creg) + expected.apply_operation_back(HGate(), [qreg[0]], []) + expected.apply_operation_back(CXGate(), [qreg[0], qreg[1]], []) + expected.apply_operation_back(HGate(), [qreg[1]], []) + expected.apply_operation_back(XGate(), [qreg[1]], []) + + self.assertEqual(self.dag, expected) def test_raise_if_substituting_dag_modifies_its_conditional(self): """Verify that we raise if the input dag modifies any of the bits in node.condition.""" From 193e224f9c052f28d86a14afc7eda2417751b451 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 3 Jun 2021 18:03:04 -0400 Subject: [PATCH 10/20] Make failing test deterministic --- .../qasm/TestBasisTranslator_skip_target.qasm | 182 ++++++++++++++++++ .../transpiler/test_basis_translator.py | 33 +--- 2 files changed, 189 insertions(+), 26 deletions(-) create mode 100644 test/python/qasm/TestBasisTranslator_skip_target.qasm diff --git a/test/python/qasm/TestBasisTranslator_skip_target.qasm b/test/python/qasm/TestBasisTranslator_skip_target.qasm new file mode 100644 index 000000000000..da1f0e65848b --- /dev/null +++ b/test/python/qasm/TestBasisTranslator_skip_target.qasm @@ -0,0 +1,182 @@ +OPENQASM 2.0; +include "qelib1.inc"; +qreg q[5]; +rz(pi/2) q[0]; +sx q[0]; +rz(pi/2) q[0]; +rz(2.07822540000000) q[2]; +cx q[2],q[1]; +rz(-2.07822540000000) q[1]; +cx q[2],q[1]; +rz(2.07822540000000) q[1]; +cx q[0],q[1]; +cx q[1],q[0]; +cx q[0],q[1]; +cx q[1],q[2]; +cx q[2],q[1]; +cx q[1],q[2]; +cx q[3],q[2]; +rz(-pi/4) q[2]; +cx q[3],q[4]; +cx q[4],q[3]; +cx q[3],q[4]; +cx q[3],q[2]; +rz(pi/4) q[2]; +cx q[4],q[3]; +cx q[3],q[4]; +cx q[4],q[3]; +cx q[3],q[2]; +rz(-pi/4) q[2]; +rz(pi/4) q[3]; +cx q[3],q[4]; +cx q[4],q[3]; +cx q[3],q[4]; +cx q[3],q[2]; +rz(3*pi/4) q[2]; +sx q[2]; +rz(pi/2) q[2]; +cx q[3],q[4]; +rz(pi/4) q[3]; +rz(-pi/4) q[4]; +cx q[3],q[4]; +cx q[2],q[3]; +cx q[3],q[2]; +cx q[2],q[3]; +cx q[2],q[1]; +rz(0.486224710000000) q[1]; +cx q[2],q[1]; +rz(-pi) q[2]; +sx q[2]; +rz(3*pi/4) q[2]; +cx q[1],q[2]; +rz(pi/4) q[1]; +cx q[0],q[1]; +cx q[1],q[0]; +cx q[0],q[1]; +rz(pi/4) q[2]; +sx q[2]; +cx q[1],q[2]; +cx q[2],q[1]; +cx q[1],q[2]; +rz(pi/2) q[4]; +sx q[4]; +rz(pi/2) q[4]; +cx q[3],q[4]; +cx q[3],q[2]; +cx q[2],q[3]; +cx q[3],q[2]; +rz(-pi/4) q[4]; +cx q[3],q[4]; +cx q[2],q[3]; +cx q[3],q[2]; +cx q[2],q[3]; +rz(pi/4) q[4]; +cx q[3],q[4]; +rz(pi/4) q[3]; +cx q[3],q[2]; +cx q[2],q[3]; +cx q[3],q[2]; +rz(-pi/4) q[4]; +cx q[3],q[4]; +cx q[3],q[2]; +rz(-pi/4) q[2]; +rz(pi/4) q[3]; +cx q[3],q[2]; +rz(-3*pi/4) q[3]; +sx q[3]; +rz(-pi/2) q[3]; +cx q[2],q[3]; +cx q[3],q[2]; +cx q[2],q[3]; +rz(0.9066446) q[3]; +rz(3*pi/4) q[4]; +sx q[4]; +rz(-2.4791672) q[4]; +cx q[4],q[3]; +rz(0.90837085) q[3]; +sx q[3]; +rz(-1.8522083) q[3]; +sx q[3]; +cx q[4],q[3]; +sx q[3]; +rz(-1.8522083) q[3]; +sx q[3]; +rz(-2.6004136) q[3]; +cx q[3],q[2]; +rz(-pi/4) q[2]; +cx q[2],q[1]; +cx q[1],q[2]; +cx q[2],q[1]; +cx q[0],q[1]; +rz(pi/4) q[1]; +cx q[3],q[2]; +cx q[2],q[3]; +cx q[3],q[2]; +cx q[2],q[1]; +rz(-pi/4) q[1]; +cx q[0],q[1]; +rz(3*pi/4) q[1]; +sx q[1]; +rz(pi/2) q[1]; +rz(pi/4) q[2]; +cx q[2],q[1]; +cx q[1],q[2]; +cx q[2],q[1]; +cx q[0],q[1]; +rz(pi/4) q[0]; +rz(-pi/4) q[1]; +cx q[0],q[1]; +rz(2.6059615) q[0]; +sx q[0]; +rz(-1.7104962) q[0]; +sx q[0]; +rz(-0.49205251) q[0]; +cx q[1],q[2]; +rz(pi/2) q[1]; +sx q[1]; +rz(pi/2) q[1]; +cx q[2],q[1]; +rz(-pi/4) q[1]; +cx q[1],q[2]; +cx q[2],q[1]; +cx q[1],q[2]; +rz(-pi/2) q[4]; +sx q[4]; +rz(-pi/2) q[4]; +cx q[3],q[4]; +cx q[3],q[2]; +rz(pi/4) q[2]; +cx q[1],q[2]; +rz(pi/4) q[1]; +rz(-pi/4) q[2]; +cx q[3],q[2]; +rz(3*pi/4) q[2]; +sx q[2]; +rz(pi/2) q[2]; +cx q[2],q[3]; +cx q[3],q[2]; +cx q[2],q[3]; +cx q[2],q[1]; +rz(-pi/4) q[1]; +rz(pi/4) q[2]; +cx q[2],q[1]; +cx q[1],q[2]; +cx q[2],q[1]; +cx q[1],q[2]; +rz(-2.499918) q[1]; +cx q[3],q[2]; +rz(-0.78925375) q[2]; +cx q[1],q[2]; +rz(-0.6416747) q[2]; +sx q[2]; +rz(-1.7578332) q[2]; +sx q[2]; +cx q[1],q[2]; +sx q[2]; +rz(-1.7578332) q[2]; +sx q[2]; +rz(1.4309284) q[2]; +rz(1.264262) q[4]; +sx q[4]; +rz(pi/2) q[4]; +cx q[4],q[3]; diff --git a/test/python/transpiler/test_basis_translator.py b/test/python/transpiler/test_basis_translator.py index e113e34694d4..ec9627758f8f 100644 --- a/test/python/transpiler/test_basis_translator.py +++ b/test/python/transpiler/test_basis_translator.py @@ -13,6 +13,7 @@ """Test the BasisTranslator pass""" +import os from numpy import pi @@ -821,37 +822,17 @@ def test_condition_set_substitute_node(self): def test_skip_target_basis_equivalences_1(self): """Test that BasisTranslator skips gates in the target_basis - #6085""" - qstr = 'OPENQASM 2.0; \ - include "qelib1.inc"; \ - qreg q[5]; \ - cu1(4.1564508) q[2],q[3]; \ - ccx q[0],q[1],q[4]; \ - rzz(0.48622471) q[0],q[2]; \ - ccx q[3],q[4],q[1]; \ - ch q[2],q[0]; \ - y q[3]; \ - cu3(2.5787688,1.3265772,3.1398664) q[1],q[4]; \ - id q[0]; \ - y q[1]; \ - t q[2]; \ - t q[3]; \ - tdg q[4]; \ - cz q[0],q[1]; \ - ccx q[2],q[4],q[3]; \ - rx(5.976651) q[1]; \ - u3(4.8520889,3.6025647,5.7475542) q[2]; \ - cswap q[0],q[3],q[4]; \ - rz(2.1885681) q[2]; \ - cu3(2.7675189,4.5725211,2.9940136) q[0],q[3]; \ - cx q[1],q[4];' circ = QuantumCircuit() - circ = circ.from_qasm_str(qstr) + qasm_file = os.path.join( + os.path.dirname(os.path.dirname(os.path.abspath(__file__))), + "qasm", + "TestBasisTranslator_skip_target.qasm", + ) + circ = circ.from_qasm_file(qasm_file) circ_transpiled = transpile( circ, - backend=FakeAthens(), basis_gates=["id", "rz", "sx", "x", "cx"], - routing_method="sabre", seed_transpiler=42, ) self.assertEqual(circ_transpiled.count_ops(), {"cx": 91, "rz": 66, "sx": 22}) From df153d52b318426b10fd99a16605fddd4ceeb3f6 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Fri, 4 Jun 2021 06:19:14 -0400 Subject: [PATCH 11/20] Fix qasm example --- .../qasm/TestBasisTranslator_skip_target.qasm | 221 ++++++------------ 1 file changed, 69 insertions(+), 152 deletions(-) diff --git a/test/python/qasm/TestBasisTranslator_skip_target.qasm b/test/python/qasm/TestBasisTranslator_skip_target.qasm index da1f0e65848b..6cfc5b264e16 100644 --- a/test/python/qasm/TestBasisTranslator_skip_target.qasm +++ b/test/python/qasm/TestBasisTranslator_skip_target.qasm @@ -1,182 +1,99 @@ OPENQASM 2.0; include "qelib1.inc"; qreg q[5]; -rz(pi/2) q[0]; -sx q[0]; -rz(pi/2) q[0]; -rz(2.07822540000000) q[2]; -cx q[2],q[1]; -rz(-2.07822540000000) q[1]; -cx q[2],q[1]; -rz(2.07822540000000) q[1]; -cx q[0],q[1]; -cx q[1],q[0]; -cx q[0],q[1]; -cx q[1],q[2]; -cx q[2],q[1]; -cx q[1],q[2]; +h q[0]; +cu1(4.1564508) q[2],q[1]; +swap q[0],q[1]; +swap q[1],q[2]; cx q[3],q[2]; -rz(-pi/4) q[2]; -cx q[3],q[4]; -cx q[4],q[3]; -cx q[3],q[4]; +tdg q[2]; +swap q[3],q[4]; cx q[3],q[2]; -rz(pi/4) q[2]; -cx q[4],q[3]; -cx q[3],q[4]; -cx q[4],q[3]; +t q[2]; +swap q[4],q[3]; cx q[3],q[2]; -rz(-pi/4) q[2]; -rz(pi/4) q[3]; -cx q[3],q[4]; -cx q[4],q[3]; -cx q[3],q[4]; +tdg q[2]; +t q[3]; +swap q[3],q[4]; cx q[3],q[2]; -rz(3*pi/4) q[2]; -sx q[2]; -rz(pi/2) q[2]; +t q[2]; +h q[2]; cx q[3],q[4]; -rz(pi/4) q[3]; -rz(-pi/4) q[4]; +t q[3]; +tdg q[4]; cx q[3],q[4]; -cx q[2],q[3]; -cx q[3],q[2]; -cx q[2],q[3]; -cx q[2],q[1]; -rz(0.486224710000000) q[1]; -cx q[2],q[1]; -rz(-pi) q[2]; -sx q[2]; -rz(3*pi/4) q[2]; -cx q[1],q[2]; -rz(pi/4) q[1]; -cx q[0],q[1]; -cx q[1],q[0]; -cx q[0],q[1]; -rz(pi/4) q[2]; -sx q[2]; -cx q[1],q[2]; -cx q[2],q[1]; -cx q[1],q[2]; -rz(pi/2) q[4]; -sx q[4]; -rz(pi/2) q[4]; +swap q[2],q[3]; +rzz(0.48622471) q[2],q[1]; +ch q[1],q[2]; +t q[1]; +swap q[0],q[1]; +id q[2]; +swap q[1],q[2]; +h q[4]; cx q[3],q[4]; -cx q[3],q[2]; -cx q[2],q[3]; -cx q[3],q[2]; -rz(-pi/4) q[4]; +swap q[3],q[2]; +tdg q[4]; cx q[3],q[4]; -cx q[2],q[3]; -cx q[3],q[2]; -cx q[2],q[3]; -rz(pi/4) q[4]; +swap q[2],q[3]; +t q[4]; cx q[3],q[4]; -rz(pi/4) q[3]; -cx q[3],q[2]; -cx q[2],q[3]; -cx q[3],q[2]; -rz(-pi/4) q[4]; +t q[3]; +swap q[3],q[2]; +tdg q[4]; cx q[3],q[4]; cx q[3],q[2]; -rz(-pi/4) q[2]; -rz(pi/4) q[3]; +tdg q[2]; +t q[3]; cx q[3],q[2]; -rz(-3*pi/4) q[3]; -sx q[3]; -rz(-pi/2) q[3]; -cx q[2],q[3]; -cx q[3],q[2]; -cx q[2],q[3]; -rz(0.9066446) q[3]; -rz(3*pi/4) q[4]; -sx q[4]; -rz(-2.4791672) q[4]; -cx q[4],q[3]; -rz(0.90837085) q[3]; -sx q[3]; -rz(-1.8522083) q[3]; -sx q[3]; -cx q[4],q[3]; -sx q[3]; -rz(-1.8522083) q[3]; -sx q[3]; -rz(-2.6004136) q[3]; +y q[3]; +t q[3]; +h q[3]; +swap q[2],q[3]; +t q[4]; +h q[4]; +cu3(2.5787688,1.3265772,3.1398664) q[4],q[3]; +tdg q[3]; cx q[3],q[2]; -rz(-pi/4) q[2]; -cx q[2],q[1]; -cx q[1],q[2]; -cx q[2],q[1]; +tdg q[2]; +swap q[2],q[1]; cx q[0],q[1]; -rz(pi/4) q[1]; -cx q[3],q[2]; -cx q[2],q[3]; -cx q[3],q[2]; +t q[1]; +swap q[3],q[2]; cx q[2],q[1]; -rz(-pi/4) q[1]; +tdg q[1]; cx q[0],q[1]; -rz(3*pi/4) q[1]; -sx q[1]; -rz(pi/2) q[1]; -rz(pi/4) q[2]; -cx q[2],q[1]; -cx q[1],q[2]; -cx q[2],q[1]; +t q[1]; +h q[1]; +t q[2]; +swap q[2],q[1]; cx q[0],q[1]; -rz(pi/4) q[0]; -rz(-pi/4) q[1]; +t q[0]; +tdg q[1]; cx q[0],q[1]; -rz(2.6059615) q[0]; -sx q[0]; -rz(-1.7104962) q[0]; -sx q[0]; -rz(-0.49205251) q[0]; -cx q[1],q[2]; -rz(pi/2) q[1]; -sx q[1]; -rz(pi/2) q[1]; -cx q[2],q[1]; -rz(-pi/4) q[1]; +u3(4.8520889,3.6025647,5.7475542) q[0]; +rz(2.1885681) q[0]; cx q[1],q[2]; +h q[1]; cx q[2],q[1]; -cx q[1],q[2]; -rz(-pi/2) q[4]; -sx q[4]; -rz(-pi/2) q[4]; -cx q[3],q[4]; +tdg q[1]; +swap q[1],q[2]; +y q[4]; +cz q[3],q[4]; cx q[3],q[2]; -rz(pi/4) q[2]; +t q[2]; cx q[1],q[2]; -rz(pi/4) q[1]; -rz(-pi/4) q[2]; +t q[1]; +tdg q[2]; cx q[3],q[2]; -rz(3*pi/4) q[2]; -sx q[2]; -rz(pi/2) q[2]; -cx q[2],q[3]; -cx q[3],q[2]; -cx q[2],q[3]; -cx q[2],q[1]; -rz(-pi/4) q[1]; -rz(pi/4) q[2]; +t q[2]; +h q[2]; +swap q[2],q[3]; cx q[2],q[1]; -cx q[1],q[2]; +tdg q[1]; +t q[2]; cx q[2],q[1]; -cx q[1],q[2]; -rz(-2.499918) q[1]; +swap q[1],q[2]; cx q[3],q[2]; -rz(-0.78925375) q[2]; -cx q[1],q[2]; -rz(-0.6416747) q[2]; -sx q[2]; -rz(-1.7578332) q[2]; -sx q[2]; -cx q[1],q[2]; -sx q[2]; -rz(-1.7578332) q[2]; -sx q[2]; -rz(1.4309284) q[2]; -rz(1.264262) q[4]; -sx q[4]; -rz(pi/2) q[4]; +cu3(2.7675189,4.5725211,2.9940136) q[1],q[2]; +rx(5.976651) q[4]; cx q[4],q[3]; From c0fc6555a3a9e176bf3b9606ea6a15ff65494778 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Fri, 4 Jun 2021 06:48:26 -0400 Subject: [PATCH 12/20] Fix lint --- test/python/transpiler/test_basis_translator.py | 1 - 1 file changed, 1 deletion(-) diff --git a/test/python/transpiler/test_basis_translator.py b/test/python/transpiler/test_basis_translator.py index ec9627758f8f..4e65d38935da 100644 --- a/test/python/transpiler/test_basis_translator.py +++ b/test/python/transpiler/test_basis_translator.py @@ -27,7 +27,6 @@ from qiskit.quantum_info import Operator from qiskit.transpiler.exceptions import TranspilerError from qiskit.transpiler.passes.basis import BasisTranslator, UnrollCustomDefinitions -from qiskit.test.mock import FakeAthens from qiskit.circuit.library.standard_gates.equivalence_library import ( From 0b5d4acd291716cb2899778a8fda52f48cf45292 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Fri, 25 Jun 2021 07:52:43 -0400 Subject: [PATCH 13/20] Update retworkx source path --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 90fba591ec67..b0171e319689 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ contextvars>=2.4;python_version<'3.7' jsonschema>=2.6 -retworkx @ git+https://github.com/mtreinish/retworkx@substitute-node +retworkx @ git+https://github.com/Qiskit/retworkx numpy>=1.17 ply>=3.10 psutil>=5 From 84d03a060e8ac812a78cce84530d3c01b8d96bcd Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 5 Aug 2021 09:28:56 -0400 Subject: [PATCH 14/20] Fix rebase issues --- qiskit/dagcircuit/dagcircuit.py | 17 ++++++++--------- tox.ini | 1 - 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index 778b9ceca0a3..6200987d949e 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -961,7 +961,7 @@ def substitute_node_with_dag(self, node, input_dag, wires=None): for input_node in in_dag.op_nodes(): in_dag.remove_op_node(input_node) for replay_node in to_replay: - in_dag.apply_operation_back(replay_node._op, replay_node.qargs, replay_node.cargs) + in_dag.apply_operation_back(replay_node.op, replay_node.qargs, replay_node.cargs) if in_dag.global_phase: self.global_phase += in_dag.global_phase @@ -1025,7 +1025,7 @@ def substitute_node_with_dag(self, node, input_dag, wires=None): self._multi_graph.add_edge(pred._node_id, succ._node_id, self_wire) def filter_fn(node): - if node.type != "op": + if not isinstance(node, DAGOpNode): return False for qarg in node.qargs: if qarg not in wire_set: @@ -1039,14 +1039,14 @@ def edge_map_fn(source, _target, self_wire): wire_id = in_dag.output_map[wire]._node_id out_index = in_dag._multi_graph.predecessor_indices(wire_id)[0] # Edge from input to output don't map (handled already) - if in_dag._multi_graph[out_index].type != "op": + if not isinstance(in_dag._multi_graph[out_index], DAGOpNode): return None # predecessor edge else: wire_id = in_dag.input_map[wire]._node_id out_index = in_dag._multi_graph.successor_indices(wire_id)[0] # Edge from input to output don't map (handled already) - if in_dag._multi_graph[out_index].type != "op": + if not isinstance(in_dag._multi_graph[out_index], DAGOpNode): return None return out_index @@ -1062,13 +1062,12 @@ def edge_weight_map(wire): # update node attributes new_node_index = node_map[old_node_index] old_node = in_dag._multi_graph[old_node_index] - condition = self._map_condition(wire_map, old_node._op.condition, self.cregs.values()) + condition = self._map_condition(wire_map, old_node.op.condition, self.cregs.values()) m_qargs = list(map(lambda x: wire_map.get(x, x), old_node.qargs)) m_cargs = list(map(lambda x: wire_map.get(x, x), old_node.cargs)) - new_node = DAGNode( - "op", op=old_node._op, qargs=m_qargs, cargs=m_cargs, nid=new_node_index - ) - new_node._op.condition = condition + new_node = DAGOpNode(old_node.op, qargs=m_qargs, cargs=m_cargs) + new_node._node_id = new_node_index + new_node.op.condition = condition self._multi_graph[new_node_index] = new_node def substitute_node(self, node, op, inplace=False): diff --git a/tox.ini b/tox.ini index 4837e9533161..1dfdaecfb28d 100644 --- a/tox.ini +++ b/tox.ini @@ -15,7 +15,6 @@ setenv = QISKIT_TEST_CAPTURE_STREAMS=1 deps = -r{toxinidir}/requirements.txt -r{toxinidir}/requirements-dev.txt - git+https://github.com/mtreinish/retworkx@substitute-node commands = stestr run {posargs} From 4e926cc95041075bbcaa48c03e714ab780730e54 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 26 Aug 2021 07:51:12 -0400 Subject: [PATCH 15/20] Bump minimum retworkx version to latest release --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index abc5b67133b6..7791d955adee 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ contextvars>=2.4;python_version<'3.7' -retworkx @ git+https://github.com/Qiskit/retworkx +retworkx>=0.10.1 numpy>=1.17 ply>=3.10 psutil>=5 From f571561850476cf106ca7c10e2153ced1face140 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 26 Aug 2021 19:24:52 -0400 Subject: [PATCH 16/20] Reduce iterations building wire maps --- qiskit/dagcircuit/dagcircuit.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index 2d892dc2c91d..6cd45f874f9c 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -992,8 +992,11 @@ def substitute_node_with_dag(self, node, input_dag, wires=None): new_wires = list(node.qargs) + list(node.cargs) + list(condition_bit_list) - wire_map = dict(zip(wires, new_wires)) - reverse_wire_map = dict(zip(new_wires, wires)) + wire_map = {} + reverse_wire_map = {} + for wire, new_wire in zip(wires, new_wires): + wire_map[wire] = new_wire + reverse_wire_map[new_wire] = wire self._check_wiremap_validity(wire_map, wires, self.input_map) if condition_bit_list: From 9e0118bf1107268fa322349757bb8d27eaae44db Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Fri, 27 Aug 2021 09:12:40 -0400 Subject: [PATCH 17/20] Use a plain list comprehension instead of a lambda map --- qiskit/dagcircuit/dagcircuit.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index 6cd45f874f9c..e101bfcb8fa4 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -1066,8 +1066,8 @@ def edge_weight_map(wire): new_node_index = node_map[old_node_index] old_node = in_dag._multi_graph[old_node_index] condition = self._map_condition(wire_map, old_node.op.condition, self.cregs.values()) - m_qargs = list(map(lambda x: wire_map.get(x, x), old_node.qargs)) - m_cargs = list(map(lambda x: wire_map.get(x, x), old_node.cargs)) + m_qargs = [wire_map.get(x, x) for x in old_node.qargs] + m_cargs = [wire_map.get(x, x) for x in old_node.cargs] new_node = DAGOpNode(old_node.op, qargs=m_qargs, cargs=m_cargs) new_node._node_id = new_node_index new_node.op.condition = condition From 9f502363dc3f971b00c45ca215bcebac08b0099a Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Mon, 30 Aug 2021 12:54:46 -0400 Subject: [PATCH 18/20] Apply suggestions from code review Co-authored-by: Kevin Krsulich --- qiskit/dagcircuit/dagcircuit.py | 4 ++-- test/python/dagcircuit/test_dagcircuit.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index 2dfb6727b9dd..74ccfca368e1 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -1039,8 +1039,8 @@ def edge_map_fn(source, _target, self_wire): wire = reverse_wire_map[self_wire] # successor edge if source == node._node_id: - wire_id = in_dag.output_map[wire]._node_id - out_index = in_dag._multi_graph.predecessor_indices(wire_id)[0] + wire_output_id = in_dag.output_map[wire]._node_id + out_index = in_dag._multi_graph.predecessor_indices(wire_output_id)[0] # Edge from input to output don't map (handled already) if not isinstance(in_dag._multi_graph[out_index], DAGOpNode): return None diff --git a/test/python/dagcircuit/test_dagcircuit.py b/test/python/dagcircuit/test_dagcircuit.py index be525eac334b..178c0ef74189 100644 --- a/test/python/dagcircuit/test_dagcircuit.py +++ b/test/python/dagcircuit/test_dagcircuit.py @@ -1208,7 +1208,7 @@ def test_substitute_circuit_one_front(self): circuit.apply_operation_back(HGate(), [v[0]], []) circuit.apply_operation_back(XGate(), [v[0]], []) - self.dag.substitute_node_with_dag(self.dag.op_nodes()[0], circuit) + self.dag.substitute_node_with_dag(next(self.dag.topological_op_nodes()), circuit) expected = DAGCircuit() qreg = QuantumRegister(3, "qr") creg = ClassicalRegister(2, "cr") @@ -1228,7 +1228,7 @@ def test_substitute_circuit_one_back(self): circuit.apply_operation_back(HGate(), [v[0]], []) circuit.apply_operation_back(XGate(), [v[0]], []) - self.dag.substitute_node_with_dag(self.dag.op_nodes()[2], circuit) + self.dag.substitute_node_with_dag(list(self.dag.topological_op_nodes())[2], circuit) expected = DAGCircuit() qreg = QuantumRegister(3, "qr") creg = ClassicalRegister(2, "cr") From 7a12eac370e00c0feec90447a842b82f9810b436 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Mon, 30 Aug 2021 13:19:31 -0400 Subject: [PATCH 19/20] Improve code comments --- qiskit/dagcircuit/dagcircuit.py | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index 74ccfca368e1..c5a97c09113f 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -1013,7 +1013,13 @@ def substitute_node_with_dag(self, node, input_dag, wires=None): "Mapped DAG would alter clbits on which it would be conditioned." ) - # Add wire from pred to succ if no ops on wire + # Add wire from pred to succ if no ops on mapped wire on ``in_dag`` + # retworkx's substitute_node_with_subgraph lacks the DAGCircuit + # context to know what to do in this case (the method won't even see + # these nodes because they're filtered) so we manually retain the + # edges prior to calling substitute_node_with_subgraph and set the + # edge_map_fn callback kwarg to skip these edges when they're + # encountered. for wire in wires: input_node = in_dag.input_map[wire] output_node = in_dag.output_map[wire] @@ -1027,6 +1033,8 @@ def substitute_node_with_dag(self, node, input_dag, wires=None): )[0] self._multi_graph.add_edge(pred._node_id, succ._node_id, self_wire) + # Exlude any nodes from in_dag that are not a DAGOpNode or are on + # bits outside the set specified by the wires kwarg def filter_fn(node): if not isinstance(node, DAGOpNode): return False @@ -1035,24 +1043,30 @@ def filter_fn(node): return False return True + # Map edges into and out of node to the appropriate node from in_dag def edge_map_fn(source, _target, self_wire): wire = reverse_wire_map[self_wire] # successor edge if source == node._node_id: wire_output_id = in_dag.output_map[wire]._node_id out_index = in_dag._multi_graph.predecessor_indices(wire_output_id)[0] - # Edge from input to output don't map (handled already) + # Edge directly from from input nodes to output nodes in in_dag are + # already handled prior to calling retworkx. Don't map these edges + # in retworkx. if not isinstance(in_dag._multi_graph[out_index], DAGOpNode): return None # predecessor edge else: - wire_id = in_dag.input_map[wire]._node_id - out_index = in_dag._multi_graph.successor_indices(wire_id)[0] - # Edge from input to output don't map (handled already) + wire_input_id = in_dag.input_map[wire]._node_id + out_index = in_dag._multi_graph.successor_indices(wire_input_id)[0] + # Edge directly from from input nodes to output nodes in in_dag are + # already handled prior to calling retworkx. Don't map these edges + # in retworkx. if not isinstance(in_dag._multi_graph[out_index], DAGOpNode): return None return out_index + # Adjust edge weights from in_dag def edge_weight_map(wire): return wire_map[wire] @@ -1060,10 +1074,10 @@ def edge_weight_map(wire): node._node_id, in_dag._multi_graph, edge_map_fn, filter_fn, edge_weight_map ) - # Iterate over nodes of input_circuit and update wires - for old_node_index in node_map: + # Iterate over nodes of input_circuit and update wiires in node objects migrated + # from in_dag + for old_node_index, new_node_index in node_map.items(): # update node attributes - new_node_index = node_map[old_node_index] old_node = in_dag._multi_graph[old_node_index] condition = self._map_condition(wire_map, old_node.op.condition, self.cregs.values()) m_qargs = [wire_map.get(x, x) for x in old_node.qargs] From c8d43f5ee1ac0d2482d57153c7b125c339d1bc51 Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Tue, 31 Aug 2021 12:26:54 +0100 Subject: [PATCH 20/20] Add reno touting performance benefits --- ...ubstitute_node_with_dag-speedup-d7d1f0d33716131d.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 releasenotes/notes/retworkx-substitute_node_with_dag-speedup-d7d1f0d33716131d.yaml diff --git a/releasenotes/notes/retworkx-substitute_node_with_dag-speedup-d7d1f0d33716131d.yaml b/releasenotes/notes/retworkx-substitute_node_with_dag-speedup-d7d1f0d33716131d.yaml new file mode 100644 index 000000000000..bac8c876fd6a --- /dev/null +++ b/releasenotes/notes/retworkx-substitute_node_with_dag-speedup-d7d1f0d33716131d.yaml @@ -0,0 +1,9 @@ +--- +features: + - | + Various transpilation internals now use new features in `retworkx + `__ 0.10 when operating on the internal + circuit representation. This can often result in speedups in calls to + :obj:`~qiskit.transpile` of around 10-40%, with greater effects at higher + optimisation levels. See `#6302 + `__ for more details.