From 3209a5703c5a76a4de5c24c1456fafd4b66ebff5 Mon Sep 17 00:00:00 2001 From: Andrea Date: Thu, 5 Oct 2023 15:44:37 +0200 Subject: [PATCH 1/9] Add T1 with prob and error bands --- .../protocols/characterization/__init__.py | 2 + .../characterization/coherence/spin_echo.py | 4 +- .../characterization/coherence/t1.py | 72 +++++++++++-------- .../coherence/t1_sequences.py | 10 +-- .../characterization/coherence/t2.py | 4 +- .../characterization/coherence/utils.py | 44 ++++++++++++ src/qibocal/update.py | 5 +- tests/runcards/protocols.yml | 9 +++ 8 files changed, 110 insertions(+), 40 deletions(-) diff --git a/src/qibocal/protocols/characterization/__init__.py b/src/qibocal/protocols/characterization/__init__.py index f0f8fda33..c7114e7c7 100644 --- a/src/qibocal/protocols/characterization/__init__.py +++ b/src/qibocal/protocols/characterization/__init__.py @@ -6,6 +6,7 @@ from .classification import single_shot_classification from .coherence.spin_echo import spin_echo from .coherence.t1 import t1 +from .coherence.t1_msr import t1_msr from .coherence.t1_sequences import t1_sequences from .coherence.t2 import t2 from .coherence.t2_sequences import t2_sequences @@ -55,6 +56,7 @@ class Operation(Enum): ramsey = ramsey ramsey_sequences = ramsey_sequences t1 = t1 + t1_msr = t1_msr t1_sequences = t1_sequences t2 = t2 t2_sequences = t2_sequences diff --git a/src/qibocal/protocols/characterization/coherence/spin_echo.py b/src/qibocal/protocols/characterization/coherence/spin_echo.py index 4bc958d39..257f28659 100644 --- a/src/qibocal/protocols/characterization/coherence/spin_echo.py +++ b/src/qibocal/protocols/characterization/coherence/spin_echo.py @@ -12,7 +12,7 @@ from qibocal.auto.operation import Parameters, Qubits, Results, Routine from ..utils import V_TO_UV -from .t1 import T1Data +from .t1_msr import T1MSRData from .utils import exp_decay, exponential_fit @@ -42,7 +42,7 @@ class SpinEchoResults(Results): """Raw fitting output.""" -class SpinEchoData(T1Data): +class SpinEchoData(T1MSRData): """SpinEcho acquisition outputs.""" diff --git a/src/qibocal/protocols/characterization/coherence/t1.py b/src/qibocal/protocols/characterization/coherence/t1.py index cd4894859..4ea65842e 100644 --- a/src/qibocal/protocols/characterization/coherence/t1.py +++ b/src/qibocal/protocols/characterization/coherence/t1.py @@ -13,9 +13,11 @@ from qibocal import update from qibocal.auto.operation import Data, Parameters, Qubits, Results, Routine -from ..utils import V_TO_UV from . import utils +COLORBAND = "rgba(0,100,80,0.2)" +COLORBAND_LINE = "rgba(255,255,255,0)" + @dataclass class T1Parameters(Parameters): @@ -37,14 +39,14 @@ class T1Parameters(Parameters): class T1Results(Results): """T1 outputs.""" - t1: dict[QubitId, float] + t1: dict[QubitId, tuple[float]] """T1 for each qubit.""" fitted_parameters: dict[QubitId, dict[str, float]] """Raw fitting output.""" -CoherenceType = np.dtype( - [("wait", np.float64), ("msr", np.float64), ("phase", np.float64)] +CoherenceProbType = np.dtype( + [("wait", np.float64), ("prob", np.float64), ("error", np.float64)] ) """Custom dtype for coherence routines.""" @@ -56,14 +58,14 @@ class T1Data(Data): data: dict[QubitId, npt.NDArray] = field(default_factory=dict) """Raw data acquired.""" - def register_qubit(self, qubit, wait, msr, phase): + def register_qubit(self, qubit, wait, prob, error): """Store output for single qubit.""" # to be able to handle the non-sweeper case shape = (1,) if np.isscalar(wait) else wait.shape - ar = np.empty(shape, dtype=CoherenceType) + ar = np.empty(shape, dtype=CoherenceProbType) ar["wait"] = wait - ar["msr"] = msr - ar["phase"] = phase + ar["prob"] = prob + ar["error"] = error if qubit in self.data: self.data[qubit] = np.rec.array(np.concatenate((self.data[qubit], ar))) else: @@ -117,7 +119,6 @@ def _acquisition(params: T1Parameters, platform: Platform, qubits: Qubits) -> T1 type=SweeperType.ABSOLUTE, ) - # create a DataUnits object to store the MSR, phase, i, q and the delay time data = T1Data() # sweep the parameter @@ -127,17 +128,16 @@ def _acquisition(params: T1Parameters, platform: Platform, qubits: Qubits) -> T1 ExecutionParameters( nshots=params.nshots, relaxation_time=params.relaxation_time, - acquisition_type=AcquisitionType.INTEGRATION, - averaging_mode=AveragingMode.CYCLIC, + acquisition_type=AcquisitionType.DISCRIMINATION, + averaging_mode=AveragingMode.SINGLESHOT, ), sweeper, ) for qubit in qubits: - result = results[ro_pulses[qubit].serial] - data.register_qubit( - qubit, wait=ro_wait_range, msr=result.magnitude, phase=result.phase - ) + probs = results[ro_pulses[qubit].serial].probability(state=1) + errors = [np.sqrt(prob * (1 - prob) / params.nshots) for prob in probs] + data.register_qubit(qubit, wait=ro_wait_range, prob=probs, error=errors) return data @@ -150,8 +150,7 @@ def _fit(data: T1Data) -> T1Results: y = p_0-p_1 e^{-x p_2}. """ - t1s, fitted_parameters = utils.exponential_fit(data) - + t1s, fitted_parameters = utils.exponential_fit_probability(data) return T1Results(t1s, fitted_parameters) @@ -159,21 +158,33 @@ def _plot(data: T1Data, qubit, fit: T1Results = None): """Plotting function for T1 experiment.""" figures = [] - fig = go.Figure() - fitting_report = None qubit_data = data[qubit] waits = qubit_data.wait + probs = qubit_data.prob + error_bars = qubit_data.error - fig.add_trace( - go.Scatter( - x=waits, - y=qubit_data.msr * V_TO_UV, - opacity=1, - name="Voltage", - showlegend=True, - legendgroup="Voltage", - ) + fig = go.Figure( + [ + go.Scatter( + x=waits, + y=probs, + opacity=1, + name="Probability of 1", + showlegend=True, + legendgroup="Probability of 1", + mode="lines", + ), + go.Scatter( + x=np.concatenate((waits, waits[::-1])), + y=np.concatenate((probs + error_bars, (probs - error_bars)[::-1])), + fill="toself", + fillcolor=COLORBAND, + line=dict(color=COLORBAND_LINE), + showlegend=True, + name="Errors", + ), + ] ) if fit is not None: @@ -192,14 +203,13 @@ def _plot(data: T1Data, qubit, fit: T1Results = None): line=go.scatter.Line(dash="dot"), ) ) - fitting_report = f"{qubit} | t1: {fit.t1[qubit]:,.0f} ns.

" + fitting_report = f"{qubit} | t1: {fit.t1[qubit][0]:,.0f} ns.

" # last part fig.update_layout( showlegend=True, - uirevision="0", # ``uirevision`` allows zooming while live plotting xaxis_title="Time (ns)", - yaxis_title="MSR (uV)", + yaxis_title="Probability of State 1", ) figures.append(fig) diff --git a/src/qibocal/protocols/characterization/coherence/t1_sequences.py b/src/qibocal/protocols/characterization/coherence/t1_sequences.py index 80c5153ea..d99e4a6c0 100644 --- a/src/qibocal/protocols/characterization/coherence/t1_sequences.py +++ b/src/qibocal/protocols/characterization/coherence/t1_sequences.py @@ -5,10 +5,12 @@ from qibocal.auto.operation import Qubits, Routine -from .t1 import T1Data, T1Parameters, _fit, _plot, _update +from . import t1, t1_msr -def _acquisition(params: T1Parameters, platform: Platform, qubits: Qubits) -> T1Data: +def _acquisition( + params: t1_msr.T1MSRParameters, platform: Platform, qubits: Qubits +) -> t1_msr.T1MSRData: r"""Data acquisition for T1 experiment. In a T1 experiment, we measure an excited qubit after a delay. Due to decoherence processes (e.g. amplitude damping channel), it is possible that, at the time of measurement, after the delay, @@ -49,7 +51,7 @@ def _acquisition(params: T1Parameters, platform: Platform, qubits: Qubits) -> T1 ) # create a DataUnits object to store the MSR, phase, i, q and the delay time - data = T1Data() + data = t1_msr.T1MSRData() # repeat the experiment as many times as defined by software_averages # sweep the parameter @@ -76,5 +78,5 @@ def _acquisition(params: T1Parameters, platform: Platform, qubits: Qubits) -> T1 return data -t1_sequences = Routine(_acquisition, _fit, _plot, _update) +t1_sequences = Routine(_acquisition, t1_msr._fit, t1_msr._plot, t1._update) """T1 Routine object.""" diff --git a/src/qibocal/protocols/characterization/coherence/t2.py b/src/qibocal/protocols/characterization/coherence/t2.py index f977b49ff..dd158a014 100644 --- a/src/qibocal/protocols/characterization/coherence/t2.py +++ b/src/qibocal/protocols/characterization/coherence/t2.py @@ -13,7 +13,7 @@ from qibocal.auto.operation import Parameters, Qubits, Results, Routine from ..utils import V_TO_UV -from . import t1, utils +from . import t1_msr, utils @dataclass @@ -42,7 +42,7 @@ class T2Results(Results): """Raw fitting output.""" -class T2Data(t1.T1Data): +class T2Data(t1_msr.T1MSRData): """T2 acquisition outputs.""" diff --git a/src/qibocal/protocols/characterization/coherence/utils.py b/src/qibocal/protocols/characterization/coherence/utils.py index c046042b0..888187e1e 100644 --- a/src/qibocal/protocols/characterization/coherence/utils.py +++ b/src/qibocal/protocols/characterization/coherence/utils.py @@ -63,3 +63,47 @@ def exponential_fit(data, zeno=None): decay[qubit] = t2 return decay, fitted_parameters + + +def exponential_fit_probability(data): + qubits = data.qubits + + decay = {} + fitted_parameters = {} + + for qubit in qubits: + probability = data[qubit].prob + + p0 = [ + 0.5, + 0.5, + 5, + ] + + try: + popt, perr = curve_fit( + exp_decay, + data[qubit].wait, + probability, + p0=p0, + maxfev=2000000, + bounds=( + [-2, -2, 0], + [2, 2, np.inf], + ), + sigma=data[qubit].error, + ) + + perr = np.sqrt(np.diag(perr)) + + except Exception as e: + log.warning(f"Exp decay fitting was not succesful. {e}") + popt = [0] * 3 + dec = 5 + perr = [1] * 3 + + fitted_parameters[qubit] = popt.tolist() + dec = popt[2] + decay[qubit] = (dec, perr[2]) + + return decay, fitted_parameters diff --git a/src/qibocal/update.py b/src/qibocal/update.py index 9a7a168ca..c9d09a69b 100644 --- a/src/qibocal/update.py +++ b/src/qibocal/update.py @@ -127,7 +127,10 @@ def CZ_amplitude(amp: float, platform: Platform, pair: QubitPairId): def t1(t1: int, platform: Platform, qubit: QubitId): """Update mean excited state value in platform for specific qubit.""" - platform.qubits[qubit].t1 = int(t1) + if isinstance(t1, tuple): + platform.qubits[qubit].t1 = int(t1[0]) + else: + platform.qubits[qubit].t1 = int(t1) def t2(t2: int, platform: Platform, qubit: QubitId): diff --git a/tests/runcards/protocols.yml b/tests/runcards/protocols.yml index ab4dfdd6f..28a36e182 100644 --- a/tests/runcards/protocols.yml +++ b/tests/runcards/protocols.yml @@ -204,6 +204,15 @@ actions: delay_before_readout_step: 2000 nshots: 1024 + - id: t1_msr + priority: 0 + operation: t1_msr + parameters: + delay_before_readout_start: 0 + delay_before_readout_end: 20_000 + delay_before_readout_step: 2000 + nshots: 1024 + - id: t1 sequences priority: 0 operation: t1_sequences From b836271aa0a156acac3579ee244866737344eaa7 Mon Sep 17 00:00:00 2001 From: Andrea Date: Thu, 5 Oct 2023 15:55:59 +0200 Subject: [PATCH 2/9] Add missing file --- .../characterization/coherence/t1_msr.py | 195 ++++++++++++++++++ 1 file changed, 195 insertions(+) create mode 100644 src/qibocal/protocols/characterization/coherence/t1_msr.py diff --git a/src/qibocal/protocols/characterization/coherence/t1_msr.py b/src/qibocal/protocols/characterization/coherence/t1_msr.py new file mode 100644 index 000000000..f6b8dbb20 --- /dev/null +++ b/src/qibocal/protocols/characterization/coherence/t1_msr.py @@ -0,0 +1,195 @@ +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, 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.auto.operation import Data, Qubits, Routine + +from ..utils import V_TO_UV +from . import t1, utils + + +@dataclass +class T1MSRParameters(t1.T1Parameters): + """T1 MSR runcard inputs.""" + + +@dataclass +class T1MSRResults(t1.T1Results): + """T1 MSR outputs.""" + + +CoherenceType = np.dtype( + [("wait", np.float64), ("msr", np.float64), ("phase", np.float64)] +) +"""Custom dtype for coherence routines.""" + + +@dataclass +class T1MSRData(Data): + """T1 acquisition outputs.""" + + data: dict[QubitId, npt.NDArray] = field(default_factory=dict) + """Raw data acquired.""" + + def register_qubit(self, qubit, wait, msr, phase): + """Store output for single qubit.""" + # to be able to handle the non-sweeper case + shape = (1,) if np.isscalar(wait) else wait.shape + ar = np.empty(shape, dtype=CoherenceType) + ar["wait"] = wait + ar["msr"] = msr + ar["phase"] = phase + if qubit in self.data: + self.data[qubit] = np.rec.array(np.concatenate((self.data[qubit], ar))) + else: + self.data[qubit] = np.rec.array(ar) + + +def _acquisition( + params: T1MSRParameters, platform: Platform, qubits: Qubits +) -> T1MSRData: + r"""Data acquisition for T1 experiment. + In a T1 experiment, we measure an excited qubit after a delay. Due to decoherence processes + (e.g. amplitude damping channel), it is possible that, at the time of measurement, after the delay, + the qubit will not be excited anymore. The larger the delay time is, the more likely is the qubit to + fall to the ground state. The goal of the experiment is to characterize the decay rate of the qubit + towards the ground state. + + Args: + params: + platform (Platform): Qibolab platform object + qubits (list): list of target qubits to perform the action + delay_before_readout_start (int): Initial time delay before ReadOut + delay_before_readout_end (list): Maximum time delay before ReadOut + delay_before_readout_step (int): Scan range step for the delay before ReadOut + software_averages (int): Number of executions of the routine for averaging results + points (int): Save data results in a file every number of points + """ + + # create a sequence of pulses for the experiment + # RX - wait t - MZ + qd_pulses = {} + ro_pulses = {} + sequence = PulseSequence() + for qubit in qubits: + qd_pulses[qubit] = platform.create_RX_pulse(qubit, start=0) + ro_pulses[qubit] = platform.create_qubit_readout_pulse( + qubit, start=qd_pulses[qubit].duration + ) + sequence.add(qd_pulses[qubit]) + sequence.add(ro_pulses[qubit]) + + # define the parameter to sweep and its range: + # wait time before readout + ro_wait_range = np.arange( + params.delay_before_readout_start, + params.delay_before_readout_end, + params.delay_before_readout_step, + ) + + sweeper = Sweeper( + Parameter.start, + ro_wait_range, + [ro_pulses[qubit] for qubit in qubits], + type=SweeperType.ABSOLUTE, + ) + + # create a DataUnits object to store the MSR, phase, i, q and the delay time + data = T1MSRData() + + # sweep the parameter + # execute the pulse sequence + results = platform.sweep( + sequence, + ExecutionParameters( + nshots=params.nshots, + relaxation_time=params.relaxation_time, + acquisition_type=AcquisitionType.INTEGRATION, + averaging_mode=AveragingMode.CYCLIC, + ), + sweeper, + ) + + for qubit in qubits: + result = results[ro_pulses[qubit].serial] + data.register_qubit( + qubit, wait=ro_wait_range, msr=result.magnitude, phase=result.phase + ) + + return data + + +def _fit(data: T1MSRData) -> T1MSRResults: + """ + Fitting routine for T1 experiment. The used model is + + .. math:: + + y = p_0-p_1 e^{-x p_2}. + """ + t1s, fitted_parameters = utils.exponential_fit(data) + + return T1MSRResults(t1s, fitted_parameters) + + +def _plot(data: T1MSRData, qubit, fit: T1MSRResults = None): + """Plotting function for T1 experiment.""" + + figures = [] + fig = go.Figure() + + fitting_report = None + qubit_data = data[qubit] + waits = qubit_data.wait + + fig.add_trace( + go.Scatter( + x=waits, + y=qubit_data.msr * V_TO_UV, + opacity=1, + name="Voltage", + showlegend=True, + legendgroup="Voltage", + ) + ) + + if fit is not None: + waitrange = np.linspace( + min(waits), + max(waits), + 2 * len(qubit_data), + ) + + params = fit.fitted_parameters[qubit] + fig.add_trace( + go.Scatter( + x=waitrange, + y=utils.exp_decay(waitrange, *params), + name="Fit", + line=go.scatter.Line(dash="dot"), + ) + ) + fitting_report = f"{qubit} | t1: {fit.t1[qubit]:,.0f} ns.

" + + # last part + fig.update_layout( + showlegend=True, + uirevision="0", # ``uirevision`` allows zooming while live plotting + xaxis_title="Time (ns)", + yaxis_title="MSR (uV)", + ) + + figures.append(fig) + + return figures, fitting_report + + +t1_msr = Routine(_acquisition, _fit, _plot, t1._update) +"""T1 MSR Routine object.""" From ec38c93893305b2ba39550ebf83f06171dc494c3 Mon Sep 17 00:00:00 2001 From: Andrea Date: Thu, 5 Oct 2023 17:16:38 +0200 Subject: [PATCH 3/9] Add T2 prob --- .../protocols/characterization/__init__.py | 2 + .../characterization/coherence/t2.py | 58 ++++--- .../characterization/coherence/t2_msr.py | 164 ++++++++++++++++++ .../coherence/t2_sequences.py | 8 +- .../characterization/coherence/utils.py | 4 +- src/qibocal/update.py | 5 +- 6 files changed, 210 insertions(+), 31 deletions(-) create mode 100644 src/qibocal/protocols/characterization/coherence/t2_msr.py diff --git a/src/qibocal/protocols/characterization/__init__.py b/src/qibocal/protocols/characterization/__init__.py index c7114e7c7..5884e2e4b 100644 --- a/src/qibocal/protocols/characterization/__init__.py +++ b/src/qibocal/protocols/characterization/__init__.py @@ -9,6 +9,7 @@ from .coherence.t1_msr import t1_msr from .coherence.t1_sequences import t1_sequences from .coherence.t2 import t2 +from .coherence.t2_msr import t2_msr from .coherence.t2_sequences import t2_sequences from .coherence.zeno import zeno from .dispersive_shift import dispersive_shift @@ -59,6 +60,7 @@ class Operation(Enum): t1_msr = t1_msr t1_sequences = t1_sequences t2 = t2 + t2_msr = t2_msr t2_sequences = t2_sequences time_of_flight_readout = time_of_flight_readout single_shot_classification = single_shot_classification diff --git a/src/qibocal/protocols/characterization/coherence/t2.py b/src/qibocal/protocols/characterization/coherence/t2.py index dd158a014..73774b0a3 100644 --- a/src/qibocal/protocols/characterization/coherence/t2.py +++ b/src/qibocal/protocols/characterization/coherence/t2.py @@ -12,8 +12,7 @@ from qibocal import update from qibocal.auto.operation import Parameters, Qubits, Results, Routine -from ..utils import V_TO_UV -from . import t1_msr, utils +from . import t1, utils @dataclass @@ -42,7 +41,7 @@ class T2Results(Results): """Raw fitting output.""" -class T2Data(t1_msr.T1MSRData): +class T2Data(t1.T1Data): """T2 acquisition outputs.""" @@ -79,9 +78,6 @@ def _acquisition( params.delay_between_pulses_step, ) - # create a DataUnits object to store the results, - # DataUnits stores by default MSR, phase, i, q - # additionally include wait time and t_max data = T2Data() sweeper = Sweeper( @@ -97,15 +93,16 @@ def _acquisition( ExecutionParameters( nshots=params.nshots, relaxation_time=params.relaxation_time, - acquisition_type=AcquisitionType.INTEGRATION, - averaging_mode=AveragingMode.CYCLIC, + acquisition_type=AcquisitionType.DISCRIMINATION, + averaging_mode=AveragingMode.SINGLESHOT, ), sweeper, ) for qubit in qubits: - result = results[ro_pulses[qubit].serial] - data.register_qubit(qubit, wait=waits, msr=result.magnitude, phase=result.phase) + probs = results[ro_pulses[qubit].serial].probability(state=1) + errors = [np.sqrt(prob * (1 - prob) / params.nshots) for prob in probs] + data.register_qubit(qubit, wait=waits, prob=probs, error=errors) return data @@ -115,7 +112,7 @@ def _fit(data: T2Data) -> T2Results: .. math:: y = p_0 - p_1 e^{-x p_2}. """ - t2s, fitted_parameters = utils.exponential_fit(data) + t2s, fitted_parameters = utils.exponential_fit_probability(data) return T2Results(t2s, fitted_parameters) @@ -123,20 +120,33 @@ def _plot(data: T2Data, qubit, fit: T2Results = None): """Plotting function for Ramsey Experiment.""" figures = [] - fig = go.Figure() fitting_report = None - qubit_data = data[qubit] + waits = qubit_data.wait + probs = qubit_data.prob + error_bars = qubit_data.error - fig.add_trace( - go.Scatter( - x=qubit_data.wait, - y=qubit_data.msr * V_TO_UV, - opacity=1, - name="Voltage", - showlegend=True, - legendgroup="Voltage", - ) + fig = go.Figure( + [ + go.Scatter( + x=waits, + y=probs, + opacity=1, + name="Probability of 1", + showlegend=True, + legendgroup="Probability of 1", + mode="lines", + ), + go.Scatter( + x=np.concatenate((waits, waits[::-1])), + y=np.concatenate((probs + error_bars, (probs - error_bars)[::-1])), + fill="toself", + fillcolor=t1.COLORBAND, + line=dict(color=t1.COLORBAND_LINE), + showlegend=True, + name="Errors", + ), + ] ) if fit is not None: @@ -159,13 +169,13 @@ def _plot(data: T2Data, qubit, fit: T2Results = None): line=go.scatter.Line(dash="dot"), ) ) - fitting_report = f"{qubit} | T2: {fit.t2[qubit]:,.0f} ns.

" + fitting_report = f"{qubit} | T2: {fit.t2[qubit][0]:,.0f} ns.

" fig.update_layout( showlegend=True, uirevision="0", # ``uirevision`` allows zooming while live plotting xaxis_title="Time (ns)", - yaxis_title="MSR (uV)", + yaxis_title="Probability of State 1", ) figures.append(fig) diff --git a/src/qibocal/protocols/characterization/coherence/t2_msr.py b/src/qibocal/protocols/characterization/coherence/t2_msr.py new file mode 100644 index 000000000..9345d5cf1 --- /dev/null +++ b/src/qibocal/protocols/characterization/coherence/t2_msr.py @@ -0,0 +1,164 @@ +from dataclasses import dataclass + +import numpy as np +import plotly.graph_objects as go +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 Qubits, Routine + +from ..utils import V_TO_UV +from . import t1_msr, t2, utils + + +@dataclass +class T2MSRParameters(t2.T2Parameters): + """T2MSR runcard inputs.""" + + +@dataclass +class T2MSRResults(t2.T2Results): + """T2MSR outputs.""" + + +class T2MSRData(t1_msr.T1MSRData): + """T2MSR acquisition outputs.""" + + +def _acquisition( + params: T2MSRParameters, + platform: Platform, + qubits: Qubits, +) -> T2MSRData: + """Data acquisition for Ramsey Experiment (detuned).""" + # create a sequence of pulses for the experiment + # RX90 - t - RX90 - MZ + ro_pulses = {} + RX90_pulses1 = {} + RX90_pulses2 = {} + sequence = PulseSequence() + for qubit in qubits: + RX90_pulses1[qubit] = platform.create_RX90_pulse(qubit, start=0) + RX90_pulses2[qubit] = platform.create_RX90_pulse( + qubit, + start=RX90_pulses1[qubit].finish, + ) + ro_pulses[qubit] = platform.create_qubit_readout_pulse( + qubit, start=RX90_pulses2[qubit].finish + ) + sequence.add(RX90_pulses1[qubit]) + sequence.add(RX90_pulses2[qubit]) + sequence.add(ro_pulses[qubit]) + + # define the parameter to sweep and its range: + waits = np.arange( + # wait time between RX90 pulses + params.delay_between_pulses_start, + params.delay_between_pulses_end, + params.delay_between_pulses_step, + ) + + # create a DataUnits object to store the results, + # DataUnits stores by default MSR, phase, i, q + # additionally include wait time and t_max + data = T2MSRData() + + sweeper = Sweeper( + Parameter.start, + waits, + [RX90_pulses2[qubit] for qubit in qubits], + type=SweeperType.ABSOLUTE, + ) + + # execute the sweep + results = platform.sweep( + sequence, + ExecutionParameters( + nshots=params.nshots, + relaxation_time=params.relaxation_time, + acquisition_type=AcquisitionType.INTEGRATION, + averaging_mode=AveragingMode.CYCLIC, + ), + sweeper, + ) + + for qubit in qubits: + result = results[ro_pulses[qubit].serial] + data.register_qubit(qubit, wait=waits, msr=result.magnitude, phase=result.phase) + return data + + +def _fit(data: T2MSRData) -> T2MSRResults: + r""" + Fitting routine for Ramsey experiment. The used model is + .. math:: + y = p_0 - p_1 e^{-x p_2}. + """ + t2s, fitted_parameters = utils.exponential_fit(data) + return T2MSRResults(t2s, fitted_parameters) + + +def _plot(data: T2MSRData, qubit, fit: T2MSRResults = None): + """Plotting function for Ramsey Experiment.""" + + figures = [] + fig = go.Figure() + fitting_report = None + + qubit_data = data[qubit] + + fig.add_trace( + go.Scatter( + x=qubit_data.wait, + y=qubit_data.msr * V_TO_UV, + opacity=1, + name="Voltage", + showlegend=True, + legendgroup="Voltage", + ) + ) + + if fit is not None: + # add fitting trace + waitrange = np.linspace( + min(qubit_data.wait), + max(qubit_data.wait), + 2 * len(qubit_data), + ) + + params = fit.fitted_parameters[qubit] + fig.add_trace( + go.Scatter( + x=waitrange, + y=utils.exp_decay( + waitrange, + *params, + ), + name="Fit", + line=go.scatter.Line(dash="dot"), + ) + ) + fitting_report = f"{qubit} | T2: {fit.t2[qubit]:,.0f} ns.

" + + fig.update_layout( + showlegend=True, + uirevision="0", # ``uirevision`` allows zooming while live plotting + xaxis_title="Time (ns)", + yaxis_title="MSR (uV)", + ) + + figures.append(fig) + + return figures, fitting_report + + +def _update(results: T2MSRResults, platform: Platform, qubit: QubitId): + update.t2(results.t2[qubit], platform, qubit) + + +t2_msr = Routine(_acquisition, _fit, _plot, _update) +"""T2MSR Routine object.""" diff --git a/src/qibocal/protocols/characterization/coherence/t2_sequences.py b/src/qibocal/protocols/characterization/coherence/t2_sequences.py index f7bfc1a2d..f26b966f6 100644 --- a/src/qibocal/protocols/characterization/coherence/t2_sequences.py +++ b/src/qibocal/protocols/characterization/coherence/t2_sequences.py @@ -5,14 +5,14 @@ from qibocal.auto.operation import Qubits, Routine -from .t2 import T2Data, T2Parameters, _fit, _plot, _update +from .t2_msr import T2MSRData, T2MSRParameters, _fit, _plot, _update def _acquisition( - params: T2Parameters, + params: T2MSRParameters, platform: Platform, qubits: Qubits, -) -> T2Data: +) -> T2MSRData: """Data acquisition for Ramsey Experiment (detuned).""" # create a sequence of pulses for the experiment # RX90 - t - RX90 - MZ @@ -44,7 +44,7 @@ def _acquisition( # create a DataUnits object to store the results, # DataUnits stores by default MSR, phase, i, q # additionally include wait time and t_max - data = T2Data() + data = T2MSRData() # sweep the parameter for wait in waits: diff --git a/src/qibocal/protocols/characterization/coherence/utils.py b/src/qibocal/protocols/characterization/coherence/utils.py index 888187e1e..098bf47ac 100644 --- a/src/qibocal/protocols/characterization/coherence/utils.py +++ b/src/qibocal/protocols/characterization/coherence/utils.py @@ -93,7 +93,7 @@ def exponential_fit_probability(data): ), sigma=data[qubit].error, ) - + popt = popt.tolist() perr = np.sqrt(np.diag(perr)) except Exception as e: @@ -102,7 +102,7 @@ def exponential_fit_probability(data): dec = 5 perr = [1] * 3 - fitted_parameters[qubit] = popt.tolist() + fitted_parameters[qubit] = popt dec = popt[2] decay[qubit] = (dec, perr[2]) diff --git a/src/qibocal/update.py b/src/qibocal/update.py index c9d09a69b..ddc769237 100644 --- a/src/qibocal/update.py +++ b/src/qibocal/update.py @@ -135,7 +135,10 @@ def t1(t1: int, platform: Platform, qubit: QubitId): def t2(t2: int, platform: Platform, qubit: QubitId): """Update mean excited state value in platform for specific qubit.""" - platform.qubits[qubit].t2 = int(t2) + if isinstance(t2, tuple): + platform.qubits[qubit].t2 = int(t2[0]) + else: + platform.qubits[qubit].t2 = int(t2) def t2_spin_echo(t2_spin_echo: float, platform: Platform, qubit: QubitId): From c37e259346696fa54de0e33c21f7c59b6724fa8b Mon Sep 17 00:00:00 2001 From: Andrea Date: Wed, 11 Oct 2023 11:56:41 +0400 Subject: [PATCH 4/9] Minor fixes --- src/qibocal/protocols/characterization/coherence/t1.py | 9 ++++++++- src/qibocal/protocols/characterization/coherence/t2.py | 5 ++++- .../protocols/characterization/coherence/utils.py | 2 +- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/qibocal/protocols/characterization/coherence/t1.py b/src/qibocal/protocols/characterization/coherence/t1.py index 4ea65842e..708dcfb7b 100644 --- a/src/qibocal/protocols/characterization/coherence/t1.py +++ b/src/qibocal/protocols/characterization/coherence/t1.py @@ -13,6 +13,7 @@ from qibocal import update from qibocal.auto.operation import Data, Parameters, Qubits, Results, Routine +from ..utils import fill_table from . import utils COLORBAND = "rgba(0,100,80,0.2)" @@ -203,7 +204,13 @@ def _plot(data: T1Data, qubit, fit: T1Results = None): line=go.scatter.Line(dash="dot"), ) ) - fitting_report = f"{qubit} | t1: {fit.t1[qubit][0]:,.0f} ns.

" + fitting_report = fill_table( + qubit, + "T1", + fit.t1[qubit][0], + fit.t1[qubit][1], + "ns", + ) # last part fig.update_layout( diff --git a/src/qibocal/protocols/characterization/coherence/t2.py b/src/qibocal/protocols/characterization/coherence/t2.py index 73774b0a3..deac5b203 100644 --- a/src/qibocal/protocols/characterization/coherence/t2.py +++ b/src/qibocal/protocols/characterization/coherence/t2.py @@ -12,6 +12,7 @@ from qibocal import update from qibocal.auto.operation import Parameters, Qubits, Results, Routine +from ..utils import fill_table from . import t1, utils @@ -169,7 +170,9 @@ def _plot(data: T2Data, qubit, fit: T2Results = None): line=go.scatter.Line(dash="dot"), ) ) - fitting_report = f"{qubit} | T2: {fit.t2[qubit][0]:,.0f} ns.

" + fitting_report = fill_table( + qubit, "T2", fit.t2[qubit][0], fit.t2[qubit][1], "ns" + ) fig.update_layout( showlegend=True, diff --git a/src/qibocal/protocols/characterization/coherence/utils.py b/src/qibocal/protocols/characterization/coherence/utils.py index 098bf47ac..59d56970a 100644 --- a/src/qibocal/protocols/characterization/coherence/utils.py +++ b/src/qibocal/protocols/characterization/coherence/utils.py @@ -95,7 +95,7 @@ def exponential_fit_probability(data): ) popt = popt.tolist() perr = np.sqrt(np.diag(perr)) - + perr[2] = 0.1 except Exception as e: log.warning(f"Exp decay fitting was not succesful. {e}") popt = [0] * 3 From ce315421dd3e593a89cfd2ff86e23abb661561f8 Mon Sep 17 00:00:00 2001 From: Andrea Date: Thu, 12 Oct 2023 11:21:34 +0400 Subject: [PATCH 5/9] Add spin echo with probabilities --- src/qibocal/auto/task.py | 1 + .../protocols/characterization/__init__.py | 2 + .../characterization/coherence/spin_echo.py | 74 +++++--- .../coherence/spin_echo_msr.py | 169 ++++++++++++++++++ .../characterization/coherence/t1.py | 5 +- src/qibocal/update.py | 11 +- tests/runcards/protocols.yml | 9 + 7 files changed, 234 insertions(+), 37 deletions(-) create mode 100644 src/qibocal/protocols/characterization/coherence/spin_echo_msr.py diff --git a/src/qibocal/auto/task.py b/src/qibocal/auto/task.py index ce82fad36..1b2fcaa49 100644 --- a/src/qibocal/auto/task.py +++ b/src/qibocal/auto/task.py @@ -111,6 +111,7 @@ def priority(self): @property def parameters(self): """Inputs parameters for self.operation.""" + print(self.action.parameters) return self.operation.parameters_type.load(self.action.parameters) @property diff --git a/src/qibocal/protocols/characterization/__init__.py b/src/qibocal/protocols/characterization/__init__.py index 5884e2e4b..5a08503ac 100644 --- a/src/qibocal/protocols/characterization/__init__.py +++ b/src/qibocal/protocols/characterization/__init__.py @@ -5,6 +5,7 @@ from .allxy.drag_pulse_tuning import drag_pulse_tuning from .classification import single_shot_classification from .coherence.spin_echo import spin_echo +from .coherence.spin_echo_msr import spin_echo_msr from .coherence.t1 import t1 from .coherence.t1_msr import t1_msr from .coherence.t1_sequences import t1_sequences @@ -65,6 +66,7 @@ class Operation(Enum): time_of_flight_readout = time_of_flight_readout single_shot_classification = single_shot_classification spin_echo = spin_echo + spin_echo_msr = spin_echo_msr allxy = allxy allxy_drag_pulse_tuning = allxy_drag_pulse_tuning drag_pulse_tuning = drag_pulse_tuning diff --git a/src/qibocal/protocols/characterization/coherence/spin_echo.py b/src/qibocal/protocols/characterization/coherence/spin_echo.py index 257f28659..35fe3b148 100644 --- a/src/qibocal/protocols/characterization/coherence/spin_echo.py +++ b/src/qibocal/protocols/characterization/coherence/spin_echo.py @@ -11,9 +11,9 @@ from qibocal import update from qibocal.auto.operation import Parameters, Qubits, Results, Routine -from ..utils import V_TO_UV -from .t1_msr import T1MSRData -from .utils import exp_decay, exponential_fit +from ..utils import fill_table +from . import t1 +from .utils import exp_decay, exponential_fit_probability @dataclass @@ -42,7 +42,7 @@ class SpinEchoResults(Results): """Raw fitting output.""" -class SpinEchoData(T1MSRData): +class SpinEchoData(t1.T1Data): """SpinEcho acquisition outputs.""" @@ -84,7 +84,7 @@ def _acquisition( ) data = SpinEchoData() - + probs = {qubit: [] for qubit in qubits} # sweep the parameter for wait in ro_wait_range: # save data as often as defined by points @@ -100,22 +100,25 @@ def _acquisition( ExecutionParameters( nshots=params.nshots, relaxation_time=params.relaxation_time, - acquisition_type=AcquisitionType.INTEGRATION, - averaging_mode=AveragingMode.CYCLIC, + acquisition_type=AcquisitionType.DISCRIMINATION, + averaging_mode=AveragingMode.SINGLESHOT, ), ) for qubit in qubits: - result = results[ro_pulses[qubit].serial] - data.register_qubit( - qubit, wait=wait, msr=result.magnitude, phase=result.phase - ) + prob = results[ro_pulses[qubit].serial].probability(state=0) + probs[qubit].append(prob) + + for qubit in qubits: + errors = [np.sqrt(prob * (1 - prob) / params.nshots) for prob in probs[qubit]] + data.register_qubit(qubit, wait=ro_wait_range, prob=probs[qubit], error=errors) + return data def _fit(data: SpinEchoData) -> SpinEchoResults: """Post-processing for SpinEcho.""" - t2Echos, fitted_parameters = exponential_fit(data) + t2Echos, fitted_parameters = exponential_fit_probability(data) return SpinEchoResults(t2Echos, fitted_parameters) @@ -124,23 +127,33 @@ def _plot(data: SpinEchoData, qubit, fit: SpinEchoResults = None): """Plotting for SpinEcho""" figures = [] - fig = go.Figure() - - # iterate over multiple data folders fitting_report = None - qubit_data = data[qubit] waits = qubit_data.wait + probs = qubit_data.prob + error_bars = qubit_data.error - fig.add_trace( - go.Scatter( - x=waits, - y=qubit_data.msr * V_TO_UV, - opacity=1, - name="Voltage", - showlegend=True, - legendgroup="Voltage", - ), + fig = go.Figure( + [ + go.Scatter( + x=waits, + y=probs, + opacity=1, + name="Probability of 0", + showlegend=True, + legendgroup="Probability of 0", + mode="lines", + ), + go.Scatter( + x=np.concatenate((waits, waits[::-1])), + y=np.concatenate((probs + error_bars, (probs - error_bars)[::-1])), + fill="toself", + fillcolor=t1.COLORBAND, + line=dict(color=t1.COLORBAND_LINE), + showlegend=True, + name="Errors", + ), + ] ) if fit is not None: @@ -161,15 +174,18 @@ def _plot(data: SpinEchoData, qubit, fit: SpinEchoResults = None): ), ) - fitting_report = ( - f"{qubit} | T2 Spin Echo: {fit.t2_spin_echo[qubit]:,.0f} ns.

" + fitting_report = fill_table( + qubit, + "T2 Spin Echo", + fit.t2_spin_echo[qubit][0], + fit.t2_spin_echo[qubit][1], + "ns", ) fig.update_layout( showlegend=True, - uirevision="0", # ``uirevision`` allows zooming while live plotting xaxis_title="Time (ns)", - yaxis_title="MSR (uV)", + yaxis_title="Probability of State 0", ) figures.append(fig) diff --git a/src/qibocal/protocols/characterization/coherence/spin_echo_msr.py b/src/qibocal/protocols/characterization/coherence/spin_echo_msr.py new file mode 100644 index 000000000..47c07cb97 --- /dev/null +++ b/src/qibocal/protocols/characterization/coherence/spin_echo_msr.py @@ -0,0 +1,169 @@ +from dataclasses import dataclass + +import numpy as np +import plotly.graph_objects as go +from qibolab import AcquisitionType, AveragingMode, ExecutionParameters +from qibolab.platform import Platform +from qibolab.pulses import PulseSequence +from qibolab.qubits import QubitId + +from qibocal import update +from qibocal.auto.operation import Qubits, Routine + +from ..utils import V_TO_UV +from . import spin_echo +from .t1_msr import T1MSRData +from .utils import exp_decay, exponential_fit + + +@dataclass +class SpinEchoMSRParameters(spin_echo.SpinEchoParameters): + """SpinEcho MSR runcard inputs.""" + + +@dataclass +class SpinEchoMSRResults(spin_echo.SpinEchoResults): + """SpinEchoMSR outputs.""" + + +class SpinEchoMSRData(T1MSRData): + """SpinEcho acquisition outputs.""" + + +def _acquisition( + params: SpinEchoMSRParameters, + platform: Platform, + qubits: Qubits, +) -> SpinEchoMSRData: + """Data acquisition for SpinEcho""" + # create a sequence of pulses for the experiment: + # Spin Echo 3 Pulses: RX(pi/2) - wait t(rotates z) - RX(pi) - wait t(rotates z) - RX(pi/2) - readout + ro_pulses = {} + RX90_pulses1 = {} + RX_pulses = {} + RX90_pulses2 = {} + sequence = PulseSequence() + for qubit in qubits: + RX90_pulses1[qubit] = platform.create_RX90_pulse(qubit, start=0) + RX_pulses[qubit] = platform.create_RX_pulse( + qubit, start=RX90_pulses1[qubit].finish + ) + RX90_pulses2[qubit] = platform.create_RX90_pulse( + qubit, start=RX_pulses[qubit].finish + ) + ro_pulses[qubit] = platform.create_qubit_readout_pulse( + qubit, start=RX90_pulses2[qubit].finish + ) + sequence.add(RX90_pulses1[qubit]) + sequence.add(RX_pulses[qubit]) + sequence.add(RX90_pulses2[qubit]) + sequence.add(ro_pulses[qubit]) + + # define the parameter to sweep and its range: + # delay between pulses + ro_wait_range = np.arange( + params.delay_between_pulses_start, + params.delay_between_pulses_end, + params.delay_between_pulses_step, + ) + + data = SpinEchoMSRData() + + # sweep the parameter + for wait in ro_wait_range: + # save data as often as defined by points + + for qubit in qubits: + RX_pulses[qubit].start = RX90_pulses1[qubit].finish + wait + RX90_pulses2[qubit].start = RX_pulses[qubit].finish + wait + ro_pulses[qubit].start = RX90_pulses2[qubit].finish + + # execute the pulse sequence + results = platform.execute_pulse_sequence( + sequence, + ExecutionParameters( + nshots=params.nshots, + relaxation_time=params.relaxation_time, + acquisition_type=AcquisitionType.INTEGRATION, + averaging_mode=AveragingMode.CYCLIC, + ), + ) + + for qubit in qubits: + result = results[ro_pulses[qubit].serial] + data.register_qubit( + qubit, wait=wait, msr=result.magnitude, phase=result.phase + ) + return data + + +def _fit(data: SpinEchoMSRData) -> SpinEchoMSRResults: + """Post-processing for SpinEcho.""" + t2Echos, fitted_parameters = exponential_fit(data) + + return SpinEchoMSRResults(t2Echos, fitted_parameters) + + +def _plot(data: SpinEchoMSRData, qubit, fit: SpinEchoMSRResults = None): + """Plotting for SpinEcho""" + + figures = [] + fig = go.Figure() + + # iterate over multiple data folders + fitting_report = None + + qubit_data = data[qubit] + waits = qubit_data.wait + + fig.add_trace( + go.Scatter( + x=waits, + y=qubit_data.msr * V_TO_UV, + opacity=1, + name="Voltage", + showlegend=True, + legendgroup="Voltage", + ), + ) + + if fit is not None: + # add fitting trace + waitrange = np.linspace( + min(waits), + max(waits), + 2 * len(qubit_data), + ) + params = fit.fitted_parameters[qubit] + + fig.add_trace( + go.Scatter( + x=waitrange, + y=exp_decay(waitrange, *params), + name="Fit", + line=go.scatter.Line(dash="dot"), + ), + ) + + fitting_report = ( + f"{qubit} | T2 Spin Echo: {fit.t2_spin_echo[qubit]:,.0f} ns.

" + ) + + fig.update_layout( + showlegend=True, + uirevision="0", # ``uirevision`` allows zooming while live plotting + xaxis_title="Time (ns)", + yaxis_title="MSR (uV)", + ) + + figures.append(fig) + + return figures, fitting_report + + +def _update(results: SpinEchoMSRResults, platform: Platform, qubit: QubitId): + update.t2_spin_echo(results.t2_spin_echo[qubit], platform, qubit) + + +spin_echo_msr = Routine(_acquisition, _fit, _plot, _update) +"""SpinEcho Routine object.""" diff --git a/src/qibocal/protocols/characterization/coherence/t1.py b/src/qibocal/protocols/characterization/coherence/t1.py index 708dcfb7b..ba15cb027 100644 --- a/src/qibocal/protocols/characterization/coherence/t1.py +++ b/src/qibocal/protocols/characterization/coherence/t1.py @@ -67,10 +67,7 @@ def register_qubit(self, qubit, wait, prob, error): ar["wait"] = wait ar["prob"] = prob ar["error"] = error - if qubit in self.data: - self.data[qubit] = np.rec.array(np.concatenate((self.data[qubit], ar))) - else: - self.data[qubit] = np.rec.array(ar) + self.data[qubit] = np.rec.array(ar) def _acquisition(params: T1Parameters, platform: Platform, qubits: Qubits) -> T1Data: diff --git a/src/qibocal/update.py b/src/qibocal/update.py index 912658a66..027df8fc3 100644 --- a/src/qibocal/update.py +++ b/src/qibocal/update.py @@ -136,7 +136,7 @@ def CZ_amplitude(amp: float, platform: Platform, pair: QubitPairId): def t1(t1: int, platform: Platform, qubit: QubitId): - """Update mean excited state value in platform for specific qubit.""" + """Update t1 value in platform for specific qubit.""" if isinstance(t1, tuple): platform.qubits[qubit].t1 = int(t1[0]) else: @@ -144,7 +144,7 @@ def t1(t1: int, platform: Platform, qubit: QubitId): def t2(t2: int, platform: Platform, qubit: QubitId): - """Update mean excited state value in platform for specific qubit.""" + """Update t2 value in platform for specific qubit.""" if isinstance(t2, tuple): platform.qubits[qubit].t2 = int(t2[0]) else: @@ -152,8 +152,11 @@ def t2(t2: int, platform: Platform, qubit: QubitId): def t2_spin_echo(t2_spin_echo: float, platform: Platform, qubit: QubitId): - """Update mean excited state value in platform for specific qubit.""" - platform.qubits[qubit].t2_spin_echo = int(t2_spin_echo) + """Update t2 echo value in platform for specific qubit.""" + if isinstance(t2_spin_echo, tuple): + platform.qubits[qubit].t2_spin_echo = int(t2_spin_echo[0]) + else: + platform.qubits[qubit].t2_spin_echo = int(t2_spin_echo) def drag_pulse_beta(beta: float, platform: Platform, qubit: QubitId): diff --git a/tests/runcards/protocols.yml b/tests/runcards/protocols.yml index ad6fc2ac9..e9143ccc1 100644 --- a/tests/runcards/protocols.yml +++ b/tests/runcards/protocols.yml @@ -238,6 +238,15 @@ actions: delay_between_pulses_step: 100 nshots: 10 + - id: t2_msr + priority: 0 + operation: t2_msr + parameters: + delay_between_pulses_start: 16 + delay_between_pulses_end: 20000 + delay_between_pulses_step: 100 + nshots: 10 + - id: t2 sequences priority: 0 operation: t2_sequences From d3165481de3ea0e93a52217e303983744e2f04f2 Mon Sep 17 00:00:00 2001 From: Andrea Date: Thu, 12 Oct 2023 16:31:14 +0400 Subject: [PATCH 6/9] Add zeno --- .../protocols/characterization/__init__.py | 2 + .../characterization/coherence/t1.py | 2 +- .../characterization/coherence/utils.py | 4 +- .../characterization/coherence/zeno.py | 92 ++++++++++--------- tests/runcards/protocols.yml | 9 +- 5 files changed, 60 insertions(+), 49 deletions(-) diff --git a/src/qibocal/protocols/characterization/__init__.py b/src/qibocal/protocols/characterization/__init__.py index 5a08503ac..f2a37ef5a 100644 --- a/src/qibocal/protocols/characterization/__init__.py +++ b/src/qibocal/protocols/characterization/__init__.py @@ -13,6 +13,7 @@ from .coherence.t2_msr import t2_msr from .coherence.t2_sequences import t2_sequences from .coherence.zeno import zeno +from .coherence.zeno_msr import zeno_msr from .dispersive_shift import dispersive_shift from .fast_reset.fast_reset import fast_reset from .flipping import flipping @@ -79,6 +80,7 @@ class Operation(Enum): resonator_frequency = resonator_frequency fast_reset = fast_reset zeno = zeno + zeno_msr = zeno_msr chsh_pulses = chsh_pulses chsh_circuits = chsh_circuits readout_mitigation_matrix = readout_mitigation_matrix diff --git a/src/qibocal/protocols/characterization/coherence/t1.py b/src/qibocal/protocols/characterization/coherence/t1.py index 748a42bc7..2b813f3ee 100644 --- a/src/qibocal/protocols/characterization/coherence/t1.py +++ b/src/qibocal/protocols/characterization/coherence/t1.py @@ -59,7 +59,7 @@ class T1Data(Data): data: dict[QubitId, npt.NDArray] = field(default_factory=dict) """Raw data acquired.""" - def register_qubit(self, qubit, wait, prob, error): + def register_qubit(self, qubit, prob, error, wait): """Store output for single qubit.""" # to be able to handle the non-sweeper case shape = (1,) if np.isscalar(wait) else wait.shape diff --git a/src/qibocal/protocols/characterization/coherence/utils.py b/src/qibocal/protocols/characterization/coherence/utils.py index 59d56970a..0693b45d1 100644 --- a/src/qibocal/protocols/characterization/coherence/utils.py +++ b/src/qibocal/protocols/characterization/coherence/utils.py @@ -72,8 +72,8 @@ def exponential_fit_probability(data): fitted_parameters = {} for qubit in qubits: + x = data[qubit].wait probability = data[qubit].prob - p0 = [ 0.5, 0.5, @@ -83,7 +83,7 @@ def exponential_fit_probability(data): try: popt, perr = curve_fit( exp_decay, - data[qubit].wait, + x, probability, p0=p0, maxfev=2000000, diff --git a/src/qibocal/protocols/characterization/coherence/zeno.py b/src/qibocal/protocols/characterization/coherence/zeno.py index ac12ed0c7..cdc56f1c0 100644 --- a/src/qibocal/protocols/characterization/coherence/zeno.py +++ b/src/qibocal/protocols/characterization/coherence/zeno.py @@ -2,7 +2,6 @@ from typing import Optional import numpy as np -import numpy.typing as npt import plotly.graph_objects as go from qibolab import AcquisitionType, AveragingMode, ExecutionParameters from qibolab.platform import Platform @@ -10,10 +9,10 @@ from qibolab.qubits import QubitId from qibocal import update -from qibocal.auto.operation import Data, Parameters, Qubits, Results, Routine +from qibocal.auto.operation import Parameters, Qubits, Results, Routine -from ..utils import V_TO_UV, table_dict, table_html -from . import utils +from ..utils import table_dict, table_html +from . import t1, utils @dataclass @@ -28,26 +27,10 @@ class ZenoParameters(Parameters): """Relaxation time (ns).""" -ZenoType = np.dtype([("msr", np.float64), ("phase", np.float64)]) -"""Custom dtype for Zeno.""" - - @dataclass -class ZenoData(Data): +class ZenoData(t1.T1Data): readout_duration: dict[QubitId, float] = field(default_factory=dict) """Readout durations for each qubit""" - data: dict[QubitId, npt.NDArray] = field(default_factory=dict) - """Raw data acquired.""" - - def register_qubit(self, qubit, msr, phase): - """Store output for single qubit.""" - ar = np.empty((1,), dtype=ZenoType) - ar["msr"] = msr - ar["phase"] = phase - if qubit in self.data: - self.data[qubit] = np.rec.array(np.concatenate((self.data[qubit], ar))) - else: - self.data[qubit] = np.rec.array(ar) @dataclass @@ -100,16 +83,23 @@ def _acquisition( ExecutionParameters( nshots=params.nshots, relaxation_time=params.relaxation_time, - acquisition_type=AcquisitionType.INTEGRATION, - averaging_mode=AveragingMode.CYCLIC, + acquisition_type=AcquisitionType.DISCRIMINATION, + averaging_mode=AveragingMode.SINGLESHOT, ), ) # retrieve and store the results for every qubit + probs = {qubit: [] for qubit in qubits} for qubit in qubits: for ro_pulse in ro_pulses[qubit]: - result = results[ro_pulse.serial] - data.register_qubit(qubit=qubit, msr=result.magnitude, phase=result.phase) + probs[qubit].append(results[ro_pulse.serial].probability(state=1)) + errors = [np.sqrt(prob * (1 - prob) / params.nshots) for prob in probs[qubit]] + data.register_qubit( + qubit, + wait=np.arange(1, len(probs[qubit]) + 1), + prob=probs[qubit], + error=errors, + ) return data @@ -121,30 +111,42 @@ def _fit(data: ZenoData) -> ZenoResults: y = p_0-p_1 e^{-x p_2}. """ - - t1s, fitted_parameters = utils.exponential_fit(data, zeno=True) + t1s, fitted_parameters = utils.exponential_fit_probability(data) return ZenoResults(t1s, fitted_parameters) def _plot(data: ZenoData, fit: ZenoResults, qubit): """Plotting function for T1 experiment.""" - figures = [] - fig = go.Figure() + figures = [] fitting_report = "" qubit_data = data[qubit] - readouts = np.arange(1, len(qubit_data.msr) + 1) - - fig.add_trace( - go.Scatter( - x=readouts, - y=qubit_data.msr * V_TO_UV, - opacity=1, - name="Voltage", - showlegend=True, - legendgroup="Voltage", - ) + probs = qubit_data.prob + error_bars = qubit_data.error + readouts = np.arange(1, len(qubit_data.prob) + 1) + + fig = go.Figure( + [ + go.Scatter( + x=readouts, + y=probs, + opacity=1, + name="Probability of 1", + showlegend=True, + legendgroup="Probability of 1", + mode="lines", + ), + go.Scatter( + x=np.concatenate((readouts, readouts[::-1])), + y=np.concatenate((probs + error_bars, (probs - error_bars)[::-1])), + fill="toself", + fillcolor=t1.COLORBAND, + line=dict(color=t1.COLORBAND_LINE), + showlegend=True, + name="Errors", + ), + ] ) if fit is not None: @@ -166,11 +168,12 @@ def _plot(data: ZenoData, fit: ZenoResults, qubit): fitting_report = table_html( table_dict( qubit, - ["Readout Pulse", "T1"], + ["T1 [ns]", "Readout Pulse [ns]"], [ - np.round(fit.zeno_t1[qubit]), - np.round(fit.zeno_t1[qubit] * data.readout_duration[qubit]), + fit.zeno_t1[qubit], + np.array(fit.zeno_t1[qubit]) * data.readout_duration[qubit], ], + display_error=True, ) ) # FIXME: Pulse duration (+ time of flight ?) @@ -178,9 +181,8 @@ def _plot(data: ZenoData, fit: ZenoResults, qubit): # last part fig.update_layout( showlegend=True, - uirevision="0", # ``uirevision`` allows zooming while live plotting xaxis_title="Number of readouts", - yaxis_title="MSR (uV)", + yaxis_title="Probability of State 1", ) figures.append(fig) diff --git a/tests/runcards/protocols.yml b/tests/runcards/protocols.yml index 1520e6011..043b8238e 100644 --- a/tests/runcards/protocols.yml +++ b/tests/runcards/protocols.yml @@ -226,7 +226,14 @@ actions: priority: 00 operation: zeno parameters: - readouts: 2 + readouts: 10 + nshots: 10 + + - id: zeno_msr + priority: 00 + operation: zeno_msr + parameters: + readouts: 10 nshots: 10 - id: t2 From f29180fca757255279b882da959b0b0204479a9d Mon Sep 17 00:00:00 2001 From: Andrea Date: Thu, 12 Oct 2023 17:17:52 +0400 Subject: [PATCH 7/9] Add ramsey_msr --- .../protocols/characterization/__init__.py | 2 + .../protocols/characterization/ramsey.py | 6 +- .../protocols/characterization/ramsey_msr.py | 289 ++++++++++++++++++ tests/runcards/protocols.yml | 19 ++ 4 files changed, 313 insertions(+), 3 deletions(-) create mode 100644 src/qibocal/protocols/characterization/ramsey_msr.py diff --git a/src/qibocal/protocols/characterization/__init__.py b/src/qibocal/protocols/characterization/__init__.py index f2a37ef5a..53e81224f 100644 --- a/src/qibocal/protocols/characterization/__init__.py +++ b/src/qibocal/protocols/characterization/__init__.py @@ -27,6 +27,7 @@ from .rabi.length import rabi_length from .rabi.length_sequences import rabi_length_sequences from .ramsey import ramsey +from .ramsey_msr import ramsey_msr from .ramsey_sequences import ramsey_sequences from .randomized_benchmarking.standard_rb import standard_rb from .readout_characterization import readout_characterization @@ -57,6 +58,7 @@ class Operation(Enum): rabi_length = rabi_length rabi_length_sequences = rabi_length_sequences ramsey = ramsey + ramsey_msr = ramsey_msr ramsey_sequences = ramsey_sequences t1 = t1 t1_msr = t1_msr diff --git a/src/qibocal/protocols/characterization/ramsey.py b/src/qibocal/protocols/characterization/ramsey.py index 909f40f17..fdee92772 100644 --- a/src/qibocal/protocols/characterization/ramsey.py +++ b/src/qibocal/protocols/characterization/ramsey.py @@ -324,7 +324,7 @@ def _plot(data: RamseyData, qubit, fit: RamseyResults = None): table_dict( qubit, [ - "Delta frequnecy [Hz]", + "Delta Frequency [Hz]", "Drive Frequency [Hz]", "T2* [ns]", "chi2 reduced", @@ -359,7 +359,7 @@ def _update(results: RamseyResults, platform: Platform, qubit: QubitId): """Ramsey Routine object.""" -def fitting(x: list, y: list, errors: list) -> list: +def fitting(x: list, y: list, errors: list = None) -> list: """ Given the inputs list `x` and outputs one `y`, this function fits the `ramsey_fit` function and returns a list with the fit parameters. @@ -372,7 +372,7 @@ def fitting(x: list, y: list, errors: list) -> list: delta_x = x_max - x_min y = (y - y_min) / delta_y x = (x - x_min) / delta_x - err = errors / delta_y + err = errors / delta_y if errors is not None else None ft = np.fft.rfft(y) freqs = np.fft.rfftfreq(len(y), x[1] - x[0]) mags = abs(ft) diff --git a/src/qibocal/protocols/characterization/ramsey_msr.py b/src/qibocal/protocols/characterization/ramsey_msr.py new file mode 100644 index 000000000..10e36d183 --- /dev/null +++ b/src/qibocal/protocols/characterization/ramsey_msr.py @@ -0,0 +1,289 @@ +from dataclasses import dataclass +from typing import Optional + +import numpy as np +import plotly.graph_objects as go +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.auto.operation import Qubits, Results, Routine + +from .ramsey import ( + PERR_EXCEPTION, + POPT_EXCEPTION, + RamseyData, + RamseyParameters, + _update, + fitting, + ramsey_fit, +) +from .utils import GHZ_TO_HZ, V_TO_UV, table_dict, table_html + + +@dataclass +class RamseyMSRParameters(RamseyParameters): + """Ramsey runcard inputs.""" + + +@dataclass +class RamseyMSRResults(Results): + """Ramsey outputs.""" + + frequency: dict[QubitId, tuple[float, Optional[float]]] + """Drive frequency [GHz] for each qubit.""" + t2: dict[QubitId, tuple[float, Optional[float]]] + """T2 for each qubit [ns].""" + delta_phys: dict[QubitId, tuple[float, Optional[float]]] + """Drive frequency [Hz] correction for each qubit.""" + fitted_parameters: dict[QubitId, list[float]] + """Raw fitting output.""" + + +RamseyMSRType = np.dtype([("wait", np.float64), ("msr", np.float64)]) +"""Custom dtype for coherence routines.""" + + +@dataclass +class RamseyMSRData(RamseyData): + """Ramsey acquisition outputs.""" + + def register_qubit(self, qubit, wait, msr): + """Store output for single qubit.""" + # to be able to handle the non-sweeper case + shape = (1,) if np.isscalar(msr) else msr.shape + ar = np.empty(shape, dtype=RamseyMSRType) + ar["wait"] = wait + ar["msr"] = msr + if qubit in self.data: + self.data[qubit] = np.rec.array(np.concatenate((self.data[qubit], ar))) + else: + self.data[qubit] = np.rec.array(ar) + + @property + def waits(self): + """ + Return a list with the waiting times without repetitions. + """ + qubit = next(iter(self.data)) + return np.unique(self.data[qubit].wait) + + +def _acquisition( + params: RamseyMSRParameters, + platform: Platform, + qubits: Qubits, +) -> RamseyMSRData: + """Data acquisition for Ramsey Experiment (detuned).""" + # create a sequence of pulses for the experiment + # RX90 - t - RX90 - MZ + ro_pulses = {} + RX90_pulses1 = {} + RX90_pulses2 = {} + freqs = {} + sequence = PulseSequence() + for qubit in qubits: + RX90_pulses1[qubit] = platform.create_RX90_pulse(qubit, start=0) + RX90_pulses2[qubit] = platform.create_RX90_pulse( + qubit, + start=RX90_pulses1[qubit].finish, + ) + ro_pulses[qubit] = platform.create_qubit_readout_pulse( + qubit, start=RX90_pulses2[qubit].finish + ) + freqs[qubit] = qubits[qubit].drive_frequency + sequence.add(RX90_pulses1[qubit]) + sequence.add(RX90_pulses2[qubit]) + sequence.add(ro_pulses[qubit]) + + # define the parameter to sweep and its range: + waits = np.arange( + # wait time between RX90 pulses + params.delay_between_pulses_start, + params.delay_between_pulses_end, + params.delay_between_pulses_step, + ) + + data = RamseyMSRData( + n_osc=params.n_osc, + t_max=params.delay_between_pulses_end, + detuning_sign=+1, + qubit_freqs=freqs, + ) + + if params.n_osc == 0: + sweeper = Sweeper( + Parameter.start, + waits, + [RX90_pulses2[qubit] for qubit in qubits], + type=SweeperType.ABSOLUTE, + ) + + # execute the sweep + results = platform.sweep( + sequence, + ExecutionParameters( + nshots=params.nshots, + relaxation_time=params.relaxation_time, + acquisition_type=AcquisitionType.INTEGRATION, + averaging_mode=AveragingMode.CYCLIC, + ), + sweeper, + ) + for qubit in qubits: + result = results[ro_pulses[qubit].serial] + # The probability errors are the standard errors of the binomial distribution + data.register_qubit( + qubit, + wait=waits, + msr=result.magnitude, + ) + + else: + for wait in waits: + for qubit in qubits: + RX90_pulses2[qubit].start = RX90_pulses1[qubit].finish + wait + ro_pulses[qubit].start = RX90_pulses2[qubit].finish + if params.n_osc != 0: + RX90_pulses2[qubit].relative_phase = ( + RX90_pulses2[qubit].start + * (-2 * np.pi) + * (params.n_osc) + / params.delay_between_pulses_end + ) + + results = platform.execute_pulse_sequence( + sequence, + ExecutionParameters( + nshots=params.nshots, + relaxation_time=params.relaxation_time, + acquisition_type=AcquisitionType.INTEGRATION, + averaging_mode=(AveragingMode.CYCLIC), + ), + ) + + for qubit in qubits: + result = results[ro_pulses[qubit].serial] + data.register_qubit( + qubit, + wait=wait, + msr=result.magnitude, + ) + return data + + +def _fit(data: RamseyMSRData) -> RamseyMSRResults: + r""" + Fitting routine for Ramsey experiment. The used model is + .. math:: + y = p_0 + p_1 sin \Big(p_2 x + p_3 \Big) e^{-x p_4}. + """ + qubits = data.qubits + waits = data.waits + popts = {} + freq_measure = {} + t2_measure = {} + delta_phys_measure = {} + for qubit in qubits: + qubit_data = data[qubit] + qubit_freq = data.qubit_freqs[qubit] + msr = qubit_data["msr"] + try: + popt, perr = fitting(waits, msr) + except: + popt = POPT_EXCEPTION + perr = PERR_EXCEPTION + + delta_fitting = popt[2] / (2 * np.pi) + delta_phys = data.detuning_sign * int( + (delta_fitting - data.n_osc / data.t_max) * GHZ_TO_HZ + ) + corrected_qubit_frequency = int(qubit_freq - delta_phys) + t2 = popt[4] + freq_measure[qubit] = ( + corrected_qubit_frequency, + perr[2] * GHZ_TO_HZ / (2 * np.pi * data.t_max), + ) + t2_measure[qubit] = (t2, perr[4]) + popts[qubit] = popt + delta_phys_measure[qubit] = ( + delta_phys, + popt[2] * GHZ_TO_HZ / (2 * np.pi * data.t_max), + ) + + return RamseyMSRResults(freq_measure, t2_measure, delta_phys_measure, popts) + + +def _plot(data: RamseyMSRData, qubit, fit: RamseyMSRResults = None): + """Plotting function for Ramsey Experiment.""" + + figures = [] + fig = go.Figure() + fitting_report = "" + + qubit_data = data.data[qubit] + waits = data.waits + msr = qubit_data["msr"] + fig = go.Figure( + [ + go.Scatter( + x=waits, + y=msr * V_TO_UV, + opacity=1, + name="Voltage", + showlegend=True, + legendgroup="Voltage", + mode="lines", + ), + ] + ) + + if fit is not None: + fig.add_trace( + go.Scatter( + x=waits, + y=ramsey_fit( + waits, + float(fit.fitted_parameters[qubit][0]), + float(fit.fitted_parameters[qubit][1]), + float(fit.fitted_parameters[qubit][2]), + float(fit.fitted_parameters[qubit][3]), + float(fit.fitted_parameters[qubit][4]), + ) + * V_TO_UV, + name="Fit", + line=go.scatter.Line(dash="dot"), + ) + ) + fitting_report = table_html( + table_dict( + qubit, + [ + "Delta Frequency [Hz]", + "Drive Frequency [Hz]", + "T2* [ns]", + ], + [ + np.round(fit.delta_phys[qubit][0], 3), + np.round(fit.frequency[qubit][0], 3), + np.round(fit.t2[qubit][0], 3), + ], + ) + ) + + fig.update_layout( + showlegend=True, + uirevision="0", # ``uirevision`` allows zooming while live plotting + xaxis_title="Time (ns)", + yaxis_title="MSR [uV]", + ) + + figures.append(fig) + + return figures, fitting_report + + +ramsey_msr = Routine(_acquisition, _fit, _plot, _update) +"""Ramsey Routine object.""" diff --git a/tests/runcards/protocols.yml b/tests/runcards/protocols.yml index 043b8238e..1be398aff 100644 --- a/tests/runcards/protocols.yml +++ b/tests/runcards/protocols.yml @@ -273,6 +273,25 @@ actions: n_osc: 2 nshots: 10 + - id: ramsey_msr + priority: 0 + operation: ramsey_msr + parameters: + delay_between_pulses_start: 0 + delay_between_pulses_end: 50 + delay_between_pulses_step: 1 + n_osc: 2 + nshots: 10 + + - id: ramsey_msr_detuned + priority: 0 + operation: ramsey_msr + parameters: + delay_between_pulses_start: 0 + delay_between_pulses_end: 50 + delay_between_pulses_step: 1 + nshots: 10 + - id: ramsey detuned sequences priority: 0 operation: ramsey_sequences From e48ec1cf66f18eac13de3f26b09c19213fbe94a2 Mon Sep 17 00:00:00 2001 From: Andrea Date: Thu, 12 Oct 2023 17:24:21 +0400 Subject: [PATCH 8/9] Add zeno MSR --- .../characterization/coherence/zeno_msr.py | 195 ++++++++++++++++++ 1 file changed, 195 insertions(+) create mode 100644 src/qibocal/protocols/characterization/coherence/zeno_msr.py diff --git a/src/qibocal/protocols/characterization/coherence/zeno_msr.py b/src/qibocal/protocols/characterization/coherence/zeno_msr.py new file mode 100644 index 000000000..9c4c8f88a --- /dev/null +++ b/src/qibocal/protocols/characterization/coherence/zeno_msr.py @@ -0,0 +1,195 @@ +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 qibolab import AcquisitionType, AveragingMode, ExecutionParameters +from qibolab.platform import Platform +from qibolab.pulses import PulseSequence +from qibolab.qubits import QubitId + +from qibocal import update +from qibocal.auto.operation import Data, Parameters, Qubits, Results, Routine + +from ..utils import V_TO_UV, table_dict, table_html +from . import utils + + +@dataclass +class ZenoParameters(Parameters): + """Zeno runcard inputs.""" + + readouts: int + "Number of readout pulses" + nshots: Optional[int] = None + """Number of shots.""" + relaxation_time: Optional[int] = None + """Relaxation time (ns).""" + + +ZenoType = np.dtype([("msr", np.float64), ("phase", np.float64)]) +"""Custom dtype for Zeno.""" + + +@dataclass +class ZenoData(Data): + readout_duration: dict[QubitId, float] = field(default_factory=dict) + """Readout durations for each qubit""" + data: dict[QubitId, npt.NDArray] = field(default_factory=dict) + """Raw data acquired.""" + + def register_qubit(self, qubit, msr, phase): + """Store output for single qubit.""" + ar = np.empty((1,), dtype=ZenoType) + ar["msr"] = msr + ar["phase"] = phase + if qubit in self.data: + self.data[qubit] = np.rec.array(np.concatenate((self.data[qubit], ar))) + else: + self.data[qubit] = np.rec.array(ar) + + +@dataclass +class ZenoResults(Results): + """Zeno outputs.""" + + zeno_t1: dict[QubitId, int] + """T1 for each qubit.""" + fitted_parameters: dict[QubitId, dict[str, float]] + """Raw fitting output.""" + + +def _acquisition( + params: ZenoParameters, + platform: Platform, + qubits: Qubits, +) -> ZenoData: + """ + In a T1_Zeno experiment, we measure an excited qubit repeatedly. Due to decoherence processes, + it is possible that, at the time of measurement, the qubit will not be excited anymore. + The quantum zeno effect consists of measuring allowing a particle's time evolution to be slowed + down by measuring it frequently enough. However, in the experiments we see that due the QND-ness of the readout + pulse that the qubit decoheres faster. + Reference: https://link.aps.org/accepted/10.1103/PhysRevLett.118.240401. + """ + + # create sequence of pulses: + sequence = PulseSequence() + RX_pulses = {} + ro_pulses = {} + ro_pulse_duration = {} + for qubit in qubits: + RX_pulses[qubit] = platform.create_RX_pulse(qubit, start=0) + sequence.add(RX_pulses[qubit]) + start = RX_pulses[qubit].finish + ro_pulses[qubit] = [] + for _ in range(params.readouts): + ro_pulse = platform.create_qubit_readout_pulse(qubit, start=start) + start += ro_pulse.duration + sequence.add(ro_pulse) + ro_pulses[qubit].append(ro_pulse) + ro_pulse_duration[qubit] = ro_pulse.duration + + # create a DataUnits object to store the results + data = ZenoData(readout_duration=ro_pulse_duration) + + # execute the first pulse sequence + results = platform.execute_pulse_sequence( + sequence, + ExecutionParameters( + nshots=params.nshots, + relaxation_time=params.relaxation_time, + acquisition_type=AcquisitionType.INTEGRATION, + averaging_mode=AveragingMode.CYCLIC, + ), + ) + + # retrieve and store the results for every qubit + for qubit in qubits: + for ro_pulse in ro_pulses[qubit]: + result = results[ro_pulse.serial] + data.register_qubit(qubit=qubit, msr=result.magnitude, phase=result.phase) + return data + + +def _fit(data: ZenoData) -> ZenoResults: + """ + Fitting routine for T1 experiment. The used model is + + .. math:: + + y = p_0-p_1 e^{-x p_2}. + """ + + t1s, fitted_parameters = utils.exponential_fit(data, zeno=True) + + return ZenoResults(t1s, fitted_parameters) + + +def _plot(data: ZenoData, fit: ZenoResults, qubit): + """Plotting function for T1 experiment.""" + figures = [] + fig = go.Figure() + + fitting_report = "" + qubit_data = data[qubit] + readouts = np.arange(1, len(qubit_data.msr) + 1) + + fig.add_trace( + go.Scatter( + x=readouts, + y=qubit_data.msr * V_TO_UV, + opacity=1, + name="Voltage", + showlegend=True, + legendgroup="Voltage", + ) + ) + + if fit is not None: + fitting_report = "" + waitrange = np.linspace( + min(readouts), + max(readouts), + 2 * len(qubit_data), + ) + params = fit.fitted_parameters[qubit] + fig.add_trace( + go.Scatter( + x=waitrange, + y=utils.exp_decay(waitrange, *params), + name="Fit", + line=go.scatter.Line(dash="dot"), + ) + ) + fitting_report = table_html( + table_dict( + qubit, + ["T1", "Readout Pulse"], + [ + np.round(fit.zeno_t1[qubit]), + np.round(fit.zeno_t1[qubit] * data.readout_duration[qubit]), + ], + ) + ) + # FIXME: Pulse duration (+ time of flight ?) + + # last part + fig.update_layout( + showlegend=True, + uirevision="0", # ``uirevision`` allows zooming while live plotting + xaxis_title="Number of readouts", + yaxis_title="MSR (uV)", + ) + + figures.append(fig) + + return figures, fitting_report + + +def _update(results: ZenoResults, platform: Platform, qubit: QubitId): + update.t1(results.zeno_t1[qubit], platform, qubit) + + +zeno_msr = Routine(_acquisition, _fit, _plot, _update) From 9bdb86ed07c39ce8228dda1d1f0dfc5515cef865 Mon Sep 17 00:00:00 2001 From: Andrea Date: Fri, 13 Oct 2023 15:36:17 +0400 Subject: [PATCH 9/9] Edoardo comments --- src/qibocal/protocols/characterization/coherence/t1.py | 2 +- src/qibocal/protocols/characterization/coherence/t2.py | 2 +- src/qibocal/protocols/characterization/coherence/utils.py | 5 ++--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/qibocal/protocols/characterization/coherence/t1.py b/src/qibocal/protocols/characterization/coherence/t1.py index 2b813f3ee..b24e12112 100644 --- a/src/qibocal/protocols/characterization/coherence/t1.py +++ b/src/qibocal/protocols/characterization/coherence/t1.py @@ -134,7 +134,7 @@ def _acquisition(params: T1Parameters, platform: Platform, qubits: Qubits) -> T1 for qubit in qubits: probs = results[ro_pulses[qubit].serial].probability(state=1) - errors = [np.sqrt(prob * (1 - prob) / params.nshots) for prob in probs] + errors = np.sqrt(probs * (1 - probs) / params.nshots) data.register_qubit(qubit, wait=ro_wait_range, prob=probs, error=errors) return data diff --git a/src/qibocal/protocols/characterization/coherence/t2.py b/src/qibocal/protocols/characterization/coherence/t2.py index 52d4cf2db..91b471ba0 100644 --- a/src/qibocal/protocols/characterization/coherence/t2.py +++ b/src/qibocal/protocols/characterization/coherence/t2.py @@ -102,7 +102,7 @@ def _acquisition( for qubit in qubits: probs = results[ro_pulses[qubit].serial].probability(state=1) - errors = [np.sqrt(prob * (1 - prob) / params.nshots) for prob in probs] + errors = np.sqrt(probs * (1 - probs) / params.nshots) data.register_qubit(qubit, wait=waits, prob=probs, error=errors) return data diff --git a/src/qibocal/protocols/characterization/coherence/utils.py b/src/qibocal/protocols/characterization/coherence/utils.py index 0693b45d1..a32bf41f4 100644 --- a/src/qibocal/protocols/characterization/coherence/utils.py +++ b/src/qibocal/protocols/characterization/coherence/utils.py @@ -7,7 +7,7 @@ def exp_decay(x, *p): - return p[0] - p[1] * np.exp(-1 * x * p[2]) + return p[0] - p[1] * np.exp(-1 * x / p[2]) def exponential_fit(data, zeno=None): @@ -52,7 +52,7 @@ def exponential_fit(data, zeno=None): (y_max - y_min) * popt[1] * np.exp(x_min * popt[2] / (x_max - x_min)), popt[2] / (x_max - x_min), ] - t2 = 1.0 / popt[2] + t2 = popt[2] except Exception as e: log.warning(f"Exp decay fitting was not succesful. {e}") @@ -95,7 +95,6 @@ def exponential_fit_probability(data): ) popt = popt.tolist() perr = np.sqrt(np.diag(perr)) - perr[2] = 0.1 except Exception as e: log.warning(f"Exp decay fitting was not succesful. {e}") popt = [0] * 3