Skip to content

Adding stay alive method for sticky elements (TWPA pumps) within shot loops#135

Merged
JacobFastQM merged 2 commits into
mainfrom
123-keep-sticky-element-alive
Jun 29, 2026
Merged

Adding stay alive method for sticky elements (TWPA pumps) within shot loops#135
JacobFastQM merged 2 commits into
mainfrom
123-keep-sticky-element-alive

Conversation

@JacobFastQM

Copy link
Copy Markdown
Contributor

Pull Request

Description

When building QUA programs using quam_builder's FluxTunableQuam and initialize_qpu, TWPA pump elements configured as sticky CW tones required being included in at least one align() statement inside every shot loop to maintain their output. Without this, the TWPA pump stops pinging after its initial sticky window, causing total signal loss at the readout and silently yielding all-zero measurement outcomes. This PR adds an additional method to FluxTunableQuam (twpa_keepalive) which injects aligns of the twpa pump with any active qubits to keep the twpa element alive without forcing an align with all inactive qubits.

Type of Change

  • 🐛 Bug fix (non-breaking change which fixes an issue)
  • ✨ New feature (non-breaking change which adds functionality)
  • 💥 Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • 📚 Documentation update
  • 🔧 Refactoring (no functional changes)
  • ✅ Test addition/update
  • 🎨 Code style/formatting update

Related Issues

Closes #123

Checklist

Code Quality

  • My code follows the project's style guidelines
  • I have run pre-commit hooks and all checks pass (pre-commit run --all-files)
  • My commits follow the Conventional Commits format
  • I have performed a self-review of my code
  • My changes generate no new warnings

Testing

  • I have added tests that prove my fix/feature works
  • All new and existing tests pass locally (pytest)
  • I have tested my changes with the example scripts (if applicable)

Documentation

  • I have updated the documentation (if needed)
  • I have added docstrings to new functions/classes
  • I have updated the CHANGELOG.md (if applicable)
  • I have checked my code and corrected any misspellings

… alive during shot loops

Modified docstring for initialize_qpu to note the need for twpa_keepalive

(#123)
@JacobFastQM

Copy link
Copy Markdown
Contributor Author

@arthurostrauss Im hesitant to introduce program-scope awareness in quam_builder. It would be implicit behavior under the hood that is difficult to debug for customers, and I prefer a requirement for an explicit call to twpa_keepalive. However, I am also totally open to refactoring if you think that would be a better approach!

@arthurostrauss

Copy link
Copy Markdown
Contributor

I see in the comments that it mentions explicitly the shot loop. Could we have a test with nested loops in QUA for different sweeps (like frequency or other) to test if the align needs only to be in the most inner loop or all of them?

Moreover,while I agree in principle that the explicit approach is the most straightforward way to make it happen, this raises a challenge for the most anticipated gate level use cases.

As QuAM builder is growing and its specs become harder to track for gate level users (who will grow, especially at IQCC), keeping in mind what to do beyond handling QUA control flow and automated circuit to QUA compilation within the program will become increasingly difficult to manage. There is probably no easy middle ground in bare QUA or QUAM as we know it, but it would be good at least to ensure that this new method you added is enough after the circuit execution and does not need to be added anywhere else for the sticky element to stay alive.

Let me know what you think.

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

I think that is a legit approach, but it adds overhead that may keep piling up on complexity for each iteration of new custom components like this that may occur in other qubits configurations, we need to start thinking about a better encapsulation strategy for enabling QUA to handle more abstract layers.

Comment thread quam_builder/architecture/superconducting/qpu/base_quam.py Outdated
Comment thread quam_builder/architecture/superconducting/qpu/base_quam.py Outdated
Comment thread quam_builder/architecture/superconducting/qpu/base_quam.py Outdated
Comment thread quam_builder/architecture/superconducting/qpu/flux_tunable_quam.py Outdated
Comment thread tests/test_twpa_keepalive.py
@JacobFastQM

Copy link
Copy Markdown
Contributor Author

I see in the comments that it mentions explicitly the shot loop. Could we have a test with nested loops in QUA for different sweeps (like frequency or other) to test if the align needs only to be in the most inner loop or all of them?

I ran a few more tests on CS_4. Using this program:

def make_program(keepalive_inner: bool, keepalive_outer: bool):
    with program() as p:
        update_frequency("twpa_pump", int(PUMP_IF))
        play("pump", "twpa_pump")
        align()  # one-off global align after init
        f = declare(int)
        n = declare(int)
        f_if = declare(int)
        with for_(f, 0, f < N_FREQS, f + 1):
            assign(f_if, 80_000_000 + f * 1_000_000)
            update_frequency("qxy", f_if)
            if keepalive_outer:
                align("twpa_pump", "qxy", "qres") # what twpa_keepalive emits
            with for_(n, 0, n < N_AVG, n + 1):
                if keepalive_inner:
                    align("twpa_pump", "qxy", "qres") # what twpa_keepalive emits
                play("x180", "qxy")
                align("qres", "qxy")
                play("readout", "qres")
                wait(SHOT_WAIT // 4, "qres")
    return p
twpa_keepalive_nested

It actually doesn't seem to matter where the align occurs in the program, outer or inner loop.

In addition, this program:

def make_program(mode: str):
    with program() as p:
        update_frequency("twpa_pump", int(PUMP_IF))
        play("pump", "twpa_pump")
        align()
        n = declare(int)
        take_branch = declare(bool)
        with for_(n, 0, n < N_SHOTS, n + 1):
            if mode == "before_if":
                align("twpa_pump", "qxy", "qres") # what twpa_keepalive emits
            assign(take_branch, ~take_branch)  # alternates: branch runs every other shot
            with if_(take_branch):
                if mode == "inside_if":
                    align("twpa_pump", "qxy", "qres") # what twpa_keepalive emits
                play("x180", "qxy")
            play("readout", "qres")
            wait(SHOT_WAIT // 4, "qres")
    return p
twpa_keepalive_conditional

shows that it works regardless of conditionals.

The bottom line being that the pump stays on as long as twpa_keepalive is reached anywhere inside the outermost loop and is robust to nesting level/conditionals.

@JacobFastQM

Copy link
Copy Markdown
Contributor Author

Moreover,while I agree in principle that the explicit approach is the most straightforward way to make it happen, this raises a challenge for the most anticipated gate level use cases.

As QuAM builder is growing and its specs become harder to track for gate level users (who will grow, especially at IQCC), keeping in mind what to do beyond handling QUA control flow and automated circuit to QUA compilation within the program will become increasingly difficult to manage. There is probably no easy middle ground in bare QUA or QUAM as we know it, but it would be good at least to ensure that this new method you added is enough after the circuit execution and does not need to be added anywhere else for the sticky element to stay alive.

Let me know what you think.

I hadn't considered gate level use and I do agree this doesn't scale there as a manual call. I see two ways we can move forward for scaling:

  1. we merge this PR with twpa_keepalive as the low-level primitive, and, in a follow-up (essentially your "direction 3" from the original issue) introduce
  • a registry of components that declare themselves "always-on," behind one BaseQuam.keep_sticky_alive(), so new sticky components don't each add their own method;
  • auto-injection of that call once per shot by whatever generates the shot loop so gate-level users never need to write it.
  1. We introduce those new features in this PR for gate level users. However, since, it needs more design work (registry shape, where injection lives, other things I'll need to figure out), the original bug fix stays unmerged longer.

Also, I hate that github doesn't allow comment chains unless its attached to a code block :(

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

I'm okay with the proposed approach 1. Let's feature this in already so IQCC can propose a consistent way of addressing this issue.
It would be great to follow up with the next PR soon so that gate level users don't have to manually adapt their script in future evolutions of the quam builder

@JacobFastQM JacobFastQM merged commit 79a5df4 into main Jun 29, 2026
5 checks passed
@JacobFastQM JacobFastQM deleted the 123-keep-sticky-element-alive branch June 29, 2026 15:44
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.

[Bug]: Sticky elements (TWPA pumps) must be included in align() within shot loops — incompatible with qubit-local macro abstraction

2 participants