Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 import T1, T1Analysis
from .qubit_spectroscopy import QubitSpectroscopy, SpectroscopyAnalysis
from .ef_spectroscopy import EFSpectroscopy
from .t2star_experiment import T2StarExperiment, T2StarAnalysis
43 changes: 43 additions & 0 deletions qiskit_experiments/characterization/ef_spectroscopy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# 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

"""

def _template_circuit(self, freq_param) -> QuantumCircuit:
"""Return the template quantum circuit."""
circuit = QuantumCircuit(1)
circuit.x(0)
circuit.append(Gate(name=self.__spec_gate_name__, num_qubits=1, params=[freq_param]), (0,))
circuit.measure_active()

return circuit
101 changes: 58 additions & 43 deletions qiskit_experiments/characterization/qubit_spectroscopy.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +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.providers.options import Options
from qiskit.qobj.utils import MeasLevel

Expand Down Expand Up @@ -195,9 +196,7 @@ class QubitSpectroscopy(BaseExperiment):
"""

__analysis_class__ = SpectroscopyAnalysis

# Supported units for spectroscopy.
__units__ = {"Hz": 1.0, "kHz": 1.0e3, "MHz": 1.0e6, "GHz": 1.0e9}
__spec_gate_name__ = "Spec"

@classmethod
def _default_run_options(cls) -> Options:
Expand Down Expand Up @@ -249,14 +248,49 @@ 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]

super().__init__([qubit])

self._frequencies = [freq * self.__units__[unit] for freq in frequencies]
self._absolute = absolute
self.set_analysis_options(xlabel=f"Frequency [{unit}]", ylabel="Signal [arb. unit]")

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.shift_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]),
)
pulse.shift_frequency(-freq_param, pulse.DriveChannel(self.physical_qubits[0]))

return schedule, freq_param

def _template_circuit(self, freq_param) -> QuantumCircuit:
"""Return the template quantum circuit."""
circuit = QuantumCircuit(1)
circuit.append(Gate(name=self.__spec_gate_name__, 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 @@ -272,57 +306,41 @@ 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,
meas_return=self.run_options.meas_return,
)
)

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

if not self._absolute:
center_freq = backend.defaults().qubit_freq_est[self.physical_qubits[0]]
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])

# Create the circuits to run
circs = []
for freq in self._frequencies:
if not self._absolute:
freq += center_freq

freq = np.round(freq, decimals=3)
freq_shift = freq
if self._absolute:
center_freq = backend.defaults().qubit_freq_est[self.physical_qubits[0]]
freq_shift -= center_freq

assigned_circ = circuit.assign_parameters({freq_param: freq}, inplace=False)
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,
"qubits": (self.physical_qubits[0],),
"xval": freq,
"xval": np.round(freq, decimals=3),
"unit": "Hz",
"amplitude": self.experiment_options.amp,
"duration": self.experiment_options.duration,
Expand All @@ -331,9 +349,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:
Expand Down
6 changes: 3 additions & 3 deletions test/calibration/experiments/test_rabi.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def test_rabi_end_to_end(self):
test_tol = 0.01
backend = RabiBackend()

rabi = Rabi(3)
rabi = Rabi(1)
rabi.set_experiment_options(amplitudes=np.linspace(-0.95, 0.95, 21))
result = rabi.run(backend).analysis_result(0)

Expand All @@ -74,15 +74,15 @@ def test_rabi_end_to_end(self):

backend = RabiBackend(amplitude_to_angle=np.pi / 2)

rabi = Rabi(3)
rabi = Rabi(1)
rabi.set_experiment_options(amplitudes=np.linspace(-0.95, 0.95, 21))
result = rabi.run(backend).analysis_result(0)
self.assertEqual(result["quality"], "computer_good")
self.assertTrue(abs(result["popt"][1] - backend.rabi_rate) < test_tol)

backend = RabiBackend(amplitude_to_angle=2.5 * np.pi)

rabi = Rabi(3)
rabi = Rabi(1)
rabi.set_experiment_options(amplitudes=np.linspace(-0.95, 0.95, 101))
result = rabi.run(backend).analysis_result(0)

Expand Down
30 changes: 17 additions & 13 deletions test/calibration/test_update_library.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)]

Expand All @@ -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])
25 changes: 4 additions & 21 deletions test/mock_iq_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,32 +17,16 @@
import numpy as np

from qiskit import QuantumCircuit
from qiskit.providers.backend import BackendV1 as Backend
from qiskit.providers.models import QasmBackendConfiguration
from qiskit.test.mock import FakeOpenPulse2Q

from qiskit.qobj.utils import MeasLevel
from qiskit.providers.options import Options
from .mock_job import MockJob


class MockIQBackend(Backend):
class MockIQBackend(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 @@ -53,10 +37,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
Loading