Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding CPMG protocol #1057

Open
wants to merge 4 commits into
base: 0.2
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added doc/source/protocols/cpmg/cpmg.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
51 changes: 51 additions & 0 deletions doc/source/protocols/cpmg/cpmg.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
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 / 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^{(N)}_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`
1 change: 1 addition & 0 deletions doc/source/protocols/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions src/qibocal/protocols/__init__.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -104,4 +105,5 @@
"standard_rb_2q_inter",
"optimize_two_qubit_gate",
"ramsey_zz",
"cpmg",
]
92 changes: 92 additions & 0 deletions src/qibocal/protocols/coherence/cpmg.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
from dataclasses import dataclass

import numpy as np
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 . import t1
from .spin_echo import SpinEchoParameters, SpinEchoResults
from .utils import dynamical_decoupling_sequence, exponential_fit_probability, plot


@dataclass
class CpmgParameters(SpinEchoParameters):
"""Cpmg runcard inputs."""

n: int = 1
"""Number of pi rotations."""


@dataclass
class CpmgResults(SpinEchoResults):
"""SpinEcho outputs."""


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)


cpmg = Routine(_acquisition, _fit, plot)
"""Cpmg Routine object."""
88 changes: 7 additions & 81 deletions src/qibocal/protocols/coherence/spin_echo.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 exp_decay, exponential_fit_probability, spin_echo_sequence
from .utils import dynamical_decoupling_sequence, exponential_fit_probability, plot


@dataclass
Expand Down Expand Up @@ -41,19 +39,19 @@ 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,
)

sweeper = Sweeper(
parameter=Parameter.duration,
values=ro_wait_range,
values=wait_range / 2,
pulses=delays,
)

Expand All @@ -70,13 +68,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,
),
Expand All @@ -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 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:
# 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 0",
)

figures.append(fig)

return figures, fitting_report


spin_echo = Routine(_acquisition, _fit, _plot, _update)
spin_echo = Routine(_acquisition, _fit, plot, _update)
"""SpinEcho Routine object."""
25 changes: 14 additions & 11 deletions src/qibocal/protocols/coherence/spin_echo_signal.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -26,15 +31,13 @@ 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
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."""
Expand All @@ -53,19 +56,19 @@ 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,
)

sweeper = Sweeper(
parameter=Parameter.duration,
values=ro_wait_range,
values=wait_range / 2,
pulses=delays,
)

Expand All @@ -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),
Expand Down Expand Up @@ -157,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,
)
)
Expand All @@ -176,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)
Expand Down
Loading
Loading