From cba0538f51e121fd1def0f54e3bb6bbb5dcaaeca Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Thu, 16 May 2024 00:28:58 +0900 Subject: [PATCH 1/7] Add transpiler mixin for lightweight transpile --- qiskit_experiments/framework/__init__.py | 4 +- .../framework/transpile_mixin.py | 72 +++++++++++ .../library/characterization/t1.py | 9 +- .../library/characterization/t2hahn.py | 9 +- .../library/characterization/t2ramsey.py | 9 +- ...add-transpiler-mixin-9b30296518e5f4ba.yaml | 72 +++++++++++ test/framework/test_transpile_mixin.py | 119 ++++++++++++++++++ 7 files changed, 287 insertions(+), 7 deletions(-) create mode 100644 qiskit_experiments/framework/transpile_mixin.py create mode 100644 releasenotes/notes/add-transpiler-mixin-9b30296518e5f4ba.yaml create mode 100644 test/framework/test_transpile_mixin.py diff --git a/qiskit_experiments/framework/__init__.py b/qiskit_experiments/framework/__init__.py index 2f4304ebf2..5c3a3c75ea 100644 --- a/qiskit_experiments/framework/__init__.py +++ b/qiskit_experiments/framework/__init__.py @@ -125,8 +125,9 @@ BackendData BackendTiming RestlessMixin + SimpleCircuitExtender -""" + """ from qiskit.providers.options import Options from qiskit_experiments.framework.backend_data import BackendData from qiskit_experiments.framework.analysis_result import AnalysisResult @@ -155,3 +156,4 @@ ) from .json import ExperimentEncoder, ExperimentDecoder from .restless_mixin import RestlessMixin +from .transpile_mixin import SimpleCircuitExtender diff --git a/qiskit_experiments/framework/transpile_mixin.py b/qiskit_experiments/framework/transpile_mixin.py new file mode 100644 index 0000000000..b618489b69 --- /dev/null +++ b/qiskit_experiments/framework/transpile_mixin.py @@ -0,0 +1,72 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Transpile mixin class.""" + +from __future__ import annotations +from typing import Protocol + +from qiskit import QuantumCircuit, QuantumRegister +from qiskit.providers import Backend + + +class TranspileMixInProtocol(Protocol): + """A protocol to define a class that can be mixed with transpiler mixins.""" + + @property + def physical_qubits(self): + ... + + @property + def backend(self) -> Backend | None: + ... + + def circuits(self) -> list[QuantumCircuit]: + ... + + def _transpiled_circuits(self) -> list[QuantumCircuit]: + ... + + +class SimpleCircuitExtender: + """A transpiler mixin class that maps virtual qubit index to physical. + + Experiment class returns virtual circuits when the backend is not set. + """ + + def _transpiled_circuits( + self: TranspileMixInProtocol, + ) -> list: + if hasattr(self.backend, "num_qubits"): + # V2 backend model + n_qubits = self.backend.num_qubits + elif hasattr(self.backend, "configuration"): + # V1 backend model + n_qubits = self.backend.configuration().n_qubits + else: + # Backend is not set. Return virtual circuits as is. + return self.circuits() + return [self._index_mapper(c, n_qubits) for c in self.circuits()] + + def _index_mapper( + self: TranspileMixInProtocol, + v_circ: QuantumCircuit, + n_qubits: int, + ) -> QuantumCircuit: + p_qregs = QuantumRegister(n_qubits) + v_p_map = {q: p_qregs[self.physical_qubits[i]] for i, q in enumerate(v_circ.qubits)} + p_circ = QuantumCircuit(p_qregs, *v_circ.cregs) + p_circ.metadata = v_circ.metadata + for inst, v_qubits, clbits in v_circ.data: + p_qubits = list(map(v_p_map.get, v_qubits)) + p_circ._append(inst, p_qubits, clbits) + return p_circ diff --git a/qiskit_experiments/library/characterization/t1.py b/qiskit_experiments/library/characterization/t1.py index 6f3c02cfc1..8290406ba1 100644 --- a/qiskit_experiments/library/characterization/t1.py +++ b/qiskit_experiments/library/characterization/t1.py @@ -19,11 +19,16 @@ from qiskit.circuit import QuantumCircuit from qiskit.providers.backend import Backend -from qiskit_experiments.framework import BackendTiming, BaseExperiment, Options +from qiskit_experiments.framework import ( + SimpleCircuitExtender, + BackendTiming, + BaseExperiment, + Options, +) from qiskit_experiments.library.characterization.analysis.t1_analysis import T1Analysis -class T1(BaseExperiment): +class T1(SimpleCircuitExtender, BaseExperiment): r"""An experiment to measure the qubit relaxation time. # section: overview diff --git a/qiskit_experiments/library/characterization/t2hahn.py b/qiskit_experiments/library/characterization/t2hahn.py index 8add080bb9..512874750c 100644 --- a/qiskit_experiments/library/characterization/t2hahn.py +++ b/qiskit_experiments/library/characterization/t2hahn.py @@ -20,11 +20,16 @@ from qiskit.circuit import Parameter from qiskit.providers.backend import Backend -from qiskit_experiments.framework import BackendTiming, BaseExperiment, Options +from qiskit_experiments.framework import ( + SimpleCircuitExtender, + BackendTiming, + BaseExperiment, + Options, +) from qiskit_experiments.library.characterization.analysis.t2hahn_analysis import T2HahnAnalysis -class T2Hahn(BaseExperiment): +class T2Hahn(SimpleCircuitExtender, BaseExperiment): r"""An experiment to measure the dephasing time insensitive to inhomogeneous broadening using Hahn echos. diff --git a/qiskit_experiments/library/characterization/t2ramsey.py b/qiskit_experiments/library/characterization/t2ramsey.py index b4b06be794..0ce0b31ed9 100644 --- a/qiskit_experiments/library/characterization/t2ramsey.py +++ b/qiskit_experiments/library/characterization/t2ramsey.py @@ -21,11 +21,16 @@ from qiskit import QuantumCircuit from qiskit.providers.backend import Backend -from qiskit_experiments.framework import BackendTiming, BaseExperiment, Options +from qiskit_experiments.framework import ( + SimpleCircuitExtender, + BackendTiming, + BaseExperiment, + Options, +) from qiskit_experiments.library.characterization.analysis.t2ramsey_analysis import T2RamseyAnalysis -class T2Ramsey(BaseExperiment): +class T2Ramsey(SimpleCircuitExtender, BaseExperiment): r"""An experiment to measure the Ramsey frequency and the qubit dephasing time sensitive to inhomogeneous broadening. diff --git a/releasenotes/notes/add-transpiler-mixin-9b30296518e5f4ba.yaml b/releasenotes/notes/add-transpiler-mixin-9b30296518e5f4ba.yaml new file mode 100644 index 0000000000..37a84006e1 --- /dev/null +++ b/releasenotes/notes/add-transpiler-mixin-9b30296518e5f4ba.yaml @@ -0,0 +1,72 @@ +--- +prelude: > + Replace this text with content to appear at the top of the section for this + release. All of the prelude content is merged together and then rendered + separately from the items listed in other parts of the file, so the text + needs to be worded so that both the prelude and the other items make sense + when read independently. This may mean repeating some details. Not every + release note requires a prelude. Usually only notes describing major + features or adding release theme details should have a prelude. +features: + - | + List new features here, or remove this section. All of the list items in + this section are combined when the release notes are rendered, so the text + needs to be worded so that it does not depend on any information only + available in another section, such as the prelude. This may mean repeating + some details. +issues: + - | + List known issues here, or remove this section. All of the list items in + this section are combined when the release notes are rendered, so the text + needs to be worded so that it does not depend on any information only + available in another section, such as the prelude. This may mean repeating + some details. +upgrade: + - | + List upgrade notes here, or remove this section. All of the list items in + this section are combined when the release notes are rendered, so the text + needs to be worded so that it does not depend on any information only + available in another section, such as the prelude. This may mean repeating + some details. +deprecations: + - | + List deprecations notes here, or remove this section. All of the list + items in this section are combined when the release notes are rendered, so + the text needs to be worded so that it does not depend on any information + only available in another section, such as the prelude. This may mean + repeating some details. +critical: + - | + Add critical notes here, or remove this section. All of the list items in + this section are combined when the release notes are rendered, so the text + needs to be worded so that it does not depend on any information only + available in another section, such as the prelude. This may mean repeating + some details. +security: + - | + Add security notes here, or remove this section. All of the list items in + this section are combined when the release notes are rendered, so the text + needs to be worded so that it does not depend on any information only + available in another section, such as the prelude. This may mean repeating + some details. +fixes: + - | + Add normal bug fixes here, or remove this section. All of the list items + in this section are combined when the release notes are rendered, so the + text needs to be worded so that it does not depend on any information only + available in another section, such as the prelude. This may mean repeating + some details. +developer: + - | + Add upgrade of protected-level API (e.g. hooks). All of the list items + in this section are combined when the release notes are rendered, so the + text needs to be worded so that it does not depend on any information only + available in another section, such as the prelude. This may mean repeating + some details. +other: + - | + Add other notes here, or remove this section. All of the list items in + this section are combined when the release notes are rendered, so the text + needs to be worded so that it does not depend on any information only + available in another section, such as the prelude. This may mean repeating + some details. diff --git a/test/framework/test_transpile_mixin.py b/test/framework/test_transpile_mixin.py new file mode 100644 index 0000000000..db95892ac3 --- /dev/null +++ b/test/framework/test_transpile_mixin.py @@ -0,0 +1,119 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Tests for transpile mixin.""" + +from test.base import QiskitExperimentsTestCase + +from qiskit import QuantumCircuit +from qiskit.providers.fake_provider import GenericBackendV2 + +from qiskit_experiments.framework import SimpleCircuitExtender, BaseExperiment + + +class TestSimpleCircuitExtender(QiskitExperimentsTestCase): + """A test for SimpleCircuitExtender MixIn.""" + + def test_transpiled_single_qubit_circuits(self): + """Test fast-transpile with single qubit circuit.""" + + class _MockExperiment(SimpleCircuitExtender, BaseExperiment): + def circuits(self) -> list: + qc1 = QuantumCircuit(1, 1) + qc1.x(0) + qc1.measure(0, 0) + qc1.metadata = {"test_val": "123"} + + qc2 = QuantumCircuit(1, 1) + qc2.sx(0) + qc2.measure(0, 0) + qc2.metadata = {"test_val": "456"} + return [qc1, qc2] + + num_qubits = 10 + + mock_backend = GenericBackendV2(num_qubits, basis_gates=["sx", "rz", "x"]) + exp = _MockExperiment((3,), backend=mock_backend) + test_circs = exp._transpiled_circuits() + + self.assertEqual(len(test_circs), 2) + c0, c1 = test_circs + + # output size + self.assertEqual(len(c0.qubits), num_qubits) + self.assertEqual(len(c1.qubits), num_qubits) + + # metadata + self.assertDictEqual(c0.metadata, {"test_val": "123"}) + + # qubit index of X gate + self.assertEqual(c0.qubits.index(c0.data[0][1][0]), 3) + + # creg index of measure + self.assertEqual(c0.clbits.index(c0.data[1][2][0]), 0) + + # metadata + self.assertDictEqual(c1.metadata, {"test_val": "456"}) + + # qubit index of SX gate + self.assertEqual(c1.qubits.index(c1.data[0][1][0]), 3) + + # creg index of measure + self.assertEqual(c1.clbits.index(c1.data[1][2][0]), 0) + + def test_transpiled_two_qubit_circuits(self): + """Test fast-transpile with two qubit circuit.""" + + class _MockExperiment(SimpleCircuitExtender, BaseExperiment): + def circuits(self) -> list: + qc = QuantumCircuit(2, 2) + qc.cx(0, 1) + qc.measure(0, 0) + qc.measure(1, 1) + return [qc] + + num_qubits = 10 + + mock_backend = GenericBackendV2(num_qubits, basis_gates=["sx", "rz", "x", "cx"]) + exp = _MockExperiment((9, 2), backend=mock_backend) + test_circ = exp._transpiled_circuits()[0] + + self.assertEqual(len(test_circ.qubits), num_qubits) + + # qubit index of CX control qubit + self.assertEqual(test_circ.qubits.index(test_circ.data[0][1][0]), 9) + + # qubit index of CX target qubit + self.assertEqual(test_circ.qubits.index(test_circ.data[0][1][1]), 2) + + # creg index of measure + self.assertEqual(test_circ.clbits.index(test_circ.data[1][2][0]), 0) + self.assertEqual(test_circ.clbits.index(test_circ.data[2][2][0]), 1) + + def test_empty_backend(self): + """Test fast-transpile without backend, which must return virtual circuit.""" + + class _MockExperiment(SimpleCircuitExtender, BaseExperiment): + def circuits(self) -> list: + qc = QuantumCircuit(1, 1) + qc.x(0) + qc.measure(0, 0) + + return [qc] + + exp = _MockExperiment((10,)) + test_circ = exp._transpiled_circuits()[0] + + self.assertEqual(len(test_circ.qubits), 1) + + # qubit index of X gate + self.assertEqual(test_circ.qubits.index(test_circ.data[0][1][0]), 0) From f283af9219daf49d8427b2aec427e94c723dcb7c Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Thu, 16 May 2024 00:32:34 +0900 Subject: [PATCH 2/7] update reno --- ...add-transpiler-mixin-9b30296518e5f4ba.yaml | 87 ++++--------------- 1 file changed, 18 insertions(+), 69 deletions(-) diff --git a/releasenotes/notes/add-transpiler-mixin-9b30296518e5f4ba.yaml b/releasenotes/notes/add-transpiler-mixin-9b30296518e5f4ba.yaml index 37a84006e1..da576e8d92 100644 --- a/releasenotes/notes/add-transpiler-mixin-9b30296518e5f4ba.yaml +++ b/releasenotes/notes/add-transpiler-mixin-9b30296518e5f4ba.yaml @@ -1,72 +1,21 @@ --- -prelude: > - Replace this text with content to appear at the top of the section for this - release. All of the prelude content is merged together and then rendered - separately from the items listed in other parts of the file, so the text - needs to be worded so that both the prelude and the other items make sense - when read independently. This may mean repeating some details. Not every - release note requires a prelude. Usually only notes describing major - features or adding release theme details should have a prelude. -features: - - | - List new features here, or remove this section. All of the list items in - this section are combined when the release notes are rendered, so the text - needs to be worded so that it does not depend on any information only - available in another section, such as the prelude. This may mean repeating - some details. -issues: - - | - List known issues here, or remove this section. All of the list items in - this section are combined when the release notes are rendered, so the text - needs to be worded so that it does not depend on any information only - available in another section, such as the prelude. This may mean repeating - some details. -upgrade: - - | - List upgrade notes here, or remove this section. All of the list items in - this section are combined when the release notes are rendered, so the text - needs to be worded so that it does not depend on any information only - available in another section, such as the prelude. This may mean repeating - some details. -deprecations: - - | - List deprecations notes here, or remove this section. All of the list - items in this section are combined when the release notes are rendered, so - the text needs to be worded so that it does not depend on any information - only available in another section, such as the prelude. This may mean - repeating some details. -critical: - - | - Add critical notes here, or remove this section. All of the list items in - this section are combined when the release notes are rendered, so the text - needs to be worded so that it does not depend on any information only - available in another section, such as the prelude. This may mean repeating - some details. -security: - - | - Add security notes here, or remove this section. All of the list items in - this section are combined when the release notes are rendered, so the text - needs to be worded so that it does not depend on any information only - available in another section, such as the prelude. This may mean repeating - some details. -fixes: - - | - Add normal bug fixes here, or remove this section. All of the list items - in this section are combined when the release notes are rendered, so the - text needs to be worded so that it does not depend on any information only - available in another section, such as the prelude. This may mean repeating - some details. developer: - | - Add upgrade of protected-level API (e.g. hooks). All of the list items - in this section are combined when the release notes are rendered, so the - text needs to be worded so that it does not depend on any information only - available in another section, such as the prelude. This may mean repeating - some details. -other: - - | - Add other notes here, or remove this section. All of the list items in - this section are combined when the release notes are rendered, so the text - needs to be worded so that it does not depend on any information only - available in another section, such as the prelude. This may mean repeating - some details. + Add a mixin class :class:`~.SimpleCircuitExtender` that automatically + implements the :meth:`.BaseExperiment._transpiled_circuits` method for + simple experiments that require neither gate translation nor routing, + i.e. experiment that directly creates ISA circuits. + This bypasses the call to the Qiskit transpiler, which makes your experiment run more performant. + For example: + + .. code-block::python + + from qiskit_experiment.framework import BaseExperiment, SimpleCircuitExtender + + class MyExperiment(SimpleCircuitExtender, BaseExperiment): + + def circuits(self): + qc = QuantumCircuit(1, 1) + qc.x(0) + qc.measure(0, 0) + return [qc] From b0bba2602e28f60194db08897e76ce2634646f24 Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Thu, 16 May 2024 03:11:27 +0900 Subject: [PATCH 3/7] fix output when backend is not set --- .../framework/transpile_mixin.py | 22 ++++++++---- test/framework/test_transpile_mixin.py | 35 +++++++++++++++++-- 2 files changed, 48 insertions(+), 9 deletions(-) diff --git a/qiskit_experiments/framework/transpile_mixin.py b/qiskit_experiments/framework/transpile_mixin.py index b618489b69..80c47a0295 100644 --- a/qiskit_experiments/framework/transpile_mixin.py +++ b/qiskit_experiments/framework/transpile_mixin.py @@ -24,14 +24,23 @@ class TranspileMixInProtocol(Protocol): @property def physical_qubits(self): - ... + """Return the device qubits for the experiment.""" @property def backend(self) -> Backend | None: - ... + """Return the backend for the experiment""" def circuits(self) -> list[QuantumCircuit]: - ... + """Return a list of experiment circuits. + + Returns: + A list of :class:`~qiskit.circuit.QuantumCircuit`. + + .. note:: + These circuits should be on qubits ``[0, .., N-1]`` for an + *N*-qubit experiment. The circuits mapped to physical qubits + are obtained via the internal :meth:`_transpiled_circuits` method. + """ def _transpiled_circuits(self) -> list[QuantumCircuit]: ... @@ -40,7 +49,8 @@ def _transpiled_circuits(self) -> list[QuantumCircuit]: class SimpleCircuitExtender: """A transpiler mixin class that maps virtual qubit index to physical. - Experiment class returns virtual circuits when the backend is not set. + When the backend is not set, the experiment class naively assumes + there are max(physical_qubits) + 1 qubits in the quantum circuits. """ def _transpiled_circuits( @@ -53,8 +63,8 @@ def _transpiled_circuits( # V1 backend model n_qubits = self.backend.configuration().n_qubits else: - # Backend is not set. Return virtual circuits as is. - return self.circuits() + # Backend is not set. Naively guess qubit size. + n_qubits = max(self.physical_qubits) + 1 return [self._index_mapper(c, n_qubits) for c in self.circuits()] def _index_mapper( diff --git a/test/framework/test_transpile_mixin.py b/test/framework/test_transpile_mixin.py index db95892ac3..0721ea9a70 100644 --- a/test/framework/test_transpile_mixin.py +++ b/test/framework/test_transpile_mixin.py @@ -12,12 +12,16 @@ """Tests for transpile mixin.""" +from typing import Sequence + from test.base import QiskitExperimentsTestCase +from test.fake_experiment import FakeAnalysis from qiskit import QuantumCircuit from qiskit.providers.fake_provider import GenericBackendV2 from qiskit_experiments.framework import SimpleCircuitExtender, BaseExperiment +from qiskit_experiments.framework.composite import ParallelExperiment class TestSimpleCircuitExtender(QiskitExperimentsTestCase): @@ -100,7 +104,7 @@ def circuits(self) -> list: self.assertEqual(test_circ.clbits.index(test_circ.data[2][2][0]), 1) def test_empty_backend(self): - """Test fast-transpile without backend, which must return virtual circuit.""" + """Test fast-transpile without backend.""" class _MockExperiment(SimpleCircuitExtender, BaseExperiment): def circuits(self) -> list: @@ -113,7 +117,32 @@ def circuits(self) -> list: exp = _MockExperiment((10,)) test_circ = exp._transpiled_circuits()[0] - self.assertEqual(len(test_circ.qubits), 1) + self.assertEqual(len(test_circ.qubits), 11) + + # qubit index of X gate + self.assertEqual(test_circ.qubits.index(test_circ.data[0][1][0]), 10) + + def test_empty_backend_with_parallel(self): + """Test fast-transpile without backend. Circuit qubit location must not overlap.""" + + class _MockExperiment(SimpleCircuitExtender, BaseExperiment): + def __init__(self, physical_qubits): + super().__init__(physical_qubits, FakeAnalysis()) + + def circuits(self) -> list: + qc = QuantumCircuit(1, 1) + qc.x(0) + qc.measure(0, 0) + + return [qc] + + exp1 = _MockExperiment((3,)) + exp2 = _MockExperiment((15,)) + pexp = ParallelExperiment([exp1, exp2], flatten_results=True) + test_circ = pexp._transpiled_circuits()[0] + + self.assertEqual(len(test_circ.qubits), 16) # qubit index of X gate - self.assertEqual(test_circ.qubits.index(test_circ.data[0][1][0]), 0) + self.assertEqual(test_circ.qubits.index(test_circ.data[0][1][0]), 3) + self.assertEqual(test_circ.qubits.index(test_circ.data[2][1][0]), 15) From b24ff2263250d7df7d049922efaf2bf494a9d1b1 Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Thu, 16 May 2024 03:13:03 +0900 Subject: [PATCH 4/7] revert T2Hahn as it uses non typical basis gates --- qiskit_experiments/library/characterization/t2hahn.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/qiskit_experiments/library/characterization/t2hahn.py b/qiskit_experiments/library/characterization/t2hahn.py index 512874750c..8add080bb9 100644 --- a/qiskit_experiments/library/characterization/t2hahn.py +++ b/qiskit_experiments/library/characterization/t2hahn.py @@ -20,16 +20,11 @@ from qiskit.circuit import Parameter from qiskit.providers.backend import Backend -from qiskit_experiments.framework import ( - SimpleCircuitExtender, - BackendTiming, - BaseExperiment, - Options, -) +from qiskit_experiments.framework import BackendTiming, BaseExperiment, Options from qiskit_experiments.library.characterization.analysis.t2hahn_analysis import T2HahnAnalysis -class T2Hahn(SimpleCircuitExtender, BaseExperiment): +class T2Hahn(BaseExperiment): r"""An experiment to measure the dephasing time insensitive to inhomogeneous broadening using Hahn echos. From 34fcfedd5d35dd0ab5d89885abe98e2d996c3617 Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Thu, 16 May 2024 03:18:18 +0900 Subject: [PATCH 5/7] remove import --- test/framework/test_transpile_mixin.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/framework/test_transpile_mixin.py b/test/framework/test_transpile_mixin.py index 0721ea9a70..e4e774a571 100644 --- a/test/framework/test_transpile_mixin.py +++ b/test/framework/test_transpile_mixin.py @@ -12,8 +12,6 @@ """Tests for transpile mixin.""" -from typing import Sequence - from test.base import QiskitExperimentsTestCase from test.fake_experiment import FakeAnalysis From bb597dad182f36896151b313b287b950a8c542d0 Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Thu, 16 May 2024 15:45:40 +0900 Subject: [PATCH 6/7] add check for basis gates, add Mixin to class name for linter --- qiskit_experiments/framework/__init__.py | 4 +-- .../framework/transpile_mixin.py | 26 ++++++++++++--- .../library/characterization/t1.py | 4 +-- .../library/characterization/t2ramsey.py | 4 +-- test/framework/test_transpile_mixin.py | 32 +++++++++++++++---- test/library/characterization/test_t1.py | 6 ++-- 6 files changed, 55 insertions(+), 21 deletions(-) diff --git a/qiskit_experiments/framework/__init__.py b/qiskit_experiments/framework/__init__.py index 5c3a3c75ea..ce96bb7052 100644 --- a/qiskit_experiments/framework/__init__.py +++ b/qiskit_experiments/framework/__init__.py @@ -125,7 +125,7 @@ BackendData BackendTiming RestlessMixin - SimpleCircuitExtender + SimpleCircuitExtenderMixin """ from qiskit.providers.options import Options @@ -156,4 +156,4 @@ ) from .json import ExperimentEncoder, ExperimentDecoder from .restless_mixin import RestlessMixin -from .transpile_mixin import SimpleCircuitExtender +from .transpile_mixin import SimpleCircuitExtenderMixin diff --git a/qiskit_experiments/framework/transpile_mixin.py b/qiskit_experiments/framework/transpile_mixin.py index 80c47a0295..26d655ca94 100644 --- a/qiskit_experiments/framework/transpile_mixin.py +++ b/qiskit_experiments/framework/transpile_mixin.py @@ -15,7 +15,7 @@ from __future__ import annotations from typing import Protocol -from qiskit import QuantumCircuit, QuantumRegister +from qiskit import QuantumCircuit, QuantumRegister, transpile from qiskit.providers import Backend @@ -46,7 +46,7 @@ def _transpiled_circuits(self) -> list[QuantumCircuit]: ... -class SimpleCircuitExtender: +class SimpleCircuitExtenderMixin: """A transpiler mixin class that maps virtual qubit index to physical. When the backend is not set, the experiment class naively assumes @@ -56,22 +56,38 @@ class SimpleCircuitExtender: def _transpiled_circuits( self: TranspileMixInProtocol, ) -> list: - if hasattr(self.backend, "num_qubits"): + if hasattr(self.backend, "target"): # V2 backend model - n_qubits = self.backend.num_qubits + # This model assumes qubit dependent instruction set, + # but we assume experiment mixed with this class doesn't have such architecture. + basis_gates = set(self.backend.target.operation_names) + n_qubits = self.backend.target.num_qubits elif hasattr(self.backend, "configuration"): # V1 backend model + basis_gates = set(self.backend.configuration().basis_gates) n_qubits = self.backend.configuration().n_qubits else: # Backend is not set. Naively guess qubit size. + basis_gates = None n_qubits = max(self.physical_qubits) + 1 - return [self._index_mapper(c, n_qubits) for c in self.circuits()] + return [self._index_mapper(c, basis_gates, n_qubits) for c in self.circuits()] def _index_mapper( self: TranspileMixInProtocol, v_circ: QuantumCircuit, + basis_gates: set[str] | None, n_qubits: int, ) -> QuantumCircuit: + if basis_gates is not None and not basis_gates.issuperset( + set(v_circ.count_ops().keys()) - {"barrier"} + ): + # In Qiskit provider model barrier is not included in target. + # Use standard circuit transpile when circuit is not ISA. + return transpile( + v_circ, + backend=self.backend, + initial_layout=list(self.physical_qubits), + ) p_qregs = QuantumRegister(n_qubits) v_p_map = {q: p_qregs[self.physical_qubits[i]] for i, q in enumerate(v_circ.qubits)} p_circ = QuantumCircuit(p_qregs, *v_circ.cregs) diff --git a/qiskit_experiments/library/characterization/t1.py b/qiskit_experiments/library/characterization/t1.py index 8290406ba1..7f3c7b5577 100644 --- a/qiskit_experiments/library/characterization/t1.py +++ b/qiskit_experiments/library/characterization/t1.py @@ -20,7 +20,7 @@ from qiskit.providers.backend import Backend from qiskit_experiments.framework import ( - SimpleCircuitExtender, + SimpleCircuitExtenderMixin, BackendTiming, BaseExperiment, Options, @@ -28,7 +28,7 @@ from qiskit_experiments.library.characterization.analysis.t1_analysis import T1Analysis -class T1(SimpleCircuitExtender, BaseExperiment): +class T1(SimpleCircuitExtenderMixin, BaseExperiment): r"""An experiment to measure the qubit relaxation time. # section: overview diff --git a/qiskit_experiments/library/characterization/t2ramsey.py b/qiskit_experiments/library/characterization/t2ramsey.py index 0ce0b31ed9..79a80975e6 100644 --- a/qiskit_experiments/library/characterization/t2ramsey.py +++ b/qiskit_experiments/library/characterization/t2ramsey.py @@ -22,7 +22,7 @@ from qiskit.providers.backend import Backend from qiskit_experiments.framework import ( - SimpleCircuitExtender, + SimpleCircuitExtenderMixin, BackendTiming, BaseExperiment, Options, @@ -30,7 +30,7 @@ from qiskit_experiments.library.characterization.analysis.t2ramsey_analysis import T2RamseyAnalysis -class T2Ramsey(SimpleCircuitExtender, BaseExperiment): +class T2Ramsey(SimpleCircuitExtenderMixin, BaseExperiment): r"""An experiment to measure the Ramsey frequency and the qubit dephasing time sensitive to inhomogeneous broadening. diff --git a/test/framework/test_transpile_mixin.py b/test/framework/test_transpile_mixin.py index e4e774a571..17ec198101 100644 --- a/test/framework/test_transpile_mixin.py +++ b/test/framework/test_transpile_mixin.py @@ -18,7 +18,7 @@ from qiskit import QuantumCircuit from qiskit.providers.fake_provider import GenericBackendV2 -from qiskit_experiments.framework import SimpleCircuitExtender, BaseExperiment +from qiskit_experiments.framework import SimpleCircuitExtenderMixin, BaseExperiment from qiskit_experiments.framework.composite import ParallelExperiment @@ -28,7 +28,7 @@ class TestSimpleCircuitExtender(QiskitExperimentsTestCase): def test_transpiled_single_qubit_circuits(self): """Test fast-transpile with single qubit circuit.""" - class _MockExperiment(SimpleCircuitExtender, BaseExperiment): + class _MockExperiment(SimpleCircuitExtenderMixin, BaseExperiment): def circuits(self) -> list: qc1 = QuantumCircuit(1, 1) qc1.x(0) @@ -43,7 +43,7 @@ def circuits(self) -> list: num_qubits = 10 - mock_backend = GenericBackendV2(num_qubits, basis_gates=["sx", "rz", "x"]) + mock_backend = GenericBackendV2(num_qubits, basis_gates=["x", "sx", "measure"]) exp = _MockExperiment((3,), backend=mock_backend) test_circs = exp._transpiled_circuits() @@ -75,7 +75,7 @@ def circuits(self) -> list: def test_transpiled_two_qubit_circuits(self): """Test fast-transpile with two qubit circuit.""" - class _MockExperiment(SimpleCircuitExtender, BaseExperiment): + class _MockExperiment(SimpleCircuitExtenderMixin, BaseExperiment): def circuits(self) -> list: qc = QuantumCircuit(2, 2) qc.cx(0, 1) @@ -85,7 +85,7 @@ def circuits(self) -> list: num_qubits = 10 - mock_backend = GenericBackendV2(num_qubits, basis_gates=["sx", "rz", "x", "cx"]) + mock_backend = GenericBackendV2(num_qubits, basis_gates=["cx", "measure"]) exp = _MockExperiment((9, 2), backend=mock_backend) test_circ = exp._transpiled_circuits()[0] @@ -104,7 +104,7 @@ def circuits(self) -> list: def test_empty_backend(self): """Test fast-transpile without backend.""" - class _MockExperiment(SimpleCircuitExtender, BaseExperiment): + class _MockExperiment(SimpleCircuitExtenderMixin, BaseExperiment): def circuits(self) -> list: qc = QuantumCircuit(1, 1) qc.x(0) @@ -123,7 +123,7 @@ def circuits(self) -> list: def test_empty_backend_with_parallel(self): """Test fast-transpile without backend. Circuit qubit location must not overlap.""" - class _MockExperiment(SimpleCircuitExtender, BaseExperiment): + class _MockExperiment(SimpleCircuitExtenderMixin, BaseExperiment): def __init__(self, physical_qubits): super().__init__(physical_qubits, FakeAnalysis()) @@ -144,3 +144,21 @@ def circuits(self) -> list: # qubit index of X gate self.assertEqual(test_circ.qubits.index(test_circ.data[0][1][0]), 3) self.assertEqual(test_circ.qubits.index(test_circ.data[2][1][0]), 15) + + def test_circuit_non_isa(self): + """Test fast-transpile with non-ISA circuit. It should use standard transpile.""" + + class _MockExperiment(SimpleCircuitExtenderMixin, BaseExperiment): + def circuits(self) -> list: + qc = QuantumCircuit(1, 1) + qc.x(0) + qc.measure(0, 0) + + return [qc] + + mock_backend = GenericBackendV2(1, basis_gates=["sx", "rz", "measure"]) + exp = _MockExperiment((0,), backend=mock_backend) + test_circ = exp._transpiled_circuits()[0] + + # gate is translated into sx-sx-measure + self.assertEqual(len(test_circ.data), 3) diff --git a/test/library/characterization/test_t1.py b/test/library/characterization/test_t1.py index 7f2d46f6ec..5c8d9db1e4 100644 --- a/test/library/characterization/test_t1.py +++ b/test/library/characterization/test_t1.py @@ -252,7 +252,7 @@ def test_t1_parallel_exp_transpile(self): instruction_durations = [] for i in range(num_qubits): instruction_durations += [ - ("rx", [i], (i + 1) * 10, "ns"), + ("x", [i], (i + 1) * 10, "ns"), ("measure", [i], (i + 1) * 1000, "ns"), ] coupling_map = [[i - 1, i] for i in range(1, num_qubits)] @@ -272,14 +272,14 @@ def test_t1_parallel_exp_transpile(self): for circ in circs: self.assertEqual(circ.num_qubits, 2) op_counts = circ.count_ops() - self.assertEqual(op_counts.get("rx"), 2) + self.assertEqual(op_counts.get("x"), 2) self.assertEqual(op_counts.get("delay"), 2) tcircs = parexp._transpiled_circuits() for circ in tcircs: self.assertEqual(circ.num_qubits, num_qubits) op_counts = circ.count_ops() - self.assertEqual(op_counts.get("rx"), 2) + self.assertEqual(op_counts.get("x"), 2) self.assertEqual(op_counts.get("delay"), 2) def test_experiment_config(self): From c3367daab2a72f77c8c19d53e1fc9b8999917106 Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Thu, 16 May 2024 15:48:07 +0900 Subject: [PATCH 7/7] update reno --- .../notes/add-transpiler-mixin-9b30296518e5f4ba.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/releasenotes/notes/add-transpiler-mixin-9b30296518e5f4ba.yaml b/releasenotes/notes/add-transpiler-mixin-9b30296518e5f4ba.yaml index da576e8d92..f956c76d6a 100644 --- a/releasenotes/notes/add-transpiler-mixin-9b30296518e5f4ba.yaml +++ b/releasenotes/notes/add-transpiler-mixin-9b30296518e5f4ba.yaml @@ -1,7 +1,7 @@ --- developer: - | - Add a mixin class :class:`~.SimpleCircuitExtender` that automatically + Add a mixin class :class:`~.SimpleCircuitExtenderMixin` that automatically implements the :meth:`.BaseExperiment._transpiled_circuits` method for simple experiments that require neither gate translation nor routing, i.e. experiment that directly creates ISA circuits. @@ -10,9 +10,9 @@ developer: .. code-block::python - from qiskit_experiment.framework import BaseExperiment, SimpleCircuitExtender + from qiskit_experiment.framework import BaseExperiment, SimpleCircuitExtenderMixin - class MyExperiment(SimpleCircuitExtender, BaseExperiment): + class MyExperiment(SimpleCircuitExtenderMixin, BaseExperiment): def circuits(self): qc = QuantumCircuit(1, 1)