diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 543ed74143fa..b5e1b146172a 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -50,6 +50,8 @@ Added - ``execute_circuits()`` and ``assemble_circuits()`` allow setting a qobj_header of type QobjHeader to add extra information to the qobj (and thus result). - Register indexing supports negative indices (#1875) +- Added new resource estimation passes: ``Depth``, ``Width``, ``Size``, ``CountOps``, and + ``NumTensorFactors``, all grouped in the ``ResourceEstimation`` analysis pass. - Added ``nodes_on_wire()`` to DAGCircuit which returns an iterator over all the operations on the given wire - Added new properties to an Instruction: @@ -61,7 +63,6 @@ Added - Added an ``Instruction.mirror()`` method that mirrors a composite instruction (reverses its sub-instructions) (#1816). - Changed ------- diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index f008e9bdf19f..cab1380657b2 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -631,7 +631,8 @@ def depth(self): if not nx.is_directed_acyclic_graph(self.multi_graph): raise DAGCircuitError("not a DAG") - return nx.dag_longest_path_length(self.multi_graph) - 1 + depth = nx.dag_longest_path_length(self.multi_graph) - 1 + return depth if depth != -1 else 0 def width(self): """Return the total number of qubits used by the circuit.""" diff --git a/qiskit/transpiler/passes/__init__.py b/qiskit/transpiler/passes/__init__.py index 75970952db6a..dbd4912fa1a8 100644 --- a/qiskit/transpiler/passes/__init__.py +++ b/qiskit/transpiler/passes/__init__.py @@ -10,6 +10,12 @@ from .unroller import Unroller from .cx_cancellation import CXCancellation from .fixed_point import FixedPoint +from .resource_estimation import ResourceEstimation +from .depth import Depth +from .size import Size +from .width import Width +from .count_ops import CountOps +from .num_tensor_factors import NumTensorFactors from .dag_fixed_point import DAGFixedPoint from .optimize_1q_gates import Optimize1qGates from .decompose import Decompose diff --git a/qiskit/transpiler/passes/count_ops.py b/qiskit/transpiler/passes/count_ops.py new file mode 100644 index 000000000000..490af95ebaf7 --- /dev/null +++ b/qiskit/transpiler/passes/count_ops.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- + +# Copyright 2019, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. + +""" An analysis pass for counting operations in a DAG circuit. +""" +from qiskit.transpiler.basepasses import AnalysisPass + + +class CountOps(AnalysisPass): + """ An analysis pass for counting operations in a DAG circuit. + """ + + def run(self, dag): + self.property_set['count_ops'] = dag.count_ops() diff --git a/qiskit/transpiler/passes/depth.py b/qiskit/transpiler/passes/depth.py new file mode 100644 index 000000000000..368b90114695 --- /dev/null +++ b/qiskit/transpiler/passes/depth.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- + +# Copyright 2019, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. + +""" An analysis pass for calculating the depth of a DAG circuit. +""" +from qiskit.transpiler.basepasses import AnalysisPass + + +class Depth(AnalysisPass): + """ An analysis pass for calculating the depth of a DAG circuit. + """ + + def run(self, dag): + self.property_set['depth'] = dag.depth() diff --git a/qiskit/transpiler/passes/num_tensor_factors.py b/qiskit/transpiler/passes/num_tensor_factors.py new file mode 100644 index 000000000000..738edcd04d5c --- /dev/null +++ b/qiskit/transpiler/passes/num_tensor_factors.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- + +# Copyright 2019, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. + +""" An analysis pass for calculating the number of tensor factors of a DAG circuit. +""" +from qiskit.transpiler.basepasses import AnalysisPass + + +class NumTensorFactors(AnalysisPass): + """ An analysis pass for calculating the number of tensor factors of a DAG circuit. + """ + + def run(self, dag): + self.property_set['num_tensor_factors'] = dag.num_tensor_factors() diff --git a/qiskit/transpiler/passes/resource_estimation.py b/qiskit/transpiler/passes/resource_estimation.py new file mode 100644 index 000000000000..8983660e43b3 --- /dev/null +++ b/qiskit/transpiler/passes/resource_estimation.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- + +# Copyright 2019, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. + +""" An analysis pass for automatically running Depth(), Width(), Size(), CountOps(), and +Tensor_Factor() +""" +from qiskit.transpiler.basepasses import AnalysisPass +from qiskit.transpiler.passes.depth import Depth +from qiskit.transpiler.passes.width import Width +from qiskit.transpiler.passes.size import Size +from qiskit.transpiler.passes.count_ops import CountOps +from qiskit.transpiler.passes.num_tensor_factors import NumTensorFactors + + +class ResourceEstimation(AnalysisPass): + """ Requires Depth(), Width(), Size(), CountOps(), and NumTensorFactors(). + """ + + def __init__(self): + super().__init__() + self.requires += [Depth(), Width(), Size(), CountOps(), NumTensorFactors()] + + def run(self, _): + pass diff --git a/qiskit/transpiler/passes/size.py b/qiskit/transpiler/passes/size.py new file mode 100644 index 000000000000..5f8cd759b57f --- /dev/null +++ b/qiskit/transpiler/passes/size.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- + +# Copyright 2019, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. + +""" An analysis pass for calculating the size of a DAG circuit. +""" +from qiskit.transpiler.basepasses import AnalysisPass + + +class Size(AnalysisPass): + """ An analysis pass for calculating the size of a DAG circuit. + """ + + def run(self, dag): + self.property_set['size'] = dag.size() diff --git a/qiskit/transpiler/passes/width.py b/qiskit/transpiler/passes/width.py new file mode 100644 index 000000000000..2ba81c19112b --- /dev/null +++ b/qiskit/transpiler/passes/width.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- + +# Copyright 2019, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. + +""" An analysis pass for calculating the width of a DAG circuit. +""" +from qiskit.transpiler.basepasses import AnalysisPass + + +class Width(AnalysisPass): + """ An analysis pass for calculating the width of a DAG circuit. + """ + + def run(self, dag): + self.property_set['width'] = dag.width() diff --git a/test/python/transpiler/test_count_ops_pass.py b/test/python/transpiler/test_count_ops_pass.py new file mode 100644 index 000000000000..bb247eedefa8 --- /dev/null +++ b/test/python/transpiler/test_count_ops_pass.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- + +# Copyright 2019, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. + +"""Depth pass testing""" + +import unittest + +from qiskit import QuantumCircuit, QuantumRegister +from qiskit.converters import circuit_to_dag +from qiskit.transpiler.passes import CountOps +from qiskit.test import QiskitTestCase + + +class TestCountOpsPass(QiskitTestCase): + """ Tests for CountOps analysis methods. """ + + def test_empty_dag(self): + """ Empty DAG has empty counts.""" + circuit = QuantumCircuit() + dag = circuit_to_dag(circuit) + + pass_ = CountOps() + _ = pass_.run(dag) + + self.assertDictEqual(pass_.property_set['count_ops'], {}) + + def test_just_qubits(self): + """ A dag with 8 operations (6 CXs and 2 Hs)""" + qr = QuantumRegister(2) + circuit = QuantumCircuit(qr) + circuit.h(qr[0]) + circuit.h(qr[1]) + circuit.cx(qr[0], qr[1]) + circuit.cx(qr[0], qr[1]) + circuit.cx(qr[0], qr[1]) + circuit.cx(qr[0], qr[1]) + circuit.cx(qr[1], qr[0]) + circuit.cx(qr[1], qr[0]) + dag = circuit_to_dag(circuit) + + pass_ = CountOps() + _ = pass_.run(dag) + + self.assertDictEqual(pass_.property_set['count_ops'], {'cx': 6, 'h': 2}) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/python/transpiler/test_depth_pass.py b/test/python/transpiler/test_depth_pass.py new file mode 100644 index 000000000000..2d039b122afb --- /dev/null +++ b/test/python/transpiler/test_depth_pass.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- + +# Copyright 2019, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. + +"""Depth pass testing""" + +import unittest + +from qiskit import QuantumCircuit, QuantumRegister +from qiskit.converters import circuit_to_dag +from qiskit.transpiler.passes import Depth +from qiskit.test import QiskitTestCase + + +class TestDepthPass(QiskitTestCase): + """ Tests for Depth analysis methods. """ + + def test_empty_dag(self): + """ Empty DAG has 0 depth """ + circuit = QuantumCircuit() + dag = circuit_to_dag(circuit) + + pass_ = Depth() + _ = pass_.run(dag) + + self.assertEqual(pass_.property_set['depth'], 0) + + def test_just_qubits(self): + """ A dag with 8 operations and no classic bits""" + qr = QuantumRegister(2) + circuit = QuantumCircuit(qr) + circuit.h(qr[0]) + circuit.h(qr[1]) + circuit.cx(qr[0], qr[1]) + circuit.cx(qr[0], qr[1]) + circuit.cx(qr[0], qr[1]) + circuit.cx(qr[0], qr[1]) + circuit.cx(qr[1], qr[0]) + circuit.cx(qr[1], qr[0]) + dag = circuit_to_dag(circuit) + + pass_ = Depth() + _ = pass_.run(dag) + + self.assertEqual(pass_.property_set['depth'], 7) + + def test_depth_one(self): + """ A dag with operations in parallel and depth 1""" + qr = QuantumRegister(2) + circuit = QuantumCircuit(qr) + circuit.h(qr[0]) + circuit.h(qr[1]) + dag = circuit_to_dag(circuit) + + pass_ = Depth() + _ = pass_.run(dag) + + self.assertEqual(pass_.property_set['depth'], 1) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/python/transpiler/test_resource_estimation_pass.py b/test/python/transpiler/test_resource_estimation_pass.py new file mode 100644 index 000000000000..07012728700a --- /dev/null +++ b/test/python/transpiler/test_resource_estimation_pass.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- + +# Copyright 2019, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. + +"""ResourceEstimation pass testing""" + +import unittest + +from qiskit import QuantumRegister, QuantumCircuit +from qiskit.transpiler import PassManager, transpile +from qiskit.transpiler.passes import ResourceEstimation +from qiskit.test import QiskitTestCase +from qiskit.test.mock import FakeRueschlikon + + +class TestResourceEstimationPass(QiskitTestCase): + """ Tests for PropertySet methods. """ + + def test_empty_dag(self): + """ Empty DAG.""" + circuit = QuantumCircuit() + passmanager = PassManager() + passmanager.append(ResourceEstimation()) + _ = transpile(circuit, FakeRueschlikon(), pass_manager=passmanager) + + self.assertEqual(passmanager.property_set['size'], 0) + self.assertEqual(passmanager.property_set['depth'], 0) + self.assertEqual(passmanager.property_set['width'], 0) + self.assertDictEqual(passmanager.property_set['count_ops'], {}) + + def test_just_qubits(self): + """ A dag with 8 operations and no classic bits""" + qr = QuantumRegister(2) + circuit = QuantumCircuit(qr) + circuit.h(qr[0]) + circuit.h(qr[1]) + circuit.cx(qr[0], qr[1]) + circuit.cx(qr[0], qr[1]) + circuit.cx(qr[0], qr[1]) + circuit.cx(qr[0], qr[1]) + circuit.cx(qr[1], qr[0]) + circuit.cx(qr[1], qr[0]) + + passmanager = PassManager() + passmanager.append(ResourceEstimation()) + _ = transpile(circuit, FakeRueschlikon(), pass_manager=passmanager) + + self.assertEqual(passmanager.property_set['size'], 8) + self.assertEqual(passmanager.property_set['depth'], 7) + self.assertEqual(passmanager.property_set['width'], 2) + self.assertDictEqual(passmanager.property_set['count_ops'], {'cx': 6, 'h': 2}) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/python/transpiler/test_size_pass.py b/test/python/transpiler/test_size_pass.py new file mode 100644 index 000000000000..6b12617cb3aa --- /dev/null +++ b/test/python/transpiler/test_size_pass.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- + +# Copyright 2019, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. + +"""Size pass testing""" + +import unittest + +from qiskit import QuantumCircuit, QuantumRegister +from qiskit.converters import circuit_to_dag +from qiskit.transpiler.passes import Size +from qiskit.test import QiskitTestCase + + +class TestSizePass(QiskitTestCase): + """ Tests for Depth analysis methods. """ + + def test_empty_dag(self): + """ Empty DAG has 0 size""" + circuit = QuantumCircuit() + dag = circuit_to_dag(circuit) + + pass_ = Size() + _ = pass_.run(dag) + + self.assertEqual(pass_.property_set['size'], 0) + + def test_just_qubits(self): + """ A dag with 8 operations and no classic bits""" + qr = QuantumRegister(2) + circuit = QuantumCircuit(qr) + circuit.h(qr[0]) + circuit.h(qr[1]) + circuit.cx(qr[0], qr[1]) + circuit.cx(qr[0], qr[1]) + circuit.cx(qr[0], qr[1]) + circuit.cx(qr[0], qr[1]) + circuit.cx(qr[1], qr[0]) + circuit.cx(qr[1], qr[0]) + dag = circuit_to_dag(circuit) + + pass_ = Size() + _ = pass_.run(dag) + + self.assertEqual(pass_.property_set['size'], 8) + + def test_depth_one(self): + """ A dag with operations in parallel and size 2""" + qr = QuantumRegister(2) + circuit = QuantumCircuit(qr) + circuit.h(qr[0]) + circuit.h(qr[1]) + dag = circuit_to_dag(circuit) + + pass_ = Size() + _ = pass_.run(dag) + + self.assertEqual(pass_.property_set['size'], 2) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/python/transpiler/test_tensor_factor_pass.py b/test/python/transpiler/test_tensor_factor_pass.py new file mode 100644 index 000000000000..1d7a63a71060 --- /dev/null +++ b/test/python/transpiler/test_tensor_factor_pass.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- + +# Copyright 2019, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. + +"""NumTensorFactors pass testing""" + +import unittest + +from qiskit import QuantumCircuit, QuantumRegister +from qiskit.converters import circuit_to_dag +from qiskit.transpiler.passes import NumTensorFactors +from qiskit.test import QiskitTestCase + + +class TestNumTensorsFactorPass(QiskitTestCase): + """ Tests for NumTensorFactors analysis methods. """ + + def test_empty_dag(self): + """ Empty DAG has 0 number of tensor factors. """ + circuit = QuantumCircuit() + dag = circuit_to_dag(circuit) + + pass_ = NumTensorFactors() + _ = pass_.run(dag) + + self.assertEqual(pass_.property_set['num_tensor_factors'], 0) + + def test_just_qubits(self): + """ A dag with 8 operations and 1 tensor factor.""" + qr = QuantumRegister(2) + circuit = QuantumCircuit(qr) + circuit.h(qr[0]) + circuit.h(qr[1]) + circuit.cx(qr[0], qr[1]) + circuit.cx(qr[0], qr[1]) + circuit.cx(qr[0], qr[1]) + circuit.cx(qr[0], qr[1]) + circuit.cx(qr[1], qr[0]) + circuit.cx(qr[1], qr[0]) + dag = circuit_to_dag(circuit) + + pass_ = NumTensorFactors() + _ = pass_.run(dag) + + self.assertEqual(pass_.property_set['num_tensor_factors'], 1) + + def test_depth_one(self): + """ A dag with operations in parallel (2 tensor factors)""" + qr = QuantumRegister(2) + circuit = QuantumCircuit(qr) + circuit.h(qr[0]) + circuit.h(qr[1]) + dag = circuit_to_dag(circuit) + + pass_ = NumTensorFactors() + _ = pass_.run(dag) + + self.assertEqual(pass_.property_set['num_tensor_factors'], 2) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/python/transpiler/test_width_pass.py b/test/python/transpiler/test_width_pass.py new file mode 100644 index 000000000000..6ac73602d103 --- /dev/null +++ b/test/python/transpiler/test_width_pass.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- + +# Copyright 2019, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. + +"""Width pass testing""" + +import unittest + +from qiskit import QuantumCircuit, QuantumRegister +from qiskit.converters import circuit_to_dag +from qiskit.transpiler.passes import Width +from qiskit.test import QiskitTestCase + + +class TestWidthPass(QiskitTestCase): + """ Tests for Depth analysis methods. """ + + def test_empty_dag(self): + """ Empty DAG has 0 depth """ + circuit = QuantumCircuit() + dag = circuit_to_dag(circuit) + + pass_ = Width() + _ = pass_.run(dag) + + self.assertEqual(pass_.property_set['width'], 0) + + def test_just_qubits(self): + """ A dag with 8 operations and no classic bits""" + qr = QuantumRegister(2) + circuit = QuantumCircuit(qr) + circuit.h(qr[0]) + circuit.h(qr[1]) + circuit.cx(qr[0], qr[1]) + circuit.cx(qr[0], qr[1]) + circuit.cx(qr[0], qr[1]) + circuit.cx(qr[0], qr[1]) + circuit.cx(qr[1], qr[0]) + circuit.cx(qr[1], qr[0]) + dag = circuit_to_dag(circuit) + + pass_ = Width() + _ = pass_.run(dag) + + self.assertEqual(pass_.property_set['width'], 2) + + +if __name__ == '__main__': + unittest.main()