Skip to content

Restrict mixer calibration to target qubits' channels - #1581

Open
jevillegasd wants to merge 6 commits into
mainfrom
mixer_calibration_per_qubit
Open

Restrict mixer calibration to target qubits' channels#1581
jevillegasd wants to merge 6 commits into
mainfrom
mixer_calibration_per_qubit

Conversation

@jevillegasd

@jevillegasd jevillegasd commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Previously calibrate_mixers configured and calibrated every channel in the platform regardless of targets. Now only the drive/probe channels of the requested qubits are touched (all targets together, to preserve intermodulation products), the report table is split per qubit, and offset_i/offset_q updates propagate to sibling channels sharing a frequency-multiplexed readout port, since those get recalibrated in hardware too.

To do this I patch the _channels_by_module method from qibolab.instruments.qblox.cluster to keep only the channels associated to the target qubit(s).

Previously calibrate_mixers configured and calibrated every channel in the platform regardless of `targets`. Now only the drive/probe channels of the requested qubits are touched (all targets together, to preserve
intermodulation products), the report table is split per qubit, and offset_i/offset_q updates propagate to sibling channels sharing a frequency-multiplexed readout port, since those get recalibrated in
hardware too.
@jevillegasd
jevillegasd requested review from a team July 2, 2026 09:12
@codecov

codecov Bot commented Jul 2, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 0% with 61 lines in your changes missing coverage. Please review.
✅ Project coverage is 91.54%. Comparing base (85a7b6f) to head (8dea4d5).
⚠️ Report is 295 commits behind head on main.

Files with missing lines Patch % Lines
src/qibocal/protocols/calibrate_mixers.py 0.00% 61 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #1581      +/-   ##
==========================================
- Coverage   91.84%   91.54%   -0.30%     
==========================================
  Files         147      147              
  Lines       11635    11679      +44     
==========================================
+ Hits        10686    10692       +6     
- Misses        949      987      +38     
Flag Coverage Δ
unittests 91.54% <0.00%> (-0.30%) ⬇️

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

Files with missing lines Coverage Δ
src/qibocal/protocols/calibrate_mixers.py 1.89% <0.00%> (-0.50%) ⬇️

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

@jevillegasd

Copy link
Copy Markdown
Contributor Author

How I handle the update is a bit dummy running first in a reduced version of the hardware (only the target channels) and later in the full cluster to update offsets (and make sure all mixers that share an LO get the same offset update).

@RoyStegeman

Copy link
Copy Markdown
Member

Hi @jevillegasd, to for me to understand, what is the main motivation for this? Is it the time saved or something else?

@jevillegasd

Copy link
Copy Markdown
Contributor Author

Hi @jevillegasd, to for me to understand, what is the main motivation for this? Is it the time saved or something else?

Hey @RoyStegeman , you may recall we talked about needing this when you have more pulses than sequencers. This may happen for example if in a platform we defined the ge, ef, and cr pulses, for two qubits, this already exhausts the available sequencers, even if they are not used simultaneously in a practical application. The mixer calibration would fail in such cases because it loads by default all existing sequencers.

Additionally, when you re-calibrate the mixer in practice you need to re-calibrate your pulse amplitudes and drag coefficients, so so it does make sense to have some level of control of which ports are being re calibrated.

@RoyStegeman
RoyStegeman self-requested a review August 19, 2026 10:52
sequencer.sideband_cal()


def _target_channels(platform: CalibrationPlatform, targets: list[str]) -> set[str]:

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
def _target_channels(platform: CalibrationPlatform, targets: list[str]) -> set[str]:
def _target_channels(platform: CalibrationPlatform, targets: list[QubitId]) -> set[ChannelId]:

Using these type aliases improves readability. I point out just here, but of course it should be done for all instances where there is now str that can be a more specific type alias.



def _acquisition(
params: CalibrateMixersParameters,

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
params: CalibrateMixersParameters,
_params: CalibrateMixersParameters,

to indicate it's not consumed

Comment thread src/qibocal/protocols/calibrate_mixers.py Outdated
Comment on lines +233 to +236
``Cluster.configure`` always configures every channel in
``Cluster._channels_by_module`` (the whole platform, by default), so the map is
narrowed temporarily and restored right after, to avoid touching unrelated modules
or leaking the restriction into later experiments.

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.

Please mention that this handling is needed because Cluster._channels_by_module is a cached property.

Also mention that try is there not because you expect it to fail, but just to be absolutely certain that if it does then the previous cluster._channels_by_module is still restored. Or if there is some other reason, explain that one.

target_channels = _target_channels(platform, [target])
mapping[target] = sorted(
{
cluster._modules[slot].short_name

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.

cluster._modules is not cached so you're generating the same dict it for each target, it can be moved out of the loop.

# round2
# offset_i/offset_q are per physical port, shared by every qubit multiplexed on it
# Calibrating one recalibrates all of them in hardware, so propagate the update to
# update to every channel on a sahred calibrated port, not just the 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.

Suggested change
# update to every channel on a sahred calibrated port, not just the targets.
# update to every channel on a shared calibrated port, not just the targets.

cal = final_cal[mod_name]
for ch_id, seq_id in channels.items():
ch = channel_mixer(ch_id)
port = PortAddress.from_path(cluster.channels[ch_id].path).ports[0] - 1

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
port = PortAddress.from_path(cluster.channels[ch_id].path).ports[0] - 1
port = PortAddress.from_path(ch.path).ports[0] - 1

Comment on lines +539 to +542
for slot, channels in results.sequencer_map.items():
mod_name = cluster._modules[slot].short_name
if mod_name not in final_cal:
continue # Skip if no calibration data for this module

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.

This is duplicated in the second round. The mod_name handling can be done only once

data: Acquisition data

Returns:
Empty results

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.

These are not empty

Comment thread src/qibocal/protocols/calibrate_mixers.py

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

Restricts Qblox mixer calibration and reporting to channels associated with requested qubits.

Changes:

  • Filters configured channels by target.
  • Produces per-target module reports.
  • Propagates port-level offsets to multiplexed sibling channels.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/qibocal/protocols/calibrate_mixers.py
Comment thread src/qibocal/protocols/calibrate_mixers.py
Comment thread src/qibocal/protocols/calibrate_mixers.py Outdated
jevillegasd and others added 5 commits September 4, 2026 11:53
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Roy Stegeman <roystegeman@live.nl>
Co-authored-by: Roy Stegeman <roystegeman@live.nl>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
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