from dataclasses import dataclass, field
from typing import Optional
import numpy as np
import numpy.typing as npt
import plotly.graph_objects as go
from plotly.subplots import make_subplots
from qibolab import AcquisitionType, AveragingMode, Parameter, PulseSequence, Sweeper
from qibocal import update
from qibocal.auto.operation import Data, Parameters, QubitId, Results, Routine
from qibocal.calibration import CalibrationPlatform
from qibocal.result import magnitude, phase
from ..utils import HZ_TO_GHZ, fit_punchout, norm, table_dict, table_html
__all__ = ["resonator_punchout", "ResonatorPunchoutData"]
[docs]
@dataclass
class ResonatorPunchoutParameters(Parameters):
"""ResonatorPunchout runcard inputs."""
freq_width: int
"""Width for frequency sweep relative to the readout frequency [Hz]."""
freq_step: int
"""Frequency step for sweep [Hz]."""
min_amp: float
"""Minimum amplitude."""
max_amp: float
"""Maximum amplitude."""
step_amp: float
"""Step amplitude."""
@dataclass
class ResonatorPunchoutResults(Results):
"""ResonatorPunchout outputs."""
readout_frequency: dict[QubitId, float]
"""Readout frequency [GHz] for each qubit."""
bare_frequency: Optional[dict[QubitId, float]]
"""Bare resonator frequency [GHz] for each qubit."""
readout_amplitude: dict[QubitId, float]
"""Readout amplitude for each qubit."""
ResPunchoutType = np.dtype(
[
("freq", np.float64),
("amp", np.float64),
("signal", np.float64),
("phase", np.float64),
]
)
"""Custom dtype for resonator punchout."""
[docs]
@dataclass
class ResonatorPunchoutData(Data):
"""ResonatorPunchout data acquisition."""
resonator_type: str
"""Resonator type."""
amplitudes: dict[QubitId, float] = field(default_factory=dict)
"""Amplitudes provided by the user."""
data: dict[QubitId, npt.NDArray[ResPunchoutType]] = field(default_factory=dict)
"""Raw data acquired."""
[docs]
def register_qubit(self, qubit, freq, amp, signal, phase):
"""Store output for single qubit."""
size = len(freq) * len(amp)
frequency, amplitude = np.meshgrid(freq, amp)
ar = np.empty(size, dtype=ResPunchoutType)
ar["freq"] = frequency.ravel()
ar["amp"] = amplitude.ravel()
ar["signal"] = signal.ravel()
ar["phase"] = phase.ravel()
self.data[qubit] = np.rec.array(ar)
def _acquisition(
params: ResonatorPunchoutParameters,
platform: CalibrationPlatform,
targets: list[QubitId],
) -> ResonatorPunchoutData:
"""Data acquisition for Punchout over amplitude."""
# create a sequence of pulses for the experiment:
# MZ
# define the parameters to sweep and their range:
# resonator frequency
delta_frequency_range = np.arange(
-params.freq_width / 2, params.freq_width / 2, params.freq_step
)
# taking advantage of multiplexing, apply the same set of gates to all qubits in parallel
ro_pulses = {}
amplitudes = {}
freq_sweepers = {}
sequence = PulseSequence()
for qubit in targets:
natives = platform.natives.single_qubit[qubit]
ro_channel, ro_pulse = natives.MZ()[0]
ro_pulses[qubit] = ro_pulse
amplitudes[qubit] = ro_pulse.probe.amplitude
sequence.append((ro_channel, ro_pulse))
probe = platform.qubits[qubit].probe
f0 = platform.config(probe).frequency
freq_sweepers[qubit] = Sweeper(
parameter=Parameter.frequency,
values=f0 + delta_frequency_range,
channels=[probe],
)
amp_sweeper = Sweeper(
parameter=Parameter.amplitude,
range=(params.min_amp, params.max_amp, params.step_amp),
pulses=[ro_pulses[qubit] for qubit in targets],
)
data = ResonatorPunchoutData(
amplitudes=amplitudes,
resonator_type=platform.resonator_type,
)
results = platform.execute(
[sequence],
[[amp_sweeper], [freq_sweepers[q] for q in targets]],
nshots=params.nshots,
relaxation_time=params.relaxation_time,
acquisition_type=AcquisitionType.INTEGRATION,
averaging_mode=AveragingMode.CYCLIC,
)
# retrieve the results for every qubit
for qubit, ro_pulse in ro_pulses.items():
# average signal, phase, i and q over the number of shots defined in the runcard
result = results[ro_pulse.id]
data.register_qubit(
qubit,
signal=magnitude(result),
phase=phase(result),
freq=freq_sweepers[qubit].values,
amp=amp_sweeper.values,
)
return data
def _fit(data: ResonatorPunchoutData, fit_type="amp") -> ResonatorPunchoutResults:
"""Fit frequency and attenuation at high and low power for a given resonator."""
return ResonatorPunchoutResults(*fit_punchout(data, fit_type))
def _plot(
data: ResonatorPunchoutData, target: QubitId, fit: ResonatorPunchoutResults = None
):
"""Plotting function for ResonatorPunchout."""
figures = []
fitting_report = ""
fig = make_subplots(
rows=1,
cols=2,
horizontal_spacing=0.1,
vertical_spacing=0.2,
subplot_titles=(
"Normalised Signal [a.u.]",
"phase [rad]",
),
)
qubit_data = data[target]
frequencies = qubit_data.freq * HZ_TO_GHZ
amplitudes = qubit_data.amp
n_amps = len(np.unique(qubit_data.amp))
n_freq = len(np.unique(qubit_data.freq))
for i in range(n_amps):
qubit_data.signal[i * n_freq : (i + 1) * n_freq] = norm(
qubit_data.signal[i * n_freq : (i + 1) * n_freq]
)
fig.add_trace(
go.Heatmap(
x=frequencies,
y=amplitudes,
z=qubit_data.signal,
colorbar_x=0.46,
),
row=1,
col=1,
)
fig.add_trace(
go.Heatmap(
x=frequencies,
y=amplitudes,
z=qubit_data.phase,
colorbar_x=1.01,
),
row=1,
col=2,
)
if fit is not None:
fig.add_trace(
go.Scatter(
x=[
fit.readout_frequency[target] * HZ_TO_GHZ,
],
y=[
fit.readout_amplitude[target],
],
mode="markers",
marker=dict(
size=8,
color="gray",
symbol="circle",
),
name="Estimated readout point",
showlegend=True,
)
)
fitting_report = table_html(
table_dict(
target,
[
"Low Power Resonator Frequency [Hz]",
"Low Power readout amplitude [a.u.]",
"High Power Resonator Frequency [Hz]",
],
[
np.round(fit.readout_frequency[target]),
np.round(fit.readout_amplitude[target], 3),
np.round(fit.bare_frequency[target]),
],
)
)
fig.update_layout(
showlegend=True,
legend=dict(orientation="h"),
)
fig.update_xaxes(title_text="Frequency [GHz]", row=1, col=1)
fig.update_xaxes(title_text="Frequency [GHz]", row=1, col=2)
fig.update_yaxes(title_text="Amplitude [a.u.]", row=1, col=1)
figures.append(fig)
return figures, fitting_report
def _update(
results: ResonatorPunchoutResults, platform: CalibrationPlatform, target: QubitId
):
update.readout_frequency(results.readout_frequency[target], platform, target)
update.bare_resonator_frequency(results.bare_frequency[target], platform, target)
update.dressed_resonator_frequency(
results.readout_frequency[target], platform, target
)
update.readout_amplitude(results.readout_amplitude[target], platform, target)
resonator_punchout = Routine(_acquisition, _fit, _plot, _update)
"""ResonatorPunchout Routine object."""