Skip to content

✨ Preserve and control compiler qubit layouts - #2553

Draft
simon1hofmann wants to merge 24 commits into
feat/target-mapping-controlsfrom
feat/compiler-layout-controls
Draft

simon1hofmann wants to merge 24 commits into
feat/target-mapping-controlsfrom
feat/compiler-layout-controls

Conversation

@simon1hofmann

@simon1hofmann simon1hofmann commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

🤖 AI text below 🤖

Description

Preserve imported transpiler layouts through compiler round trips, and expose native initial-placement controls with an input-to-site mapping report. This extends the original native-layout work to fix #2070.

The shared mqt.layout module attribute records logical inputs, initial physical positions, optional routing assignments, output order, source register groups, and ancillary inputs. It stores no Python objects or SDK-specific attributes. Only the existing Qiskit 2.5 adapter reads and reconstructs SDK objects; placement, routing, validation, and layout lifetime rules use Core code. No Qiskit transpilation or synthesis algorithms are copied into Core or invoked by native compilation.

Copies, MLIR serialization, and plain QC/QCO conversions preserve imported provenance without changing instruction semantics. Resource-changing transformations and public custom pass pipelines invalidate it. SDK export rejects stale provenance, and formats that cannot retain it require explicit discard.

Dependency: stacked on #2551 (feat/target-mapping-controls). Layout compilation accepts seed, timing, statistics, mapping trials, refinement iterations, and routing lookahead only through CompilationOptions. Replace separate timing/statistics keywords with options=CompilationOptions(...). The explicit input assignment and detached mapping result retain their existing contracts.

Fixes #2070

Changelog and upgrade-guide entries are deferred to release preparation under the current project policy. Required migrations are described below.

Preserve imported layouts

from qiskit import QuantumCircuit, transpile
from mqt.core.mlir import QCProgram, QIRProfile

circuit = QuantumCircuit(2)
circuit.h(0)
circuit.cx(0, 1)
circuit = transpile(
    circuit, coupling_map=[[0, 1], [1, 0]],
    initial_layout=[1, 0], optimization_level=0,
)
program = QCProgram.from_qiskit(circuit)
restored = program.copy().to_qco().to_qc().to_qiskit()
assert restored.layout.final_index_layout() == circuit.layout.final_index_layout()

# Accept losing provenance before conversion to a format without layouts.
program.discard_layout()
bitcode = program.to_qir(QIRProfile.BASE).to_bitcode()

C++ callers use Program::discardLayout(). The CLI equivalent is:

mqt-cc input.mlir --discard-layout --emit=qir-base -o output.ll

Control native placement

from mqt.core.mlir import (
    CompilationOptions, CompilerTarget, MappingOptions, PayloadEncoding, PayloadFormat,
    PayloadSpecification, QCProgram, TargetEnvironment,
)

target = CompilerTarget(
    3,
    connectivity=CompilerTarget.Connectivity([(0, 1), (1, 2)]),
    native_operations=CompilerTarget.NativeOperations.unrestricted(),
)
environment = TargetEnvironment(
    target,
    PayloadSpecification(
        PayloadFormat("qir", "2.1.0", "base", PayloadEncoding.BINARY),
        [], optional_capabilities_known=True,
    ),
)
program = QCProgram.from_openqasm_str('''OPENQASM 3.1;
include "stdgates.inc";
qubit[2] q;
h q[0]; cx q[0], q[1];
''').to_qco()
layout = program.compile_for_target_with_layout(
    environment, initial_layout=[0, 2], options=CompilationOptions(seed=42, mapping=MappingOptions(trials=4, iterations=2, lookahead=10)),
)
print(layout.allocation_sizes)
print(layout.initial_layout)
print(layout.final_layout)

The example's forced placement routes the two logical qubits through the three-site line. It reports allocation sizes [2] and the requested initial sites [0, 2]. A particular final assignment is not a cross-version guarantee.

Omit initial_layout or pass [] for automatic placement. Set both seed and trials to make automatic mapping independent of CPU count for a fixed build, input, and target. A supplied layout bypasses trials and refinement iterations; lookahead still controls routing. Iterations default to one and must be positive. Lookahead defaults to 20 additional two-qubit gates, and zero considers only the current gate.

Each layout entry is a target site ID, including sparse IDs. Input order is entry-block allocation order with tensor slots flattened in ascending index order. allocation_sizes retains allocation boundaries. This order is independent of which qubit is used first. Classical measurement destinations retain their semantics.

Limitations

  • SDK interchange supports Qiskit 2.5 TranspileLayout: initial/final assignments, partial maps, physical gaps, loose and ancillary inputs, source-register ordering, and nontrivial output permutations. Input indices must be contiguous and physical references must exist. Partial final maps need a complete output-wire order; SDK helpers requiring total layouts can still reject partial assignments. Bare Layout values are rejected. Other SDK minor versions need their own adapter.
  • Cleanup, optimization, routing, allocation changes, reuse, and public custom pass pipelines conservatively replace imported metadata with mqt.layout_invalidated. They do not compose an earlier SDK layout with a new target-compilation result. Call discard_layout() before SDK export after invalidation.
  • OpenQASM, QIR/LLVM, and jeff cannot preserve this provenance and reject retained or invalidated metadata until explicitly discarded. Discarding metadata does not change operations.
  • Raw IR editors and callers assembling external pass managers must preserve correspondence, update the metadata, or invalidate it before changing resource identity or order. Schema verification cannot detect every stale same-width external edit.
  • Native initial_layout requires one distinct target site ID per input; partial placement constraints are not supported. This is separate from preserving partial imported metadata.
  • Native placement accepts fixed-size local entry-block allocations. Dynamic, nested, and already physical inputs are rejected. Idle slots count against target capacity; inputs removed before this call cannot be recovered.
  • MappingResult is a detached snapshot of one compilation and is not serialized or updated by later edits. The low-level result must outlive its pass manager and is published only on success. CLI placement input and report serialization remain outside this PR.
  • Existing circuit-import, routing, synthesis, and payload limitations still apply. Program contents must not be reused after failed target compilation.

Validation

  • 625 Python compiler, translation, and QDMI tests passed with the native SC-provider registry.
  • 239 native compiler tests and 37 metadata tests passed, covering layout schema, serialization, discard, output guards, routing, failure publication, shared options, iteration validation, and explicit placement with zero lookahead.
  • Three CLI CTests and four GoogleTest CLI checks passed, covering mapping options, seed overrides, layout export guards, explicit discard, import retention, and transformation invalidation.
  • Repository lint and full changed-file C++ lint against origin/main pass; regenerated bindings include the shared options API.
  • Both Python usage examples execute successfully.

The earlier native patch coverage measurement at 3178e825c was 419/438 production lines (95.7%), including 130/130 in QubitLayout.cpp. This update adds shared-options regression coverage and changes no coverage thresholds or exclusions; the new CI measurement is pending.

Codex assisted with the implementation, tests, documentation, and description. This remains a draft for human review. CI for the latest push is pending.

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.
  • 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).
  • I have disclosed AI assistance in the PR description.
  • I confirm that I have personally reviewed and understood all AI-generated content, and accept full responsibility for it.

@codecov

codecov Bot commented Sep 12, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.86466% with 22 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...lir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp 91.1% 16 Missing ⚠️
mlir/lib/Compiler/TargetCompilation.cpp 88.3% 5 Missing ⚠️
mlir/lib/Compiler/Pipeline.cpp 97.3% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@simon1hofmann simon1hofmann added feature New feature or request MLIR Anything related to MLIR c++ Anything related to C++ code python Anything related to Python code labels Sep 12, 2026
@simon1hofmann
simon1hofmann added this pull request to stack #2555 September 13, 2026 11:25
@simon1hofmann simon1hofmann self-assigned this Sep 13, 2026
@simon1hofmann simon1hofmann changed the title ✨ Expose native initial and final qubit layouts ✨ Preserve and control compiler qubit layouts Sep 13, 2026
@mergify mergify Bot added the conflict label Sep 14, 2026
simon1hofmann and others added 19 commits September 14, 2026 11:18
🤖 *AI text below* 🤖

Thread the native mapper seed and trial count through C++, Python, device compilation, and mqt-cc. Preserve existing defaults and validate zero trials before rewriting the program.

Assisted-by: GPT-6 via Codex
🤖 *AI text below* 🤖

Assisted-by: GPT-6 via Codex
🤖 *AI text below* 🤖

Group timing, statistics, mapping trials, and an optional compiler seed in
CompilationOptions. Preserve legacy flag calls and propagate explicit seeds
through mapping, custom pipelines, and numerical synthesis. Capture scoped
seed metadata in crash reproducers and validate its module-level contract.

Replace the mapping CLI script with native checks and cover seed precedence,
wide seeds, numerical retries, compatibility, and failure cleanup.

Assisted-by: GPT-6 via Codex
🤖 *AI text below* 🤖

Pass synthesis mapping options to the shared preparation pass so invalid
trial counts fail before rewriting the input. Cover synthesis alongside
both target compilation connectivity modes in the existing regression.

Assisted-by: GPT-6 via Codex
🤖 *AI text below* 🤖

Remove the timing/statistics overloads and Python keyword compatibility
layer. Compiler entry points now take CompilationOptions directly, with
binding arguments copied before releasing the GIL. Keep omitted options
distinct for submission so compiled payloads reject compiler settings.

Update native callers, instrumentation tests, documentation, and generated
stubs. This intentionally breaks callers that use the separate flags.

Assisted-by: GPT-6 via Codex
🤖 *AI text below* 🤖

Keep the plan scoped to Core's compiler API, remove the obsolete flag
compatibility statement, and record the current validation results.

Assisted-by: GPT-6 via Codex
🤖 *AI text below* 🤖

Add layout refinement iterations and routing lookahead to MappingOptions
and the CLI. Preserve the defaults of one refinement round and twenty
additional gates; permit zero lookahead and reject zero iterations before
target compilation or synthesis changes the program.

Extend forwarding, repeatability, and CLI validation coverage. Keep all
compiler settings grouped in CompilationOptions.

Assisted-by: GPT-6 via Codex
🤖 *AI text below* 🤖

Regenerate the Python interface, apply initializer conventions, and record
validation for the added mapping controls.

Assisted-by: GPT-6 via Codex
🤖 *AI text below* 🤖

Preserve source allocation order during native target compilation, accept a complete initial placement, and return a detached mapping snapshot.

Assisted-by: GPT-6 via Codex
🤖 *AI text below* 🤖

Assisted-by: GPT-6 via Codex
🤖 *AI text below* 🤖

Assisted-by: GPT-6 via Codex
🤖 *AI text below* 🤖

Exercise tensor slots, workspace sites, complete routed amplitudes, automatic placement, and invalid inputs in the native suite so C++ patch coverage includes these contracts.

Assisted-by: GPT-6 via Codex
🤖 *AI text below* 🤖

Exercise layout tracking with user barriers and reject quantum entry arguments in native tests. Apply the required C++ lint fixes to the coverage regressions.

Assisted-by: GPT-6 via Codex
🤖 *AI text below* 🤖

Record the expanded native layout regressions and measured coverage of changed production lines.

Assisted-by: GPT-6 via Codex
🤖 *AI text below* 🤖

Reuse Layout identity and swaps to satisfy requested placement and assign workspace deterministically. Remove the unreachable zero-size allocation branch and inline the single dynamic-allocation test case while preserving its diagnostic assertion.

Assisted-by: GPT-6 via Codex
🤖 *AI text below* 🤖

Extend the native layout controls with frontend-neutral layout metadata.
Preserve supported SDK layouts across copies and dialect conversions,
invalidate provenance at transformation boundaries, and require explicit
discard before exports that cannot retain it. Cover partial assignments,
ancillas, routing, stale metadata, and native serialization boundaries.

Assisted-by: GPT-6 via Codex
🤖 *AI text below* 🤖

Reserve SDK reconstruction storage, keep pass override visibility, and
apply the required native initializer and pointer declaration style.
Record the completed regression and coverage checks.

Assisted-by: GPT-6 via Codex
🤖 *AI text below* 🤖

Compare imported instruction semantics with and without layout metadata,
clarify partial-layout helper limitations, and record the final checks.

Assisted-by: GPT-6 via Codex
🤖 *AI text below* 🤖

Give layout invalidation a stable pipeline argument and register its
constructor. Saved cleanup pipelines can then be parsed and replayed
instead of containing an unknown anonymous pass name.

The existing failing replay regression and all five mqt-cc tests pass.

Assisted-by: GPT-6 via Codex
🤖 *AI text below* 🤖

Move layout CLI coverage into the existing native test suite and remove
its CMake script. Preserve export loss guards, explicit discard, import
retention, and invalidation by optimization and custom pipelines.

Assisted-by: GPT-6 via Codex
🤖 *AI text below* 🤖

Follow the initializer-list style enforced by the full-file C++ lint
check for the new layout CLI tests.

Assisted-by: GPT-6 via Codex
🤖 *AI text below* 🤖

Keep layout compilation on the shared CompilationOptions API and exercise
mapping iterations and zero lookahead through layout compilation. Document
which controls apply to an explicit initial layout.

Assisted-by: GPT-6 via Codex
🤖 *AI text below* 🤖

Regenerate layout stubs against the shared mapping controls, apply repository
formatting, and update the validation record for the rebased stack.

Assisted-by: GPT-6 via Codex
@simon1hofmann
simon1hofmann force-pushed the feat/compiler-layout-controls branch from e1add22 to def00d3 Compare September 14, 2026 09:36
@mergify mergify Bot added the conflict label Sep 14, 2026
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 conflict feature New feature or request MLIR Anything related to MLIR python Anything related to Python code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

✨ Preserve Qiskit transpiler layouts in compiler programs

1 participant