"""Experiment to compute detuning from flux pulses."""
from dataclasses import dataclass, field
import numpy as np
import numpy.typing as npt
import plotly.graph_objects as go
from qibolab import (
AcquisitionType,
AveragingMode,
Delay,
Parameter,
Platform,
Pulse,
PulseSequence,
Rectangular,
Sweeper,
)
from qibocal import update
from qibocal.auto.operation import Data, Parameters, QubitId, Results, Routine
[docs]@dataclass
class FluxAmplitudeFrequencyParameters(Parameters):
"""FluxAmplitudeFrequency runcard inputs."""
amplitude_min: int
"""Minimum flux pulse amplitude."""
amplitude_max: int
"""Maximum flux amplitude."""
amplitude_step: int
"""Flux pulse amplitude step."""
duration: float
"""Flux pulse duration."""
[docs]@dataclass
class FluxAmplitudeFrequencyResults(Results):
"""FluxAmplitudeFrequency outputs."""
detuning: dict[QubitId, float] = field(default_factory=dict)
"""Frequency detuning."""
fitted_parameters: dict[tuple[QubitId, str], list[float]] = field(
default_factory=dict
)
"""Fitted parameters for every qubit."""
def __contains__(self, target: QubitId):
return target in self.detuning
FluxAmplitudeFrequencyType = np.dtype([("amplitude", float), ("prob_1", np.float64)])
"""Custom dtype for FluxAmplitudeFrequency."""
[docs]def ramsey_flux(
platform: Platform,
qubit: QubitId,
amplitude: float,
duration: int,
measure: str,
):
"""Compute sequences at fixed amplitude of flux pulse for <X> and <Y>"""
assert measure in ["X", "Y"]
native = platform.natives.single_qubit[qubit]
drive_channel, ry90 = native.R(theta=np.pi / 2, phi=np.pi / 2)[0]
_, rx90 = native.R(theta=np.pi / 2)[0]
ro_channel, ro_pulse = native.MZ()[0]
flux_channel = platform.qubits[qubit].flux
flux_pulse = Pulse(duration=duration, amplitude=amplitude, envelope=Rectangular())
# create the sequences
sequence = PulseSequence()
if measure == "X":
sequence.extend(
[
(drive_channel, ry90),
(flux_channel, Delay(duration=ry90.duration)),
(flux_channel, flux_pulse),
(drive_channel, Delay(duration=flux_pulse.duration)),
(drive_channel, ry90),
(
ro_channel,
Delay(duration=ry90.duration + flux_pulse.duration + ry90.duration),
),
(ro_channel, ro_pulse),
]
)
else:
sequence.extend(
[
(drive_channel, ry90),
(flux_channel, Delay(duration=ry90.duration)),
(flux_channel, flux_pulse),
(drive_channel, Delay(duration=flux_pulse.duration)),
(drive_channel, rx90),
(
ro_channel,
Delay(duration=ry90.duration + flux_pulse.duration + rx90.duration),
),
(ro_channel, ro_pulse),
]
)
return sequence
[docs]@dataclass
class FluxAmplitudeFrequencyData(Data):
"""FluxAmplitudeFrequency acquisition outputs."""
flux_pulse_duration: float
"""Flux pulse amplitude."""
data: dict[tuple[QubitId, str], npt.NDArray[FluxAmplitudeFrequencyType]] = field(
default_factory=dict
)
[docs]def _acquisition(
params: FluxAmplitudeFrequencyParameters,
platform: Platform,
targets: list[QubitId],
) -> FluxAmplitudeFrequencyData:
data = FluxAmplitudeFrequencyData(
flux_pulse_duration=params.duration,
)
amplitudes = np.arange(
params.amplitude_min, params.amplitude_max, params.amplitude_step
)
options = dict(
nshots=params.nshots,
acquisition_type=AcquisitionType.DISCRIMINATION,
averaging_mode=AveragingMode.CYCLIC,
)
for measure in ["X", "Y"]:
sequence = PulseSequence()
for qubit in targets:
sequence += ramsey_flux(
platform,
qubit,
duration=params.duration,
amplitude=params.amplitude_max / 2,
measure=measure,
)
sweeper = Sweeper(
parameter=Parameter.amplitude,
range=(params.amplitude_min, params.amplitude_max, params.amplitude_step),
pulses=[
pulse[1]
for pulse in sequence
if pulse[0] in [platform.qubits[target].flux for target in targets]
and isinstance(pulse[1], Pulse)
],
)
result = platform.execute([sequence], [[sweeper]], **options)
for qubit in targets:
ro_pulse = list(sequence.channel(platform.qubits[qubit].acquisition))[-1]
data.register_qubit(
FluxAmplitudeFrequencyType,
(qubit, measure),
dict(
amplitude=amplitudes,
prob_1=result[ro_pulse.id],
),
)
return data
[docs]def _fit(data: FluxAmplitudeFrequencyData) -> FluxAmplitudeFrequencyResults:
fitted_parameters = {}
detuning = {}
qubits = np.unique([i[0] for i in data.data]).tolist()
for qubit in qubits:
amplitudes = data[qubit, "X"].amplitude
X_exp = 1 - 2 * data[qubit, "X"].prob_1
Y_exp = 1 - 2 * data[qubit, "Y"].prob_1
phase = np.unwrap(np.angle(X_exp + 1j * Y_exp))
# normalize phase
phase -= phase[0]
det = phase / data.flux_pulse_duration / 2 / np.pi
fitted_parameters[qubit] = np.polyfit(amplitudes, det, 2).tolist()
detuning[qubit] = det.tolist()
return FluxAmplitudeFrequencyResults(
detuning=detuning, fitted_parameters=fitted_parameters
)
[docs]def _plot(
data: FluxAmplitudeFrequencyData,
fit: FluxAmplitudeFrequencyResults,
target: QubitId,
):
"""FluxAmplitudeFrequency plots."""
fig = go.Figure()
amplitude = data[(target, "X")].amplitude
if fit is not None:
fig.add_trace(
go.Scatter(
x=amplitude,
y=fit.detuning[target],
name="Detuning",
)
)
fig.add_trace(
go.Scatter(
x=amplitude,
y=np.polyval(fit.fitted_parameters[target], amplitude),
name="fit",
)
)
fig.update_layout(
showlegend=True,
xaxis_title="Flux pulse amplitude [a.u.]",
yaxis_title="Detuning [GHz]",
)
return [fig], ""
[docs]def _update(results: FluxAmplitudeFrequencyResults, platform: Platform, qubit: QubitId):
update.flux_coefficients(results.fitted_parameters[qubit], platform, qubit)
flux_amplitude_frequency = Routine(_acquisition, _fit, _plot, _update)