Skip to content

Rabi Refactor - #1561

Open
lballerio wants to merge 22 commits into
mainfrom
rabi_refactor
Open

Rabi Refactor#1561
lballerio wants to merge 22 commits into
mainfrom
rabi_refactor

Conversation

@lballerio

@lballerio lballerio commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

This PR makes the following changes:

  1. fix the Rabi documentation after the refactor/renaming
  2. overall refactor of rabi folder:
    a. utils.py has now been splitted in 3 modules: acquisition.py, which contains all rabi sequence functions, processing.py, which contains functions for plotting and fitting and parent_classes.py, which defines the common Data, Parameters and Results classes.
    b. all rabi experiments refactored accordingly
  3. introduced a range-like convention for input parameters.
  4. In all Rabi experiments was inserted drive_lines optional input, a list of qubits which indicates the list of physical lines to use for each qubit; also some quality checks of the input are performed:
    a. if defined, drive_lines and targets must have the same length
    b. the must not be repeated elements in the union of drive_lines and targets.
  5. Addition of the microwave_crosstalk_matrix field in calibration.py with also a validator for Calibration class and get ad set function for this matrix.
  6. Every rabi_amplitude experiment now also saves in microwave_crosstalk_matrix the fitted amplitude from the signal.
  7. In Calibration class now is also saved the fitted amplitude of non-crosstalk Rabi experiment for each qubit; this is used later to update microwave_crosstalk_matrix.
  8. deleted flux crosstalk matrix update in update.py and propagated this change in flux_dependence folder.
  9. introduction of microwave_cancellation_phase.py module for calibrating mw crosstalk phase.
  10. Propagated the changes in tests folder.

@lballerio

Copy link
Copy Markdown
Contributor Author

check #1108 for crosstalk calibration references.

@lballerio
lballerio force-pushed the rabi_refactor branch 2 times, most recently from 20bc18e to 4a9e81a Compare June 30, 2026 14:49
@lballerio
lballerio marked this pull request as ready for review June 30, 2026 14:49
@lballerio
lballerio requested review from a team June 30, 2026 14:49
@codecov

codecov Bot commented Jun 30, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 82.62108% with 122 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.39%. Comparing base (d624dec) to head (2659d50).
⚠️ Report is 15 commits behind head on main.

Files with missing lines Patch % Lines
.../qibocal/protocols/microwave_cancellation_phase.py 33.33% 82 Missing ⚠️
src/qibocal/protocols/rabi/acquisition.py 82.08% 12 Missing ⚠️
src/qibocal/protocols/rabi/processing.py 79.06% 9 Missing ⚠️
src/qibocal/protocols/rabi/parent_classes.py 93.16% 8 Missing ⚠️
src/qibocal/protocols/rabi/ef.py 87.09% 4 Missing ⚠️
src/qibocal/calibration/calibration.py 93.18% 3 Missing ⚠️
src/qibocal/calibration/serialize.py 81.25% 3 Missing ⚠️
...bocal/protocols/flux_dependence/qubit_crosstalk.py 94.73% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #1561      +/-   ##
==========================================
- Coverage   91.41%   90.39%   -1.02%     
==========================================
  Files         148      151       +3     
  Lines       11844    12049     +205     
==========================================
+ Hits        10827    10892      +65     
- Misses       1017     1157     +140     
Flag Coverage Δ
unittests 90.39% <82.62%> (-1.02%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/qibocal/protocols/__init__.py 94.54% <100.00%> (+0.31%) ⬆️
...tocols/flux_dependence/flux_amplitude_frequency.py 100.00% <100.00%> (ø)
...protocols/flux_dependence/qubit_flux_dependence.py 97.52% <100.00%> (ø)
...ocols/flux_dependence/resonator_flux_dependence.py 97.56% <100.00%> (ø)
src/qibocal/protocols/rabi/amplitude.py 96.36% <100.00%> (-0.52%) ⬇️
src/qibocal/protocols/rabi/amplitude_frequency.py 98.00% <100.00%> (+0.04%) ⬆️
...bocal/protocols/rabi/amplitude_frequency_signal.py 94.17% <100.00%> (-4.28%) ⬇️
src/qibocal/protocols/rabi/amplitude_signal.py 96.61% <100.00%> (-0.96%) ⬇️
src/qibocal/protocols/rabi/length.py 96.61% <100.00%> (-0.83%) ⬇️
src/qibocal/protocols/rabi/length_frequency.py 98.01% <100.00%> (-0.02%) ⬇️
... and 12 more

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors the Rabi protocol implementations to support driving a target qubit using a different physical drive line, while also consolidating shared Rabi data/processing/acquisition logic and modernizing calibration crosstalk handling.

Changes:

  • Add drive_lines support across Rabi parameter models and acquisition routines to enable cross-drive (target ≠ drive line) experiments.
  • Split and reuse common Rabi logic via new parent_classes.py, acquisition.py, and processing.py, updating protocol modules accordingly.
  • Replace raw crosstalk matrices with a Matrix model (flux + microwave) and update related protocols/tests to use the new API.

Reviewed changes

Copilot reviewed 24 out of 24 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
tests/test_protocols.py Updates tests to new Rabi data class names used by extract_rabi.
tests/test_calibration.py Adapts serialization tests to the new Matrix-backed crosstalk matrices and new Calibration(...) constructor usage.
tests/calibration_scripts/rx_calibration.py Removes chi2 / small-correction guards in the calibration script.
src/qibocal/update.py Removes the legacy crosstalk_matrix updater helper (call sites updated elsewhere).
src/qibocal/protocols/utils.py Tightens typing for frequency/period estimation helpers.
src/qibocal/protocols/rabi/utils.py Refactors/retains shared Rabi utilities; adds typing and sequence helpers (now partially duplicated with new acquisition module).
src/qibocal/protocols/rabi/processing.py New shared post-processing module (fit functions, plotting, update logic including microwave crosstalk update).
src/qibocal/protocols/rabi/parent_classes.py New shared dataclasses for Rabi parameters/results/data, including drive_lines plumbing.
src/qibocal/protocols/rabi/length.py Migrates length-classification protocol to shared acquisition/processing and drive_lines support.
src/qibocal/protocols/rabi/length_signal.py Migrates length-signal protocol to shared acquisition/processing and drive_lines support.
src/qibocal/protocols/rabi/length_frequency.py Migrates length+frequency classification chevron to shared acquisition/processing and drive_lines support.
src/qibocal/protocols/rabi/length_frequency_signal.py Migrates length+frequency signal chevron to shared acquisition/processing and drive_lines support.
src/qibocal/protocols/rabi/ef.py Refactors EF Rabi to new shared parameter/data types and adds cross-drive support.
src/qibocal/protocols/rabi/amplitude.py Migrates amplitude-classification protocol to shared acquisition/processing and drive_lines support.
src/qibocal/protocols/rabi/amplitude_signal.py Migrates amplitude-signal protocol to shared acquisition/processing and drive_lines support.
src/qibocal/protocols/rabi/amplitude_frequency.py Migrates amplitude+frequency classification chevron to shared acquisition/processing and drive_lines support.
src/qibocal/protocols/rabi/amplitude_frequency_signal.py Migrates amplitude+frequency signal chevron to shared acquisition/processing and drive_lines support.
src/qibocal/protocols/rabi/acquisition.py New shared acquisition module implementing drive-line validation and sequence building.
src/qibocal/protocols/flux_dependence/resonator_flux_dependence.py Switches to new flux_crosstalk_matrix[...] access API.
src/qibocal/protocols/flux_dependence/qubit_flux_dependence.py Updates flux crosstalk update path to the new matrix API.
src/qibocal/protocols/flux_dependence/qubit_crosstalk.py Updates acquisition/update logic to the new flux crosstalk matrix API.
src/qibocal/protocols/flux_dependence/flux_amplitude_frequency.py Updates crosstalk update path to the new matrix API.
src/qibocal/calibration/serialize.py Makes deserializers accept already-materialized matrix/ndarray inputs.
src/qibocal/calibration/calibration.py Introduces Matrix model and adds flux_crosstalk_matrix + microwave_crosstalk_matrix with validation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/qibocal/protocols/rabi/ef.py
Comment thread src/qibocal/protocols/rabi/ef.py
Comment thread src/qibocal/protocols/rabi/ef.py
Comment thread src/qibocal/protocols/rabi/parent_classes.py
Comment thread src/qibocal/protocols/utils.py
Comment thread src/qibocal/calibration/calibration.py
Comment thread src/qibocal/protocols/rabi/utils.py Outdated
Comment thread src/qibocal/protocols/rabi/utils.py Outdated
@lballerio
lballerio requested a review from alecandido July 1, 2026 07:39
@lballerio
lballerio marked this pull request as draft July 1, 2026 07:43
@lballerio
lballerio marked this pull request as ready for review July 1, 2026 07:43
@RoyStegeman
RoyStegeman requested a review from Copilot July 1, 2026 07:46

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 24 changed files in this pull request and generated 4 comments.

Comment on lines +107 to +109
data = RabiAmplitudeFreqClassificationData(
drive_lines=drive_lines, durations=durations, rx90=params.rx90
)
Comment on lines +109 to +111
data = RabiAmplitudeFreqSignalData(
drive_lines=drive_lines, durations=durations, rx90=params.rx90
)
Comment thread src/qibocal/calibration/calibration.py Outdated
Comment on lines +218 to +219
if self.flux_crosstalk_matrix.matrix.shape[0] != self.nqubits:
raise ValueError("Drive crosstalk matrix must have as many rows as qubits.")
Comment thread src/qibocal/calibration/calibration.py Outdated
Comment thread src/qibocal/protocols/rabi/parent_classes.py Outdated

@alecandido alecandido left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the time being, just about serialization

Comment thread src/qibocal/calibration/calibration.py Outdated
Comment thread src/qibocal/calibration/calibration.py Outdated
Comment thread src/qibocal/calibration/calibration.py Outdated
Comment thread src/qibocal/calibration/calibration.py
Comment thread src/qibocal/calibration/serialize.py Outdated
@lballerio
lballerio marked this pull request as draft July 16, 2026 14:21
@lballerio
lballerio marked this pull request as ready for review July 17, 2026 14:24
@lballerio
lballerio marked this pull request as draft July 21, 2026 14:16
@lballerio
lballerio marked this pull request as ready for review July 21, 2026 14:22
@lballerio
lballerio marked this pull request as draft July 21, 2026 15:06
@lballerio
lballerio marked this pull request as ready for review July 22, 2026 06:02

@alecandido alecandido left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I'm kind at half of it. But this PR is definitely way too big, let me cut the first review here.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I understand the point, but parent_classes.py sounds a bit inaccurate.

On the generic side, structures.py could be a candidate. But data.py is also an option - though it can be slightly misleading, since we have *Data classes.
Or even just classes.py would be more accurate on its own (some of the classes are directly used).

:noindex:

We must note that all Rabi protocols takes as an optional input also `drive_lines`, which indicates the lines to use for each qubit.
If this parameter is set it has to be of the same length of `targets` list and there must not be repeated elements in these two lists, otherwise an error is raised.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not so clear. I would recommend adding an example of a good parameters' choice, and a bad one.

"""T2 hanh echo [ns]."""
rb_fidelity: Measure | None = None
"""Standard rb pulse fidelity."""
rabi_ampl_oscillation: dict[Literal["signal", "classification"], float] = Field(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could give a name to this Literal[...] type, since we may use in multiple places.

An alternative option could be using a StrEnum. But, until we want to use the Enum to simplify some kind of consumption (iterating it, dynamically checking if it is part of the set, and similar) a type alias of the Literal is more than enough.
Let's avoid overcomplicating things, until we have a use case (which lifts the "over" part).

Comment on lines +187 to +188
if self.flux_crosstalk_matrix is None or self.flux_crosstalk_matrix.size == 0:
self.flux_crosstalk_matrix = np.eye(self.nqubits)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure whether it is a good idea, but in principle you do not need this to be part of a custom validator, and you do not need None as a placeholder default.
It looks like default-setting callables in Pydantic are allowed to depend on previous fields https://pydantic.dev/docs/validation/latest/concepts/fields/#default-values

Thus, you could replace this with:

    flux_crosstalk_matrix: NdArray = Field(default=lambda data: np.eye(len(data["single_qubits"])))

Comment on lines +199 to +209
If the matrix is missing or empty, initialize it as a complex-valued
matrix of shape (nqubits, nqubits) filled with infinity. If provided,
verify the matrix shape is correct and convert its dtype to complex.
"""
if (
self.microwave_crosstalk_matrix is None
or self.microwave_crosstalk_matrix.size == 0
):
self.microwave_crosstalk_matrix = np.full(
(self.nqubits, self.nqubits), np.inf, dtype=complex
)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand why you want to initialize it with infinity.

We are not even measuring it in decibels - which is fine, since floats can already do a great job at representing exponents.
Thus, why not np.zeros()?

Comment on lines +10 to +11
pulse_duration_range: tuple[float, float, float] | None = None
"""Pulse duration [ns] range."""

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pulse_duration_range: tuple[float, float, float] | None = None
"""Pulse duration [ns] range."""
duration: tuple[float, float, float] | None = None
"""Pulse duration [ns] range."""

This is already unambiguous.

Comment on lines +50 to +51
ampl_range: tuple[float, float, float] | None = None
"""Pulse minimum amplitude [a.u] range."""

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above:

Suggested change
ampl_range: tuple[float, float, float] | None = None
"""Pulse minimum amplitude [a.u] range."""
amplitude: tuple[float, float, float] | None = None
"""Pulse minimum amplitude [a.u] range."""

Comment on lines +84 to +85
freq_range: tuple[int, int, int] | None = None
"""Frequency range as an offset."""

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
freq_range: tuple[int, int, int] | None = None
"""Frequency range as an offset."""
frequency: tuple[int, int, int] | None = None
"""Frequency range as an offset."""

Comment on lines +113 to +114
freq_range: tuple[int, int, int] | None = None
"""Frequency range as an offset."""

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
freq_range: tuple[int, int, int] | None = None
"""Frequency range as an offset."""
frequency: tuple[int, int, int] | None = None
"""Frequency range as an offset."""

Comment on lines +109 to +111
@dataclass
class RabiAmplitudeFrequencyParameters(RabiAmplitudeParameters):
"""RabiChevronAmplitude runcard inputs."""

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure how well does it work with @dataclass, but usually, to reduce this kind of duplication, one uses mixins:

@dataclass
class FrequencyMixin:
    freq_range: tuple[int, int, int] | None = None
    """Frequency range as an offset."""
    min_freq: int | None = None
    """Minimum frequency as an offset."""
    max_freq: int | None = None
    """Maximum frequency as an offset."""
    step_freq: int | None = None
    """Frequency to use as step for the scan."""

    @property
    def frequency_range(self) -> tuple[float, float, float]:
        """
        Return a tuple with the duration times of the pulses.
        """
        if self.freq_range is None:
            return (self.min_freq, self.max_freq, self.step_freq)
        return self.freq_range

    def __post_init__(self):
        super().__post_init__()

        if any([f is None for f in self.frequency_range]):
            raise ValueError("Valid frequency offset range not inserted.")

class RabiLengthFrequencyParameters(RabiLengthParameters, FrequencyMixin):
    """RabiChevronLength runcard inputs."""

class RabiAmplitudeFrequencyParameters(RabiAmplitudeParameters, FrequencyMixin):
    """RabiChevronAmplitude runcard inputs."""

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Use CYCLIC instead of SINGLESHOT for Rabi

4 participants