From 0b625f3ed03226fa260dc7fa20593b2c91ffabe1 Mon Sep 17 00:00:00 2001 From: Andrea Date: Mon, 9 Dec 2024 18:35:51 +0400 Subject: [PATCH 1/4] feat: Adding CPMG protocol --- src/qibocal/protocols/__init__.py | 2 + src/qibocal/protocols/coherence/cpmg.py | 172 ++++++++++++++++++ src/qibocal/protocols/coherence/spin_echo.py | 18 +- .../protocols/coherence/spin_echo_signal.py | 19 +- src/qibocal/protocols/coherence/utils.py | 64 ++++--- tests/runcards/protocols.yml | 23 ++- 6 files changed, 242 insertions(+), 56 deletions(-) create mode 100644 src/qibocal/protocols/coherence/cpmg.py diff --git a/src/qibocal/protocols/__init__.py b/src/qibocal/protocols/__init__.py index f6aa87e61..8ab73abcd 100644 --- a/src/qibocal/protocols/__init__.py +++ b/src/qibocal/protocols/__init__.py @@ -1,6 +1,7 @@ from .allxy.allxy import allxy from .allxy.allxy_resonator_depletion_tuning import allxy_resonator_depletion_tuning from .classification import single_shot_classification +from .coherence.cpmg import cpmg from .coherence.spin_echo import spin_echo from .coherence.spin_echo_signal import spin_echo_signal from .coherence.t1 import t1 @@ -104,4 +105,5 @@ "standard_rb_2q_inter", "optimize_two_qubit_gate", "ramsey_zz", + "cpmg", ] diff --git a/src/qibocal/protocols/coherence/cpmg.py b/src/qibocal/protocols/coherence/cpmg.py new file mode 100644 index 000000000..92ab29b38 --- /dev/null +++ b/src/qibocal/protocols/coherence/cpmg.py @@ -0,0 +1,172 @@ +from dataclasses import dataclass, field +from typing import Optional + +import numpy as np +import plotly.graph_objects as go +from qibolab import AcquisitionType, AveragingMode, Parameter, Sweeper + +from qibocal.auto.operation import QubitId, Routine +from qibocal.calibration import CalibrationPlatform +from qibocal.result import probability + +from ..utils import table_dict, table_html +from . import t1 +from .spin_echo import SpinEchoParameters, SpinEchoResults, _update +from .utils import dynamical_decoupling_sequence, exp_decay, exponential_fit_probability + + +@dataclass +class CpmgParameters(SpinEchoParameters): + """Cpmg runcard inputs.""" + + n: int = 1 + """Number of pi rotations.""" + + +@dataclass +class CpmgResults(SpinEchoResults): + """SpinEcho outputs.""" + + chi2: Optional[dict[QubitId, tuple[float, Optional[float]]]] = field( + default_factory=dict + ) + """Chi squared estimate mean value and error.""" + + +class CpmgData(t1.T1Data): + """SpinEcho acquisition outputs.""" + + +def _acquisition( + params: CpmgParameters, + platform: CalibrationPlatform, + targets: list[QubitId], +) -> CpmgData: + """Data acquisition for Cpmg""" + # create a sequence of pulses for the experiment: + sequence, delays = dynamical_decoupling_sequence( + platform, targets, n=params.n, kind="CPMG" + ) + + # define the parameter to sweep and its range: + # delay between pulses + wait_range = np.arange( + params.delay_between_pulses_start, + params.delay_between_pulses_end, + params.delay_between_pulses_step, + ) + + sweeper = Sweeper( + parameter=Parameter.duration, + values=wait_range / 2 / params.n, + pulses=delays, + ) + + results = platform.execute( + [sequence], + [[sweeper]], + nshots=params.nshots, + relaxation_time=params.relaxation_time, + acquisition_type=AcquisitionType.DISCRIMINATION, + averaging_mode=AveragingMode.SINGLESHOT, + ) + + data = CpmgData() + for qubit in targets: + ro_pulse = list(sequence.channel(platform.qubits[qubit].acquisition))[-1] + result = results[ro_pulse.id] + prob = probability(result, state=1) + error = np.sqrt(prob * (1 - prob) / params.nshots) + data.register_qubit( + t1.CoherenceProbType, + (qubit), + dict( + wait=wait_range, + prob=prob, + error=error, + ), + ) + + return data + + +def _fit(data: CpmgData) -> CpmgResults: + """Post-processing for Cpmg.""" + t2Echos, fitted_parameters, pcovs, chi2 = exponential_fit_probability(data) + return CpmgResults(t2Echos, fitted_parameters, pcovs, chi2) + + +def _plot(data: CpmgData, target: QubitId, fit: CpmgResults = None): + """Plotting for Cpmg""" + + figures = [] + # iterate over multiple data folders + fitting_report = "" + + qubit_data = data[target] + waits = qubit_data.wait + probs = qubit_data.prob + error_bars = qubit_data.error + + 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: + # add fitting trace + waitrange = np.linspace( + min(waits), + max(waits), + 2 * len(qubit_data), + ) + params = fit.fitted_parameters[target] + + fig.add_trace( + go.Scatter( + x=waitrange, + y=exp_decay(waitrange, *params), + name="Fit", + line=go.scatter.Line(dash="dot"), + ), + ) + fitting_report = table_html( + table_dict( + target, + ["T2 Spin Echo [ns]", "chi2 reduced"], + [fit.t2_spin_echo[target], fit.chi2[target]], + display_error=True, + ) + ) + + fig.update_layout( + showlegend=True, + xaxis_title="Time [ns]", + yaxis_title="Probability of State 1", + ) + + figures.append(fig) + + return figures, fitting_report + + +cpmg = Routine(_acquisition, _fit, _plot, _update) +"""Cpmg Routine object.""" diff --git a/src/qibocal/protocols/coherence/spin_echo.py b/src/qibocal/protocols/coherence/spin_echo.py index 22b90c390..e8c4f9d5c 100644 --- a/src/qibocal/protocols/coherence/spin_echo.py +++ b/src/qibocal/protocols/coherence/spin_echo.py @@ -12,7 +12,7 @@ from ..utils import table_dict, table_html from . import t1 from .spin_echo_signal import SpinEchoSignalParameters, SpinEchoSignalResults, _update -from .utils import exp_decay, exponential_fit_probability, spin_echo_sequence +from .utils import dynamical_decoupling_sequence, exp_decay, exponential_fit_probability @dataclass @@ -41,11 +41,11 @@ def _acquisition( ) -> SpinEchoData: """Data acquisition for SpinEcho""" # create a sequence of pulses for the experiment: - sequence, delays = spin_echo_sequence(platform, targets) + sequence, delays = dynamical_decoupling_sequence(platform, targets, kind="CP") # define the parameter to sweep and its range: # delay between pulses - ro_wait_range = np.arange( + wait_range = np.arange( params.delay_between_pulses_start, params.delay_between_pulses_end, params.delay_between_pulses_step, @@ -53,7 +53,7 @@ def _acquisition( sweeper = Sweeper( parameter=Parameter.duration, - values=ro_wait_range, + values=wait_range / 2, pulses=delays, ) @@ -70,13 +70,13 @@ def _acquisition( for qubit in targets: ro_pulse = list(sequence.channel(platform.qubits[qubit].acquisition))[-1] result = results[ro_pulse.id] - prob = probability(result, state=0) + prob = probability(result, state=1) error = np.sqrt(prob * (1 - prob) / params.nshots) data.register_qubit( t1.CoherenceProbType, (qubit), dict( - wait=ro_wait_range, + wait=wait_range, prob=prob, error=error, ), @@ -109,9 +109,9 @@ def _plot(data: SpinEchoData, target: QubitId, fit: SpinEchoResults = None): x=waits, y=probs, opacity=1, - name="Probability of 0", + name="Probability of 1", showlegend=True, - legendgroup="Probability of 0", + legendgroup="Probability of 1", mode="lines", ), go.Scatter( @@ -155,7 +155,7 @@ def _plot(data: SpinEchoData, target: QubitId, fit: SpinEchoResults = None): fig.update_layout( showlegend=True, xaxis_title="Time [ns]", - yaxis_title="Probability of State 0", + yaxis_title="Probability of State 1", ) figures.append(fig) diff --git a/src/qibocal/protocols/coherence/spin_echo_signal.py b/src/qibocal/protocols/coherence/spin_echo_signal.py index 8321e6217..378fa0883 100644 --- a/src/qibocal/protocols/coherence/spin_echo_signal.py +++ b/src/qibocal/protocols/coherence/spin_echo_signal.py @@ -12,7 +12,12 @@ from ... import update from ..utils import table_dict, table_html from .t1_signal import T1SignalData -from .utils import CoherenceType, exp_decay, exponential_fit, spin_echo_sequence +from .utils import ( + CoherenceType, + dynamical_decoupling_sequence, + exp_decay, + exponential_fit, +) @dataclass @@ -26,8 +31,6 @@ class SpinEchoSignalParameters(Parameters): delay_between_pulses_step: int """Step delay between pulses [ns].""" single_shot: bool = False - """If ``True`` save single shot signal data.""" - unrolling: bool = False @dataclass @@ -53,11 +56,11 @@ def _acquisition( ) -> SpinEchoSignalData: """Data acquisition for SpinEcho""" # create a sequence of pulses for the experiment: - sequence, delays = spin_echo_sequence(platform, targets) + sequence, delays = dynamical_decoupling_sequence(platform, targets, kind="CP") # define the parameter to sweep and its range: # delay between pulses - ro_wait_range = np.arange( + wait_range = np.arange( params.delay_between_pulses_start, params.delay_between_pulses_end, params.delay_between_pulses_step, @@ -65,7 +68,7 @@ def _acquisition( sweeper = Sweeper( parameter=Parameter.duration, - values=ro_wait_range, + values=wait_range / 2, pulses=delays, ) @@ -86,9 +89,9 @@ def _acquisition( result = results[ro_pulse.id] signal = magnitude(result) if params.single_shot: - _wait = np.array(len(signal) * [ro_wait_range]) + _wait = np.array(len(signal) * [wait_range]) else: - _wait = ro_wait_range + _wait = wait_range data.register_qubit( CoherenceType, (qubit), diff --git a/src/qibocal/protocols/coherence/utils.py b/src/qibocal/protocols/coherence/utils.py index 3b686b576..c96d0a563 100644 --- a/src/qibocal/protocols/coherence/utils.py +++ b/src/qibocal/protocols/coherence/utils.py @@ -30,43 +30,53 @@ def average_single_shots(data_type, single_shots): return data -def spin_echo_sequence(platform: Platform, targets: list[QubitId], wait: int = 0): - """Create pulse sequence for spin-echo routine. - - Spin Echo 3 Pulses: RX(pi/2) - wait t(rotates z) - RX(pi) - wait t(rotates z) - RX(pi/2) - readout +def dynamical_decoupling_sequence( + platform: Platform, + targets: list[QubitId], + wait: int = 0, + n: int = 1, + kind: str = "CPMG", +) -> tuple[PulseSequence, list[Delay]]: + """Create dynamical decoupling sequence. + + Two sequences are available: + - CP: RX90 (wait RX wait )^N RX90 + - CPMG: RX90 (wait RY wait )^N RX90 """ + + assert kind in ["CPMG", "CP"], f"Unknown sequence {kind}, please use CP or CPMG" sequence = PulseSequence() all_delays = [] for qubit in targets: natives = platform.natives.single_qubit[qubit] qd_channel, rx90_pulse = natives.R(theta=np.pi / 2)[0] - _, rx_pulse = natives.RX()[0] + _, pulse = natives.R(phi=np.pi / 2)[0] if kind == "CPMG" else natives.RX()[0] ro_channel, ro_pulse = natives.MZ()[0] - delays = [ - Delay(duration=wait), - Delay(duration=wait), - Delay(duration=wait), - Delay(duration=wait), - ] + drive_delays = 2 * n * [Delay(duration=wait)] + ro_delays = 2 * n * [Delay(duration=wait)] - sequence.extend( - [ - (qd_channel, rx90_pulse), - (qd_channel, delays[0]), - (qd_channel, rx_pulse), - (qd_channel, delays[1]), - (qd_channel, rx90_pulse), - ( - ro_channel, - Delay(duration=2 * rx90_pulse.duration + rx_pulse.duration), - ), - (ro_channel, delays[2]), - (ro_channel, delays[3]), - (ro_channel, ro_pulse), - ] + sequence.append((qd_channel, rx90_pulse)) + + for i in range(n): + sequence.append((qd_channel, drive_delays[2 * i])) + sequence.append((ro_channel, ro_delays[2 * i])) + sequence.append((qd_channel, pulse)) + sequence.append((qd_channel, drive_delays[2 * i + 1])) + sequence.append((ro_channel, ro_delays[2 * i + 1])) + + sequence.append((qd_channel, rx90_pulse)) + + sequence.append( + ( + ro_channel, + Delay(duration=2 * rx90_pulse.duration + n * pulse.duration), + ) ) - all_delays.extend(delays) + + sequence.append((ro_channel, ro_pulse)) + all_delays.extend(drive_delays) + all_delays.extend(ro_delays) return sequence, all_delays diff --git a/tests/runcards/protocols.yml b/tests/runcards/protocols.yml index e02fe071d..ddb5fa968 100644 --- a/tests/runcards/protocols.yml +++ b/tests/runcards/protocols.yml @@ -468,42 +468,41 @@ actions: delay_between_pulses_step: 1 nshots: 10 - - id: spin_echo unrolling + - id: spin_echo operation: spin_echo parameters: delay_between_pulses_start: 0 delay_between_pulses_end: 5 delay_between_pulses_step: 1 nshots: 10 - unrolling: true - - id: spin_echo_signal - operation: spin_echo_signal + + - id: CPMG + operation: cpmg parameters: delay_between_pulses_start: 0 - delay_between_pulses_end: 5 - delay_between_pulses_step: 1 + delay_between_pulses_end: 400 + delay_between_pulses_step: 10 nshots: 10 + n: 4 - - id: spin_echo_signal_single_shot - priority: 0 + - id: spin_echo_signal operation: spin_echo_signal parameters: delay_between_pulses_start: 0 delay_between_pulses_end: 5 delay_between_pulses_step: 1 - single_shot: True nshots: 10 - - id: spin_echo_signal unrolling + - id: spin_echo_signal_single_shot + priority: 0 operation: spin_echo_signal parameters: delay_between_pulses_start: 0 delay_between_pulses_end: 5 delay_between_pulses_step: 1 + single_shot: True nshots: 10 - unrolling: true - - id: flipping operation: flipping From db00d755cbcbf119e672aba9525ed43468673443 Mon Sep 17 00:00:00 2001 From: Andrea Date: Mon, 9 Dec 2024 20:45:26 +0400 Subject: [PATCH 2/4] feat: Adding documentation for CPMG --- doc/source/protocols/cpmg/cpmg.png | Bin 0 -> 31323 bytes doc/source/protocols/cpmg/cpmg.rst | 50 ++++++++++++++++++++++++ doc/source/protocols/index.rst | 1 + src/qibocal/protocols/coherence/cpmg.py | 2 +- 4 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 doc/source/protocols/cpmg/cpmg.png create mode 100644 doc/source/protocols/cpmg/cpmg.rst diff --git a/doc/source/protocols/cpmg/cpmg.png b/doc/source/protocols/cpmg/cpmg.png new file mode 100644 index 0000000000000000000000000000000000000000..eaf7c175af07f75725de3e3921002c6e26f32d19 GIT binary patch literal 31323 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 literal 0 HcmV?d00001 diff --git a/doc/source/protocols/cpmg/cpmg.rst b/doc/source/protocols/cpmg/cpmg.rst new file mode 100644 index 000000000..09d3c5750 --- /dev/null +++ b/doc/source/protocols/cpmg/cpmg.rst @@ -0,0 +1,50 @@ +CPMG sequence +============= + +In this section we show how to run the dynamical decoupling sequence CPMG. + +The CPMG sequence consists in applying N equally spaced :math:`\pi` pulses +within two :math:`\pi` pulses. By increasing the number of :math:`\pi` pulses :math:`T_2` +should increase until reaching the :math:`2 T_1` limit. + + +The fit is again a dumped exponential of the following form: + +.. math:: + + p_e(t) = A + B e^{ - t / T_2} + + +Parameters +^^^^^^^^^^ + +.. autoclass:: qibocal.protocols.coherence.cpmg.CpmgParameters + :noindex: + +Example +^^^^^^^ + +A possible runcard to launch a CPMG experiment could be the following: + +.. code-block:: yaml + + - id: CPMG + operation: cpmg + parameters: + delay_between_pulses_end: 100000 + delay_between_pulses_start: 4 + delay_between_pulses_step: 1000 + n: 10 + nshots: 1000 + +The expected output is the following: + +.. image:: cpmg.png + +:math:`T_2` is determined by fitting the output signal using +the formula presented above. + +Requirements +^^^^^^^^^^^^ + +- :ref:`single-shot` diff --git a/doc/source/protocols/index.rst b/doc/source/protocols/index.rst index 65d13f95d..95c5600a2 100644 --- a/doc/source/protocols/index.rst +++ b/doc/source/protocols/index.rst @@ -22,6 +22,7 @@ In this section we introduce the basics of all protocols supported by ``qibocal` t1/t1 t2/t2 t2_echo/t2_echo + cpmg/cpmg flux/single flux/crosstalk singleshot diff --git a/src/qibocal/protocols/coherence/cpmg.py b/src/qibocal/protocols/coherence/cpmg.py index 92ab29b38..e5407119f 100644 --- a/src/qibocal/protocols/coherence/cpmg.py +++ b/src/qibocal/protocols/coherence/cpmg.py @@ -151,7 +151,7 @@ def _plot(data: CpmgData, target: QubitId, fit: CpmgResults = None): fitting_report = table_html( table_dict( target, - ["T2 Spin Echo [ns]", "chi2 reduced"], + ["T2", "chi2 reduced"], [fit.t2_spin_echo[target], fit.chi2[target]], display_error=True, ) From 6fcf9d6a83b9f2c001aa5174f0e238bc24dfa870 Mon Sep 17 00:00:00 2001 From: Andrea Date: Thu, 12 Dec 2024 22:47:24 +0400 Subject: [PATCH 3/4] doc: Improve documentation --- doc/source/protocols/cpmg/cpmg.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/doc/source/protocols/cpmg/cpmg.rst b/doc/source/protocols/cpmg/cpmg.rst index 09d3c5750..bde28739c 100644 --- a/doc/source/protocols/cpmg/cpmg.rst +++ b/doc/source/protocols/cpmg/cpmg.rst @@ -4,15 +4,16 @@ CPMG sequence In this section we show how to run the dynamical decoupling sequence CPMG. The CPMG sequence consists in applying N equally spaced :math:`\pi` pulses -within two :math:`\pi` pulses. By increasing the number of :math:`\pi` pulses :math:`T_2` -should increase until reaching the :math:`2 T_1` limit. +within two :math:`\pi / 2` pulses. By increasing the number of :math:`\pi` pulses :math:`T_2` +should increase since the estimation is less sensitive to noises of the type :math:`1/f` +eventually reaching the :math:`2 T_1` limit. The fit is again a dumped exponential of the following form: .. math:: - p_e(t) = A + B e^{ - t / T_2} + p_e(t) = A + B e^{ - t / T^{(N)}_2} Parameters From 1a8f9f768d821523ba6e65f771d0cf55a5080931 Mon Sep 17 00:00:00 2001 From: Andrea Date: Thu, 12 Dec 2024 23:17:18 +0400 Subject: [PATCH 4/4] refactor: Reduce code duplication --- src/qibocal/protocols/coherence/cpmg.py | 88 +------------------ src/qibocal/protocols/coherence/spin_echo.py | 78 +--------------- .../protocols/coherence/spin_echo_signal.py | 6 +- src/qibocal/protocols/coherence/t1.py | 7 +- src/qibocal/protocols/coherence/t2.py | 79 +---------------- src/qibocal/protocols/coherence/utils.py | 75 +++++++++++++++- src/qibocal/protocols/coherence/zeno.py | 6 +- 7 files changed, 89 insertions(+), 250 deletions(-) diff --git a/src/qibocal/protocols/coherence/cpmg.py b/src/qibocal/protocols/coherence/cpmg.py index e5407119f..a81bb0458 100644 --- a/src/qibocal/protocols/coherence/cpmg.py +++ b/src/qibocal/protocols/coherence/cpmg.py @@ -1,18 +1,15 @@ -from dataclasses import dataclass, field -from typing import Optional +from dataclasses import dataclass import numpy as np -import plotly.graph_objects as go from qibolab import AcquisitionType, AveragingMode, Parameter, Sweeper from qibocal.auto.operation import QubitId, Routine from qibocal.calibration import CalibrationPlatform from qibocal.result import probability -from ..utils import table_dict, table_html from . import t1 -from .spin_echo import SpinEchoParameters, SpinEchoResults, _update -from .utils import dynamical_decoupling_sequence, exp_decay, exponential_fit_probability +from .spin_echo import SpinEchoParameters, SpinEchoResults +from .utils import dynamical_decoupling_sequence, exponential_fit_probability, plot @dataclass @@ -27,11 +24,6 @@ class CpmgParameters(SpinEchoParameters): class CpmgResults(SpinEchoResults): """SpinEcho outputs.""" - chi2: Optional[dict[QubitId, tuple[float, Optional[float]]]] = field( - default_factory=dict - ) - """Chi squared estimate mean value and error.""" - class CpmgData(t1.T1Data): """SpinEcho acquisition outputs.""" @@ -96,77 +88,5 @@ def _fit(data: CpmgData) -> CpmgResults: return CpmgResults(t2Echos, fitted_parameters, pcovs, chi2) -def _plot(data: CpmgData, target: QubitId, fit: CpmgResults = None): - """Plotting for Cpmg""" - - figures = [] - # iterate over multiple data folders - fitting_report = "" - - qubit_data = data[target] - waits = qubit_data.wait - probs = qubit_data.prob - error_bars = qubit_data.error - - 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: - # add fitting trace - waitrange = np.linspace( - min(waits), - max(waits), - 2 * len(qubit_data), - ) - params = fit.fitted_parameters[target] - - fig.add_trace( - go.Scatter( - x=waitrange, - y=exp_decay(waitrange, *params), - name="Fit", - line=go.scatter.Line(dash="dot"), - ), - ) - fitting_report = table_html( - table_dict( - target, - ["T2", "chi2 reduced"], - [fit.t2_spin_echo[target], fit.chi2[target]], - display_error=True, - ) - ) - - fig.update_layout( - showlegend=True, - xaxis_title="Time [ns]", - yaxis_title="Probability of State 1", - ) - - figures.append(fig) - - return figures, fitting_report - - -cpmg = Routine(_acquisition, _fit, _plot, _update) +cpmg = Routine(_acquisition, _fit, plot) """Cpmg Routine object.""" diff --git a/src/qibocal/protocols/coherence/spin_echo.py b/src/qibocal/protocols/coherence/spin_echo.py index e8c4f9d5c..acc12f8f4 100644 --- a/src/qibocal/protocols/coherence/spin_echo.py +++ b/src/qibocal/protocols/coherence/spin_echo.py @@ -2,17 +2,15 @@ from typing import Optional import numpy as np -import plotly.graph_objects as go from qibolab import AcquisitionType, AveragingMode, Parameter, Sweeper from qibocal.auto.operation import QubitId, Routine from qibocal.calibration import CalibrationPlatform from qibocal.result import probability -from ..utils import table_dict, table_html from . import t1 from .spin_echo_signal import SpinEchoSignalParameters, SpinEchoSignalResults, _update -from .utils import dynamical_decoupling_sequence, exp_decay, exponential_fit_probability +from .utils import dynamical_decoupling_sequence, exponential_fit_probability, plot @dataclass @@ -91,77 +89,5 @@ def _fit(data: SpinEchoData) -> SpinEchoResults: return SpinEchoResults(t2Echos, fitted_parameters, pcovs, chi2) -def _plot(data: SpinEchoData, target: QubitId, fit: SpinEchoResults = None): - """Plotting for SpinEcho""" - - figures = [] - # iterate over multiple data folders - fitting_report = "" - - qubit_data = data[target] - waits = qubit_data.wait - probs = qubit_data.prob - error_bars = qubit_data.error - - 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: - # add fitting trace - waitrange = np.linspace( - min(waits), - max(waits), - 2 * len(qubit_data), - ) - params = fit.fitted_parameters[target] - - fig.add_trace( - go.Scatter( - x=waitrange, - y=exp_decay(waitrange, *params), - name="Fit", - line=go.scatter.Line(dash="dot"), - ), - ) - fitting_report = table_html( - table_dict( - target, - ["T2 Spin Echo [ns]", "chi2 reduced"], - [fit.t2_spin_echo[target], fit.chi2[target]], - display_error=True, - ) - ) - - fig.update_layout( - showlegend=True, - xaxis_title="Time [ns]", - yaxis_title="Probability of State 1", - ) - - figures.append(fig) - - return figures, fitting_report - - -spin_echo = Routine(_acquisition, _fit, _plot, _update) +spin_echo = Routine(_acquisition, _fit, plot, _update) """SpinEcho Routine object.""" diff --git a/src/qibocal/protocols/coherence/spin_echo_signal.py b/src/qibocal/protocols/coherence/spin_echo_signal.py index 378fa0883..9be106488 100644 --- a/src/qibocal/protocols/coherence/spin_echo_signal.py +++ b/src/qibocal/protocols/coherence/spin_echo_signal.py @@ -37,7 +37,7 @@ class SpinEchoSignalParameters(Parameters): class SpinEchoSignalResults(Results): """SpinEchoSignal outputs.""" - t2_spin_echo: dict[QubitId, Union[float, list[float]]] + t2: dict[QubitId, Union[float, list[float]]] """T2 echo for each qubit.""" fitted_parameters: dict[QubitId, dict[str, float]] """Raw fitting output.""" @@ -160,7 +160,7 @@ def _plot(data: SpinEchoSignalData, target: QubitId, fit: SpinEchoSignalResults table_dict( target, ["T2 Spin Echo [ns]"], - [np.round(fit.t2_spin_echo[target])], + [np.round(fit.t2[target])], display_error=True, ) ) @@ -179,7 +179,7 @@ def _plot(data: SpinEchoSignalData, target: QubitId, fit: SpinEchoSignalResults def _update( results: SpinEchoSignalResults, platform: CalibrationPlatform, target: QubitId ): - update.t2_spin_echo(results.t2_spin_echo[target], platform, target) + update.t2_spin_echo(results.t2[target], platform, target) spin_echo_signal = Routine(_acquisition, _fit, _plot, _update) diff --git a/src/qibocal/protocols/coherence/t1.py b/src/qibocal/protocols/coherence/t1.py index 0db2a3a97..d6630a3aa 100644 --- a/src/qibocal/protocols/coherence/t1.py +++ b/src/qibocal/protocols/coherence/t1.py @@ -13,9 +13,6 @@ from ..utils import table_dict, table_html from . import t1_signal, utils -COLORBAND = "rgba(0,100,80,0.2)" -COLORBAND_LINE = "rgba(255,255,255,0)" - @dataclass class T1Parameters(t1_signal.T1SignalParameters): @@ -125,8 +122,8 @@ def _plot(data: T1Data, target: QubitId, fit: T1Results = None): 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), + fillcolor=utils.COLORBAND, + line=dict(color=utils.COLORBAND_LINE), showlegend=True, name="Errors", ), diff --git a/src/qibocal/protocols/coherence/t2.py b/src/qibocal/protocols/coherence/t2.py index 6a6d9d672..7d8111249 100644 --- a/src/qibocal/protocols/coherence/t2.py +++ b/src/qibocal/protocols/coherence/t2.py @@ -2,7 +2,6 @@ from typing import Optional import numpy as np -import plotly.graph_objects as go from qibolab import AcquisitionType, AveragingMode, Parameter, Sweeper from qibocal.auto.operation import QubitId, Routine @@ -10,7 +9,6 @@ from ...result import probability from ..ramsey.utils import ramsey_sequence -from ..utils import table_dict, table_html from . import t1, t2_signal, utils @@ -93,80 +91,5 @@ def _fit(data: T2Data) -> T2Results: return T2Results(t2s, fitted_parameters, pcovs, chi2) -def _plot(data: T2Data, target: QubitId, fit: T2Results = None): - """Plotting function for Ramsey Experiment.""" - - figures = [] - fitting_report = "" - qubit_data = data[target] - waits = qubit_data.wait - probs = qubit_data.prob - error_bars = qubit_data.error - - 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: - # add fitting trace - waitrange = np.linspace( - min(qubit_data.wait), - max(qubit_data.wait), - 2 * len(qubit_data), - ) - - params = fit.fitted_parameters[target] - 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( - target, - [ - "T2 [ns]", - "chi2 reduced", - ], - [fit.t2[target], fit.chi2[target]], - display_error=True, - ) - ) - fig.update_layout( - showlegend=True, - xaxis_title="Time [ns]", - yaxis_title="Probability of State 1", - ) - - figures.append(fig) - - return figures, fitting_report - - -t2 = Routine(_acquisition, _fit, _plot, t2_signal._update) +t2 = Routine(_acquisition, _fit, utils.plot, t2_signal._update) """T2 Routine object.""" diff --git a/src/qibocal/protocols/coherence/utils.py b/src/qibocal/protocols/coherence/utils.py index c96d0a563..418590fca 100644 --- a/src/qibocal/protocols/coherence/utils.py +++ b/src/qibocal/protocols/coherence/utils.py @@ -1,17 +1,21 @@ import numpy as np +import plotly.graph_objects as go from qibolab import Delay, Platform, PulseSequence from scipy.optimize import curve_fit from qibocal.auto.operation import QubitId from qibocal.config import log -from ..utils import chi2_reduced +from ..utils import chi2_reduced, table_dict, table_html CoherenceType = np.dtype( [("wait", np.float64), ("signal", np.float64), ("phase", np.float64)] ) """Custom dtype for coherence routines.""" +COLORBAND = "rgba(0,100,80,0.2)" +COLORBAND_LINE = "rgba(255,255,255,0)" + def average_single_shots(data_type, single_shots): """Convert single shot acquisition results of signal routines to averaged. @@ -192,3 +196,72 @@ def exponential_fit_probability(data, zeno=False): log.warning(f"Exponential decay fit failed for qubit {qubit} due to {e}") return decay, fitted_parameters, pcovs, chi2 + + +def plot(data, target: QubitId, fit=None) -> tuple[list[go.Figure], str]: + """Plotting function for spin-echo or CPMG protocol.""" + + figures = [] + fitting_report = "" + qubit_data = data[target] + waits = qubit_data.wait + probs = qubit_data.prob + error_bars = qubit_data.error + + 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: + waitrange = np.linspace( + min(waits), + max(waits), + 2 * len(qubit_data), + ) + params = fit.fitted_parameters[target] + + fig.add_trace( + go.Scatter( + x=waitrange, + y=exp_decay(waitrange, *params), + name="Fit", + line=go.scatter.Line(dash="dot"), + ), + ) + fitting_report = table_html( + table_dict( + target, + ["T2", "chi2 reduced"], + [fit.t2[target], fit.chi2[target]], + display_error=True, + ) + ) + + fig.update_layout( + showlegend=True, + xaxis_title="Time [ns]", + yaxis_title="Probability of State 1", + ) + + figures.append(fig) + + return figures, fitting_report diff --git a/src/qibocal/protocols/coherence/zeno.py b/src/qibocal/protocols/coherence/zeno.py index 2ed4d4fec..50b4e7eb4 100644 --- a/src/qibocal/protocols/coherence/zeno.py +++ b/src/qibocal/protocols/coherence/zeno.py @@ -10,7 +10,7 @@ from qibocal.calibration import CalibrationPlatform from ...result import probability -from ..utils import table_dict, table_html +from ..utils import COLORBAND, COLORBAND_LINE, table_dict, table_html from . import t1, utils @@ -150,8 +150,8 @@ def _plot(data: ZenoData, fit: ZenoResults, target: QubitId): 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), + fillcolor=COLORBAND, + line=dict(color=COLORBAND_LINE), showlegend=True, name="Errors", ),