From 120a594f34be8380d4a1229c517963597629d527 Mon Sep 17 00:00:00 2001 From: Edoardo-Pedicillo Date: Tue, 26 Nov 2024 12:51:35 +0400 Subject: [PATCH 1/5] feat: xyz_timing protocol --- src/qibocal/protocols/__init__.py | 2 + src/qibocal/protocols/xyz_timing.py | 171 ++++++++++++++++++++++++++++ 2 files changed, 173 insertions(+) create mode 100644 src/qibocal/protocols/xyz_timing.py diff --git a/src/qibocal/protocols/__init__.py b/src/qibocal/protocols/__init__.py index f6aa87e61..cae853c47 100644 --- a/src/qibocal/protocols/__init__.py +++ b/src/qibocal/protocols/__init__.py @@ -52,6 +52,7 @@ optimize_two_qubit_gate, ) from .two_qubit_state_tomography import two_qubit_state_tomography +from .xyz_timing import xyz_timing __all__ = [ "allxy", @@ -104,4 +105,5 @@ "standard_rb_2q_inter", "optimize_two_qubit_gate", "ramsey_zz", + "xyz_timing", ] diff --git a/src/qibocal/protocols/xyz_timing.py b/src/qibocal/protocols/xyz_timing.py new file mode 100644 index 000000000..91c5ade8a --- /dev/null +++ b/src/qibocal/protocols/xyz_timing.py @@ -0,0 +1,171 @@ +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, + Pulse, + PulseSequence, + Rectangular, + Sweeper, +) + +from qibocal.auto.operation import Data, Parameters, Results, Routine +from qibocal.calibration.calibration import QubitId +from qibocal.calibration.platform import CalibrationPlatform +from qibocal.result import probability + +COLORBAND = "rgba(0,100,80,0.2)" +COLORBAND_LINE = "rgba(255,255,255,0)" + + +@dataclass +class XYZTimingResults(Results): + pass + + +@dataclass +class XYZTimingParameters(Parameters): + + flux_amplitude: float + delay_step: float + delay_stop: float + flux_pulse_duration: float + + +XYZTimingType = np.dtype( + [("delay", np.float64), ("prob", np.float64), ("errors", np.float64)] +) + + +@dataclass +class XYZTimingData(Data): + + data: dict[QubitId, npt.NDArray] = field(default_factory=dict) + """Raw data acquired.""" + + +def _acquisition( + params: XYZTimingParameters, + platform: CalibrationPlatform, + targets: list[QubitId], +) -> XYZTimingData: + + data = XYZTimingData() + natives = platform.natives.single_qubit + delays = np.arange( + 0, + params.delay_stop, + params.delay_step, + ) + sequence = PulseSequence() + flux_delays = [] + for qubit in targets: + drive_channel = platform.qubits[qubit].drive + flux_channel = platform.qubits[qubit].flux + ro_channel = platform.qubits[qubit].acquisition + drive_pulse = natives[qubit].RX()[0] + readout_pulse = natives[qubit].MZ()[0] + + flux_pulse = Pulse( + duration=params.flux_pulse_duration, + amplitude=params.flux_amplitude, + relative_phase=0, + envelope=Rectangular(), + ) + qd_delay = Delay(duration=params.flux_pulse_duration) + flux_delay = Delay(duration=0) + flux_delays.append(flux_delay) + + sequence.extend( + [ + (drive_channel, qd_delay), + drive_pulse, + (flux_channel, flux_delay), + (flux_channel, flux_pulse), + ] + ) + sequence.align([drive_channel, flux_channel, ro_channel]) + sequence.append(readout_pulse) + + sweeper = Sweeper( + parameter=Parameter.duration, + values=delays, + pulses=flux_delays, + ) + + results = platform.execute( + [sequence], + [[sweeper]], + nshots=params.nshots, + relaxation_time=params.relaxation_time, + acquisition_type=AcquisitionType.DISCRIMINATION, + averaging_mode=AveragingMode.SINGLESHOT, + ) + + for qubit in targets: + ro_pulse = list(sequence.channel(platform.qubits[qubit].acquisition)) + ro_pulse = list(sequence.channel(platform.qubits[qubit].acquisition))[-1] + probs = probability(results[ro_pulse.id], state=1) + # The probability errors are the standard errors of the binomial distribution + errors = [np.sqrt(prob * (1 - prob) / params.nshots) for prob in probs] + data.register_qubit( + XYZTimingType, + (qubit), + dict( + delay=delays, + prob=probs, + errors=errors, + ), + ) + return data + + +def _fit(data: XYZTimingData) -> XYZTimingResults: + return XYZTimingResults() + + +def _plot(data: XYZTimingData, target: QubitId, fit: XYZTimingResults = None): + figures = [] + qubit_data = data.data[target] + delays = qubit_data.delay + probs = qubit_data.prob + error_bars = qubit_data.errors + fig = go.Figure( + [ + go.Scatter( + x=delays, + y=probs, + opacity=1, + name="Probability of State 0", + showlegend=True, + legendgroup="Probability of State 0", + mode="lines", + ), + go.Scatter( + x=np.concatenate((delays, delays[::-1])), + y=np.concatenate((probs + error_bars, (probs - error_bars)[::-1])), + fill="toself", + fillcolor=COLORBAND, + line=dict(color=COLORBAND_LINE), + showlegend=True, + name="Errors", + ), + ] + ) + fig.update_layout( + showlegend=True, + xaxis_title="Time [ns]", + yaxis_title="Ground state probability", + ) + + figures.append(fig) + + return figures, "" + + +xyz_timing = Routine(_acquisition, _fit, _plot) From 40cfe4ed0b4e42001bbc2fe9ea22714089c3ecb8 Mon Sep 17 00:00:00 2001 From: Edoardo-Pedicillo Date: Fri, 6 Dec 2024 13:08:37 +0800 Subject: [PATCH 2/5] fix: use padding instead of delays --- src/qibocal/protocols/xyz_timing.py | 130 ++++++++++++++-------------- 1 file changed, 67 insertions(+), 63 deletions(-) diff --git a/src/qibocal/protocols/xyz_timing.py b/src/qibocal/protocols/xyz_timing.py index 91c5ade8a..889f33cdf 100644 --- a/src/qibocal/protocols/xyz_timing.py +++ b/src/qibocal/protocols/xyz_timing.py @@ -3,16 +3,7 @@ import numpy as np import numpy.typing as npt import plotly.graph_objects as go -from qibolab import ( - AcquisitionType, - AveragingMode, - Delay, - Parameter, - Pulse, - PulseSequence, - Rectangular, - Sweeper, -) +from qibolab import AcquisitionType, AveragingMode, Custom, Delay, Pulse, PulseSequence from qibocal.auto.operation import Data, Parameters, Results, Routine from qibocal.calibration.calibration import QubitId @@ -57,71 +48,84 @@ def _acquisition( data = XYZTimingData() natives = platform.natives.single_qubit - delays = np.arange( + durations = np.arange( 0, params.delay_stop, params.delay_step, ) - sequence = PulseSequence() flux_delays = [] - for qubit in targets: - drive_channel = platform.qubits[qubit].drive - flux_channel = platform.qubits[qubit].flux - ro_channel = platform.qubits[qubit].acquisition - drive_pulse = natives[qubit].RX()[0] - readout_pulse = natives[qubit].MZ()[0] - - flux_pulse = Pulse( - duration=params.flux_pulse_duration, - amplitude=params.flux_amplitude, - relative_phase=0, - envelope=Rectangular(), - ) - qd_delay = Delay(duration=params.flux_pulse_duration) - flux_delay = Delay(duration=0) - flux_delays.append(flux_delay) - - sequence.extend( - [ - (drive_channel, qd_delay), - drive_pulse, - (flux_channel, flux_delay), - (flux_channel, flux_pulse), - ] - ) - sequence.align([drive_channel, flux_channel, ro_channel]) - sequence.append(readout_pulse) - - sweeper = Sweeper( - parameter=Parameter.duration, - values=delays, - pulses=flux_delays, - ) + sequences = [] + ro_pulses = [] + for duration in durations: + ro_pulses.append([]) + for qubit in targets: + sequence = PulseSequence() + drive_channel = platform.qubits[qubit].drive + flux_channel = platform.qubits[qubit].flux + ro_channel = platform.qubits[qubit].acquisition + drive_pulse = natives[qubit].RX()[0] + readout_pulse = natives[qubit].MZ()[0] + + flux_pulse = Pulse( + duration=params.flux_pulse_duration, + amplitude=params.flux_amplitude, + relative_phase=0, + envelope=Custom( + i_=np.concatenate([np.zeros(duration), np.ones(50), np.zeros(10)]), + q_=np.zeros(duration), + ), + ) + # flux_pulse = Pulse( + # duration=params.flux_pulse_duration, + # amplitude=params.flux_amplitude, + # relative_phase=0, + # envelope=Rectangular(), + # ) + qd_delay = Delay(duration=params.flux_pulse_duration) + # flux_delay = Delay(duration=0) + # flux_delays.append(flux_delay) + + sequence.extend( + [ + (drive_channel, qd_delay), + drive_pulse, + (flux_channel, flux_pulse), + ] + ) + sequence.align([drive_channel, flux_channel, ro_channel]) + sequence.append(readout_pulse) + sequences.append(sequence) + ro_pulses[-1].append(readout_pulse) + # sweeper = Sweeper( + # parameter=Parameter.duration, + # values=delays, + # pulses=flux_delays, + # ) results = platform.execute( - [sequence], - [[sweeper]], + sequences, nshots=params.nshots, relaxation_time=params.relaxation_time, acquisition_type=AcquisitionType.DISCRIMINATION, averaging_mode=AveragingMode.SINGLESHOT, ) - - for qubit in targets: - ro_pulse = list(sequence.channel(platform.qubits[qubit].acquisition)) - ro_pulse = list(sequence.channel(platform.qubits[qubit].acquisition))[-1] - probs = probability(results[ro_pulse.id], state=1) - # The probability errors are the standard errors of the binomial distribution - errors = [np.sqrt(prob * (1 - prob) / params.nshots) for prob in probs] - data.register_qubit( - XYZTimingType, - (qubit), - dict( - delay=delays, - prob=probs, - errors=errors, - ), - ) + for i, duration in enumerate(durations): + for j, qubit in enumerate(targets): + ro_pulse = ro_pulses[i][j][1] + print(ro_pulse) + prob = probability(results[ro_pulse.id], state=1) + print(prob) + # The probability errors are the standard errors of the binomial distribution + error = np.sqrt(prob * (1 - prob) / params.nshots) + data.register_qubit( + XYZTimingType, + (qubit), + dict( + delay=np.array([duration]), + prob=np.array([prob]), + errors=np.array([error]), + ), + ) return data From 6650a1b27c7ecfef9a0904bcd4fe96d63197a2f7 Mon Sep 17 00:00:00 2001 From: Edoardo-Pedicillo Date: Fri, 6 Dec 2024 10:50:40 +0400 Subject: [PATCH 3/5] fix: same duration for drive and flux pulse --- src/qibocal/protocols/xyz_timing.py | 54 ++++++++++++++--------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/src/qibocal/protocols/xyz_timing.py b/src/qibocal/protocols/xyz_timing.py index 889f33cdf..c5527c539 100644 --- a/src/qibocal/protocols/xyz_timing.py +++ b/src/qibocal/protocols/xyz_timing.py @@ -12,6 +12,7 @@ COLORBAND = "rgba(0,100,80,0.2)" COLORBAND_LINE = "rgba(255,255,255,0)" +PADDING_FLUX = 10 @dataclass @@ -23,9 +24,8 @@ class XYZTimingResults(Results): class XYZTimingParameters(Parameters): flux_amplitude: float - delay_step: float - delay_stop: float - flux_pulse_duration: float + delay_step: int + delay_stop: int XYZTimingType = np.dtype( @@ -36,6 +36,8 @@ class XYZTimingParameters(Parameters): @dataclass class XYZTimingData(Data): + pulse_duration: dict[QubitId, int] + """Duration of the drive and flux pulse""" data: dict[QubitId, npt.NDArray] = field(default_factory=dict) """Raw data acquired.""" @@ -46,16 +48,21 @@ def _acquisition( targets: list[QubitId], ) -> XYZTimingData: - data = XYZTimingData() natives = platform.natives.single_qubit durations = np.arange( - 0, + 1, params.delay_stop, params.delay_step, ) flux_delays = [] sequences = [] ro_pulses = [] + drive_durations = {} + for qubit in targets: + drive_pulse = natives[qubit].RX()[0] + drive_durations[qubit] = int(drive_pulse[1].duration) + + data = XYZTimingData(pulse_duration=drive_durations) for duration in durations: ro_pulses.append([]) for qubit in targets: @@ -65,25 +72,24 @@ def _acquisition( ro_channel = platform.qubits[qubit].acquisition drive_pulse = natives[qubit].RX()[0] readout_pulse = natives[qubit].MZ()[0] - + drive_duration = int(drive_pulse[1].duration) + total_flux_duration = duration + drive_duration + PADDING_FLUX flux_pulse = Pulse( - duration=params.flux_pulse_duration, + duration=total_flux_duration, amplitude=params.flux_amplitude, relative_phase=0, envelope=Custom( - i_=np.concatenate([np.zeros(duration), np.ones(50), np.zeros(10)]), - q_=np.zeros(duration), + i_=np.concatenate( + [ + np.zeros(duration), + np.ones(drive_duration), + np.zeros(PADDING_FLUX), + ] + ), + q_=np.zeros(total_flux_duration), ), ) - # flux_pulse = Pulse( - # duration=params.flux_pulse_duration, - # amplitude=params.flux_amplitude, - # relative_phase=0, - # envelope=Rectangular(), - # ) - qd_delay = Delay(duration=params.flux_pulse_duration) - # flux_delay = Delay(duration=0) - # flux_delays.append(flux_delay) + qd_delay = Delay(duration=drive_duration) sequence.extend( [ @@ -96,11 +102,6 @@ def _acquisition( sequence.append(readout_pulse) sequences.append(sequence) ro_pulses[-1].append(readout_pulse) - # sweeper = Sweeper( - # parameter=Parameter.duration, - # values=delays, - # pulses=flux_delays, - # ) results = platform.execute( sequences, @@ -112,9 +113,7 @@ def _acquisition( for i, duration in enumerate(durations): for j, qubit in enumerate(targets): ro_pulse = ro_pulses[i][j][1] - print(ro_pulse) prob = probability(results[ro_pulse.id], state=1) - print(prob) # The probability errors are the standard errors of the binomial distribution error = np.sqrt(prob * (1 - prob) / params.nshots) data.register_qubit( @@ -139,10 +138,11 @@ def _plot(data: XYZTimingData, target: QubitId, fit: XYZTimingResults = None): delays = qubit_data.delay probs = qubit_data.prob error_bars = qubit_data.errors + x = delays - data.pulse_duration[target] fig = go.Figure( [ go.Scatter( - x=delays, + x=x, y=probs, opacity=1, name="Probability of State 0", @@ -151,7 +151,7 @@ def _plot(data: XYZTimingData, target: QubitId, fit: XYZTimingResults = None): mode="lines", ), go.Scatter( - x=np.concatenate((delays, delays[::-1])), + x=np.concatenate((x, x[::-1])), y=np.concatenate((probs + error_bars, (probs - error_bars)[::-1])), fill="toself", fillcolor=COLORBAND, From 0ff181dc20580d5d3c8194567433dbcf1f384b89 Mon Sep 17 00:00:00 2001 From: Edoardo-Pedicillo Date: Mon, 9 Dec 2024 15:46:37 +0400 Subject: [PATCH 4/5] feat: fit data in xyz-timing --- src/qibocal/protocols/xyz_timing.py | 46 +++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/src/qibocal/protocols/xyz_timing.py b/src/qibocal/protocols/xyz_timing.py index c5527c539..581733e60 100644 --- a/src/qibocal/protocols/xyz_timing.py +++ b/src/qibocal/protocols/xyz_timing.py @@ -3,7 +3,9 @@ import numpy as np import numpy.typing as npt import plotly.graph_objects as go +from qibo.models.error_mitigation import curve_fit from qibolab import AcquisitionType, AveragingMode, Custom, Delay, Pulse, PulseSequence +from scipy import special from qibocal.auto.operation import Data, Parameters, Results, Routine from qibocal.calibration.calibration import QubitId @@ -17,7 +19,9 @@ @dataclass class XYZTimingResults(Results): - pass + fitted_parameters: dict[QubitId, list[float]] + fitted_errors: dict[QubitId, list[float]] + delays: dict[QubitId, float] @dataclass @@ -128,8 +132,46 @@ def _acquisition( return data +def fit_function(x, a, b, c, d): + return a + b * (special.erf(x - c) - special.erf(x + d)) + + def _fit(data: XYZTimingData) -> XYZTimingResults: - return XYZTimingResults() + qubits = data.qubits + params = {} + errors = {} + delays = {} + for qubit in qubits: + data_qubit = qubits[qubit] + delay = data_qubit.delay + prob = data_qubit.prob + err = data_qubit.errors + initial_pars = [ + 1, + -0.5, + data.pulse_duration[qubit] / 2, + data.pulse_duration[qubit] / 2, + ] + fit_parameters, perr = curve_fit( + fit_function, + delay - data.pulse_duration[qubit], + prob, + p0=initial_pars, + sigma=err, + ) + + err = np.sqrt(np.diag(perr)).tolist() + params[qubit] = fit_parameters + errors[qubit] = err + delays[qubit] = [ + (fit_parameters[3] - fit_parameters[2]) / 2, + np.linalg.norm([err[2], err[3]]) / 2, + ] + return XYZTimingResults( + fitted_parameters=params, + fitted_errors=errors, + delays=delays, + ) def _plot(data: XYZTimingData, target: QubitId, fit: XYZTimingResults = None): From 8d6e59c9ac41c478eb79addc38c21a5254523e95 Mon Sep 17 00:00:00 2001 From: Edoardo-Pedicillo Date: Mon, 9 Dec 2024 19:02:03 +0400 Subject: [PATCH 5/5] feat: report delay --- src/qibocal/protocols/xyz_timing.py | 47 +++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/src/qibocal/protocols/xyz_timing.py b/src/qibocal/protocols/xyz_timing.py index 581733e60..d57220707 100644 --- a/src/qibocal/protocols/xyz_timing.py +++ b/src/qibocal/protocols/xyz_timing.py @@ -12,6 +12,8 @@ from qibocal.calibration.platform import CalibrationPlatform from qibocal.result import probability +from .utils import table_dict, table_html + COLORBAND = "rgba(0,100,80,0.2)" COLORBAND_LINE = "rgba(255,255,255,0)" PADDING_FLUX = 10 @@ -132,8 +134,8 @@ def _acquisition( return data -def fit_function(x, a, b, c, d): - return a + b * (special.erf(x - c) - special.erf(x + d)) +def fit_function(x, a, b, c, d, e): + return a + b * (special.erf(e * x - c) - special.erf(e * x + d)) def _fit(data: XYZTimingData) -> XYZTimingResults: @@ -142,15 +144,16 @@ def _fit(data: XYZTimingData) -> XYZTimingResults: errors = {} delays = {} for qubit in qubits: - data_qubit = qubits[qubit] + data_qubit = data.data[qubit] delay = data_qubit.delay prob = data_qubit.prob err = data_qubit.errors initial_pars = [ 1, - -0.5, + 0.5, data.pulse_duration[qubit] / 2, data.pulse_duration[qubit] / 2, + 1, ] fit_parameters, perr = curve_fit( fit_function, @@ -161,11 +164,11 @@ def _fit(data: XYZTimingData) -> XYZTimingResults: ) err = np.sqrt(np.diag(perr)).tolist() - params[qubit] = fit_parameters + params[qubit] = fit_parameters.tolist() errors[qubit] = err delays[qubit] = [ - (fit_parameters[3] - fit_parameters[2]) / 2, - np.linalg.norm([err[2], err[3]]) / 2, + (fit_parameters[2] - fit_parameters[3]) / (2 * fit_parameters[4]), + float(np.linalg.norm([err[2], err[3]]) / 2) / fit_parameters[4] ** 2, ] return XYZTimingResults( fitted_parameters=params, @@ -181,13 +184,23 @@ def _plot(data: XYZTimingData, target: QubitId, fit: XYZTimingResults = None): probs = qubit_data.prob error_bars = qubit_data.errors x = delays - data.pulse_duration[target] + fitting_report = "" fig = go.Figure( [ go.Scatter( x=x, y=probs, opacity=1, - name="Probability of State 0", + name="Probability of State 1", + showlegend=True, + legendgroup="Probability of State 1", + mode="lines", + ), + go.Scatter( + x=x, + y=fit_function(x, *fit.fitted_parameters[target]), + opacity=1, + name="Fit", showlegend=True, legendgroup="Probability of State 0", mode="lines", @@ -203,15 +216,29 @@ def _plot(data: XYZTimingData, target: QubitId, fit: XYZTimingResults = None): ), ] ) + if fit is not None: + fig.add_vline( + x=fit.delays[target][0], + line=dict(color="grey", width=3, dash="dash"), + ) + fitting_report = table_html( + table_dict( + target, + ["Flux pulse delay [ns]"], + [fit.delays[target]], + display_error=True, + ) + ) + fig.update_layout( showlegend=True, xaxis_title="Time [ns]", - yaxis_title="Ground state probability", + yaxis_title="Excited state probability", ) figures.append(fig) - return figures, "" + return figures, fitting_report xyz_timing = Routine(_acquisition, _fit, _plot)