Skip to content

✨ Implement Trials For Mapping Pass#1568

Merged
burgholzer merged 10 commits intomainfrom
enh/mapping-pass-initial-placement
Mar 18, 2026
Merged

✨ Implement Trials For Mapping Pass#1568
burgholzer merged 10 commits intomainfrom
enh/mapping-pass-initial-placement

Conversation

@MatthiasReumann
Copy link
Collaborator

@MatthiasReumann MatthiasReumann commented Mar 17, 2026

Description

This pull request adds LightSABRE-like trials (configurable via pass options) to improve the mapping result. To avoid that the A* search gets stuck, this pull request also adds a configurable maxIterations parameter.

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:

  • I have disclosed the use of AI tools in the PR description as per our AI Usage Guidelines.
  • 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.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 17, 2026

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Multi-trial place-and-route with configurable trial count and seed for reproducible results
    • Parallel execution of mapping trials when multithreading is enabled
    • Automatic selection of the best circuit layout to minimize SWAPs
  • Public API

    • Added mapping pass options: ntrials and seed
    • Added Architecture.maxDegree() accessor
  • Tests & Docs

    • Tests updated to exercise new options; changelog entry added

Walkthrough

Adds randomized multi-trial mapping to the QCO place-and-route pass via two new pass options (ntrials, seed), runs multiple (possibly parallel) mapping trials (identity + random layouts), selects the trial with minimal SWAPs, and commits its layout and swaps to the IR.

Changes

Cohort / File(s) Summary
Pass Definition
mlir/include/mlir/Dialect/QCO/Transforms/Passes.td
Added pass options ntrials (std::size_t, default 4) and seed (std::size_t, default 42); description updated to mention multiple/parallel trials.
Mapping Core Logic
mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp
Introduced multi-trial mapping framework: Layout::random(...), TrialResult, runMappingTrial(), findBestTrial(), commitTrial(); trials may run in parallel; routing accepts an onSwaps callback; RNG and threading utilities added; replaced single-run SABRE flow.
Architecture API
mlir/include/.../Mapping/Architecture.h, mlir/lib/.../Mapping/Architecture.cpp
Added Architecture::maxDegree() accessor and its implementation.
Public API / Tests
mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp, .../MappingPassOptions (header)
Added ntrials and seed to qco::MappingPassOptions; unit test updated to set ntrials=8, seed=1337.
Changelog
CHANGELOG.md
Added PR reference [#1568] to Unreleased "Added" entry and PR links appendix.

Sequence Diagram

sequenceDiagram
    rect rgba(200,200,255,0.5)
    participant Pass as MappingPass
    end
    rect rgba(200,255,200,0.5)
    participant RNG as RandomEngine
    participant Factory as TrialFactory
    end
    rect rgba(255,200,200,0.5)
    participant Mapper as MappingRoutine
    participant Selector as ResultSelector
    participant IR as IRCommitter
    end

    Pass->>RNG: init(seed)
    Pass->>Factory: build(identity + nrandom(ntrials))
    Factory-->>Pass: trial layouts

    par parallel trials
        Pass->>Mapper: runMappingTrial(layout)
        Mapper->>Mapper: perform routing, collect SWAPs
        Mapper-->>Pass: TrialResult(layout, swaps)
    end

    Pass->>Selector: findBestTrial(all TrialResult)
    Selector-->>Pass: BestTrialResult
    Pass->>IR: commitTrial(BestTrialResult)
    IR-->>Pass: IR updated
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested labels

enhancement

Suggested reviewers

  • burgholzer
  • denialhaag

Poem

🐰 I hop through seeds and trials with glee,

I shuffle layouts till the swaps are few,
In parallel I dart, choose the best to be,
I stitch the wires and settle qubit crew,
A little hop — the mapping's fresh and new.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: implementing trials for the mapping pass, which is the core feature described in the PR.
Description check ✅ Passed The PR description includes all required template sections with appropriate details, completed checklist items, and disclosure of AI-assisted content.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch enh/mapping-pass-initial-placement
📝 Coding Plan
  • Generate coding plan for human review comments

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 and usage tips.

Tip

CodeRabbit can scan for known vulnerabilities in your dependencies using OSV Scanner.

OSV Scanner will automatically detect and report security vulnerabilities in your project's dependencies. No additional configuration is required.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

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 the current code and only fix it if needed.

Inline comments:
In `@mlir/include/mlir/Dialect/QCO/Transforms/Passes.td`:
- Around line 53-54: Update the documentation for the pass option `ntrials` in
Passes.td to explicitly state that the pass always evaluates the identity layout
in addition to the randomized trials (i.e., `ntrials` specifies the number of
additional random trials), so that `ntrials = 0` still runs the identity
baseline; make the same wording change at the second occurrence noted around
lines 65-66 so both descriptions consistently document that the identity layout
is evaluated in addition to the `ntrials` random trials.

In `@mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp`:
- Around line 101-104: The code uses std::iota and std::optional but relies on
transitive includes; add direct includes for <numeric> and <optional> to the top
of the file so these symbols are guaranteed available. Update the include list
in mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp (affecting uses in the
static function Layout random(...) and the other occurrence around line 361) by
adding `#include` <numeric> and `#include` <optional> to ensure std::iota and
std::optional are provided explicitly.

In `@mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp`:
- Around line 107-111: The test calls qco::createMappingPass with a
MappingPassOptions struct but doesn’t set the randomized seed, so tests can
become nondeterministic if the default changes; update the MappingPassOptions
initializer used in the test (the call to qco::createMappingPass /
MappingPassOptions in test_mapping.cpp) to include an explicit .seed value
(e.g., a fixed integer) alongside .nlookahead, .alpha, .lambda, .niterations,
and .ntrials to make the mapping trials deterministic.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 51b35893-e79e-485d-a7e6-a308235085cd

📥 Commits

Reviewing files that changed from the base of the PR and between a5cf121 and a18eeb8.

📒 Files selected for processing (3)
  • mlir/include/mlir/Dialect/QCO/Transforms/Passes.td
  • mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp
  • mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp

@MatthiasReumann MatthiasReumann added c++ Anything related to C++ code MLIR Anything related to MLIR labels Mar 17, 2026
@MatthiasReumann MatthiasReumann self-assigned this Mar 17, 2026
@codecov
Copy link

codecov bot commented Mar 17, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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)

326-356: 🧹 Nitpick | 🔵 Trivial

Make the seeded trials reproducible per function.

Line 326 seeds a single std::mt19937_64 before the function loop, so the trial sequence for a given func::FuncOp changes when unrelated functions are added or reordered earlier in the module. Since seed is now a user-facing pass option, it would be better to derive a local RNG inside the loop from (this->seed, function identity/index) and keep each function stable on its own.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp` around lines 326 - 356,
The RNG is currently seeded once (std::mt19937_64 rng{this->seed}) outside the
func::FuncOp loop, causing the sequence of Layout::random trials for a given
function to shift when other functions change; fix by deriving a per-function
RNG inside the loop using the pass seed combined with a stable function
identifier (e.g., func.getName().str() or an incrementing function index).
Replace the single outer rng with creating a local std::mt19937_64 (or seeded
via std::seed_seq) for each func (use this->seed plus a hash/uint32 of func
identity) and use that local rng when calling Layout::random so each
func::FuncOp produces reproducible trials independent of module ordering.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp`:
- Around line 351-356: The current trials use Layout::random(arch.nqubits(),
rng()) which permutes all program indices including synthetic suffix
[dyn.size(), arch.nqubits()), wasting trials; change Layout::random (or add an
overload) so it only shuffles the first dynCount program slots (use dyn.size()
as the count) and then deterministically assigns the remaining hardware slots to
the synthetic program indices, and update the call in Mapping.cpp (where
trials.emplace_back(...) is used) to pass dyn.size() (or call the new overload)
so only real dynamic qubits are randomized while synthetic indices remain fixed.

---

Outside diff comments:
In `@mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp`:
- Around line 326-356: The RNG is currently seeded once (std::mt19937_64
rng{this->seed}) outside the func::FuncOp loop, causing the sequence of
Layout::random trials for a given function to shift when other functions change;
fix by deriving a per-function RNG inside the loop using the pass seed combined
with a stable function identifier (e.g., func.getName().str() or an incrementing
function index). Replace the single outer rng with creating a local
std::mt19937_64 (or seeded via std::seed_seq) for each func (use this->seed plus
a hash/uint32 of func identity) and use that local rng when calling
Layout::random so each func::FuncOp produces reproducible trials independent of
module ordering.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 2d3abc37-5aaa-4d00-8c88-c71c1007233c

📥 Commits

Reviewing files that changed from the base of the PR and between a18eeb8 and bbe9884.

📒 Files selected for processing (1)
  • mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp`:
- Around line 532-535: The loop condition uses the comma operator so
!frontier.empty() is discarded and i < maxIterations is the only check, which
can call frontier.top() on an empty queue; change the while condition to require
both !frontier.empty() && i < maxIterations so the loop stops when the frontier
is empty or the iteration limit is reached (update the loop controlling
expression where variables frontier, i, and maxIterations are used before
calling frontier.top()/frontier.pop()).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 071bea0f-49fc-405f-8611-a9f097c6c7d5

📥 Commits

Reviewing files that changed from the base of the PR and between bbe9884 and 81dd478.

📒 Files selected for processing (4)
  • CHANGELOG.md
  • mlir/include/mlir/Dialect/QCO/Transforms/Passes.td
  • mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp
  • mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp

@burgholzer burgholzer added this to the MLIR Support milestone Mar 17, 2026
Copy link
Member

@burgholzer burgholzer left a comment

Choose a reason for hiding this comment

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

This looks very clean to me. I only had one minor observation.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp (1)

101-112: 🧹 Nitpick | 🔵 Trivial

Randomization includes synthetic qubit indices.

The Layout::random(nqubits, seed) shuffles all nqubits indices uniformly. When dyn.size() < arch.nqubits(), the synthetic program indices in [dyn.size(), arch.nqubits()) are also permuted. Since these never affect routing quality, equivalent placements of the actual qubits are counted as distinct trials.

Consider restricting the shuffle to only the first dyn.size() indices, assigning the remaining hardware slots deterministically. This would make better use of the trial budget for narrow circuits.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp` around lines 101 - 112,
Layout::random currently shuffles all nqubits including synthetic program
indices, causing equivalent placements to be counted multiple times; change it
to only randomize the first dyn.size() program indices and leave the remaining
hardware slots deterministic. Concretely, in Layout::random(const std::size_t
nqubits, const std::size_t seed) (and any callers that pass
arch.nqubits()/dyn.size()), build the mapping so that you fill indices [0,
dyn.size()) then apply std::ranges::shuffle or mt19937_64 only to that prefix,
and then call Layout::add(prog, hw) via the enumerate(mapping) loop so that
program qubits are permuted but the synthetic indices in [dyn.size(),
arch.nqubits()) remain in a fixed order.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp`:
- Around line 101-112: Layout::random currently shuffles all nqubits including
synthetic program indices, causing equivalent placements to be counted multiple
times; change it to only randomize the first dyn.size() program indices and
leave the remaining hardware slots deterministic. Concretely, in
Layout::random(const std::size_t nqubits, const std::size_t seed) (and any
callers that pass arch.nqubits()/dyn.size()), build the mapping so that you fill
indices [0, dyn.size()) then apply std::ranges::shuffle or mt19937_64 only to
that prefix, and then call Layout::add(prog, hw) via the enumerate(mapping) loop
so that program qubits are permuted but the synthetic indices in [dyn.size(),
arch.nqubits()) remain in a fixed order.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: e2c17013-65af-45cf-892f-86c51dcc5008

📥 Commits

Reviewing files that changed from the base of the PR and between 83e9890 and 7fe9d84.

📒 Files selected for processing (3)
  • mlir/include/mlir/Dialect/QCO/Transforms/Mapping/Architecture.h
  • mlir/lib/Dialect/QCO/Transforms/Mapping/Architecture.cpp
  • mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@mlir/lib/Dialect/QCO/Transforms/Mapping/Architecture.cpp`:
- Line 16: The loop that computes the maximum degree using std::max should be
replaced with LLVM's helper to follow mlir conventions: locate the code in
Architecture.cpp (the max-degree reduction loop near lines 77-80, e.g., inside
the function that computes maxDegree or similar) and replace the manual
std::max-based iteration with llvm::max_element (from llvm/ADT/STLExtras.h) over
the container holding degrees, then dereference the returned iterator to get the
max value; also add the appropriate LLVM header include and remove the
unnecessary <algorithm> use for this purpose.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: b872f515-e784-4715-8ffb-740d3ad85bd6

📥 Commits

Reviewing files that changed from the base of the PR and between 7fe9d84 and b6cdef3.

📒 Files selected for processing (1)
  • mlir/lib/Dialect/QCO/Transforms/Mapping/Architecture.cpp

Copy link
Member

@burgholzer burgholzer left a comment

Choose a reason for hiding this comment

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

Very clean. Let's get this in before conflicts creep up.

@burgholzer burgholzer merged commit e80ead8 into main Mar 18, 2026
50 of 52 checks passed
@burgholzer burgholzer deleted the enh/mapping-pass-initial-placement branch March 18, 2026 13:23
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