Skip to content

Simplify single shot protocols#1278

Open
andrea-pasquale wants to merge 22 commits into
mainfrom
refactor-ssr
Open

Simplify single shot protocols#1278
andrea-pasquale wants to merge 22 commits into
mainfrom
refactor-ssr

Conversation

@andrea-pasquale

@andrea-pasquale andrea-pasquale commented Oct 30, 2025

Copy link
Copy Markdown
Contributor

Closes #1241
In this PR I'm planning to simplify the code related to the single experiments.
Some modifications include:

  • Use LDA to compute angle threshold
  • Drop outdated parameters related to ML classifiers
  • Simplify plot using directly plotly
  • Add confusion matrix
  • Propagate changes to qutrit classification
  • Plot qutrit fit
  • Update readout optimization protocols

@andrea-pasquale andrea-pasquale added this to the Protocols milestone Oct 30, 2025
@andrea-pasquale andrea-pasquale self-assigned this Oct 30, 2025
@alecandido alecandido changed the title Simply single shot protocols Simplify single shot protocols Oct 30, 2025
@codecov

codecov Bot commented Oct 31, 2025

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.75%. Comparing base (ed74278) to head (77c7329).

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #1278      +/-   ##
==========================================
- Coverage   96.86%   96.75%   -0.11%     
==========================================
  Files         133      131       -2     
  Lines       10457    10139     -318     
==========================================
- Hits        10129     9810     -319     
- Misses        328      329       +1     
Flag Coverage Δ
unittests 96.75% <100.00%> (-0.11%) ⬇️

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

Files with missing lines Coverage Δ
src/qibocal/cli/report.py 100.00% <100.00%> (ø)
...qibocal/protocols/classification/classification.py 100.00% <100.00%> (+5.45%) ⬆️
.../protocols/classification/qutrit_classification.py 100.00% <100.00%> (ø)
src/qibocal/protocols/classification/utils.py 100.00% <100.00%> (ø)
...cols/qubit_spectroscopies/qubit_spectroscopy_ef.py 100.00% <100.00%> (ø)
src/qibocal/protocols/rabi/ef.py 100.00% <ø> (ø)
...ocal/protocols/readout/readout_characterization.py 100.00% <100.00%> (ø)
...qibocal/protocols/readout_optimization/__init__.py 100.00% <100.00%> (ø)
...ibocal/protocols/readout_optimization/amplitude.py 100.00% <100.00%> (ø)
...ibocal/protocols/readout_optimization/frequency.py 100.00% <100.00%> (ø)
... and 2 more

... and 5 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.

@alecandido alecandido linked an issue Oct 31, 2025 that may be closed by this pull request
@andrea-pasquale andrea-pasquale marked this pull request as ready for review November 4, 2025 11:40
@andrea-pasquale

andrea-pasquale commented Nov 4, 2025

Copy link
Copy Markdown
Contributor Author

I believe that this should be ready to review now.
Here is how the readout optimization protocols are looking now: http://login.qrccluster.com:9000/dvp18iWdQSOweRyNVWh63g==/
Here is the "new" single shot and readout characterization: http://login.qrccluster.com:9000/AsPZh9W8QeWQEAxUbaTSCA==/

In the last report you can see how by lowering the readout amplitude we get better QND reducing the excitations to state 2.

@jevillegasdatTII

Copy link
Copy Markdown
Contributor

Is it possible that some of the data is mislabeled in the readout characterization?

newplot (2)

Here the distribution of the last measurement of state |0> is almost identical to the first one of state |1> and very differnt form the first measurement. I get the same with other qubits in the same QPU

newplot (3)

@alecandido

alecandido commented Jan 13, 2026

Copy link
Copy Markdown
Member

Is it possible that some of the data is mislabeled in the readout characterization?

Everything is possible :)

Here the distribution of the last measurement of state |0> is almost identical to the first one of state |1> and very differnt form the first measurement.

I agree, it would make sense that if you say you are measuring the same thing, they should be in the same position.

@lballerio will certainly take a look into this (as soon as he's done with some of the other open PRs 🙄)

jevillegasd and others added 2 commits January 14, 2026 11:21
    Add average states markers in ssro plots and change the scaling so that the clusters are more visible.
@jevillegasdatTII

jevillegasdatTII commented Jan 14, 2026

Copy link
Copy Markdown
Contributor

The plots in the SSRO stretch ugly with the report window; in #1334 I added an anchor to the x axis that helps the plot be "prettier" and added the average state markers.

This:
newplot (6)

Instead of:
newplot (7)

@jevillegasdatTII

Copy link
Copy Markdown
Contributor

In the amplitude optimization the swept value of amplitude appears to be relative to the amplitude in the platform and not absolute. For example running between 0.05 and 1 I get thius

newplot (15)

But SSRO with pulse amplitude = 0.95 (best ro amplitude) returns:
newplot (17)
Assignment Fidelity | 0.767

While SSRO with pulse amplitde = 0.15 returns
newplot (16)
Assignment Fidelity | 0.966

Clearly very different to those computed in the amplitude optimization plot. I am only guessing the swept value is re;lative and not absolute because if I set the amplitude to 1 and run the sweep, I get
newplot (18)
I checked the qibocal implementation and it does look like is sweeping the amplitude parameter correctly (in fact I don't know if relative sweepers for amplitude are still supported)

@lballerio

Copy link
Copy Markdown
Contributor

Hi @jevillegasdatTII, did you open an issue?

	modified:   src/qibocal/protocols/classification/classification.py

@jevillegasd jevillegasd 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.

im ok with the changes as they are and it can be worked around the smaller fixes commented (sweeps being realitve) afterwards.

@alecandido

Copy link
Copy Markdown
Member

@lballerio is about to double-check this with QPU 118, as soon as he gets suitable frequencies for the qubits.

Whenever he confirms as well, we can merge

@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.

Here is my review about this PR...some comments are docs/ minor fixes, but I think some comments, especially about 1->2 transition experiments are more relevant.
However I briefly tested the experiments and they seem to work, but I would not merge now the PR.

Comment thread src/qibocal/cli/report.py
html_list = []
for figure in figures:
figure.write_html(buffer, include_plotlyjs=False, full_html=False)
figure.write_html(buffer, include_plotlyjs="cdn", full_html=False)

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 want to link write_html help function and focus on the discussion about include_plotlyjs parameter:

write_html_api_reference

Long story short, this configuration of include_plotlyjs requires an active internet connection (except if cached).
Is it what we want?

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.

Yes, that's reasonable. Otherwise, each report would be just 3 MB larger, only because of repeated content. Most of the time, you also need to be connected to the internet to retrieve these reports.

The only other interesting option would be directory, and self-host the JavaScript package.
This is fine, but requires more effort on our side. Also because of potential upgrades and compatibility concerns.

Until this becomes explicitly a problem, I would keep cdn.

Comment on lines 30 to 32
ROC_LENGHT = 800
ROC_WIDTH = 800
DEFAULT_CLASSIFIER = "qubit_fit"

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.

ROC_LENGHT, ROC_WIDTH and DEFAULT_CLASSIFIER are not used in this script nor in other files.

Suggested change
ROC_LENGHT = 800
ROC_WIDTH = 800
DEFAULT_CLASSIFIER = "qubit_fit"

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.

They were, before this PR.

Since now they are not any longer used, it makes perfect sense to remove them. Good catch :)

Comment thread src/qibocal/protocols/classification/classification.py
Comment thread src/qibocal/protocols/classification/classification.py
Comment thread src/qibocal/protocols/classification/classification.py
Comment on lines +132 to +135
platform.channels[platform.qubits[q].drive].lo: {
"frequency": platform.config(platform.qubits[q].drive).frequency
+ params.lo_offset * GHZ_TO_HZ
},

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.

same as previous comment, here we are moving LO frequencies, don't we have to recalibrate mixers then?

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.

Yes, we should: much better to drop this shift, and keep it manual (for the time being).

If strongly needed, we could automate this process later on, when we will have stabilized the mixer calibration API.

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 think here the file name and the protocol name is misleading.
I think ef.py is quite meaningless, and also I realized that this is a signal experiment, not a probability, but we export it as rabi_amplitude_ef, which is inconsistent with all other protocols where we can measure both in probability and signel+phase.

Additionally, I was wondering if it is worth to put rabi and rabi_ef all together in a single protocol and use a keyword to trigger either 0->1 or 1->2 transitions since the experiments are the same and rabi_ef code often recall rabi classes and fuctions.
Also might be worth to implement all Rabi experiments even for 1->2 transition (?)
The bottleneck might be that is quite boring and refactoring the code is time consuming.

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 think ef.py is quite meaningless, and also I realized that this is a signal experiment, not a probability, but we export it as rabi_amplitude_ef, which is inconsistent with all other protocols where we can measure both in probability and signel+phase.

Agreed. We could call the file amplitude_signal_ef.py and the protocol rabi_amplitude_signal_ef.

Additionally, I was wondering if it is worth to put rabi and rabi_ef all together in a single protocol and use a keyword to trigger either 0->1 or 1->2 transitions since the experiments are the same and rabi_ef code often recall rabi classes and fuctions.
Also might be worth to implement all Rabi experiments even for 1->2 transition (?)
The bottleneck might be that is quite boring and refactoring the code is time consuming.

It could be worth, to deduplicate code. But let's not do it in this PR. You could open an issue about it.

Comment on lines +118 to +119
signal=result.T[0],
phase=result.T[1],

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.

are we sure results are signal phase and magnitude and not I-Q components?

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.

Actually, since they are coming directly from Qibolab, they are exactly IQ pairs.

This is a plain bug

Comment on lines 91 to 92
qubit: float(platform.calibration.single_qubits[qubit].qubit.frequency_01)
for qubit in targets

@lballerio lballerio Feb 27, 2026

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.

shouldn't this be the drive frequency instead?

this is what I was thinking about:

Suggested change
qubit: float(platform.calibration.single_qubits[qubit].qubit.frequency_01)
for qubit in targets
qubit: float(platform.config(platform.qubits[qubit].drive).frequency)
for qubit in targets

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.

Fine

Comment on lines +226 to +229
# # mask values where fidelity is below 80%
# averaged_qnd[grids["fidelity"] < 0.8] = np.nan
# # exclude values where QND is larger than 1
# averaged_qnd[averaged_qnd > 1] = np.nan

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.

indeed, in my last calibration with qpus 118 and 156 I had problems with the old implementation of this protocol since all fidelities where <80%, hence all the data were np.nan and the experiment failed. I think this mask is useless, or at least we can decrease the threshold to 50%.

Suggested change
# # mask values where fidelity is below 80%
# averaged_qnd[grids["fidelity"] < 0.8] = np.nan
# # exclude values where QND is larger than 1
# averaged_qnd[averaged_qnd > 1] = np.nan

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.

No, I would not put it to 50%. I agree it is just useless.

If we want to consider taking a different point, we can engineer a suitable cost function (not necessarily linear).
Same thing for the plots: if we want to avoid losing details in the high-fidelity patch, we could even change the colorscale with a non-linear rescaling. Though, it is possibly not even needed.

@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.

I mostly read @lballerio's comments, and the related code. However, I also had a quick look to some of the other changes.

All in all, conditional to the result to be functioning, we do not need a detailed review of all the code changes. Which are massive, mostly because of a huge refactoring (but you could assume the old code to be messy and unreliable - no need to spend ages navigating the diff).

However, as @lballerio already pointed out, there is at least one outright bug (if I'm not missing anything myself). And I would implement the small improvements he suggested, postponing the larger changes (possibly opening issues, if relevant).

@alecandido

Copy link
Copy Markdown
Member

Btw, once the review is done, the PR needs to be rebased. Though it should be straightforward, since GitHub is not reporting any conflict

@alecandido alecandido linked an issue Apr 1, 2026 that may be closed by this pull request
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

5 participants