From 22b255a872678c9bdc285fd9af8c8de87911d61c Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 19 Oct 2022 09:42:49 -0400 Subject: [PATCH 1/4] Fix parallel dispatch with target argument in transpile() If a custom target was specified when calling the transpile() function with more than one circuit the parallel dispatch of the shared custom target would fail because the basis_gates created from the target was a dict keys type. Pickle is unable to serialize a dict keys object apparently and this would cause an exception to be raised. This commit fixes this by casting the basis_gates from the target as a list so that it can be serialized. --- qiskit/compiler/transpiler.py | 2 +- ...t-transpile-parallel-772f943a08d0570b.yaml | 7 +++++ test/python/compiler/test_transpiler.py | 28 +++++++++++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/fix-target-transpile-parallel-772f943a08d0570b.yaml diff --git a/qiskit/compiler/transpiler.py b/qiskit/compiler/transpiler.py index b44940eb7bab..6f91f1f93396 100644 --- a/qiskit/compiler/transpiler.py +++ b/qiskit/compiler/transpiler.py @@ -635,7 +635,7 @@ def _parse_transpile_args( if coupling_map is None: coupling_map = target.build_coupling_map() if basis_gates is None: - basis_gates = target.operation_names + basis_gates = list(target.operation_names) if instruction_durations is None: instruction_durations = target.durations() if inst_map is None: diff --git a/releasenotes/notes/fix-target-transpile-parallel-772f943a08d0570b.yaml b/releasenotes/notes/fix-target-transpile-parallel-772f943a08d0570b.yaml new file mode 100644 index 000000000000..e42320017597 --- /dev/null +++ b/releasenotes/notes/fix-target-transpile-parallel-772f943a08d0570b.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + Fixed an issue with the :func:`~.transpile` where it would previously + fail with a ``TypeError`` if a custom :class:`~.Target` object was + passed in via the ``target`` argument and a list of multiple circuits + were specified for the ``circuits`` argument. diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index 0a9f2027edad..89d45551ae9b 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -1617,3 +1617,31 @@ def test_custom_multiple_circuits(self): self.assertEqual(len(transpiled), 2) self.assertEqual(transpiled[0], expected) self.assertEqual(transpiled[1], expected) + + +@ddt +class TestTranspileParallel(QiskitTestCase): + """Test transpile() in parallel.""" + + def setUp(self): + super().setUp() + original_value = os.getenv("QISKIT_PARALLEL", None) + + def restore_parallel(): + os.environ["QISKIT_PARALLEL"] = original_value + + self.addCleanup(restore_parallel) + del os.environ["QISKIT_PARALLEL"] + + @data(0, 1, 2, 3) + def test_parallel_with_target(self, opt_level): + """Test that parallel dispatch works with a manual target.""" + qc = QuantumCircuit(2) + qc.h(0) + qc.cx(0, 1) + qc.measure_all() + target = FakeMumbaiV2().target + res = transpile([qc] * 3, target=target, optimization_level=opt_level) + self.assertIsInstance(res, list) + for circ in res: + self.assertIsInstance(circ, QuantumCircuit) From 949703cf31b9133f211ba612626b7bd0c942d399 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 19 Oct 2022 12:18:58 -0400 Subject: [PATCH 2/4] Use unittest.mock.patch.dict to set parallel execution in tests --- test/python/compiler/test_transpiler.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index 89d45551ae9b..932f01592eb9 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -1623,16 +1623,6 @@ def test_custom_multiple_circuits(self): class TestTranspileParallel(QiskitTestCase): """Test transpile() in parallel.""" - def setUp(self): - super().setUp() - original_value = os.getenv("QISKIT_PARALLEL", None) - - def restore_parallel(): - os.environ["QISKIT_PARALLEL"] = original_value - - self.addCleanup(restore_parallel) - del os.environ["QISKIT_PARALLEL"] - @data(0, 1, 2, 3) def test_parallel_with_target(self, opt_level): """Test that parallel dispatch works with a manual target.""" @@ -1641,7 +1631,8 @@ def test_parallel_with_target(self, opt_level): qc.cx(0, 1) qc.measure_all() target = FakeMumbaiV2().target - res = transpile([qc] * 3, target=target, optimization_level=opt_level) + with patch.dict("os.environ", {"QISKIT_PARALLEL": "FALSE"}): + res = transpile([qc] * 3, target=target, optimization_level=opt_level) self.assertIsInstance(res, list) for circ in res: self.assertIsInstance(circ, QuantumCircuit) From 319d31e6cd2a9351d381f14779b08bf43414e7ec Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 19 Oct 2022 16:59:36 -0400 Subject: [PATCH 3/4] Fix parallel flag for test --- test/python/compiler/test_transpiler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index 932f01592eb9..5251acb14fe6 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -1631,7 +1631,7 @@ def test_parallel_with_target(self, opt_level): qc.cx(0, 1) qc.measure_all() target = FakeMumbaiV2().target - with patch.dict("os.environ", {"QISKIT_PARALLEL": "FALSE"}): + with patch.dict("os.environ", {"QISKIT_PARALLEL": "TRUE"}): res = transpile([qc] * 3, target=target, optimization_level=opt_level) self.assertIsInstance(res, list) for circ in res: From 9d2cfd2a67ba8a8f85c3d784cee92b32b213e760 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 19 Oct 2022 17:29:00 -0400 Subject: [PATCH 4/4] Fix parallel test setup In the previous patches we were attempting to use unittest.mock.patch.dict to override the env variable used to control parallel execution in qiskit. However, the issue with doing this is that the env variable is read at import time and stored in a global variable as we don't expect the env variable to change dynamically during the execution of a script. To workaround this the mocks are removed and instead a setUp() method is added to the test class to override the whatever the environment default is and instead hardcode parallel_map to run in parallel for the test and then switch it back to the earlier value after the test finishes. This lets us dynamically adjust the default behavior for parallel execution for this test class. --- test/python/compiler/test_transpiler.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index 004b5ae31b31..292134f9b58b 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -60,6 +60,7 @@ from qiskit.quantum_info import Operator, random_unitary from qiskit.transpiler.passmanager_config import PassManagerConfig from qiskit.transpiler.preset_passmanagers import level_0_pass_manager +from qiskit.tools import parallel class CustomCX(Gate): @@ -1795,6 +1796,18 @@ def test_custom_multiple_circuits(self): class TestTranspileParallel(QiskitTestCase): """Test transpile() in parallel.""" + def setUp(self): + super().setUp() + + # Force parallel execution to True to test multiprocessing for this class + original_val = parallel.PARALLEL_DEFAULT + + def restore_default(): + parallel.PARALLEL_DEFAULT = original_val + + self.addCleanup(restore_default) + parallel.PARALLEL_DEFAULT = True + @data(0, 1, 2, 3) def test_parallel_with_target(self, opt_level): """Test that parallel dispatch works with a manual target.""" @@ -1803,8 +1816,7 @@ def test_parallel_with_target(self, opt_level): qc.cx(0, 1) qc.measure_all() target = FakeMumbaiV2().target - with patch.dict("os.environ", {"QISKIT_PARALLEL": "TRUE"}): - res = transpile([qc] * 3, target=target, optimization_level=opt_level) + res = transpile([qc] * 3, target=target, optimization_level=opt_level) self.assertIsInstance(res, list) for circ in res: self.assertIsInstance(circ, QuantumCircuit)