Skip to content

Commit

Permalink
Merge pull request #970 from qiboteam/ramsey_zz
Browse files Browse the repository at this point in the history
Ramsey ZZ protocol
  • Loading branch information
Edoardo-Pedicillo authored Oct 22, 2024
2 parents 35c53bd + edb558f commit 317131c
Show file tree
Hide file tree
Showing 6 changed files with 372 additions and 1 deletion.
38 changes: 38 additions & 0 deletions doc/source/protocols/ramsey/ramsey.rst
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,44 @@ Example

Note that in this case error bars will not be provided.


Measuring the ZZ coupling with a Ramsey experiment
---------------------------------------------------

By repeating the Ramsey experiment while putting one of the neighbor qubits in
state :math:`\ket{1}` we can have an estimate on the ZZ coupling :math:`\zeta`.
The ZZ coupling :math:`\zeta` is a residual interaction which leads to shifts in
the frequency of the qubit when one of the neighbor is in the excited state.
Given that through a Ramsey experiment we can measure carefully the frequency of the qubit,
by comparing the outcome of a standard Ramsey experiment with the outcome when one of the neighbor
qubit is excited we can infer the ZZ coupling term :math:`\zeta`. For superconducting platforms
without tunable couplers such terms is expected to be of the order of a few hundred kHz.

Parameters
^^^^^^^^^^

.. autoclass:: qibocal.protocols.ramsey.ramsey_zz.RamseyZZParameters
:noindex:


Example
^^^^^^^

.. code-block:: yaml
- id: ramsey zz
operation: ramsey_zz
parameters:
delay_between_pulses_end: 2000
delay_between_pulses_start: 10
delay_between_pulses_step: 50
detuning: 500000
nshots: 1024
target_qubit: D1
.. image:: ramsey_zz.png

Requirements
^^^^^^^^^^^^

Expand Down
Binary file added doc/source/protocols/ramsey/ramsey_zz.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions src/qibocal/protocols/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
from .rabi.length_signal import rabi_length_signal
from .ramsey.ramsey import ramsey
from .ramsey.ramsey_signal import ramsey_signal
from .ramsey.ramsey_zz import ramsey_zz
from .randomized_benchmarking.filtered_rb import filtered_rb
from .randomized_benchmarking.standard_rb import standard_rb
from .randomized_benchmarking.standard_rb_2q import standard_rb_2q
Expand Down Expand Up @@ -148,4 +149,5 @@
"standard_rb_2q",
"standard_rb_2q_inter",
"optimize_two_qubit_gate",
"ramsey_zz",
]
303 changes: 303 additions & 0 deletions src/qibocal/protocols/ramsey/ramsey_zz.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,303 @@
from dataclasses import dataclass, field, fields
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 qibolab.sweeper import Parameter, Sweeper, SweeperType

from ...auto.operation import Routine
from ...config import log
from ..utils import table_dict, table_html
from .ramsey import (
COLORBAND,
COLORBAND_LINE,
RamseySignalData,
RamseySignalParameters,
RamseySignalResults,
RamseyType,
_update,
)
from .utils import fitting, process_fit, ramsey_fit, ramsey_sequence


@dataclass
class RamseyZZParameters(RamseySignalParameters):
"""RamseyZZ runcard inputs."""

target_qubit: Optional[QubitId] = None
"""Target qubit that will be excited."""


@dataclass
class RamseyZZResults(RamseySignalResults):
"""RamseyZZ outputs."""

def __contains__(self, qubit: QubitId):
# TODO: to be improved
return all(
list(getattr(self, field.name))[0][0] == qubit
for field in fields(self)
if isinstance(getattr(self, field.name), dict)
)


@dataclass
class RamseyZZData(RamseySignalData):
"""RamseyZZ acquisition outputs."""

target_qubit: Optional[QubitId] = None
"""Qubit that will be excited."""
data: dict[tuple[QubitId, str], npt.NDArray[RamseyType]] = field(
default_factory=dict
)
"""Raw data acquired."""


def _acquisition(
params: RamseyZZParameters,
platform: Platform,
targets: list[QubitId],
) -> RamseyZZData:
"""Data acquisition for RamseyZZ Experiment.
Standard Ramsey experiment repeated twice.
In the second execution one qubit is brought to the excited state.
"""

waits = np.arange(
params.delay_between_pulses_start,
params.delay_between_pulses_end,
params.delay_between_pulses_step,
)

options = ExecutionParameters(
nshots=params.nshots,
relaxation_time=params.relaxation_time,
acquisition_type=AcquisitionType.DISCRIMINATION,
averaging_mode=AveragingMode.SINGLESHOT,
)

data = RamseyZZData(
detuning=params.detuning,
qubit_freqs={
qubit: platform.qubits[qubit].native_gates.RX.frequency for qubit in targets
},
target_qubit=params.target_qubit,
)

for setup in ["I", "X"]:
if not params.unrolling:
sequence = PulseSequence()
for qubit in targets:
sequence += ramsey_sequence(
platform=platform,
qubit=qubit,
detuning=params.detuning,
target_qubit=params.target_qubit if setup == "X" else None,
)

sweeper = Sweeper(
Parameter.start,
waits,
[sequence.get_qubit_pulses(qubit).qd_pulses[-1] for qubit in targets],
type=SweeperType.ABSOLUTE,
)

# execute the sweep
results = platform.sweep(
sequence,
options,
sweeper,
)

for qubit in targets:
probs = results[qubit].probability(state=1)
errors = [np.sqrt(prob * (1 - prob) / params.nshots) for prob in probs]

else:
sequences, all_ro_pulses = [], []
probs, errors = [], []
for wait in waits:
sequence = PulseSequence()
for qubit in targets:
sequence += ramsey_sequence(
platform=platform,
qubit=qubit,
wait=wait,
detuning=params.detuning,
target_qubit=params.target_qubit if setup == "X" else None,
)

sequences.append(sequence)
all_ro_pulses.append(sequence.ro_pulses)

results = platform.execute_pulse_sequences(sequences, options)

for wait, ro_pulses in zip(waits, all_ro_pulses):
for qubit in targets:
prob = results[ro_pulses[qubit].serial][0].probability(state=1)
probs.append(prob)
errors.append(np.sqrt(prob * (1 - prob) / params.nshots))

for qubit in targets:
data.register_qubit(
RamseyType,
(qubit, setup),
dict(
wait=waits,
prob=probs,
errors=errors,
),
)

return data


def _fit(data: RamseyZZData) -> RamseyZZResults:
"""Fitting procedure for RamseyZZ protocol.
Standard Ramsey fitting procedure is applied for both version of
the experiment.
"""
waits = data.waits
popts = {}
freq_measure = {}
t2_measure = {}
delta_phys_measure = {}
delta_fitting_measure = {}
for qubit in data.qubits:
for setup in ["I", "X"]:
qubit_data = data[qubit, setup]
qubit_freq = data.qubit_freqs[qubit]
probs = qubit_data["prob"]
try:
popt, perr = fitting(waits, probs, qubit_data.errors)
(
freq_measure[qubit, setup],
t2_measure[qubit, setup],
delta_phys_measure[qubit, setup],
delta_fitting_measure[qubit, setup],
popts[qubit, setup],
) = process_fit(popt, perr, qubit_freq, data.detuning)
except Exception as e:
log.warning(f"Ramsey fitting failed for qubit {qubit} due to {e}.")
return RamseyZZResults(
detuning=data.detuning,
frequency=freq_measure,
t2=t2_measure,
delta_phys=delta_phys_measure,
delta_fitting=delta_fitting_measure,
fitted_parameters=popts,
)


def _plot(data: RamseyZZData, target: QubitId, fit: RamseyZZResults = None):
"""Plotting function for Ramsey Experiment."""

figures = []
fitting_report = ""

waits = data[target, "I"].wait
probs_I = data.data[target, "I"].prob
probs_X = data.data[target, "X"].prob

error_bars_I = data.data[target, "I"].errors
error_bars_X = data.data[target, "X"].errors
fig = go.Figure(
[
go.Scatter(
x=waits,
y=probs_I,
opacity=1,
name="I",
showlegend=True,
legendgroup="I ",
mode="lines",
),
go.Scatter(
x=np.concatenate((waits, waits[::-1])),
y=np.concatenate(
(probs_I + error_bars_I, (probs_I - error_bars_I)[::-1])
),
fill="toself",
fillcolor=COLORBAND,
line=dict(color=COLORBAND_LINE),
showlegend=True,
name="Errors I",
),
go.Scatter(
x=waits,
y=probs_X,
opacity=1,
name="X",
showlegend=True,
legendgroup="X",
mode="lines",
),
go.Scatter(
x=np.concatenate((waits, waits[::-1])),
y=np.concatenate(
(probs_X + error_bars_X, (probs_X - error_bars_X)[::-1])
),
fill="toself",
fillcolor=COLORBAND,
line=dict(color=COLORBAND_LINE),
showlegend=True,
name="Errors X",
),
]
)

if fit is not None:
fig.add_trace(
go.Scatter(
x=waits,
y=ramsey_fit(waits, *fit.fitted_parameters[target, "I"]),
name="Fit I",
line=go.scatter.Line(dash="dot"),
)
)

fig.add_trace(
go.Scatter(
x=waits,
y=ramsey_fit(waits, *fit.fitted_parameters[target, "X"]),
name="Fit X",
line=go.scatter.Line(dash="dot"),
)
)
fitting_report = table_html(
table_dict(
data.target_qubit,
[
"ZZ [kHz]",
],
[
np.round(
(fit.frequency[target, "X"][0] - fit.frequency[target, "I"][0])
* 1e-3,
0,
),
],
)
)

fig.update_layout(
showlegend=True,
xaxis_title="Time [ns]",
yaxis_title="Excited state probability",
)

figures.append(fig)

return figures, fitting_report


ramsey_zz = Routine(_acquisition, _fit, _plot, _update)
"""Ramsey Routine object."""
6 changes: 5 additions & 1 deletion src/qibocal/protocols/ramsey/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ def ramsey_sequence(
platform: Platform,
qubit: QubitId,
wait: int = 0,
detuning: Optional[int] = None,
detuning: Optional[int] = 0,
target_qubit: Optional[QubitId] = None,
):
"""Pulse sequence used in Ramsey (detuned) experiments.
Expand All @@ -49,6 +50,9 @@ def ramsey_sequence(
)

sequence.add(first_pi_half_pulse, second_pi_half_pulse, readout_pulse)
if target_qubit is not None:
x_pulse_target_qubit = platform.create_RX_pulse(target_qubit, start=0)
sequence.add(x_pulse_target_qubit)
return sequence


Expand Down
Loading

0 comments on commit 317131c

Please sign in to comment.