Skip to content

fix(noise): honor GeminiNoiseModelABC scaling_factor in squin lowering#786

Merged
weinbe58 merged 6 commits into
mainfrom
worktree-honor-noise-scaling
Jul 10, 2026
Merged

fix(noise): honor GeminiNoiseModelABC scaling_factor in squin lowering#786
weinbe58 merged 6 commits into
mainfrom
worktree-honor-noise-scaling

Conversation

@weinbe58

@weinbe58 weinbe58 commented Jul 7, 2026

Copy link
Copy Markdown
Member

Summary

Fixes #785.

generate_simple_noise_model / generate_logical_noise_model read the raw *_px fields off the GeminiNoiseModelABC (e.g. noise_model.mover_px), which ignore the model's scaling_factor. The scaled values only live behind the getter properties (mover_pauli_rates, two_qubit_pauli, …). As a result, in the squin/stim path every value of scaling_factor produced identical noise — it worked at the cirq level but was silently a no-op downstream (see the discussion in QuEraComputing/bloqade-circuit#836).

Changes

  • Single-qubit Pauli rates now come from the scaled getters: mover_pauli_rates, sitter_pauli_rates, local_pauli_rates, global_pauli_rates, cz_unpaired_pauli_rates.
  • Correlated CZ probabilities come from two_qubit_pauli.error_probabilities (scaled), with .get(k, 0.0) so a rate that scales to zero (and gets dropped from the dict) no longer risks a KeyError.
  • Loss probabilities are left raw on purpose — upstream scaling_factor only scales Pauli rates, not loss. Per-category loss scaling is the genuinely-new axis tracked upstream in bloqade-circuit#836. Using the getters here also future-proofs lanes against that upstream work.
  • New python/tests/test_noise_scaling.py walks the generated squin kernel IR and asserts the baked-in constants reflect scaling_factor across all noise kernels, that loss stays raw, that scaling_factor=0 zeroes rates without KeyError, and (regression) that distinct scales now produce distinct constants.

Verification

  • test_noise_scaling.py + test_noise_init_generator.py: 37 passed
  • rewrite/move2squin/test_noise.py + test_device.py: 27 passed, 3 skipped
  • black / isort / ruff / pyright: clean

🤖 Generated with Claude Code

generate_simple_noise_model and generate_logical_noise_model read the raw
`*_px` fields off the noise model, which ignore `scaling_factor`. The scaled
values only live behind the getter properties, so in the squin/stim path every
value of `scaling_factor` produced identical noise.

Read the scaled getters instead (mover_pauli_rates, sitter_pauli_rates,
local_pauli_rates, global_pauli_rates, cz_unpaired_pauli_rates, and
two_qubit_pauli for the correlated CZ rates). The paired-rate lookup now
defaults missing keys to 0.0 so a rate that scales to zero can't raise a
KeyError. Loss probabilities are left raw on purpose: upstream scaling_factor
only scales Pauli rates, not loss (per-category loss scaling is tracked in
QuEraComputing/bloqade-circuit#836).

Add test_noise_scaling.py, which walks the generated squin kernel IR and
asserts the baked-in constants reflect scaling_factor for all noise kernels,
that loss stays raw, that scaling_factor=0 zeroes rates without KeyError, and
that distinct scales now produce distinct constants (the original bug).

Fixes #785

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 7, 2026 17:38
@weinbe58 weinbe58 added the backport v0.10 Backport to release-0-10 label Jul 7, 2026
@codecov

codecov Bot commented Jul 7, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.66667% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
python/bloqade/lanes/noise_model.py 91.66% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

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

Pull request overview

Fixes a squin/stim-lowering bug where GeminiNoiseModelABC.scaling_factor was effectively ignored by generate_simple_noise_model / generate_logical_noise_model, by switching from raw *_px fields to the scaled getter APIs for Pauli noise rates.

Changes:

  • Read single-qubit Pauli rates via *_pauli_rates getters so scaling_factor is honored in generated squin kernels.
  • Read correlated CZ Pauli probabilities from two_qubit_pauli.error_probabilities and default missing keys to 0.0 to avoid KeyError at scaling_factor=0.
  • Add a new test suite that inspects squin kernel IR constants to regression-test scaling behavior and ensure loss probabilities remain unscaled.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
python/bloqade/lanes/noise_model.py Switches noise parameter sourcing from raw fields to scaled getter APIs during squin kernel construction.
python/tests/test_noise_scaling.py Adds regression tests that walk squin kernel IR constants to verify scaling behavior and loss non-scaling.

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

Comment thread python/bloqade/lanes/noise_model.py Outdated
Comment thread python/tests/test_noise_scaling.py Outdated
Comment thread python/tests/test_noise_scaling.py Outdated
@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown

☂️ Code Coverage

current status: ✅

Overall Coverage

Statements Covered Coverage Threshold Status
11684 10038 86% 0% 🟢

New Files

No new covered files...

Modified Files

File Coverage Status
python/bloqade/lanes/noise_model.py 59% 🟢
TOTAL 59% 🟢

updated for commit: ea9f8d7 by action🐍

weinbe58 and others added 3 commits July 7, 2026 13:57
Use isinstance(stmt.value, PyAttr) / IList to access .data instead of relying
on Data[T], which newer pyright rejects (reportAttributeAccessIssue).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
unwrap() is the idiomatic kirin API for extracting the underlying value from a
Data attribute and is typed on the base class, so it needs no PyAttr/IList
narrowing and satisfies pyright.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Rename the second cz_unpaired_loss_prob assignment to cz_paired_loss_prob so
  the paired CZ gate loss no longer shadows the unpaired loss variable.
- Derive test expectations from the scaled getter APIs (*_pauli_rates,
  two_qubit_pauli.error_probabilities) instead of re-deriving raw * scale, so
  the tests validate that lanes honors the getters and stay valid if upstream
  changes how scaling is applied.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@weinbe58 weinbe58 requested a review from jasonhan3 July 7, 2026 20:30

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

In general, for local_loss_prob, global_loss_prob, cz_gate_loss_prob, cz_unpaired_loss_prob, move_loss_prob, sit_loss_prob, I don't know why you don't call the corresponding getters defined in MoveNoiseModelABC ? It's mentioned in the PR description that you don't but it's unclear to me why, as the issue here: QuEraComputing/bloqade-circuit#836 has a loss_error_scale parameter.

)

cz_unpaired_loss_prob = noise_model.cz_gate_loss_prob
cz_paired_loss_prob = noise_model.cz_gate_loss_prob

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 using cz_paired_errors on MoveNoiseModelABC if we want to use the properties instead of the direct field?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

That doesn't include the scale the API's are all messed up upstream

@jasonhan3 jasonhan3 Jul 8, 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.

Ah ok sounds good! So we'll probably update the downstream API's when the upstream ones are updated?

I guess, do we anticipate that the @Property API's here: https://github.com/QuEraComputing/bloqade-circuit/blob/06116fd450aaeefebb4769bc42290f60ece02671/src/bloqade/qasm2/dialects/noise/model.py would have the scale factor? If so we can proactively change it; if not then we can just leave it alone until upstream API's are changed

if loss:
groups = ilist.map(pair_qubit, ilist.range(len(controls)))
squin.broadcast.correlated_qubit_loss(cz_unpaired_loss_prob, groups)
squin.broadcast.correlated_qubit_loss(cz_paired_loss_prob, groups)

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 comment; shouldn't this be using cz_paired_errors on MoveNoiseModelABC if we want to use the properties instead of the direct field?

@weinbe58

weinbe58 commented Jul 8, 2026

Copy link
Copy Markdown
Member Author

Note on scope: this PR only scales the Pauli rates, not the loss probabilities. That's a deliberate limitation of the current upstream API — GeminiNoiseModelABC.scaling_factor (and its getter properties) only apply the scale factor to the Pauli rates; there is no getter that returns loss probabilities with the scale factor applied. So lanes has no way to obtain scaled loss values without re-implementing the scaling itself, which would drift from the upstream source of truth.

Per-category loss scaling (loss_error_scale) is the genuinely-new axis tracked upstream in QuEraComputing/bloqade-circuit#836. Once that lands and exposes a scaled-loss getter, we can switch the loss reads here over to it.

@weinbe58 weinbe58 removed the backport v0.10 Backport to release-0-10 label Jul 10, 2026

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

LGTM!

@weinbe58 weinbe58 enabled auto-merge (squash) July 10, 2026 19:15
@weinbe58 weinbe58 merged commit 76d0666 into main Jul 10, 2026
17 of 19 checks passed
@weinbe58 weinbe58 deleted the worktree-honor-noise-scaling branch July 10, 2026 19:25
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.

Make sure upstream scale factor in GeminiNoiseModelABC is honored

3 participants