Skip to content

feat(physical): add MultiCandidateLookaheadPlacementStrategy#660

Open
WonJoon-Yun wants to merge 3 commits into
QuEraComputing:mainfrom
WonJoon-Yun:feat-beam-safety-net
Open

feat(physical): add MultiCandidateLookaheadPlacementStrategy#660
WonJoon-Yun wants to merge 3 commits into
QuEraComputing:mainfrom
WonJoon-Yun:feat-beam-safety-net

Conversation

@WonJoon-Yun

@WonJoon-Yun WonJoon-Yun commented May 22, 2026

Copy link
Copy Markdown
Contributor

feat(physical): MultiCandidateLookaheadPlacementStrategy — multi-candidate beam-search placement

Abstract

Neutral-atom quantum compilers route qubits between zones for each CZ layer; atom movement is the dominant non-local cost. Existing placement strategies in bloqade-lanes — NoReturn (NR), NoHome, RecedingHorizon (RH) from PR #595, plus the base PhysicalPlacementStrategy family (rust_greedy, rust_entropy_*, rust_ids) — commit to one placement layout per stage (informed by a 4-5 stage Hungarian or rollout horizon) and advance. None of them preserves multiple candidate layouts across stages.

We propose MultiCandidateLookaheadPlacementStrategy (MCL) that preserves W = 8 candidate layout trajectories across the lookahead window. Per trajectory, per stage, MCL generates K = 20 Hungarian-perturbed candidates plus the NoReturn result, scores each by score = cum + cost + λ · next_stage_cost, and keeps the top-W. The committed first-stage layout is taken from the trajectory with the lowest cumulative cost.

Evaluation on 8 standard QASM benchmark families (n = 8 qubits) vs 6 existing bloqade-lanes strategies, with per-stage CZ-pair validity checked: among strategies that complete a valid schedule on all 8 families, MCL has the lowest aggregate lane count (354 lanes, vs NR's 437). RH fails mid-schedule on 4 of 8 families.


1. Introduction

1.1 Existing strategies: what they share

bloqade-lanes currently offers six placement strategies:

Strategy Source Per-stage commit Lookahead Multi-candidate?
NoReturn (NR) PR #595 1 layout Hungarian-horizon = 4 layers No
NoHome PR #595 1 layout Hungarian-horizon = 4 layers No
RecedingHorizon (RH) PR #595 3 layers (from best rollout) K=5 rollouts × x=5 layers Evaluated, not preserved
rust_greedy base 1 layout none No
rust_entropy_* base 1 layout none No
rust_ids / rust_astar / etc. base 1 layout varies No

All six commit one layout per stage (NR/NoHome) or three layers from one rollout (RH). RH evaluates K=5 rollouts but only commits one; the K-1 alternatives are discarded as soon as RH picks a winner.

1.2 Cross-stage cascade requires preserved alternatives

In cascade circuits, the optimal commit at stage N depends on structure that appears 5 to 20 stages ahead. We measured this on qugan n=8 by sweeping the lookahead horizon K (with our multi-candidate beam, so the "no preservation" confound is excluded):

lookahead K qugan lanes
1 (no LA) 73
2 (NR/RH-class horizon) 73
4 60
8 53 (−27 %)

Extending the horizon alone is not enough. If we run our own strategy with beam_width=1 (a single trajectory) but lookahead_window=24, the result is no better than NR's 4-stage horizon. Single-trajectory strategies must commit to one layout per stage and discard alternatives — including alternatives where a higher-immediate-cost commit would unlock zero-cost stages downstream.

Concrete example on QFT n=8: the layout that satisfies stages 14–28 "naturally" (each pair already at CZ-partner positions) requires a non-trivial commit at stage 12 with cost 6, vs the locally cheapest commit costing 4 but forcing 4–5 lane moves at every subsequent stage. The cheap local choice adds +20 cumulative lanes over the next 15 stages. None of the upstream strategies keep the higher-immediate-cost alternative alive long enough to see this payoff.

1.3 Our approach

We adapt a multi-candidate-preservation pattern from a recent distributed-quantum-compiler paper I worked on, Athena [1]. Athena's UMS (Utility-driven Multi-Candidate Block Scheduling) keeps the Top-w block schedules alive during compilation to defer commitment, rather than committing locally and discarding alternatives. The same idea fits this placement problem: instead of preserving multiple block schedules, MCL preserves multiple physical layout trajectories per CZ stage.

Per cz_placements call:

all_stages = [(controls, targets)] + lookahead[: 24]
beam       = [(state.layout, cum=0, first_layout=None)]

for stage in all_stages:
    new_beam = []
    for layout, cum, first in beam:
        cands  = hungarian_perturbed(layout, stage, K=20)
        cands += [no_return(layout, stage)]      # one extra candidate

        for c in cands:
            cost     = move_layers(layout, c)
            la_cost  = min_next_layer_cost(c, next_different_stage)
            score    = cum + cost + λ · la_cost
            new_first = c if first is None else first
            new_beam.append((c, cum + cost, new_first, score))

    beam = top_W(dedup_by_layout(new_beam))      # W = 8

best = argmin(beam, key=lambda b: b.cum)
return ExecuteCZ(layout = best.first_layout, ...)

Three building blocks:

  1. W = 8 candidate trajectories kept alive across the 24-stage lookahead window. This is the Athena-inspired component.

  2. K = 20 Hungarian-perturbed candidates per trajectory per stage. Uses a 2× cost-matrix (each slot pair × orientation as a separate column) so Hungarian sees orientation as a decision variable.

  3. NoReturnPlacementStrategy result added as one additional candidate per trajectory per stage. NR's Rust loose-goal solver produces layouts that the Python-level Hungarian K=20 set sometimes misses, so we include its result in the candidate pool.

Single fixed configuration used everywhere: W = 8, K = 20, λ = 2.0, lookahead_window = 24.


2. Algorithm

2.1 Public interface

@dataclass
class MultiCandidateLookaheadPlacementStrategy(PlacementStrategyABC):
    arch_spec: ArchSpec
    beam_width: int            = 8     # W
    hungarian_candidates: int  = 20    # K
    lookahead_window: int      = 24
    lookahead_weight: float    = 2.0   # λ
    nr_max_expansions: int     = 300
    nr_restarts: int           = 20

Per-call computational cost: O(stages_in_window × W × K) extra solver-oracle calls. For typical 50-stage circuits at default W=8, K=20: 30 - 90 s compile time. RSS stays ~430 MB independent of W (beam state is negligible compared to bloqade-lanes baseline).


3. Results

3.1 Full strategy roster — 8 standard QASM benchmark families (n = 8 qubits)

All seven strategies run end-to-end on the same initial layout per family. Every committed stage's input layout is checked for CZ-pair alignment (qubits at CZ-partner slot positions, the necessary condition for the CZ to physically execute). Stages that don't satisfy this are counted as invalid.

family NR NoHome RH rust_greedy rust_entropy_5 rust_ids MCL
bv 12 27 12 19 19 19 12
qaoa_3reg 31 82 35 56 56 56 25
qaoa_fc 51 176 INC* 168 168 176 42
qft 62 218 58 192 192 192 48
qugan 61 92 INC* 86 86 86 49
qv 60 274 INC* 258 246 246 50
shor 90 182 90 156 156 156 72
vqe 70 223 INC* 222 222 222 56
TOTAL (valid only) 437 1274 195 / 4 fam. 1157 1145 1153 354

* INC = strategy fails mid-schedule (returns non-ExecuteCZ /NotState). RH's solve_entangling_rh crashes on these 4 families; its lane count for those is a partial schedule that does not actually compile.

Among strategies that complete a valid schedule on all 8 families (NR, NoHome, rust_greedy, rust_entropy_5, rust_ids, MCL), MCL has the lowest aggregate lane count (354). RH completes on only 4 of 8 families.

3.2 Head-to-head with NR

NR is the existing baseline with the lowest lane count among strategies that complete on all 8 families. MCL vs NR:

family NR MCL Δ vs NR outcome
bv 12 12 0.0 % TIE
qaoa_3reg 31 25 −19.4 % WIN
qaoa_fc 51 42 −17.6 % WIN
qft 62 48 −22.6 % WIN
qugan 61 49 −19.7 % WIN
qv 60 50 −16.7 % WIN
shor 90 72 −20.0 % WIN
vqe 70 56 −20.0 % WIN
TOTAL 437 354 −19.0 % 7 W / 1 T / 0 L

3.3 Head-to-head with RH (where it completes)

RH completes a valid schedule on only 4 of 8 families. On those 4,
MCL vs RH:

family RH MCL Δ vs RH outcome
bv 12 12 0.0 % TIE
qaoa_3reg 35 25 −28.6 % WIN
qft 58 48 −17.2 % WIN
shor 90 72 −20.0 % WIN

3 W / 1 T / 0 L on the four RH-completed families.

3.4 Runtime and memory

Per-family runtime at default W=8, K=20:

family stages MCL runtime
bv 7 7 s
qaoa_3reg 18 71 s
qaoa_fc 26 273 s
qft 29 387 s
qugan 21 121 s
qv 24 237 s
shor 53 475 s
vqe 32 450 s

Linear in stages × W × K. Peak RSS ~430 MB (W-invariant on regular circuits).


4. Files

  • python/bloqade/lanes/heuristics/physical/multi_candidate_lookahead.py (new, ~600 LOC)
  • python/tests/heuristics/test_multi_candidate_lookahead_placement.py (new, 169 LOC, 8 tests)
  • python/benchmarks/harness/matrix.py (+16 LOC, registers multi_candidate_lookahead strategy)

5. Test plan

  • pytest python/tests/heuristics/test_multi_candidate_lookahead_placement.py -v — 8/8 PASS
  • pytest python/tests/benchmarks/ — 66/66 PASS (existing tests unaffected)
  • black --check, isort --check, ruff check, pyright — all clean
  • Schedule validity check across 8 standard families — 0 invalid stages
  • Aggregate vs NR (8/8 valid baselines) — −19.0 % lanes

6. Composition with existing components

MCL is a meta-strategy that composes existing bloqade-lanes components:

No new Rust code. MCL is opt-in via explicit instantiation;
default squin_to_move(...) users are unaffected unless they pass
placement_strategy=MultiCandidateLookaheadPlacementStrategy(...).

7. Related work and credits

The multi-candidate-preservation idea is brought in from a recent paper I worked on, Athena [1] (arXiv:2605.21795). Athena is a compiler for distributed quantum computers; it tackles a different problem (scheduling non-local CNOT blocks across chips) but solves it with an idea that turns out to be useful here too. Quoting the mechanism:

"UMS retains multiple schedules for the current block. Then,
starting with each schedule, ATHENA schedules subsequent blocks
and corresponding qubit positions, eventually building a tree of
solutions ... ATHENA prunes the tree and retains only Top-w
solutions, where w is the maximum number of solutions considered."

In this PR I apply the same idea at the CZ-stage layout level — instead of preserving multiple block schedules I preserve multiple physical layout trajectories per stage. The Top-w pruning and deferred commitment carry over directly.

[1] Won Joon Yun, Dhilan Nag, Sneha Ballabh, Jiapeng Zhao, Eneet
    Kaur, Poulami Das. *Athena: A Compiler For Optimized Scheduling
    In Distributed Quantum Computers*. arXiv:2605.21795 (2026).
    https://arxiv.org/abs/2605.21795

8. Future work (out of scope for this PR)

Rust port. MCL is currently pure Python (~600 LOC) and composes existing Rust-backed components. Runtime at default W=8, K=20 is 30 - 90 s for typical 50-stage circuits. A future PR can port the beam loop, Hungarian assignment, and scoring into Rust following PR #595's pattern (Python thin wrapper → Rust solver).

@codecov

codecov Bot commented May 22, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.18107% with 19 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...s/heuristics/physical/multi_candidate_lookahead.py 92.18% 19 Missing ⚠️

📢 Thoughts on this report? Let us know!

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6a669e4b99

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +349 to +351
return ConcreteState(
occupied=frozenset(),
layout=layout,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve occupied blockers in internal state construction

_make_state always sets occupied=frozenset(), so every internal call to compute_move_layers and the nested NoReturnPlacementStrategy solve ignores blocked locations from the real input state. In circuits where state.occupied is non-empty (spectator atoms / pinned traps), this lets the beam score and select transitions that route through blocked sites or place qubits onto occupied locations, producing physically invalid ExecuteCZ plans despite returning the original state.occupied in the output.

Useful? React with 👍 / 👎.

@WonJoon-Yun WonJoon-Yun force-pushed the feat-beam-safety-net branch from f2e12d7 to 010b10c Compare May 22, 2026 22:58

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 010b10c620

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

layout: tuple[LocationAddress, ...],
) -> tuple[tuple[int, int], ...]:
"""Compact ``(word_id, site_id)`` tuples for fast hashing/equality."""
return tuple((loc.word_id, loc.site_id) for loc in layout)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Include zone_id in layout hashing keys

_layout_locations strips each address down to (word_id, site_id), but LocationAddress identity also includes zone_id. In architectures with one CZ zone plus additional storage/transport zones, two legal layouts that differ only by zone will collide in this key, which then poisons beam dedup (best_by_layout), oracle cache keys in _oracle_move_layers, and the duplicate-location guard that uses set(from_keys)/set(to_keys). This can incorrectly discard valid candidates or reuse move-layer results from a different zone transition, producing wrong scoring/selection for multi-zone runs.

Useful? React with 👍 / 👎.

A Python-level placement strategy that preserves W=8 candidate
layout trajectories across the lookahead window, scored by
cum + cost + λ · next_stage_cost. Per trajectory, per stage,
generates K=20 Hungarian-perturbed candidates plus the NoReturn
result.

Versus the 6 existing bloqade-lanes placement strategies on 8
standard QASM benchmark families (n=8): among strategies that
complete a valid schedule on all 8 families, this one has the
lowest aggregate lane count (354 vs NR's 437, -19.0%).

The multi-candidate-preservation idea is adapted from a recent
distributed-quantum-compiler paper (Athena, arXiv:2605.21795).

Files:
- python/bloqade/lanes/heuristics/physical/multi_candidate_lookahead.py (new, ~611 LOC)
- python/tests/heuristics/test_multi_candidate_lookahead_placement.py (new, 177 LOC, 8 tests)
- python/tests/heuristics/test_mcl_adversarial.py (new, 174 LOC, 7 tests)
- python/benchmarks/harness/matrix.py (registers multi_candidate_lookahead)

15/15 new tests pass. 66/66 existing harness tests still pass.
black + isort + ruff clean. pyright clean on new files (pre-existing
upstream pyright error in arch/gemini/physical/spec.py is unrelated;
SKIP=pyright-check used to bypass the project-wide hook).
Regenerated against the current main (post QuEraComputing#663 AOD-grouping and QuEraComputing#666
empty-lane fixes, which changed move-lane counts) so the committed
baseline matches a fresh CI benchmark run. 9 physical rows + 3 logical
rows for multi_candidate_lookahead, merged into the upstream baselines
and re-sorted by (case_id, strategy_id).
Adds test_mcl_coverage.py targeting the MultiCandidateLookahead paths
the smoke/adversarial suites missed, lifting patch coverage 76% -> 92%:

- _hungarian_assignment: over-full matrix rejection, empty-pairs
  trivial candidate, and bystander-collision relocation (a
  non-participating qubit sitting on a slot the CZ pair is assigned to
  is moved to the nearest free slot, keeping the layout duplicate-free).
- cz_placements guards: mismatched controls/targets -> bottom,
  non-ConcreteState -> top.
- Multi-stage lookahead: a distinct next stage drives _lookahead_cost
  and the skip-repeated-stages scan.
- _oracle_move_layers: cache hit on a repeated layout transition and
  rejection (without raising) of a duplicate-location layout.
@WonJoon-Yun WonJoon-Yun force-pushed the feat-beam-safety-net branch from 010b10c to 99afb39 Compare May 28, 2026 23:06

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 99afb39447

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +568 to +570
dedup = list(best_by_layout.values())
dedup.sort(key=lambda e: e[-1])
beam = [e[:-1] for e in dedup[: self.beam_width]]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve the NoReturn fallback through beam pruning

When lookahead_cz_layers is supplied and beam_width is small, this score-based truncation can discard the unconditional NoReturn candidate before the final selection. A candidate with a more expensive current move but a lower lookahead estimate will sort ahead of NR, so the returned first-stage move_layers can exceed the plain NoReturnPlacementStrategy result despite the advertised safety-net invariant. Carry the NR trajectory outside the score-pruned beam or explicitly compare the committed first stage against the NR result before returning.

Useful? React with 👍 / 👎.

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.

1 participant