Skip to content

Improve flipping sequence generation - #1507

Merged
sorewachigauyo merged 1 commit into
mainfrom
optimize-flipping
Jun 3, 2026
Merged

Improve flipping sequence generation#1507
sorewachigauyo merged 1 commit into
mainfrom
optimize-flipping

Conversation

@sorewachigauyo

Copy link
Copy Markdown
Contributor

While testing #1504, I noticed that the flipping sequence generation was a bit slow especially with the default settings.

I tried to speed it up by moving the detuned pulse generation out of the loop and using list.extend instead of multiple list.append calls. Not sure if there is a problem for the other instruments if the same pulse object is reused.

QPU test to check if flipping still works
flipping.tar.gz

Small perf test and script

New flipping sequence generation completed in 2.848101232200861s
New flipping sequence generation completed in 2.8209174927324057s
New flipping sequence generation completed in 2.805138450115919s
New flipping sequence generation completed in 2.8024905808269978s
New flipping sequence generation completed in 2.812884099781513s
Previous flipping sequence generation completed in 7.275865795090795s
Previous flipping sequence generation completed in 7.24155573733151s
Previous flipping sequence generation completed in 7.248768800869584s
Previous flipping sequence generation completed in 7.233881413936615s
Previous flipping sequence generation completed in 7.333734508603811s
import time


import numpy as np
from qibocal import update
from qibocal.auto.operation import QubitId
from qibocal.calibration import CalibrationPlatform, create_calibration_platform
from qibolab import PulseSequence


def flipping_sequence_new(
    platform: CalibrationPlatform,
    qubit: QubitId,
    delta_amplitude: float,
    flips: int,
    rx90: bool,
):
    """Pulse sequence for flipping experiment."""

    natives = platform.natives.single_qubit[qubit]
    sequence = natives.R(theta=np.pi / 2)

    if rx90:
        qd_channel, qd_pulse = natives.RX90()[0]
    else:
        qd_channel, qd_pulse = natives.RX()[0]

    qd_detuned = update.replace(
        qd_pulse, amplitude=qd_pulse.amplitude + delta_amplitude
    )
    sequence.extend([(qd_channel, qd_detuned)] * (flips * (4 if rx90 else 2)))
    sequence |= natives.MZ()

    return sequence

def flipping_sequence(
    platform: CalibrationPlatform,
    qubit: QubitId,
    delta_amplitude: float,
    flips: int,
    rx90: bool,
):
    """Pulse sequence for flipping experiment."""

    sequence = PulseSequence()
    natives = platform.natives.single_qubit[qubit]

    sequence |= natives.R(theta=np.pi / 2)

    for _ in range(flips):
        if rx90:
            qd_channel, qd_pulse = natives.RX90()[0]
        else:
            qd_channel, qd_pulse = natives.RX()[0]

        qd_detuned = update.replace(
            qd_pulse, amplitude=qd_pulse.amplitude + delta_amplitude
        )
        sequence.append((qd_channel, qd_detuned))
        sequence.append((qd_channel, qd_detuned))

        if rx90:
            sequence.append((qd_channel, qd_detuned))
            sequence.append((qd_channel, qd_detuned))

    sequence |= natives.MZ()

    return sequence

flips_range = range(0, 21, 1)
delta_amplitude_range = np.arange(-0.05,0.05,0.001)
platform = create_calibration_platform("sinq20")


for k in range(5):
    start = time.perf_counter()
    for qb in range(20):
        for flips in flips_range:
            for delta_amp in delta_amplitude_range:
                flipping_sequence_new(platform, qb, delta_amp, flips, False)
    end = time.perf_counter()
    print(f"New flipping sequence generation completed in {end-start}s")


for k in range(5):
    start = time.perf_counter()
    for qb in range(20):
        for flips in flips_range:
            for delta_amp in delta_amplitude_range:
                flipping_sequence(platform, qb, delta_amp, flips, False)
    end = time.perf_counter()
    print(f"Previous flipping sequence generation completed in {end-start}s")

@sorewachigauyo
sorewachigauyo requested review from a team May 19, 2026 09:18
@codecov

codecov Bot commented May 19, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 83.33333% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 94.36%. Comparing base (5d3dcb7) to head (6e267a6).

Files with missing lines Patch % Lines
src/qibocal/protocols/flipping.py 83.33% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #1507      +/-   ##
==========================================
- Coverage   94.41%   94.36%   -0.06%     
==========================================
  Files         136      136              
  Lines       10659    10653       -6     
==========================================
- Hits        10064    10053      -11     
- Misses        595      600       +5     
Flag Coverage Δ
unittests 94.36% <83.33%> (-0.06%) ⬇️

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

Files with missing lines Coverage Δ
src/qibocal/protocols/flipping.py 93.66% <83.33%> (+1.09%) ⬆️

... and 4 files 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.

qd_detuned = update.replace(
qd_pulse, amplitude=qd_pulse.amplitude + delta_amplitude
)
sequence.extend([(qd_channel, qd_detuned)] * (flips * (4 if rx90 else 2)))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't think this will be a big issue because in flipping we are not sweeping over no pulse nor channel, so even though the pulses have the same UUID this should in principle still work. However if you do a list comprehension maybe you can achieve still a speedup but this time you'll avoid duplicating the same pulse N times, since UUID will be different everytime.

Suggested change
sequence.extend([(qd_channel, qd_detuned)] * (flips * (4 if rx90 else 2)))
sequence.extend([(qd_channel, qd_detuned) for _ in range(flips * (4 if rx90 else 2))])

@sorewachigauyo sorewachigauyo May 19, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I increased the max flips to 50 just to exaggerate the example
Proposed method (avg): 6.909229274839163s
List comprehension (avg): 7.0412845250219105s

Also, both methods preserve the UUID since its just using the same object as a reference

natives = platform.natives.single_qubit[0]
qd_channel, qd_pulse = natives.RX()[0]
qd_detuned = update.replace(
    qd_pulse, amplitude=qd_pulse.amplitude + 0.05
)
subseq = [(qd_channel, qd_detuned) for _ in range(2)]
print(subseq[0][1].id == subseq[1][1].id)

subseq = [(qd_channel, qd_detuned)] * 2
print(subseq[0][1].id == subseq[1][1].id)
True
True

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ah yeah you're right, actually I was wrong, either you call the replace function in the list comprehension or maybe we can use model_copy method, maybe even new method of _PulseLike class.

@lballerio lballerio left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This PR is no more linked to #1508, for which we need to recycle the same pulse for the entire experiment;
this function is then used only for the flipping, which does not sweep over any pulse, hence we don't have memory problems and the number of identical pulses is no more a constraint.
If for you is fine to use even here the same pulse for different qubits and for the entire experiment, then this PR is fine to merge for me.

@sorewachigauyo
sorewachigauyo added this pull request to the merge queue Jun 3, 2026
Merged via the queue into main with commit 73806ae Jun 3, 2026
38 of 39 checks passed
@sorewachigauyo
sorewachigauyo deleted the optimize-flipping branch June 3, 2026 06:51
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.

3 participants