from dataclasses import dataclass, field
import numpy as np
import numpy.typing as npt
import plotly.graph_objects as go
from qibolab import AveragingMode, ExecutionParameters
from qibolab.platform import Platform
from qibolab.pulses import PulseSequence
from qibolab.qubits import QubitId
from qibocal.auto.operation import Data, Parameters, Results, Routine
[docs]@dataclass
class AllXYParameters(Parameters):
"""AllXY runcard inputs."""
beta_param: float = None
"""Beta parameter for drag pulse."""
unrolling: bool = False
"""If ``True`` it uses sequence unrolling to deploy multiple sequences in a single instrument call.
Defaults to ``False``."""
[docs]@dataclass
class AllXYResults(Results):
"""AllXY outputs."""
AllXYType = np.dtype([("prob", np.float64), ("gate", "<U5"), ("errors", np.float64)])
[docs]@dataclass
class AllXYData(Data):
"""AllXY acquisition outputs."""
beta_param: float = None
"""Beta parameter for drag pulse."""
data: dict[QubitId, npt.NDArray] = field(default_factory=dict)
"""Raw data acquired."""
gatelist = [
["I", "I"],
["Xp", "Xp"],
["Yp", "Yp"],
["Xp", "Yp"],
["Yp", "Xp"],
["X9", "I"],
["Y9", "I"],
["X9", "Y9"],
["Y9", "X9"],
["X9", "Yp"],
["Y9", "Xp"],
["Xp", "Y9"],
["Yp", "X9"],
["X9", "Xp"],
["Xp", "X9"],
["Y9", "Yp"],
["Yp", "Y9"],
["Xp", "I"],
["Yp", "I"],
["X9", "X9"],
["Y9", "Y9"],
]
[docs]def _acquisition(
params: AllXYParameters,
platform: Platform,
targets: list[QubitId],
) -> AllXYData:
r"""
Data acquisition for allXY experiment.
The AllXY experiment is a simple test of the calibration of single qubit gatesThe qubit (initialized in the |0> state)
is subjected to two back-to-back single-qubit gates and measured. In each round, we run 21 different gate pairs:
ideally, the first 5 return the qubit to |0>, the next 12 drive it to superposition state, and the last 4 put the
qubit in |1> state.
"""
# create a Data object to store the results
data = AllXYData(beta_param=params.beta_param)
# repeat the experiment as many times as defined by software_averages
# for iteration in range(params.software_averages):
sequences, all_ro_pulses = [], []
for gates in gatelist:
sequences.append(PulseSequence())
all_ro_pulses.append({})
for qubit in targets:
sequences[-1], all_ro_pulses[-1][qubit] = add_gate_pair_pulses_to_sequence(
platform, gates, qubit, sequences[-1], beta_param=params.beta_param
)
# execute the pulse sequence
options = ExecutionParameters(
nshots=params.nshots, averaging_mode=AveragingMode.CYCLIC
)
if params.unrolling:
results = platform.execute_pulse_sequences(sequences, options)
else:
results = [
platform.execute_pulse_sequence(sequence, options) for sequence in sequences
]
for ig, (gates, ro_pulses) in enumerate(zip(gatelist, all_ro_pulses)):
gate = "-".join(gates)
for qubit in targets:
serial = ro_pulses[qubit].serial
if params.unrolling:
prob = results[serial][ig].probability(0)
z_proj = 2 * prob - 1
else:
prob = results[ig][serial].probability(0)
z_proj = 2 * prob - 1
errors = 2 * np.sqrt(prob * (1 - prob) / params.nshots)
data.register_qubit(
AllXYType,
(qubit),
dict(
prob=np.array([z_proj]),
gate=np.array([gate]),
errors=np.array([errors]),
),
)
# finally, save the remaining data
return data
[docs]def add_gate_pair_pulses_to_sequence(
platform: Platform,
gates,
qubit,
sequence,
sequence_delay=0,
readout_delay=0,
beta_param=None,
):
pulse_duration = platform.create_RX_pulse(qubit, start=0).duration
# All gates have equal pulse duration
sequence_duration = sequence.get_qubit_pulses(qubit).duration + sequence_delay
pulse_start = sequence.get_qubit_pulses(qubit).duration + sequence_delay
for gate in gates:
if gate == "I":
pass
if gate == "Xp":
if beta_param == None:
RX_pulse = platform.create_RX_pulse(
qubit,
start=pulse_start,
)
else:
RX_pulse = platform.create_RX_drag_pulse(
qubit,
start=pulse_start,
beta=beta_param,
)
sequence.add(RX_pulse)
if gate == "X9":
if beta_param == None:
RX90_pulse = platform.create_RX90_pulse(
qubit,
start=pulse_start,
)
else:
RX90_pulse = platform.create_RX90_drag_pulse(
qubit,
start=pulse_start,
beta=beta_param,
)
sequence.add(RX90_pulse)
if gate == "Yp":
if beta_param == None:
RY_pulse = platform.create_RX_pulse(
qubit,
start=pulse_start,
relative_phase=np.pi / 2,
)
else:
RY_pulse = platform.create_RX_drag_pulse(
qubit,
start=pulse_start,
relative_phase=np.pi / 2,
beta=beta_param,
)
sequence.add(RY_pulse)
if gate == "Y9":
if beta_param == None:
RY90_pulse = platform.create_RX90_pulse(
qubit,
start=pulse_start,
relative_phase=np.pi / 2,
)
else:
RY90_pulse = platform.create_RX90_drag_pulse(
qubit,
start=pulse_start,
relative_phase=np.pi / 2,
beta=beta_param,
)
sequence.add(RY90_pulse)
sequence_duration += pulse_duration
pulse_start = sequence_duration
# RO pulse starting just after pair of gates
ro_pulse = platform.create_qubit_readout_pulse(
qubit, start=sequence_duration + readout_delay
)
sequence.add(ro_pulse)
return sequence, ro_pulse
[docs]def _fit(_data: AllXYData) -> AllXYResults:
"""Post-Processing for allXY"""
return AllXYResults()
# allXY
[docs]def _plot(data: AllXYData, target: QubitId, fit: AllXYResults = None):
"""Plotting function for allXY."""
figures = []
fitting_report = ""
fig = go.Figure()
qubit_data = data[target]
error_bars = qubit_data.errors
probs = qubit_data.prob
gates = qubit_data.gate
fig.add_trace(
go.Scatter(
x=gates,
y=probs,
error_y=dict(
type="data",
array=error_bars,
visible=True,
),
mode="markers",
text=gatelist,
textposition="bottom center",
name="Expectation value",
showlegend=True,
legendgroup="group1",
),
)
fig.add_hline(
y=0,
line_width=2,
line_dash="dash",
line_color="grey",
)
fig.add_hline(
y=1,
line_width=2,
line_dash="dash",
line_color="grey",
)
fig.add_hline(
y=-1,
line_width=2,
line_dash="dash",
line_color="grey",
)
fig.update_layout(
showlegend=True,
xaxis_title="Gate sequence number",
yaxis_title="Expectation value of Z",
)
figures.append(fig)
return figures, fitting_report
allxy = Routine(_acquisition, _fit, _plot)
"""AllXY Routine object."""