From 58ad219142c22dd9f1a0e2de266152be94dd55f5 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Fri, 4 Jun 2021 16:39:35 +0200 Subject: [PATCH 01/13] * Added the option to give a preparation circuit to spectroscopy to eneable, e.g. spec on 1<->2. --- .../characterization/qubit_spectroscopy.py | 9 ++++++ test/test_qubit_spectroscopy.py | 28 +++++++++++++++++-- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/qiskit_experiments/characterization/qubit_spectroscopy.py b/qiskit_experiments/characterization/qubit_spectroscopy.py index 5aaf3f2480..df727d7015 100644 --- a/qiskit_experiments/characterization/qubit_spectroscopy.py +++ b/qiskit_experiments/characterization/qubit_spectroscopy.py @@ -343,6 +343,7 @@ def __init__( frequencies: Union[List[float], np.array], unit: Optional[str] = "Hz", absolute: bool = True, + pre_circuit: Optional[QuantumCircuit] = None, ): """ A spectroscopy experiment run by setting the frequency of the qubit drive. @@ -361,6 +362,9 @@ def __init__( to 'Hz'. absolute: Boolean to specify if the frequencies are absolute or relative to the qubit frequency in the backend. + pre_circuit: An optional quantum circuit done before the spectroscopy pulse. This + circuit allows, for instance, to prepare the first excited sate and perform + spectroscopy on the 1 <-> 2 transition of a transmon. Raises: QiskitError: if there are less than three frequency shifts or if the unit is not known. @@ -374,6 +378,7 @@ def __init__( self._frequencies = [freq * self.__units__[unit] for freq in frequencies] self._absolute = absolute + self._pre_circuit = pre_circuit super().__init__([qubit]) @@ -416,6 +421,10 @@ def circuits(self, backend: Optional[Backend] = None): circuit = QuantumCircuit(1) circuit.append(gate, (0,)) circuit.add_calibration(gate, (self.physical_qubits[0],), sched, params=[freq_param]) + + if self._pre_circuit is not None: + circuit = self._pre_circuit.compose(circuit) + circuit.measure_active() if not self._absolute: diff --git a/test/test_qubit_spectroscopy.py b/test/test_qubit_spectroscopy.py index 73e1c2ee45..e68022f10f 100644 --- a/test/test_qubit_spectroscopy.py +++ b/test/test_qubit_spectroscopy.py @@ -17,6 +17,7 @@ import numpy as np from qiskit.qobj.utils import MeasLevel from qiskit.test import QiskitTestCase +from qiskit import QiskitError, QuantumCircuit from qiskit_experiments.characterization.qubit_spectroscopy import QubitSpectroscopy from qiskit_experiments.test.mock_iq_backend import TestJob, IQTestBackend @@ -36,7 +37,7 @@ def __init__( ): """Initialize the spectroscopy backend.""" - self.__configuration__["basis_gates"] = ["spec"] + self.__configuration__["basis_gates"] = ["spec", "x"] self._linewidth = line_width self._freq_offset = freq_offset @@ -66,7 +67,14 @@ def run( "header": {"metadata": circ.metadata}, } - set_freq = float(circ.data[0][0].params[0]) + set_freq = None + for inst in circ.data: + if inst[0].name == "Spec": + set_freq = float(inst[0].params[0]) + + if set_freq is None: + raise QiskitError("Spectroscopy does not have a Spec instruction.") + delta_freq = set_freq - self._freq_offset prob = np.exp(-(delta_freq ** 2) / (2 * self._linewidth ** 2)) @@ -147,3 +155,19 @@ def test_spectroscopy_end2end_kerneled(self): self.assertTrue(result["value"] > 4.9e6) self.assertEqual(result["quality"], "computer_good") self.assertTrue(result["ydata_err"] is None) + + def test_spectroscopy12_end2end_classified(self): + """End to end test of the spectroscopy experiment with an x pulse.""" + + backend = SpectroscopyBackend(line_width=2e6) + + prep = QuantumCircuit(1) + prep.x(0) + + spec = QubitSpectroscopy(3, np.linspace(-10.0, 10.0, 21), unit="MHz", pre_circuit=prep) + spec.set_run_options(meas_level=MeasLevel.CLASSIFIED) + result = spec.run(backend).analysis_result(0) + + self.assertTrue(abs(result["value"]) < 1e6) + self.assertTrue(result["success"]) + self.assertEqual(result["quality"], "computer_good") From 14ae4384d51ad1b3131cd26a64460e1ad9b9b50b Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Fri, 4 Jun 2021 16:41:59 +0200 Subject: [PATCH 02/13] * Added preparation circuit name to metadata. --- qiskit_experiments/characterization/qubit_spectroscopy.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/qiskit_experiments/characterization/qubit_spectroscopy.py b/qiskit_experiments/characterization/qubit_spectroscopy.py index df727d7015..ed337c3029 100644 --- a/qiskit_experiments/characterization/qubit_spectroscopy.py +++ b/qiskit_experiments/characterization/qubit_spectroscopy.py @@ -454,6 +454,9 @@ def circuits(self, backend: Optional[Backend] = None): if not self._absolute: assigned_circ.metadata["center frequency"] = center_freq + if self._pre_circuit is not None: + assigned_circ.metadata["preparation circuit name"] = self._pre_circuit.name + try: assigned_circ.metadata["dt"] = getattr(backend.configuration(), "dt") except AttributeError as no_dt: From 3d8b6dab897579356f70339d1143af516a994c9f Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Mon, 7 Jun 2021 21:33:14 +0200 Subject: [PATCH 03/13] * Added e-f spectroscopy experiment. --- .../characterization/ef_spectroscopy.py | 44 ++++++++++++++ .../characterization/qubit_spectroscopy.py | 57 ++++++++++--------- test/test_qubit_spectroscopy.py | 13 +++-- 3 files changed, 81 insertions(+), 33 deletions(-) create mode 100644 qiskit_experiments/characterization/ef_spectroscopy.py diff --git a/qiskit_experiments/characterization/ef_spectroscopy.py b/qiskit_experiments/characterization/ef_spectroscopy.py new file mode 100644 index 0000000000..c889e196c9 --- /dev/null +++ b/qiskit_experiments/characterization/ef_spectroscopy.py @@ -0,0 +1,44 @@ +# 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. + +"""Spectroscopy for the e-f transition.""" + +from qiskit import QuantumCircuit +from qiskit.circuit import Gate + +from qiskit_experiments.characterization.qubit_spectroscopy import QubitSpectroscopy + + +class EFSpectroscopy(QubitSpectroscopy): + """Class that runs spectroscopy on the e-f transition by scanning the frequency. + + The circuits produced by spectroscopy, i.e. + + .. parsed-literal:: + + ┌───┐┌────────────┐ ░ ┌─┐ + q_0: ┤ X ├┤ Spec(freq) ├─░─┤M├ + └───┘└────────────┘ ░ └╥┘ + measure: 1/═══════════════════════╩═ + 0 + + """ + + @staticmethod + def _template_circuit(freq_param) -> QuantumCircuit: + """Return the template quantum circuit.""" + circuit = QuantumCircuit(1) + circuit.x(0) + circuit.append(Gate(name="Spec", num_qubits=1, params=[freq_param]), (0,)) + circuit.measure_active() + + return circuit diff --git a/qiskit_experiments/characterization/qubit_spectroscopy.py b/qiskit_experiments/characterization/qubit_spectroscopy.py index ed337c3029..93a45e3756 100644 --- a/qiskit_experiments/characterization/qubit_spectroscopy.py +++ b/qiskit_experiments/characterization/qubit_spectroscopy.py @@ -343,7 +343,6 @@ def __init__( frequencies: Union[List[float], np.array], unit: Optional[str] = "Hz", absolute: bool = True, - pre_circuit: Optional[QuantumCircuit] = None, ): """ A spectroscopy experiment run by setting the frequency of the qubit drive. @@ -378,10 +377,35 @@ def __init__( self._frequencies = [freq * self.__units__[unit] for freq in frequencies] self._absolute = absolute - self._pre_circuit = pre_circuit super().__init__([qubit]) + def _schedule(self) -> Tuple[pulse.ScheduleBlock, Parameter]: + """Create the spectroscopy schedule.""" + freq_param = Parameter("frequency") + with pulse.build(name="spectroscopy") as schedule: + pulse.set_frequency(freq_param, pulse.DriveChannel(self.physical_qubits[0])) + pulse.play( + pulse.GaussianSquare( + duration=self.experiment_options.duration, + amp=self.experiment_options.amp, + sigma=self.experiment_options.sigma, + width=self.experiment_options.width, + ), + pulse.DriveChannel(self.physical_qubits[0]), + ) + + return schedule, freq_param + + @staticmethod + def _template_circuit(freq_param) -> QuantumCircuit: + """Return the template quantum circuit.""" + circuit = QuantumCircuit(1) + circuit.append(Gate(name="Spec", num_qubits=1, params=[freq_param]), (0,)) + circuit.measure_active() + + return circuit + def circuits(self, backend: Optional[Backend] = None): """Create the circuit for the spectroscopy experiment. @@ -403,29 +427,9 @@ def circuits(self, backend: Optional[Backend] = None): raise QiskitError("Cannot run spectroscopy relative to qubit without a backend.") # Create a template circuit - freq_param = Parameter("frequency") - with pulse.build(backend=backend, name="spectroscopy") as sched: - pulse.set_frequency(freq_param, pulse.DriveChannel(self.physical_qubits[0])) - pulse.play( - pulse.GaussianSquare( - duration=self.experiment_options.duration, - amp=self.experiment_options.amp, - sigma=self.experiment_options.sigma, - width=self.experiment_options.width, - ), - pulse.DriveChannel(self.physical_qubits[0]), - ) - - gate = Gate(name="Spec", num_qubits=1, params=[freq_param]) - - circuit = QuantumCircuit(1) - circuit.append(gate, (0,)) - circuit.add_calibration(gate, (self.physical_qubits[0],), sched, params=[freq_param]) - - if self._pre_circuit is not None: - circuit = self._pre_circuit.compose(circuit) - - circuit.measure_active() + sched, freq_param = self._schedule() + circuit = self._template_circuit(freq_param) + circuit.add_calibration("Spec", (self.physical_qubits[0],), sched, params=[freq_param]) if not self._absolute: center_freq = backend.defaults().qubit_freq_est[self.physical_qubits[0]] @@ -454,9 +458,6 @@ def circuits(self, backend: Optional[Backend] = None): if not self._absolute: assigned_circ.metadata["center frequency"] = center_freq - if self._pre_circuit is not None: - assigned_circ.metadata["preparation circuit name"] = self._pre_circuit.name - try: assigned_circ.metadata["dt"] = getattr(backend.configuration(), "dt") except AttributeError as no_dt: diff --git a/test/test_qubit_spectroscopy.py b/test/test_qubit_spectroscopy.py index e68022f10f..ab969ff976 100644 --- a/test/test_qubit_spectroscopy.py +++ b/test/test_qubit_spectroscopy.py @@ -17,9 +17,10 @@ import numpy as np from qiskit.qobj.utils import MeasLevel from qiskit.test import QiskitTestCase -from qiskit import QiskitError, QuantumCircuit +from qiskit import QiskitError from qiskit_experiments.characterization.qubit_spectroscopy import QubitSpectroscopy +from qiskit_experiments.characterization.ef_spectroscopy import EFSpectroscopy from qiskit_experiments.test.mock_iq_backend import TestJob, IQTestBackend @@ -161,13 +162,15 @@ def test_spectroscopy12_end2end_classified(self): backend = SpectroscopyBackend(line_width=2e6) - prep = QuantumCircuit(1) - prep.x(0) - - spec = QubitSpectroscopy(3, np.linspace(-10.0, 10.0, 21), unit="MHz", pre_circuit=prep) + spec = EFSpectroscopy(3, np.linspace(-10.0, 10.0, 21), unit="MHz") spec.set_run_options(meas_level=MeasLevel.CLASSIFIED) result = spec.run(backend).analysis_result(0) self.assertTrue(abs(result["value"]) < 1e6) self.assertTrue(result["success"]) self.assertEqual(result["quality"], "computer_good") + + # Test the circuits + circ = spec.circuits(backend)[0] + self.assertEqual(circ.data[0][0].name, "x") + self.assertEqual(circ.data[1][0].name, "Spec") From 16c65125ae75a97de53f1c3e038f53ad49190972 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Mon, 7 Jun 2021 21:34:00 +0200 Subject: [PATCH 04/13] * Added e-f spectroscopy to the init. --- qiskit_experiments/characterization/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qiskit_experiments/characterization/__init__.py b/qiskit_experiments/characterization/__init__.py index 3fcb3e7178..44915d56b6 100644 --- a/qiskit_experiments/characterization/__init__.py +++ b/qiskit_experiments/characterization/__init__.py @@ -39,4 +39,5 @@ """ from .t1_experiment import T1Experiment, T1Analysis from .qubit_spectroscopy import QubitSpectroscopy, SpectroscopyAnalysis +from .ef_spectroscopy import EFSpectroscopy from .t2star_experiment import T2StarExperiment, T2StarAnalysis From de9daf01ecf9095a634f527dda558e31bc7ca7fb Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Tue, 8 Jun 2021 08:59:33 +0200 Subject: [PATCH 05/13] * Unit conversion. --- .../characterization/qubit_spectroscopy.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/qiskit_experiments/characterization/qubit_spectroscopy.py b/qiskit_experiments/characterization/qubit_spectroscopy.py index 93a45e3756..7a5ef1893c 100644 --- a/qiskit_experiments/characterization/qubit_spectroscopy.py +++ b/qiskit_experiments/characterization/qubit_spectroscopy.py @@ -20,6 +20,7 @@ from qiskit.exceptions import QiskitError from qiskit.providers import Backend import qiskit.pulse as pulse +from qiskit.utils import apply_prefix from qiskit.qobj.utils import MeasLevel from qiskit.providers.options import Options @@ -316,9 +317,6 @@ class QubitSpectroscopy(BaseExperiment): __analysis_class__ = SpectroscopyAnalysis - # Supported units for spectroscopy. - __units__ = {"Hz": 1.0, "kHz": 1.0e3, "MHz": 1.0e6, "GHz": 1.0e9} - @classmethod def _default_run_options(cls) -> Options: """Default options values for the experiment :meth:`run` method.""" @@ -372,10 +370,7 @@ def __init__( if len(frequencies) < 3: raise QiskitError("Spectroscopy requires at least three frequencies.") - if unit not in self.__units__: - raise QiskitError(f"Unsupported unit: {unit}.") - - self._frequencies = [freq * self.__units__[unit] for freq in frequencies] + self._frequencies = [apply_prefix(freq, unit) for freq in frequencies] self._absolute = absolute super().__init__([qubit]) From 6a8add8efa2f1b08d0bc24548a386eb409d5a84f Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Tue, 8 Jun 2021 09:09:04 +0200 Subject: [PATCH 06/13] * Fix unit conversion. --- qiskit_experiments/characterization/qubit_spectroscopy.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/qiskit_experiments/characterization/qubit_spectroscopy.py b/qiskit_experiments/characterization/qubit_spectroscopy.py index 7a5ef1893c..bdf47e1243 100644 --- a/qiskit_experiments/characterization/qubit_spectroscopy.py +++ b/qiskit_experiments/characterization/qubit_spectroscopy.py @@ -370,7 +370,11 @@ def __init__( if len(frequencies) < 3: raise QiskitError("Spectroscopy requires at least three frequencies.") - self._frequencies = [apply_prefix(freq, unit) for freq in frequencies] + if unit == "Hz": + self._frequencies = frequencies + else: + self._frequencies = [apply_prefix(freq, unit) for freq in frequencies] + self._absolute = absolute super().__init__([qubit]) From 056ee9cd77013de99477ba0daace1340325f5aae Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Tue, 8 Jun 2021 10:18:44 +0200 Subject: [PATCH 07/13] * Added the backend to _schedule in spectroscopy. * Improved MockIQBackend. --- .../characterization/qubit_spectroscopy.py | 13 +++++------ .../characterization/t1_experiment.py | 2 +- qiskit_experiments/test/mock_iq_backend.py | 23 +++---------------- test/test_qubit_spectroscopy.py | 16 ++++++------- 4 files changed, 18 insertions(+), 36 deletions(-) diff --git a/qiskit_experiments/characterization/qubit_spectroscopy.py b/qiskit_experiments/characterization/qubit_spectroscopy.py index bdf47e1243..6e05c440bb 100644 --- a/qiskit_experiments/characterization/qubit_spectroscopy.py +++ b/qiskit_experiments/characterization/qubit_spectroscopy.py @@ -27,8 +27,7 @@ from qiskit_experiments.analysis.curve_fitting import curve_fit from qiskit_experiments.base_analysis import BaseAnalysis from qiskit_experiments.base_experiment import BaseExperiment -from qiskit_experiments import AnalysisResult -from qiskit_experiments import ExperimentData +from qiskit_experiments.experiment_data import AnalysisResult, ExperimentData from qiskit_experiments.data_processing.processor_library import get_to_signal_processor from qiskit_experiments.analysis import plotting @@ -379,11 +378,11 @@ def __init__( super().__init__([qubit]) - def _schedule(self) -> Tuple[pulse.ScheduleBlock, Parameter]: + def _schedule(self, backend: Optional[Backend] = None) -> Tuple[pulse.ScheduleBlock, Parameter]: """Create the spectroscopy schedule.""" freq_param = Parameter("frequency") - with pulse.build(name="spectroscopy") as schedule: - pulse.set_frequency(freq_param, pulse.DriveChannel(self.physical_qubits[0])) + with pulse.build(backend=backend, name="spectroscopy") as schedule: + pulse.set_frequency(freq_param, pulse.drive_channel(self.physical_qubits[0])) pulse.play( pulse.GaussianSquare( duration=self.experiment_options.duration, @@ -391,7 +390,7 @@ def _schedule(self) -> Tuple[pulse.ScheduleBlock, Parameter]: sigma=self.experiment_options.sigma, width=self.experiment_options.width, ), - pulse.DriveChannel(self.physical_qubits[0]), + pulse.drive_channel(self.physical_qubits[0]), ) return schedule, freq_param @@ -426,7 +425,7 @@ def circuits(self, backend: Optional[Backend] = None): raise QiskitError("Cannot run spectroscopy relative to qubit without a backend.") # Create a template circuit - sched, freq_param = self._schedule() + sched, freq_param = self._schedule(backend) circuit = self._template_circuit(freq_param) circuit.add_calibration("Spec", (self.physical_qubits[0],), sched, params=[freq_param]) diff --git a/qiskit_experiments/characterization/t1_experiment.py b/qiskit_experiments/characterization/t1_experiment.py index 510742cc97..ab02ab886a 100644 --- a/qiskit_experiments/characterization/t1_experiment.py +++ b/qiskit_experiments/characterization/t1_experiment.py @@ -26,7 +26,7 @@ from qiskit_experiments.analysis.curve_fitting import process_curve_data, curve_fit from qiskit_experiments.analysis.data_processing import level2_probability from qiskit_experiments.analysis import plotting -from qiskit_experiments import AnalysisResult +from qiskit_experiments.experiment_data import AnalysisResult class T1Analysis(BaseAnalysis): diff --git a/qiskit_experiments/test/mock_iq_backend.py b/qiskit_experiments/test/mock_iq_backend.py index 35138428d1..a6cff1382b 100644 --- a/qiskit_experiments/test/mock_iq_backend.py +++ b/qiskit_experiments/test/mock_iq_backend.py @@ -16,8 +16,8 @@ import numpy as np from qiskit.providers.backend import BackendV1 as Backend +from qiskit.test.mock import FakeOpenPulse2Q from qiskit.providers import JobV1 -from qiskit.providers.models import QasmBackendConfiguration from qiskit.result import Result @@ -43,25 +43,9 @@ def cancel(self): pass -class IQTestBackend(Backend): +class IQTestBackend(FakeOpenPulse2Q): """An abstract backend for testing that can mock IQ data.""" - __configuration__ = { - "backend_name": "simulator", - "backend_version": "0", - "n_qubits": int(1), - "basis_gates": [], - "gates": [], - "local": True, - "simulator": True, - "conditional": False, - "open_pulse": False, - "memory": True, - "max_shots": int(1e6), - "coupling_map": [], - "dt": 0.1, - } - def __init__( self, iq_cluster_centers: Tuple[float, float, float, float] = (1.0, 1.0, -1.0, -1.0), @@ -72,10 +56,9 @@ def __init__( """ self._iq_cluster_centers = iq_cluster_centers self._iq_cluster_width = iq_cluster_width - self._rng = np.random.default_rng(0) - super().__init__(QasmBackendConfiguration(**self.__configuration__)) + super().__init__() def _default_options(self): """Default options of the test backend.""" diff --git a/test/test_qubit_spectroscopy.py b/test/test_qubit_spectroscopy.py index ab969ff976..718ec294ac 100644 --- a/test/test_qubit_spectroscopy.py +++ b/test/test_qubit_spectroscopy.py @@ -38,13 +38,13 @@ def __init__( ): """Initialize the spectroscopy backend.""" - self.__configuration__["basis_gates"] = ["spec", "x"] + super().__init__(iq_cluster_centers, iq_cluster_width) + + self.configuration().basis_gates = ["spec", "x"] self._linewidth = line_width self._freq_offset = freq_offset - super().__init__(iq_cluster_centers, iq_cluster_width) - # pylint: disable = arguments-differ def run( self, circuits, shots=1024, meas_level=MeasLevel.KERNELED, meas_return="single", **options @@ -107,7 +107,7 @@ def test_spectroscopy_end2end_classified(self): backend = SpectroscopyBackend(line_width=2e6) - spec = QubitSpectroscopy(3, np.linspace(-10.0, 10.0, 21), unit="MHz") + spec = QubitSpectroscopy(1, np.linspace(-10.0, 10.0, 21), unit="MHz") spec.set_run_options(meas_level=MeasLevel.CLASSIFIED) result = spec.run(backend).analysis_result(0) @@ -118,7 +118,7 @@ def test_spectroscopy_end2end_classified(self): # Test if we find still find the peak when it is shifted by 5 MHz. backend = SpectroscopyBackend(line_width=2e6, freq_offset=5.0e6) - spec = QubitSpectroscopy(3, np.linspace(-10.0, 10.0, 21), unit="MHz") + spec = QubitSpectroscopy(1, np.linspace(-10.0, 10.0, 21), unit="MHz") spec.set_run_options(meas_level=MeasLevel.CLASSIFIED) result = spec.run(backend).analysis_result(0) @@ -131,7 +131,7 @@ def test_spectroscopy_end2end_kerneled(self): backend = SpectroscopyBackend(line_width=2e6) - spec = QubitSpectroscopy(3, np.linspace(-10.0, 10.0, 21), unit="MHz") + spec = QubitSpectroscopy(1, np.linspace(-10.0, 10.0, 21), unit="MHz") result = spec.run(backend).analysis_result(0) self.assertTrue(abs(result["value"]) < 1e6) @@ -141,7 +141,7 @@ def test_spectroscopy_end2end_kerneled(self): # Test if we find still find the peak when it is shifted by 5 MHz. backend = SpectroscopyBackend(line_width=2e6, freq_offset=5.0e6) - spec = QubitSpectroscopy(3, np.linspace(-10.0, 10.0, 21), unit="MHz") + spec = QubitSpectroscopy(1, np.linspace(-10.0, 10.0, 21), unit="MHz") result = spec.run(backend).analysis_result(0) self.assertTrue(result["value"] < 5.1e6) @@ -162,7 +162,7 @@ def test_spectroscopy12_end2end_classified(self): backend = SpectroscopyBackend(line_width=2e6) - spec = EFSpectroscopy(3, np.linspace(-10.0, 10.0, 21), unit="MHz") + spec = EFSpectroscopy(1, np.linspace(-10.0, 10.0, 21), unit="MHz") spec.set_run_options(meas_level=MeasLevel.CLASSIFIED) result = spec.run(backend).analysis_result(0) From 9243d829a2e7c357f0e2dd00d420df09beec6e02 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Tue, 8 Jun 2021 10:21:41 +0200 Subject: [PATCH 08/13] * Removed unused docstring. --- qiskit_experiments/characterization/qubit_spectroscopy.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/qiskit_experiments/characterization/qubit_spectroscopy.py b/qiskit_experiments/characterization/qubit_spectroscopy.py index 6e05c440bb..22c7f981ea 100644 --- a/qiskit_experiments/characterization/qubit_spectroscopy.py +++ b/qiskit_experiments/characterization/qubit_spectroscopy.py @@ -358,9 +358,6 @@ def __init__( to 'Hz'. absolute: Boolean to specify if the frequencies are absolute or relative to the qubit frequency in the backend. - pre_circuit: An optional quantum circuit done before the spectroscopy pulse. This - circuit allows, for instance, to prepare the first excited sate and perform - spectroscopy on the 1 <-> 2 transition of a transmon. Raises: QiskitError: if there are less than three frequency shifts or if the unit is not known. From f7830c69aab2cbd083adb2a4eadf8286de4e2639 Mon Sep 17 00:00:00 2001 From: Daniel Egger <38065505+eggerdj@users.noreply.github.com> Date: Tue, 15 Jun 2021 17:46:42 +0200 Subject: [PATCH 09/13] Update qiskit_experiments/characterization/qubit_spectroscopy.py Co-authored-by: Naoki Kanazawa --- qiskit_experiments/characterization/qubit_spectroscopy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit_experiments/characterization/qubit_spectroscopy.py b/qiskit_experiments/characterization/qubit_spectroscopy.py index d6541c11f1..bfc0a36704 100644 --- a/qiskit_experiments/characterization/qubit_spectroscopy.py +++ b/qiskit_experiments/characterization/qubit_spectroscopy.py @@ -256,7 +256,7 @@ def __init__( self._absolute = absolute self.set_analysis_options(xlabel=f"Frequency [{unit}]", ylabel="Signal [arb. unit]") - def _schedule(self, backend: Optional[Backend] = None) -> Tuple[pulse.ScheduleBlock, Parameter]: + def _spect_gate_schedule(self, backend: Optional[Backend] = None) -> Tuple[pulse.ScheduleBlock, Parameter]: """Create the spectroscopy schedule.""" freq_param = Parameter("frequency") with pulse.build(backend=backend, name="spectroscopy") as schedule: From 00d915f2a685581611296320482c7838f7e9dce3 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Wed, 16 Jun 2021 10:39:49 +0200 Subject: [PATCH 10/13] * Refactored tests. --- .../characterization/qubit_spectroscopy.py | 49 ++++++++++--------- test/test_qubit_spectroscopy.py | 48 +++++++++++------- 2 files changed, 56 insertions(+), 41 deletions(-) diff --git a/qiskit_experiments/characterization/qubit_spectroscopy.py b/qiskit_experiments/characterization/qubit_spectroscopy.py index bfc0a36704..fadd0c92ac 100644 --- a/qiskit_experiments/characterization/qubit_spectroscopy.py +++ b/qiskit_experiments/characterization/qubit_spectroscopy.py @@ -12,17 +12,15 @@ """Spectroscopy experiment class.""" -from typing import List, Dict, Any, Union, Optional +from typing import Any, Dict, List, Optional, Tuple, Union import numpy as np -import qiskit.pulse as pulse from qiskit import QuantumCircuit from qiskit.circuit import Gate, Parameter from qiskit.exceptions import QiskitError from qiskit.providers import Backend import qiskit.pulse as pulse from qiskit.utils import apply_prefix -from qiskit.qobj.utils import MeasLevel from qiskit.providers.options import Options from qiskit.qobj.utils import MeasLevel @@ -35,7 +33,6 @@ get_opt_error, ) from qiskit_experiments.base_experiment import BaseExperiment -from qiskit_experiments.experiment_data import AnalysisResult, ExperimentData from qiskit_experiments.data_processing.processor_library import get_to_signal_processor @@ -254,13 +251,21 @@ def __init__( super().__init__([qubit]) self._absolute = absolute - self.set_analysis_options(xlabel=f"Frequency [{unit}]", ylabel="Signal [arb. unit]") - def _spect_gate_schedule(self, backend: Optional[Backend] = None) -> Tuple[pulse.ScheduleBlock, Parameter]: + if not self._absolute: + self.set_analysis_options(xlabel="Frequency shift [Hz]") + else: + self.set_analysis_options(xlabel="Frequency [Hz]") + + self.set_analysis_options(ylabel="Signal [arb. unit]") + + def _spec_gate_schedule( + self, backend: Optional[Backend] = None + ) -> Tuple[pulse.ScheduleBlock, Parameter]: """Create the spectroscopy schedule.""" freq_param = Parameter("frequency") with pulse.build(backend=backend, name="spectroscopy") as schedule: - pulse.set_frequency(freq_param, pulse.drive_channel(self.physical_qubits[0])) + pulse.shift_frequency(freq_param, pulse.drive_channel(self.physical_qubits[0])) pulse.play( pulse.GaussianSquare( duration=self.experiment_options.duration, @@ -270,6 +275,7 @@ def _spect_gate_schedule(self, backend: Optional[Backend] = None) -> Tuple[pulse ), pulse.drive_channel(self.physical_qubits[0]), ) + pulse.shift_frequency(-freq_param, pulse.drive_channel(self.physical_qubits[0])) return schedule, freq_param @@ -296,10 +302,10 @@ def circuits(self, backend: Optional[Backend] = None): Raises: QiskitError: - - If relative frequencies are used but no backend was given. + - If absolute frequencies are used but no backend is given. - If the backend configuration does not define dt. """ - # TODO this is temporarily logic. Need update of circuit data and processor logic. + # TODO this is temporary logic. Need update of circuit data and processor logic. self.set_analysis_options( data_processor=get_to_signal_processor( meas_level=self.run_options.meas_level, @@ -307,28 +313,30 @@ def circuits(self, backend: Optional[Backend] = None): ) ) - if not backend and not self._absolute: - raise QiskitError("Cannot run spectroscopy relative to qubit without a backend.") + if backend is None and self._absolute: + raise QiskitError("Cannot run spectroscopy absolute to qubit without a backend.") # Create a template circuit - sched, freq_param = self._schedule(backend) + sched, freq_param = self._spec_gate_schedule(backend) circuit = self._template_circuit(freq_param) circuit.add_calibration("Spec", (self.physical_qubits[0],), sched, params=[freq_param]) - if not self._absolute: - center_freq = backend.defaults().qubit_freq_est[self.physical_qubits[0]] - # Create the circuits to run circs = [] for freq in self._frequencies: - if not self._absolute: - freq += center_freq - assigned_circ = circuit.assign_parameters({freq_param: freq}, inplace=False) + freq_shift = freq + if self._absolute: + center_freq = backend.defaults().qubit_freq_est[self.physical_qubits[0]] + freq_shift -= center_freq + + freq_shift = np.round(freq_shift, decimals=3) + + assigned_circ = circuit.assign_parameters({freq_param: freq_shift}, inplace=False) assigned_circ.metadata = { "experiment_type": self._type, "qubit": self.physical_qubits[0], - "xval": freq, + "xval": np.round(freq, decimals=3), "unit": "Hz", "amplitude": self.experiment_options.amp, "duration": self.experiment_options.duration, @@ -337,9 +345,6 @@ def circuits(self, backend: Optional[Backend] = None): "schedule": str(sched), } - if not self._absolute: - assigned_circ.metadata["center frequency"] = center_freq - try: assigned_circ.metadata["dt"] = getattr(backend.configuration(), "dt") except AttributeError as no_dt: diff --git a/test/test_qubit_spectroscopy.py b/test/test_qubit_spectroscopy.py index d6b5ff33f1..51ef9af0f0 100644 --- a/test/test_qubit_spectroscopy.py +++ b/test/test_qubit_spectroscopy.py @@ -69,15 +69,16 @@ def run( "header": {"metadata": circ.metadata}, } - set_freq = None + shift_freq = None for inst in circ.data: if inst[0].name == "Spec": - set_freq = float(inst[0].params[0]) + shift_freq = float(inst[0].params[0]) - if set_freq is None: + if shift_freq is None: raise QiskitError("Spectroscopy does not have a Spec instruction.") - delta_freq = set_freq - self._freq_offset + delta_freq = shift_freq - self._freq_offset + prob = np.exp(-(delta_freq ** 2) / (2 * self._linewidth ** 2)) if meas_level == MeasLevel.CLASSIFIED: @@ -107,54 +108,58 @@ def test_spectroscopy_end2end_classified(self): """End to end test of the spectroscopy experiment.""" backend = SpectroscopyBackend(line_width=2e6) + qubit = 1 + freq01 = backend.defaults().qubit_freq_est[qubit] + frequencies = np.linspace(freq01 - 10.0e6, freq01 + 10.0e6, 21) - spec = QubitSpectroscopy(1, np.linspace(-10.0, 10.0, 21), unit="MHz") + spec = QubitSpectroscopy(qubit, frequencies, unit="Hz") spec.set_run_options(meas_level=MeasLevel.CLASSIFIED) result = spec.run(backend).analysis_result(0) value = get_opt_value(result, "freq") - self.assertTrue(abs(value) < 1e6) + self.assertTrue(4.999e9 < value < 5.001e9) self.assertTrue(result["success"]) self.assertEqual(result["quality"], "computer_good") # Test if we find still find the peak when it is shifted by 5 MHz. backend = SpectroscopyBackend(line_width=2e6, freq_offset=5.0e6) - spec = QubitSpectroscopy(1, np.linspace(-10.0, 10.0, 21), unit="MHz") + spec = QubitSpectroscopy(qubit, frequencies, unit="Hz") spec.set_run_options(meas_level=MeasLevel.CLASSIFIED) result = spec.run(backend).analysis_result(0) value = get_opt_value(result, "freq") - self.assertTrue(value < 5.1e6) - self.assertTrue(value > 4.9e6) + self.assertTrue(5.0049e9 < value < 5.0051e9) self.assertEqual(result["quality"], "computer_good") def test_spectroscopy_end2end_kerneled(self): """End to end test of the spectroscopy experiment on IQ data.""" backend = SpectroscopyBackend(line_width=2e6) + qubit = 0 + freq01 = backend.defaults().qubit_freq_est[qubit] + frequencies = np.linspace(freq01 - 10.0e6, freq01 + 10.0e6, 21) / 1e6 - spec = QubitSpectroscopy(1, np.linspace(-10.0, 10.0, 21), unit="MHz") + spec = QubitSpectroscopy(qubit, frequencies, unit="MHz") result = spec.run(backend).analysis_result(0) value = get_opt_value(result, "freq") - self.assertTrue(abs(value) < 1e6) + self.assertTrue(freq01 - 2e6 < value < freq01 + 2e6) self.assertTrue(result["success"]) self.assertEqual(result["quality"], "computer_good") # Test if we find still find the peak when it is shifted by 5 MHz. backend = SpectroscopyBackend(line_width=2e6, freq_offset=5.0e6) - spec = QubitSpectroscopy(1, np.linspace(-10.0, 10.0, 21), unit="MHz") + spec = QubitSpectroscopy(qubit, frequencies, unit="MHz") result = spec.run(backend).analysis_result(0) value = get_opt_value(result, "freq") - self.assertTrue(value < 5.1e6) - self.assertTrue(value > 4.9e6) + self.assertTrue(freq01 + 3e6 < value < freq01 + 8e6) self.assertEqual(result["quality"], "computer_good") spec.set_run_options(meas_return="avg") @@ -162,21 +167,26 @@ def test_spectroscopy_end2end_kerneled(self): value = get_opt_value(result, "freq") - self.assertTrue(value < 5.1e6) - self.assertTrue(value > 4.9e6) + self.assertTrue(freq01 + 3e6 < value < freq01 + 8e6) self.assertEqual(result["quality"], "computer_good") - self.assertTrue(result["ydata_err"] is None) def test_spectroscopy12_end2end_classified(self): """End to end test of the spectroscopy experiment with an x pulse.""" backend = SpectroscopyBackend(line_width=2e6) + qubit = 0 + freq01 = backend.defaults().qubit_freq_est[qubit] + frequencies = np.linspace(freq01 - 10.0e6, freq01 + 10.0e6, 21) - spec = EFSpectroscopy(1, np.linspace(-10.0, 10.0, 21), unit="MHz") + # Note that the backend is not sophisticated enough to simulate an e-f + # transition so we run the test with g-e. + spec = EFSpectroscopy(qubit, frequencies, unit="Hz") spec.set_run_options(meas_level=MeasLevel.CLASSIFIED) result = spec.run(backend).analysis_result(0) - self.assertTrue(abs(result["value"]) < 1e6) + value = get_opt_value(result, "freq") + + self.assertTrue(freq01 - 2e6 < value < freq01 + 2e6) self.assertTrue(result["success"]) self.assertEqual(result["quality"], "computer_good") From 17b84350b84b4ee4d63c20f83bd8181497b83c99 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Thu, 24 Jun 2021 08:32:43 +0200 Subject: [PATCH 11/13] * Updated test to the refactored MockBackend --- test/calibration/test_update_library.py | 30 ++++++++++++++----------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/test/calibration/test_update_library.py b/test/calibration/test_update_library.py index 40c3efde07..ee85c7db91 100644 --- a/test/calibration/test_update_library.py +++ b/test/calibration/test_update_library.py @@ -51,13 +51,14 @@ def test_amplitude(self): cals.add_schedule(xp) cals.add_schedule(x90p) - rabi = Rabi(3) + qubit = 1 + rabi = Rabi(qubit) rabi.set_experiment_options(amplitudes=np.linspace(-0.95, 0.95, 21)) exp_data = rabi.run(RabiBackend()) - for qubit in [0, 3]: + for qubit_ in [0, 1]: with self.assertRaises(CalibrationError): - cals.get_schedule("xp", qubits=qubit) + cals.get_schedule("xp", qubits=qubit_) to_update = [(np.pi, "amp", "xp"), (np.pi / 2, "amp", x90p)] @@ -75,34 +76,37 @@ def test_amplitude(self): rate = 2 * np.pi * result["popt"][1] amp = np.round(np.pi / rate, decimals=8) with pulse.build(name="xp") as expected: - pulse.play(pulse.Gaussian(160, amp, 40), pulse.DriveChannel(3)) + pulse.play(pulse.Gaussian(160, amp, 40), pulse.DriveChannel(qubit)) - self.assertEqual(cals.get_schedule("xp", qubits=3), expected) + self.assertEqual(cals.get_schedule("xp", qubits=qubit), expected) amp = np.round(0.5 * np.pi / rate, decimals=8) with pulse.build(name="xp") as expected: - pulse.play(pulse.Gaussian(160, amp, 40), pulse.DriveChannel(3)) + pulse.play(pulse.Gaussian(160, amp, 40), pulse.DriveChannel(qubit)) - self.assertEqual(cals.get_schedule("x90p", qubits=3), expected) + self.assertEqual(cals.get_schedule("x90p", qubits=qubit), expected) def test_frequency(self): """Test calibrations update from spectroscopy.""" - backend = SpectroscopyBackend(line_width=2e6, freq_offset=5.0e6) + qubit = 1 + peak_offset = 5.0e6 + backend = SpectroscopyBackend(line_width=2e6, freq_offset=peak_offset) + freq01 = backend.defaults().qubit_freq_est[qubit] + frequencies = np.linspace(freq01 - 10.0e6, freq01 + 10.0e6, 21) / 1e6 - spec = QubitSpectroscopy(3, np.linspace(-10.0, 10.0, 21), unit="MHz") + spec = QubitSpectroscopy(qubit, frequencies, unit="MHz") spec.set_run_options(meas_level=MeasLevel.CLASSIFIED) exp_data = spec.run(backend) result = exp_data.analysis_result(0) value = get_opt_value(result, "freq") - self.assertTrue(value < 5.1e6) - self.assertTrue(value > 4.9e6) + self.assertTrue(freq01 + peak_offset - 2e6 < value < freq01 + peak_offset + 2e6) self.assertEqual(result["quality"], "computer_good") # Test the integration with the BackendCalibrations cals = BackendCalibrations(FakeAthens()) - self.assertNotEqual(cals.get_qubit_frequencies()[3], result["popt"][2]) + self.assertNotEqual(cals.get_qubit_frequencies()[qubit], result["popt"][2]) Frequency.update(cals, exp_data) - self.assertEqual(cals.get_qubit_frequencies()[3], result["popt"][2]) + self.assertEqual(cals.get_qubit_frequencies()[qubit], result["popt"][2]) From 6284f93ed6209338558a02fd0b158802a3ff0085 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Thu, 24 Jun 2021 08:39:14 +0200 Subject: [PATCH 12/13] * removed pulse.drive_channel as it fails on some backends. --- qiskit_experiments/characterization/qubit_spectroscopy.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qiskit_experiments/characterization/qubit_spectroscopy.py b/qiskit_experiments/characterization/qubit_spectroscopy.py index d613386ff8..273146d179 100644 --- a/qiskit_experiments/characterization/qubit_spectroscopy.py +++ b/qiskit_experiments/characterization/qubit_spectroscopy.py @@ -269,7 +269,7 @@ def _spec_gate_schedule( """Create the spectroscopy schedule.""" freq_param = Parameter("frequency") with pulse.build(backend=backend, name="spectroscopy") as schedule: - pulse.shift_frequency(freq_param, pulse.drive_channel(self.physical_qubits[0])) + pulse.shift_frequency(freq_param, pulse.DriveChannel(self.physical_qubits[0])) pulse.play( pulse.GaussianSquare( duration=self.experiment_options.duration, @@ -277,9 +277,9 @@ def _spec_gate_schedule( sigma=self.experiment_options.sigma, width=self.experiment_options.width, ), - pulse.drive_channel(self.physical_qubits[0]), + pulse.DriveChannel(self.physical_qubits[0]), ) - pulse.shift_frequency(-freq_param, pulse.drive_channel(self.physical_qubits[0])) + pulse.shift_frequency(-freq_param, pulse.DriveChannel(self.physical_qubits[0])) return schedule, freq_param From 44e3d5e8b0c1ad41b321dd40126e8c5d645a5f28 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Thu, 24 Jun 2021 10:52:41 +0200 Subject: [PATCH 13/13] * Added class variable to fix the name of the spec gate. --- qiskit_experiments/characterization/ef_spectroscopy.py | 5 ++--- qiskit_experiments/characterization/qubit_spectroscopy.py | 6 +++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/qiskit_experiments/characterization/ef_spectroscopy.py b/qiskit_experiments/characterization/ef_spectroscopy.py index c889e196c9..8e32d11253 100644 --- a/qiskit_experiments/characterization/ef_spectroscopy.py +++ b/qiskit_experiments/characterization/ef_spectroscopy.py @@ -33,12 +33,11 @@ class EFSpectroscopy(QubitSpectroscopy): """ - @staticmethod - def _template_circuit(freq_param) -> QuantumCircuit: + def _template_circuit(self, freq_param) -> QuantumCircuit: """Return the template quantum circuit.""" circuit = QuantumCircuit(1) circuit.x(0) - circuit.append(Gate(name="Spec", num_qubits=1, params=[freq_param]), (0,)) + circuit.append(Gate(name=self.__spec_gate_name__, num_qubits=1, params=[freq_param]), (0,)) circuit.measure_active() return circuit diff --git a/qiskit_experiments/characterization/qubit_spectroscopy.py b/qiskit_experiments/characterization/qubit_spectroscopy.py index 273146d179..15a616a5e9 100644 --- a/qiskit_experiments/characterization/qubit_spectroscopy.py +++ b/qiskit_experiments/characterization/qubit_spectroscopy.py @@ -196,6 +196,7 @@ class QubitSpectroscopy(BaseExperiment): """ __analysis_class__ = SpectroscopyAnalysis + __spec_gate_name__ = "Spec" @classmethod def _default_run_options(cls) -> Options: @@ -283,11 +284,10 @@ def _spec_gate_schedule( return schedule, freq_param - @staticmethod - def _template_circuit(freq_param) -> QuantumCircuit: + def _template_circuit(self, freq_param) -> QuantumCircuit: """Return the template quantum circuit.""" circuit = QuantumCircuit(1) - circuit.append(Gate(name="Spec", num_qubits=1, params=[freq_param]), (0,)) + circuit.append(Gate(name=self.__spec_gate_name__, num_qubits=1, params=[freq_param]), (0,)) circuit.measure_active() return circuit