Skip to content

Commit

Permalink
Merge pull request #539 from qiboteam/w12_protocol
Browse files Browse the repository at this point in the history
Add protocol for calibration of pulse to generate state 2
  • Loading branch information
andrea-pasquale authored Oct 23, 2023
2 parents e322b4e + 86c6741 commit 6d00390
Show file tree
Hide file tree
Showing 9 changed files with 363 additions and 7 deletions.
4 changes: 4 additions & 0 deletions src/qibocal/protocols/characterization/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@
resonator_flux,
)
from .qubit_spectroscopy import qubit_spectroscopy
from .qubit_spectroscopy_ef import qubit_spectroscopy_ef
from .rabi.amplitude import rabi_amplitude
from .rabi.ef import rabi_amplitude_ef
from .rabi.length import rabi_length
from .rabi.length_sequences import rabi_length_sequences
from .ramsey import ramsey
Expand Down Expand Up @@ -88,4 +90,6 @@ class Operation(Enum):
readout_mitigation_matrix = readout_mitigation_matrix
twpa_frequency = twpa_frequency
twpa_power = twpa_power
rabi_amplitude_ef = rabi_amplitude_ef
qubit_spectroscopy_ef = qubit_spectroscopy_ef
resonator_amplitude = resonator_amplitude
4 changes: 3 additions & 1 deletion src/qibocal/protocols/characterization/allxy/allxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,9 @@ def _acquisition(
z_proj = 2 * results[ro_pulses[qubit].serial].probability(0) - 1
# store the results
gate = "-".join(gates)
data.register_qubit(AllXYType, (qubit), dict(prob=z_proj, gate=gate))
data.register_qubit(
AllXYType, (qubit), dict(prob=np.array([z_proj]), gate=np.array([gate]))
)
# finally, save the remaining data
return data

Expand Down
181 changes: 181 additions & 0 deletions src/qibocal/protocols/characterization/qubit_spectroscopy_ef.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
from dataclasses import asdict, dataclass, field

import numpy as np
from qibolab import AcquisitionType, AveragingMode, ExecutionParameters
from qibolab.platform import Platform
from qibolab.pulses import PulseSequence
from qibolab.qubits import QubitId
from qibolab.sweeper import Parameter, Sweeper, SweeperType

from qibocal import update
from qibocal.auto.operation import Qubits, Routine

from .qubit_spectroscopy import (
QubitSpectroscopyData,
QubitSpectroscopyParameters,
QubitSpectroscopyResults,
_fit,
)
from .resonator_spectroscopy import ResSpecType
from .utils import GHZ_TO_HZ, HZ_TO_GHZ, spectroscopy_plot, table_dict, table_html

DEFAULT_ANHARMONICITY = 300e6
"""Initial guess for anharmonicity."""


@dataclass
class QubitSpectroscopyEFParameters(QubitSpectroscopyParameters):
"""QubitSpectroscopyEF runcard inputs."""


@dataclass
class QubitSpectroscopyEFResults(QubitSpectroscopyResults):
"""QubitSpectroscopyEF outputs."""

anharmonicity: dict[QubitId, float] = field(default_factory=dict)


@dataclass
class QubitSpectroscopyEFData(QubitSpectroscopyData):
"""QubitSpectroscopy acquisition outputs."""

drive_frequencies: dict[QubitId, float] = field(default_factory=dict)


def _fit_ef(data: QubitSpectroscopyEFData) -> QubitSpectroscopyEFResults:
results = _fit(data)
anharmoncities = {
qubit: data.drive_frequencies[qubit] * HZ_TO_GHZ - results.frequency[qubit]
for qubit in data.qubits
}
params = asdict(results)
params.update({"anharmonicity": anharmoncities})

return QubitSpectroscopyEFResults(**params)


def _acquisition(
params: QubitSpectroscopyEFParameters, platform: Platform, qubits: Qubits
) -> QubitSpectroscopyEFData:
"""Data acquisition for qubit spectroscopy ef protocol.
Similar to a qubit spectroscopy with the difference that the qubit is first
excited to the state 1. This protocols aims at finding the transition frequency between
state 1 and the state 2. The anharmonicity is also computed.
If the RX12 frequency is not present in the runcard the sweep is performed around the
qubit drive frequency shifted by DEFAULT_ANHARMONICITY, an hardcoded parameter editable
in this file.
"""
# create a sequence of pulses for the experiment:
# long drive probing pulse - MZ

# taking advantage of multiplexing, apply the same set of gates to all qubits in parallel
sequence = PulseSequence()
ro_pulses = {}
qd_pulses = {}
rx_pulses = {}
amplitudes = {}
drive_frequencies = {}
for qubit in qubits:
rx_pulses[qubit] = platform.create_RX_pulse(qubit, start=0)
drive_frequencies[qubit] = rx_pulses[qubit].frequency
qd_pulses[qubit] = platform.create_qubit_drive_pulse(
qubit, start=rx_pulses[qubit].finish, duration=params.drive_duration
)
if platform.qubits[qubit].native_gates.RX12.frequency is None:
qd_pulses[qubit].frequency = (
rx_pulses[qubit].frequency - DEFAULT_ANHARMONICITY
)
else:
qd_pulses[qubit].frequency = platform.qubits[
qubit
].native_gates.RX12.frequency

if params.drive_amplitude is not None:
qd_pulses[qubit].amplitude = params.drive_amplitude

amplitudes[qubit] = qd_pulses[qubit].amplitude

ro_pulses[qubit] = platform.create_qubit_readout_pulse(
qubit, start=qd_pulses[qubit].finish
)
sequence.add(rx_pulses[qubit])
sequence.add(qd_pulses[qubit])
sequence.add(ro_pulses[qubit])

# define the parameter to sweep and its range:
# sweep only before qubit frequency
delta_frequency_range = np.arange(
-params.freq_width, params.freq_width, params.freq_step
)
sweeper = Sweeper(
Parameter.frequency,
delta_frequency_range,
pulses=[qd_pulses[qubit] for qubit in qubits],
type=SweeperType.OFFSET,
)

# Create data structure for data acquisition.
data = QubitSpectroscopyEFData(
resonator_type=platform.resonator_type,
amplitudes=amplitudes,
drive_frequencies=drive_frequencies,
)

results = platform.sweep(
sequence,
ExecutionParameters(
nshots=params.nshots,
relaxation_time=params.relaxation_time,
acquisition_type=AcquisitionType.INTEGRATION,
averaging_mode=AveragingMode.CYCLIC,
),
sweeper,
)

# retrieve the results for every qubit
for qubit, ro_pulse in ro_pulses.items():
# average msr, phase, i and q over the number of shots defined in the runcard
result = results[ro_pulse.serial]
# store the results
data.register_qubit(
ResSpecType,
(qubit),
dict(
msr=result.magnitude,
phase=result.phase,
freq=delta_frequency_range + qd_pulses[qubit].frequency,
),
)
return data


def _plot(data: QubitSpectroscopyEFData, qubit, fit: QubitSpectroscopyEFResults):
"""Plotting function for QubitSpectroscopy."""
figures, report = spectroscopy_plot(data, qubit, fit)
if fit is not None:
report = table_html(
table_dict(
qubit,
["Frequency 1->2", "Amplitude", "Anharmonicity"],
[
np.round(fit.frequency[qubit] * GHZ_TO_HZ, 0),
fit.amplitude[qubit],
np.round(fit.anharmonicity[qubit] * GHZ_TO_HZ, 0),
],
)
)

return figures, report


def _update(results: QubitSpectroscopyEFResults, platform: Platform, qubit: QubitId):
"""Update w12 frequency"""
update.frequency_12_transition(results.frequency[qubit], platform, qubit)
update.anharmonicity(results.anharmonicity[qubit], platform, qubit)


qubit_spectroscopy_ef = Routine(_acquisition, _fit_ef, _plot, _update)
"""QubitSpectroscopyEF Routine object."""
124 changes: 124 additions & 0 deletions src/qibocal/protocols/characterization/rabi/ef.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
from dataclasses import dataclass

import numpy as np
from qibolab import AcquisitionType, AveragingMode, ExecutionParameters
from qibolab.platform import Platform
from qibolab.pulses import PulseSequence
from qibolab.qubits import QubitId
from qibolab.sweeper import Parameter, Sweeper, SweeperType

from qibocal import update
from qibocal.auto.operation import Qubits, Routine

from . import amplitude, utils


@dataclass
class RabiAmplitudeEFParameters(amplitude.RabiAmplitudeParameters):
"""RabiAmplitudeEF runcard inputs."""


@dataclass
class RabiAmplitudeEFResults(amplitude.RabiAmplitudeResults):
"""RabiAmplitudeEF outputs."""


@dataclass
class RabiAmplitudeEFData(amplitude.RabiAmplitudeData):
"""RabiAmplitude data acquisition."""


def _acquisition(
params: RabiAmplitudeEFParameters, platform: Platform, qubits: Qubits
) -> RabiAmplitudeEFData:
r"""
Data acquisition for Rabi EF experiment sweeping amplitude.
The rabi protocol is performed after exciting the qubit to state 1.
This protocol allows to compute the amplitude of the RX12 pulse to excite
the qubit to state 2 starting from state 1.
"""

# create a sequence of pulses for the experiment
sequence = PulseSequence()
qd_pulses = {}
ro_pulses = {}
rx_pulses = {}
durations = {}
for qubit in qubits:
rx_pulses[qubit] = platform.create_RX_pulse(qubit, start=0)
qd_pulses[qubit] = platform.create_RX_pulse(
qubit, start=rx_pulses[qubit].finish
)
if params.pulse_length is not None:
qd_pulses[qubit].duration = params.pulse_length

durations[qubit] = qd_pulses[qubit].duration
ro_pulses[qubit] = platform.create_qubit_readout_pulse(
qubit, start=qd_pulses[qubit].finish
)
sequence.add(rx_pulses[qubit])
sequence.add(qd_pulses[qubit])
sequence.add(ro_pulses[qubit])

# define the parameter to sweep and its range:
# qubit drive pulse amplitude
qd_pulse_amplitude_range = np.arange(
params.min_amp_factor,
params.max_amp_factor,
params.step_amp_factor,
)
sweeper = Sweeper(
Parameter.amplitude,
qd_pulse_amplitude_range,
[qd_pulses[qubit] for qubit in qubits],
type=SweeperType.FACTOR,
)

# create a DataUnits object to store the results,
# DataUnits stores by default MSR, phase, i, q
# additionally include qubit drive pulse amplitude
data = RabiAmplitudeEFData(durations=durations)

# sweep the parameter
results = platform.sweep(
sequence,
ExecutionParameters(
nshots=params.nshots,
relaxation_time=params.relaxation_time,
acquisition_type=AcquisitionType.INTEGRATION,
averaging_mode=AveragingMode.CYCLIC,
),
sweeper,
)
for qubit in qubits:
# average msr, phase, i and q over the number of shots defined in the runcard
result = results[ro_pulses[qubit].serial]
data.register_qubit(
amplitude.RabiAmpType,
(qubit),
dict(
amp=qd_pulses[qubit].amplitude * qd_pulse_amplitude_range,
msr=result.magnitude,
phase=result.phase,
),
)
return data


def _plot(data: RabiAmplitudeEFData, qubit, fit: RabiAmplitudeEFResults = None):
"""Plotting function for RabiAmplitude."""
figures, report = utils.plot(data, qubit, fit)
if report is not None:
report = report.replace("Pi pulse", "Pi pulse 12")
return figures, report


def _update(results: RabiAmplitudeEFResults, platform: Platform, qubit: QubitId):
"""Update RX2 amplitude"""
update.drive_12_amplitude(results.amplitude[qubit], platform, qubit)


rabi_amplitude_ef = Routine(_acquisition, amplitude._fit, _plot, _update)
"""RabiAmplitudeEF Routine object."""
4 changes: 2 additions & 2 deletions src/qibocal/protocols/characterization/rabi/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def rabi_length_fit(x, p0, p1, p2, p3, p4):


def plot(data, qubit, fit):
if data.__class__.__name__ == "RabiAmplitudeData":
if "RabiAmplitude" in data.__class__.__name__:
quantity = "amp"
title = "Amplitude (dimensionless)"
fitting = rabi_amplitude_fit
Expand All @@ -44,7 +44,7 @@ def plot(data, qubit, fit):
horizontal_spacing=0.1,
vertical_spacing=0.1,
subplot_titles=(
"MSR (V)",
"MSR (uV)",
"phase (rad)",
),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -395,8 +395,8 @@ def _plot(data: CZVirtualZData, fit: CZVirtualZResults, qubit):


def _update(results: CZVirtualZResults, platform: Platform, qubit_pair: QubitPairId):
if qubit_pair[0] > qubit_pair[1]:
qubit_pair = (qubit_pair[1], qubit_pair[0])
# FIXME: quick fix for qubit order
qubit_pair = tuple(sorted(qubit_pair))
update.virtual_phases(results.virtual_phase[qubit_pair], platform, qubit_pair)


Expand Down
14 changes: 12 additions & 2 deletions src/qibocal/update.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,8 +169,14 @@ def drag_pulse_beta(beta: float, platform: Platform, qubit: QubitId):

def sweetspot(sweetspot: float, platform: Platform, qubit: QubitId):
platform.qubits[qubit].sweetspot = float(sweetspot)
if platform.qubits[qubit].flux is not None:
platform.qubits[qubit].flux.offset = sweetspot


def frequency_12_transition(frequency: int, platform: Platform, qubit: QubitId):
platform.qubits[qubit].native_gates.RX12.frequency = int(frequency * GHZ_TO_HZ)


def drive_12_amplitude(amplitude: float, platform: Platform, qubit: QubitId):
platform.qubits[qubit].native_gates.RX12.amplitude = float(amplitude)


def twpa_frequency(frequency: int, platform: Platform, qubit: QubitId):
Expand All @@ -179,3 +185,7 @@ def twpa_frequency(frequency: int, platform: Platform, qubit: QubitId):

def twpa_power(power: float, platform: Platform, qubit: QubitId):
platform.qubits[qubit].twpa.local_oscillator.power = float(power)


def anharmonicity(anharmonicity: float, platform: Platform, qubit: QubitId):
platform.qubits[qubit].anharmonicity = int(anharmonicity * GHZ_TO_HZ)
Loading

0 comments on commit 6d00390

Please sign in to comment.