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, 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 Data, Parameters, Results, Routine
from .utils import HZ_TO_GHZ, fit_punchout, norm, table_dict, table_html
[docs]@dataclass
class ResonatorPunchoutAttenuationParameters(Parameters):
"""ResonatorPunchoutAttenuation runcard inputs."""
freq_width: int
"""Width for frequency sweep relative to the readout frequency [Hz]."""
freq_step: int
"""Frequency step for sweep [Hz]."""
min_att: int
"""Attenuation minimum value [dB]."""
max_att: int
"""Attenuation maximum value [dB]."""
step_att: int
"""Attenuation step [dB]."""
[docs]@dataclass
class ResonatorPunchoutAttenuationResults(Results):
"""ResonatorPunchoutAttenation 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_attenuation: dict[QubitId, int]
"""Readout attenuation [dB] for each qubit."""
ResPunchoutAttType = np.dtype(
[
("freq", np.float64),
("att", np.float64),
("signal", np.float64),
("phase", np.float64),
]
)
"""Custom dtype for resonator punchout."""
[docs]@dataclass
class ResonatorPunchoutAttenuationData(Data):
"""ResonatorPunchoutAttenuation data acquisition."""
resonator_type: str
"""Resonator type."""
data: dict[QubitId, npt.NDArray[ResPunchoutAttType]] = field(default_factory=dict)
"""Raw data acquired."""
[docs] def register_qubit(self, qubit, freq, att, signal, phase):
"""Store output for single qubit."""
size = len(freq) * len(att)
ar = np.empty(size, dtype=ResPunchoutAttType)
frequency, attenuation = np.meshgrid(freq, att)
ar["freq"] = frequency.ravel()
ar["att"] = attenuation.ravel()
ar["signal"] = signal.ravel()
ar["phase"] = phase.ravel()
self.data[qubit] = np.rec.array(ar)
[docs]def _acquisition(
params: ResonatorPunchoutAttenuationParameters,
platform: Platform,
targets: list[QubitId],
) -> ResonatorPunchoutAttenuationData:
"""Data acquisition for Punchout over attenuation."""
# create a sequence of pulses for the experiment:
# MZ
# taking advantage of multiplexing, apply the same set of gates to all qubits in parallel
sequence = PulseSequence()
ro_pulses = {}
for qubit in targets:
ro_pulses[qubit] = platform.create_qubit_readout_pulse(qubit, start=0)
sequence.add(ro_pulses[qubit])
# 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
)
freq_sweeper = Sweeper(
Parameter.frequency,
delta_frequency_range,
[ro_pulses[qubit] for qubit in targets],
type=SweeperType.OFFSET,
)
# attenuation
attenuation_range = np.arange(params.min_att, params.max_att, params.step_att)
att_sweeper = Sweeper(
Parameter.attenuation,
attenuation_range,
qubits=[platform.qubits[qubit] for qubit in targets],
type=SweeperType.ABSOLUTE,
)
data = ResonatorPunchoutAttenuationData(resonator_type=platform.resonator_type)
results = platform.sweep(
sequence,
ExecutionParameters(
nshots=params.nshots,
relaxation_time=params.relaxation_time,
acquisition_type=AcquisitionType.INTEGRATION,
averaging_mode=AveragingMode.CYCLIC,
),
att_sweeper,
freq_sweeper,
)
# retrieve the results for every qubit
for qubit in targets:
result = results[ro_pulses[qubit].serial]
data.register_qubit(
qubit,
signal=result.magnitude,
phase=result.phase,
freq=delta_frequency_range + ro_pulses[qubit].frequency,
att=attenuation_range,
)
# # Temporary fixe to force to reset the attenuation to the original value in qblox
# # sweeper method returning to orig value not working for attenuation
# # After fitting punchout the reload_settings will be called automatically
# platform.reload_settings()
# save data
return data
[docs]def _fit(
data: ResonatorPunchoutAttenuationData, fit_type="att"
) -> ResonatorPunchoutAttenuationResults:
"""Fit frequency and attenuation at high and low power for a given resonator."""
return ResonatorPunchoutAttenuationResults(*fit_punchout(data, fit_type))
[docs]def _plot(
data: ResonatorPunchoutAttenuationData,
target: QubitId,
fit: ResonatorPunchoutAttenuationResults = None,
):
"""Plotting for ResonatorPunchoutAttenuation."""
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
attenuations = qubit_data.att
n_att = len(np.unique(qubit_data.att))
n_freq = len(np.unique(qubit_data.freq))
for i in range(n_att):
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=attenuations,
z=qubit_data.signal,
colorbar_x=0.46,
),
row=1,
col=1,
)
fig.update_xaxes(title_text="Frequency [GHz]", row=1, col=1)
fig.update_yaxes(title_text="Attenuation [dB]", row=1, col=1)
fig.add_trace(
go.Heatmap(
x=frequencies,
y=attenuations,
z=qubit_data.phase,
colorbar_x=1.01,
),
row=1,
col=2,
)
fig.update_xaxes(title_text="Frequency [GHz]", 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_attenuation[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]",
"Readout Attenuation [dB]",
"High Power Resonator Frequency [Hz]",
],
[
np.round(fit.readout_frequency[target], 0),
fit.readout_attenuation[target],
np.round(fit.bare_frequency[target]),
],
)
)
fig.update_layout(
showlegend=True,
legend=dict(orientation="h"),
)
figures.append(fig)
return figures, fitting_report
[docs]def _update(
results: ResonatorPunchoutAttenuationResults, platform: Platform, target: QubitId
):
update.readout_frequency(results.readout_frequency[target], platform, target)
update.bare_resonator_frequency(results.bare_frequency[target], platform, target)
update.readout_attenuation(results.readout_attenuation[target], platform, target)
resonator_punchout_attenuation = Routine(_acquisition, _fit, _plot, _update)
"""ResonatorPunchoutAttenuation Routine object."""