Skip to content

✨ Implement "vote-and-restore" strategy for qco.index_switch - #1951

Merged
burgholzer merged 27 commits into
mainfrom
feat/map-index-switch-op
Aug 3, 2026
Merged

✨ Implement "vote-and-restore" strategy for qco.index_switch#1951
burgholzer merged 27 commits into
mainfrom
feat/map-index-switch-op

Conversation

@MatthiasReumann

@MatthiasReumann MatthiasReumann commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

Description

This pull request implements the "vote-and-restore" strategy for qco.index_switch operation. The final layout of each case region is considered as a "vote" of the respective case (the "voter") and the average (winning) layout is determined using the Borda Rank.

Checklist

  • The pull request only contains commits that are focused and relevant to this change.
  • I have added appropriate tests that cover the new/changed functionality.
  • I have updated the documentation to reflect these changes.
  • I have added entries to the changelog for any noteworthy additions, changes, fixes, or removals.
  • I have added migration instructions to the upgrade guide (if needed).
  • The changes follow the project's style guidelines and introduce no new warnings.
  • The changes are fully tested and pass the CI checks.
  • I have reviewed my own code changes.

If PR contains AI-assisted content:

  • Any agent that created, edited, or submitted GitHub content was explicitly authorized for that scope, as required by our AI Usage Guidelines.
  • Every agent-authored or agent-edited public text body begins with the visible disclosure 🤖 *AI text below* 🤖 (titles are exempt).
  • AI-assisted commits include an Assisted-by: [Model Name] via [Tool Name] footer.
  • I confirm that I have personally reviewed and understood all AI-generated content, and accept full responsibility for it.

@MatthiasReumann MatthiasReumann self-assigned this Jul 27, 2026
@MatthiasReumann MatthiasReumann added c++ Anything related to C++ code MLIR Anything related to MLIR labels Jul 27, 2026
@MatthiasReumann MatthiasReumann added this to the MLIR Support milestone Jul 27, 2026
@mergify mergify Bot added the conflict label Jul 27, 2026
@mergify mergify Bot removed the conflict label Jul 27, 2026
@MatthiasReumann
MatthiasReumann marked this pull request as ready for review July 27, 2026 13:12
@MatthiasReumann

MatthiasReumann commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

@MatthiasReumann: I’ll review the changes for the qco.index_switch vote-and-restore implementation.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@MatthiasReumann, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 40 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: faffc7fb-bbaf-4938-b6fa-02a4990ddb36

📥 Commits

Reviewing files that changed from the base of the PR and between d14b33f and d4e181a.

📒 Files selected for processing (5)
  • CHANGELOG.md
  • mlir/include/mlir/Dialect/QCO/Utils/Layout.h
  • mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp
  • mlir/lib/Dialect/QCO/Utils/Layout.cpp
  • mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp
📝 Walkthrough

Walkthrough

Layout gains mapping-based construction, while QCO mapping now handles IndexSwitchOp branch routing, layout convergence, terminator realignment, and executability validation. A nested loop-and-switch mapping test and changelog references were added.

Changes

QCO mapping and layout

Layer / File(s) Summary
Layout construction API
mlir/include/mlir/Dialect/QCO/Utils/Layout.h, mlir/lib/Dialect/QCO/Utils/Layout.cpp
Adds mapping-based layout construction and refactors random layout creation to use the new factory.
Recursive routing and branch convergence
mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp
Updates scheduling, tied-value dispatch, IndexSwitchOp branch routing, winner-layout restoration, and hot-mode terminator realignment.
Nested control-flow validation
mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp, CHANGELOG.md
Extends executability mapping for IndexSwitchOp regions, adds MapNestedForSwitch, and records the pull request in the changelog.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant MappingPass
  participant IndexSwitchOp
  participant RoutingBundles
  participant Layout
  MappingPass->>IndexSwitchOp: read tied operands and regions
  MappingPass->>RoutingBundles: dispatch child routing bundles
  RoutingBundles->>Layout: produce branch layouts
  MappingPass->>Layout: vote for winning layout
  MappingPass->>RoutingBundles: restore branches to winning layout
  MappingPass->>IndexSwitchOp: realign yielded qubit values
Loading

Possibly related PRs

Suggested labels: enhancement

Suggested reviewers: burgholzer, denialhaag

Poem

I hop through switches, neat and bright,
Mapping qubits left and right.
Branches vote, then settle true,
Loops align their values too.
Tests now dance through nested flow—
Squeak! The layouts know where to go.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change: implementing vote-and-restore for qco.index_switch.
Description check ✅ Passed The description includes a summary, motivation/context, and a complete checklist, so it mostly matches the template.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch feat/map-index-switch-op

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp (1)

1458-1464: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Stale comment: the loop now realigns the scf::ForOp terminator too.

The note says the procedure is not required for scf::ForOp, but the unified loop applies it to every region including ForOp's. Either drop the exception from the comment or skip ForOp explicitly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp` around lines 1458 -
1464, Update the comment above the loop iterating over op regions and children
to remove the outdated exception claiming realignment is unnecessary for
scf::ForOp, since the loop also processes its terminator; leave the unified loop
behavior unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp`:
- Around line 1271-1285: Add invariant Default branches to both TypeSwitches in
dispatch, including the children construction and exit handling switches. In
each Default branch, call the established report_fatal_error convention for
unsupported region-carrying operations instead of returning an empty vector or
default Layout; preserve the existing handling for all recognized operation
types.

In `@mlir/lib/Dialect/QCO/Utils/Layout.cpp`:
- Around line 30-34: Validate mapping in Layout::fromMapping before calling add:
ensure every hardware index is within the layout size and appears exactly once,
rejecting or asserting duplicates and out-of-range values. Preserve normal
construction for valid permutations and prevent add from receiving invalid
entries.

In `@mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp`:
- Around line 1190-1200: Update the caseBodies construction to store owning
callable objects whose lifetime extends through the qcoIndexSwitch invocation,
rather than non-owning function_refs to temporary lambdas. Capture the per-case
index by value so each callable retains its intended gate count, and remove the
stray semicolon after the construction loop.

---

Outside diff comments:
In `@mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp`:
- Around line 1458-1464: Update the comment above the loop iterating over op
regions and children to remove the outdated exception claiming realignment is
unnecessary for scf::ForOp, since the loop also processes its terminator; leave
the unified loop behavior unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 27277a89-401d-4356-963a-a15d42a41c3b

📥 Commits

Reviewing files that changed from the base of the PR and between 88d1caf and 8aba7a4.

📒 Files selected for processing (5)
  • CHANGELOG.md
  • mlir/include/mlir/Dialect/QCO/Utils/Layout.h
  • mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp
  • mlir/lib/Dialect/QCO/Utils/Layout.cpp
  • mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp

Comment thread mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp
Comment thread mlir/lib/Dialect/QCO/Utils/Layout.cpp
Comment thread mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp Outdated

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@mlir/include/mlir/Dialect/QCO/Utils/Layout.h`:
- Around line 35-41: Make Layout(const size_t nqubits) private, placing it under
the class’s private section so callers cannot create zero-initialized
non-permutation layouts. Preserve access for the static fromMapping method,
which can use this constructor while constructing a valid mapping.

In `@mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp`:
- Around line 1038-1049: Update the static vote function to validate that every
layout in the input range has the same qubit count as the first layout before
indexing scores or computing rank weights. Reject or assert mismatched layouts,
preserving the existing scoring behavior for uniformly sized layouts.
- Around line 1439-1453: Remove std::move from the returns of convergedLayout in
the IfOp case and winner in the IndexSwitchOp case. Both variables are const, so
moving falls back to copying; return them directly to allow normal copy elision
or implicit move behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 222b31f7-2730-42ae-965e-fae31462a532

📥 Commits

Reviewing files that changed from the base of the PR and between 88d1caf and 8aba7a4.

📒 Files selected for processing (5)
  • CHANGELOG.md
  • mlir/include/mlir/Dialect/QCO/Utils/Layout.h
  • mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp
  • mlir/lib/Dialect/QCO/Utils/Layout.cpp
  • mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp

Comment thread mlir/include/mlir/Dialect/QCO/Utils/Layout.h Outdated
Comment thread mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp
Comment thread mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp Outdated
@codecov

codecov Bot commented Jul 28, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.70115% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...lir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp 97.3% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

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

Actionable comments posted: 1

♻️ Duplicate comments (3)
mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp (2)

1147-1148: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Seed the generator deterministically for reproducible failures.

std::random_device still makes every CI run pick different per-case gate counts. With ntrials = 1, a routing failure becomes an unreproducible flake.

🐛 Proposed fix
-  std::random_device rd;
-  std::mt19937 gen(rd());
+  std::mt19937 gen(42);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp` around lines
1147 - 1148, Replace the nondeterministic std::random_device seeding in the
test’s generator setup with a fixed deterministic seed for std::mt19937, while
preserving the existing per-case gate-count generation behavior so routing
failures can be reproduced across runs.

1218-1220: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Unreachable return to_vector(args); still present.

Dead code after the unconditional return on the preceding statement, same as previously flagged.

♻️ Proposed fix
     return llvm::to_vector(
         builder.qcoIndexSwitch(index, args, cases, caseBodies, defaultCase));
-    return to_vector(args);
   });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp` around lines
1218 - 1220, Remove the unreachable second return from the helper containing the
builder.qcoIndexSwitch call. Keep the unconditional llvm::to_vector result
return and delete the subsequent to_vector(args) statement.
mlir/lib/Dialect/QCO/Utils/Layout.cpp (1)

30-36: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

fromMapping still doesn't validate the mapping is a permutation.

Same finding as a prior review of this function: add() only bounds-checks, so duplicate hardware indices are silently accepted, leaving programToHardware_/hardwareToProgram_ inconsistent. Today's two call sites (random, vote in Mapping.cpp) happen to always pass valid permutations, but nothing enforces that for future callers.

🐛 Proposed debug validation
 Layout Layout::fromMapping(ArrayRef<size_t> mapping) {
   Layout layout(mapping.size());
+  SmallVector<bool> seen(mapping.size(), false);
   for (const auto [prog, hw] : enumerate(mapping)) {
+    assert(hw < mapping.size() && !seen[hw] &&
+           "mapping must be a permutation");
+    seen[hw] = true;
     layout.add(prog, hw);
   }
   return layout;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mlir/lib/Dialect/QCO/Utils/Layout.cpp` around lines 30 - 36, Update
Layout::fromMapping to validate that mapping is a permutation before or while
populating the layout: reject duplicate hardware indices and any out-of-range
values, rather than relying on add() alone. Preserve the existing construction
behavior for valid mappings and ensure invalid input cannot leave
programToHardware_ and hardwareToProgram_ inconsistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@mlir/include/mlir/Dialect/QCO/Utils/Layout.h`:
- Around line 78-86: Change the sized Layout constructor to private access
instead of protected. Keep it available to Layout’s static members fromMapping
and random, while preventing derived classes from constructing layouts with
uninitialized, non-permutation mappings and preserving the invariants used by
swap, restore, vote, and operator==.

---

Duplicate comments:
In `@mlir/lib/Dialect/QCO/Utils/Layout.cpp`:
- Around line 30-36: Update Layout::fromMapping to validate that mapping is a
permutation before or while populating the layout: reject duplicate hardware
indices and any out-of-range values, rather than relying on add() alone.
Preserve the existing construction behavior for valid mappings and ensure
invalid input cannot leave programToHardware_ and hardwareToProgram_
inconsistent.

In `@mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp`:
- Around line 1147-1148: Replace the nondeterministic std::random_device seeding
in the test’s generator setup with a fixed deterministic seed for std::mt19937,
while preserving the existing per-case gate-count generation behavior so routing
failures can be reproduced across runs.
- Around line 1218-1220: Remove the unreachable second return from the helper
containing the builder.qcoIndexSwitch call. Keep the unconditional
llvm::to_vector result return and delete the subsequent to_vector(args)
statement.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: faffc7fb-bbaf-4938-b6fa-02a4990ddb36

📥 Commits

Reviewing files that changed from the base of the PR and between d14b33f and ac3977f.

📒 Files selected for processing (5)
  • CHANGELOG.md
  • mlir/include/mlir/Dialect/QCO/Utils/Layout.h
  • mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp
  • mlir/lib/Dialect/QCO/Utils/Layout.cpp
  • mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp

Comment thread mlir/include/mlir/Dialect/QCO/Utils/Layout.h Outdated

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

This looks really clean!
I just have one minor observation where I'd like your input. Otherwise this can go in!

Comment thread mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp Outdated
Reject duplicate and out-of-range hardware indices before constructing a layout and cover the public factory contract with focused tests.

Assisted-by: GPT-5.6 via Codex
Assisted-by: GPT-5.6 via Codex
Assisted-by: GPT-5.6 via Codex
Assisted-by: GPT-5.6 via Codex
@burgholzer

Copy link
Copy Markdown
Member

🤖 AI text below 🤖

Q-01: does the Borda vote have the intended semantics for Layout?

The current implementation treats getProgramToHardware() as a preference ranking: position 0 receives the highest weight, position 1 the next-highest weight, and so on. However, those positions are program-qubit indices, not stated preferences. In effect, the hardware location of program qubit 0 contributes more points than the location of program qubit 1, etc.

That makes the selected layout sensitive to an otherwise semantics-preserving renaming of logical qubits. The hardware-index tie-break can amplify this. The examples below use [h0, h1, ...] to mean “program qubit i is mapped to hardware qubit hi.” Restore costs are minimum adjacent-SWAP counts on a line topology.

Three-qubit example

The voters

  • [0, 1, 2]
  • [0, 1, 2]
  • [1, 2, 0]

produce Borda scores [4, 4, 1], so the hardware-index tie-break selects [0, 1, 2].

After a cyclic renaming of program qubits, the equivalent voters are

  • [2, 0, 1]
  • [2, 0, 1]
  • [0, 1, 2]

Equivariance under renaming would make the winner [2, 0, 1], i.e., the correspondingly renamed original winner. The current Borda calculation instead selects [0, 2, 1]. On a three-node line, restoring all voters to the equivariant winner costs 2 adjacent SWAPs in total; restoring them to the Borda winner costs 7.

A smaller transposition example also changes the result: voters [0, 2, 1], [0, 2, 1], [1, 0, 2] should inherit winner [0, 2, 1] from the renamed original election, but the current tie-break selects [0, 1, 2] (line cost 2 versus 3).

Four-qubit example

The voters

  • [0, 1, 2, 3]
  • [0, 1, 3, 2]
  • [0, 2, 1, 3]

select [0, 1, 2, 3]. After the cyclic program renaming, the equivalent voters are

  • [3, 0, 1, 2]
  • [2, 0, 1, 3]
  • [3, 0, 2, 1]

The correspondingly renamed winner is [3, 0, 1, 2], but the current vote selects [0, 3, 2, 1]. On a four-node line, the total minimum restore cost is 2 for the equivariant winner and 16 for the Borda winner.

So the central question is: is prioritizing lower-numbered program qubits intentional? If it is, the current rule should probably be documented as such rather than described as an “average” layout. If it is not, a topology-aware alternative would be to choose the branch-layout medoid: select the child layout minimizing the sum of restore costs from all child layouts, with an explicit deterministic tie-break. Benchmarking parent restoration, the current Borda rule, and such a medoid on representative mapped programs would make the trade-off measurable.

The added regression test deliberately does not decide this question: its routed case layouts are identical, so it only proves that the vote-and-restore path is actually used.

@MatthiasReumann

Copy link
Copy Markdown
Collaborator Author

@burgholzer

Q-01: does the Borda vote have the intended semantics for Layout?

I am not really sure where this "cycling renaming" comes from, but here's how I see the problem:

Each of the N cases ends with a layout. Each layout is equivalent to a program-to-hardware mapping

prog (0, 1, 2, 3, ..., M)
  hw (0, 1, 2, 3, ..., M) // Using the identity here.

The goal is to find a "average" "winning" layout - or in other words - a program-to-hardware mapping. Finding this mapping should be somewhat efficient / fast.

The Borda rank assigns points based on the position of the candidate (here: hardware index) on its ballot (here: program-to-hardware mapping). The program-qubit with index 0 will always be at position 0 and program-qubit with index M will always be at position M in this voting system. Consequently, from a program-qubit perspective 0 will always win and M will always loose.

Thus, only the order of hardware indices determines the points. For the example given by Cody

  • U: (2, 0, 1)
  • V: (2, 0, 1)
  • W: (0, 1, 2)

the Borda Ranking is

Candidate U Points V Points W points Total
0 1 1 2 3
1 0 0 1 2
2 2 2 0 4

The winner is thus

prog (0, 1, 2)
  hw (2, 0, 1)

Given that the program indices are ordered as discussed above, this is the correct choice.

@burgholzer

Copy link
Copy Markdown
Member

🤖 AI text below 🤖

I put the entire reasoning through another round of scutinizing and took some time to go through the reasoning, see below. To me the argumentation here makes sense and the proposed restauration strategy feels more natural and performant. What do you think?

@MatthiasReumann Thanks for laying out your interpretation. I think there are two separate points here: one arithmetic issue in the example and one more fundamental question about what the aggregation should optimize.

First, for the three ballots

  • U: (2, 0, 1)
  • V: (2, 0, 1)
  • W: (0, 1, 2)

I get the following totals:

Hardware candidate U V W Total
0 1 1 2 4
1 0 0 1 1
2 2 2 0 4

Thus, candidates 0 and 2 tie with four points. Since the implementation breaks Borda ties by the lower hardware index, it returns (0, 2, 1), not (2, 0, 1).

The more fundamental question is whether program-qubit indices should serve as preference ranks in the first place.

The implementation interprets a layout (h0, h1, ..., hM) as a ballot ranking hardware candidates according to the program index occupying them. Consequently, the hardware occupied by program qubit 0 always receives the maximum points, while the hardware occupied by the last program qubit receives none. Program qubits are not actually candidates in this election; their numeric indices define the preference order.

That is a valid Borda construction if ascending program index is intentionally an ordinal ranking. However, in the mapping pass, program indices are identities derived from extraction order. I do not see a reason why program qubit 0 should have greater importance than program qubit 1, or why the numeric distance between program indices should be meaningful for restoration.

This is where the renaming argument comes from. If we consistently rename the logical qubits throughout the program and every branch layout, we have only renamed the tokens being routed. The hardware topology and the required physical movement have not changed. The restoration cost between two layouts is invariant under such a consistent renaming. Therefore, a convergence heuristic intended to minimize restoration overhead should ideally produce the correspondingly renamed result.

This problem is not limited to Borda ties. For example, consider:

  • A: (0, 1, 2)
  • B: (0, 2, 1)
  • C: (1, 0, 2)

The Borda scores are (5, 3, 1), so the result is strictly (0, 1, 2).

Now consistently swap program-qubit identities 1 and 2. The equivalent layouts are:

  • A′: (0, 2, 1)
  • B′: (0, 1, 2)
  • C′: (1, 2, 0)

Their scores are (4, 3, 2), so Borda again strictly returns (0, 1, 2). The correspondingly renamed original winner would be (0, 2, 1). There are no score ties in either calculation. On a three-qubit line, restoring to the renamed original winner requires two adjacent SWAPs in total, while restoring to the new Borda result requires three.

There are also counterexamples that do not rely on renaming at all. Consider three branch layouts on a four-qubit line:

  • A: (0, 2, 1, 3)
  • A again: (0, 2, 1, 3)
  • B: (1, 3, 0, 2)

Despite two of the three branches agreeing exactly on A, Borda produces scores (7, 5, 4, 2) and selects the identity layout (0, 1, 2, 3), which no branch proposed.

Restoring all branches to A requires two adjacent SWAPs in total. Restoring them to the Borda result requires five. This is tie-free and shows that the Borda objective can disagree substantially with the restoration cost we ultimately care about.

Given that minimizing inserted SWAPs and restoration overhead is the actual objective, would it make more sense to choose a topology-aware medoid among the branch layouts?

Concretely, for every child layout Lj, we could evaluate

cost(Lj) = sum_i restore(Li, Lj).size()

and select the child layout with the smallest total cost. The restoration sequences for the winning candidate could be cached and inserted directly, avoiding another round of computation.

This has several properties that seem attractive here:

  • it directly minimizes the quantity we subsequently insert;
  • it accounts for the actual device topology through restore;
  • it is invariant under consistently renaming program qubits;
  • it always selects an already existing, valid branch layout;
  • it cannot manufacture a consensus layout that is farther from every relevant branch under the restoration metric.

The selection requires quadratically many restore evaluations in the number of regions. That seems acceptable to me for an index_switch, although we should benchmark it for switches with many regions. For equal-cost candidates, we could use stable region order as a deterministic tie-break rather than program- or hardware-index ordering.

What do you think about replacing the Borda consensus with this branch-layout medoid? If compile time turns out to be a concern, we could compare the two strategies on representative switches before deciding, but the medoid seems more closely aligned with the optimization goal of the pass.

@burgholzer

Copy link
Copy Markdown
Member

Side note: The PR itself is obviously functionally correct and passes CI. So I am also happy to continue to merge this after resolving the conflicts and defer to a follow up.

@mergify mergify Bot added the conflict label Jul 29, 2026
@mergify mergify Bot removed the conflict label Aug 3, 2026
@MatthiasReumann

Copy link
Copy Markdown
Collaborator Author

@burgholzer

Many thanks! Besides me failing to add three numbers, I now know where this is coming from 👍🏻

I think the "topology-aware" argument is the strongest one here. I will try to find a solution for that.

However, I am not sure if I am a fan of the medoid idea as this will add a significant overhead for many-case switches. Then, one could argue "just restore to the origin" is a better runtime / quality tradeoff.

Otherwise, let's merge this for now to get #1867 going again.

@burgholzer

Copy link
Copy Markdown
Member

@burgholzer

Many thanks! Besides me failing to add three numbers, I now know where this is coming from 👍🏻

I think the "topology-aware" argument is the strongest one here. I will try to find a solution for that.

However, I am not sure if I am a fan of the medoid idea as this will add a significant overhead for many-case switches. Then, one could argue "just restore to the origin" is a better runtime / quality tradeoff.

Otherwise, let's merge this for now to get #1867 going again.

I agree, let's merge for now and improve in a follow-up! 👍🏻

I am not so sure if the overhead would really be that bad. But, to be honest, all of this is hypothetical. I have not yet really seen a use of the index switch in an algorithm, which would allow us to judge what to prioritize for performance.

@burgholzer
burgholzer merged commit fe19354 into main Aug 3, 2026
33 checks passed
@burgholzer
burgholzer deleted the feat/map-index-switch-op branch August 3, 2026 07:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

c++ Anything related to C++ code MLIR Anything related to MLIR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants