Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions qiskit_experiments/characterization/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
44 changes: 44 additions & 0 deletions qiskit_experiments/characterization/ef_spectroscopy.py
Original file line number Diff line number Diff line change
@@ -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)
Comment thread
eggerdj marked this conversation as resolved.
circuit.append(Gate(name="Spec", num_qubits=1, params=[freq_param]), (0,))
Comment thread
eggerdj marked this conversation as resolved.
Outdated
circuit.measure_active()

return circuit
62 changes: 35 additions & 27 deletions qiskit_experiments/characterization/qubit_spectroscopy.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@
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_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

Expand Down Expand Up @@ -316,9 +316,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."""
Expand Down Expand Up @@ -369,14 +366,41 @@ 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}.")
if unit == "Hz":
self._frequencies = frequencies
else:
self._frequencies = [apply_prefix(freq, unit) for freq in frequencies]

self._frequencies = [freq * self.__units__[unit] for freq in frequencies]
self._absolute = absolute

super().__init__([qubit])

def _schedule(self, backend: Optional[Backend] = None) -> Tuple[pulse.ScheduleBlock, Parameter]:
Comment thread
eggerdj marked this conversation as resolved.
Outdated
"""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]))
Comment thread
eggerdj marked this conversation as resolved.
Outdated
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.drive_channel(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.

Expand All @@ -398,25 +422,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])
circuit.measure_active()
sched, freq_param = self._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]]
Expand Down
2 changes: 1 addition & 1 deletion qiskit_experiments/characterization/t1_experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
23 changes: 3 additions & 20 deletions qiskit_experiments/test/mock_iq_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand All @@ -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),
Expand All @@ -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."""
Expand Down
43 changes: 35 additions & 8 deletions test/test_qubit_spectroscopy.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
import numpy as np
from qiskit.qobj.utils import MeasLevel
from qiskit.test import QiskitTestCase
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


Expand All @@ -36,13 +38,13 @@ def __init__(
):
"""Initialize the spectroscopy backend."""

self.__configuration__["basis_gates"] = ["spec"]
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
Expand All @@ -66,7 +68,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))

Expand Down Expand Up @@ -98,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)

Expand All @@ -109,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)

Expand All @@ -122,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)
Expand All @@ -132,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)
Expand All @@ -147,3 +156,21 @@ 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)

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)

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")