Skip to content

Conversation

@lsschmid
Copy link
Collaborator

@lsschmid lsschmid commented Nov 20, 2025

Description

Thir PR extends the Hybrid Mapper within QMAP. In particular in includes the following features:

  1. Extended Hybrid Mapping
    • added support for Bridge Gates
    • added initial Mapping strategies
    • added support for more atom moves (passby and flying ancillas [limited for now])
    • general bugfixes and improvements
  2. Hybrid Synthesis
    • a variant of the Hybrid Mapper which can evaluate multiple possible circuits, map them all end then chooses the best option to map (used for the ZX paper)
    • also includes PyBindings to use it from qiskit
  3. NaViz support
    • The routed circuit can now be exported to .naviz and .namachine which can then be fed into NaViz
  4. Added missing documentation on both mappers above.
  5. Some general bugfixes and improvements

Fixes #466

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.

…t if staying still to avoid problems while MoveGroup generation
Copilot AI review requested due to automatic review settings December 5, 2025 12:42
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR extends the Hybrid Mapper for neutral atom quantum computers by adding:

  • Extended hybrid mapping with support for bridge gates, multiple initial mapping strategies, enhanced atom movements (passby and flying ancillas), and various bugfixes
  • Hybrid synthesis functionality for evaluating and selecting optimal circuits from multiple candidates
  • NaViz export support for visualization (.naviz and .namachine formats)
  • Comprehensive documentation and Python bindings

Reviewed changes

Copilot reviewed 41 out of 42 changed files in this pull request and generated no comments.

Show a summary per file
File Description
test/python/hybrid_mapper/test_hybrid_synthesis.py New tests for hybrid synthesis mapper with multiple architectures
test/python/hybrid_mapper/test_hybrid_mapper.py Updated tests using MapperParameters instead of HybridMapperParameters, added QASM export validation
test/hybridmap/test_utils.cpp New utility tests for coordinate mapping, move vectors, and bridge circuits
test/hybridmap/test_scheduler.cpp New scheduler result serialization tests
test/hybridmap/test_mapping.cpp New tests for mapping exceptions and edge cases
test/hybridmap/test_hybridmap.cpp Extensive refactoring with new parametrized tests, exception tests, and output validation
test/hybridmap/test_hybrid_synthesis_map.cpp New C++ tests for hybrid synthesis mapper functionality
test/hybridmap/test_hardware_qubits.cpp New tests for hardware qubit management and pathfinding
test/hybridmap/test_architecture.cpp New architecture loading, parsing, and animation tests
test/hybridmap/circuits/*.qasm New test circuit files for multi-qubit and long random scenarios
test/hybridmap/architectures/*.json Updated architecture configurations with new parameters
src/hybridmap/*.cpp Major implementation updates including bridge gates, flying ancillas, passby moves, NaViz support, and refactored scheduling/mapping logic
include/hybridmap/*.hpp Updated headers with comprehensive documentation, new data structures (BridgeCircuits, FlyingAncilla, PassByComb), and additional mapping methods
python/mqt/qmap/hybrid_mapper.pyi Updated Python bindings reflecting renamed classes and new synthesis mapper interface
src/hybridmap/CMakeLists.txt Added spdlog dependency
docs/index.md Added reference to new hybrid mapper documentation

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

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: 7

Caution

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

⚠️ Outside diff range comments (3)
include/hybridmap/Mapping.hpp (1)

170-178: Linear search in getCircQubit is acceptable but could be optimized.

The current implementation performs a linear search through circToHw to find the circuit qubit for a given hardware qubit. This is O(n) where n is the number of mapped qubits. For typical circuit sizes this is fine, but if performance becomes a concern, consider maintaining a reverse mapping (hwToCirc).

src/hybridmap/HybridNeutralAtomMapper.cpp (1)

1386-1454: Minor: dead local and soft shuttling‑layer limit in move‑comb generation.

getMoveCombinationsToPosition declares a MoveCombs const moveCombinations; local that is never used; it can be dropped. Also, getAllMoveCombinations checks allMoves.size() > limitShuttlingLayer before adding the new moves, so the effective limit can be exceeded by the size of the last batch. If you need a hard cap, consider checking again after addMoveCombs or changing the condition to >= and/or bounding the number of added combinations.

Also applies to: 1456-1476

include/hybridmap/HardwareQubits.hpp (1)

369-375: getInitHwPos returns a copy, which is safe but has overhead.

The method constructs and returns a new std::map each call. This is safe but creates overhead. If called frequently in hot paths, consider caching the result or returning a const reference to initialHwPos directly.

♻️ Duplicate comments (11)
src/hybridmap/Mapping.cpp (1)

51-52: Semantic type mismatch in hwIndices persists.

hwIndices is declared as std::vector<HwQubit> but stores circuit qubit indices (qi, qn of type qc::Qubit). While invalidCirc is correctly typed as qc::Qubit, the container type should match:

-  std::vector<HwQubit> hwIndices(hwQubits.getNumQubits(), invalidCirc);
+  std::vector<qc::Qubit> hwIndices(hwQubits.getNumQubits(), invalidCirc);

This preserves semantic clarity even if the underlying types are identical.

include/hybridmap/HybridSynthesisMapper.hpp (2)

118-122: saveSynthesizedQc silently ignores write failures.

The function doesn't check if the file was successfully opened or if writing succeeded. Consider adding a basic check:

   [[maybe_unused]] void saveSynthesizedQc(const std::string& filename) const {
     std::ofstream ofs(filename);
+    if (!ofs) {
+      throw std::runtime_error("Cannot open file for writing: " + filename);
+    }
     synthesizedQc.dumpOpenQASM(ofs, false);
     ofs.close();
   }

77-85: Consider documenting single-use intent or resetting base state.

initMapping resets local state (mappedQc, synthesizedQc, mapping) but doesn't reset inherited state (e.g., stats, mappedQcAOD). If instances are intended for single-run use, document this constraint. Otherwise, consider calling a base-class reset.

   void initMapping(const size_t nQubits) {
     if (nQubits > arch->getNpositions()) {
       throw std::runtime_error("Not enough qubits in architecture.");
     }
+    // Reset inherited state for reuse
+    stats = MapperStats();
+    mappedQcAOD = qc::QuantumComputation(arch->getNpositions());
     mappedQc = qc::QuantumComputation(arch->getNpositions());
     synthesizedQc = qc::QuantumComputation(nQubits);
     mapping = Mapping(nQubits);
     initialized = true;
   }
src/hybridmap/NeutralAtomLayer.cpp (1)

31-37: initAllQubits assumes dag.size() equals number of qubits.

This has been flagged in a previous review. The assumption that dag is qubit-indexed and dag.size() equals the qubit count should be documented or use an explicit qubit count source.

include/hybridmap/NeutralAtomLayer.hpp (1)

91-98: Consider reserving ends vector for consistency.

The constructor reserves space for iterators and candidates but not for ends. While this is a minor optimization concern, adding ends.reserve(dag.size()) would be consistent with the other reservations.

   explicit NeutralAtomLayer(DAG graph, const bool isFrontLayer,
                             const uint32_t lookaheadDepth = 1)
       : dag(std::move(graph)), lookaheadDepth(lookaheadDepth),
         isFrontLayer(isFrontLayer) {
     iterators.reserve(dag.size());
     candidates.reserve(dag.size());
+    ends.reserve(dag.size());
     for (auto& i : dag) {
include/hybridmap/Mapping.hpp (1)

23-28: Missing standard library headers for self-contained compilation.

The header uses std::deque, std::unique_ptr (line 41), and std::ranges::any_of (line 188) but doesn't include <deque>, <memory>, or <ranges>. While transitive includes may work on some compilers, explicitly including these headers makes the file self-contained and more portable.

 #include <algorithm>
 #include <cstddef>
+#include <deque>
+#include <memory>
+#include <ranges>
 #include <set>
 #include <stdexcept>
 #include <string>
 #include <utility>
src/hybridmap/HybridNeutralAtomMapper.cpp (1)

407-482: Out‑of‑bounds risk in applyFlyingAncilla index arithmetic.

applyFlyingAncilla uses allCoords[i] and increments i by 2 per iteration over faComb.moves, while allCoords is built from the gate’s targets+controls (2 entries for 2‑qubit gates). When faComb.moves.size() > 1 (possible if convertMoveCombToFlyingAncillaComb produces entries for both gate qubits), i takes values 0, 2, ..., so the second iteration reads allCoords[2] with allCoords.size() == 2, which is out of bounds and leads to UB. This is the same issue called out in an earlier review and remains unresolved.

You should either:

  • Enforce that faComb.moves.size() <= 1 for 2‑qubit gates (e.g., by truncating bestFAs to a single entry or asserting the invariant in convertMoveCombToFlyingAncillaComb), or
  • Rework applyFlyingAncilla to associate each FlyingAncilla with an explicit gate coordinate instead of relying on allCoords[i] with a stride of 2 (e.g., store the relevant gate coord in FlyingAncilla and use that directly), eliminating the fragile indexing.

Until this is fixed, certain flying‑ancilla move combinations can trigger buffer overruns.

src/hybridmap/MoveToAodConverter.cpp (1)

321-325: Unsigned subtraction can underflow when end < start.

The delta calculation uses unsigned subtraction:

const auto start = static_cast<std::uint32_t>(dim == Dimension::X ? origin.x : origin.y);
const auto end = static_cast<std::uint32_t>(dim == Dimension::X ? final.x : final.y);
const auto delta = static_cast<qc::fp>(end - start);

When end < start, this underflows before the cast to qc::fp, producing a large positive value instead of the expected negative delta. This was flagged in a past review but appears unaddressed.

Apply this fix:

-  const auto delta = static_cast<qc::fp>(end - start);
+  const auto delta = static_cast<qc::fp>(
+      static_cast<int32_t>(end) - static_cast<int32_t>(start));
include/hybridmap/HybridNeutralAtomMapper.hpp (1)

612-640: I/O methods lack error checking despite documented @throw behavior.

The documentation mentions @throw std::runtime_error On file I/O failure, but saveMappedQcQasm and saveMappedQcAodQasm don't verify stream state after opening or writing. This was flagged in a past review but appears unaddressed.

Either add error checking:

void saveMappedQcQasm(const std::string& filename) const {
  std::ofstream ofs(filename);
  if (!ofs) {
    throw std::runtime_error("Failed to open file: " + filename);
  }
  mappedQc.dumpOpenQASM(ofs, false);
  if (!ofs.good()) {
    throw std::runtime_error("Failed to write to file: " + filename);
  }
}

Or remove the @throw specification from the documentation.

include/hybridmap/NeutralAtomArchitecture.hpp (2)

22-35: Unused includes remain in the file.

Per a previous review, <array>, <cmath>, <iterator>, and <utility> were identified as unused in this header. These includes increase compilation time without providing value.

Remove the unused includes:

-#include <array>
 #include <cassert>
-#include <cmath>
 #include <cstddef>
 #include <cstdint>
 #include <fstream>
 #include <iostream>
-#include <iterator>
 #include <map>
 #include <set>
 #include <spdlog/spdlog.h>
 #include <stdexcept>
 #include <string>
-#include <utility>
 #include <vector>

573-576: Use OpType::AodMove for consistency with other shuttling time calculations.

This method uses qc::OpType::Move while all other shuttling time queries in the codebase (including getAnimationMachine) use qc::OpType::AodMove. The JSON configuration files may define different values for these operation types (e.g., rubidium_hybrid.json has "move": 0.55 vs "aod_move": 0.3), causing this inconsistency to produce incorrect timing estimates.

Apply this fix:

   [[nodiscard]] qc::fp getVectorShuttlingTime(const MoveVector& v) const {
     return v.getLength() * getInterQubitDistance() /
-           getShuttlingTime(qc::OpType::Move);
+           getShuttlingTime(qc::OpType::AodMove);
   }

Please verify the intended semantics: is Move vs AodMove a meaningful distinction in your timing model, or should this indeed use AodMove for consistency?

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6858379 and 9fa38b7.

📒 Files selected for processing (23)
  • bindings/hybrid_mapper/hybrid_mapper.cpp (4 hunks)
  • include/hybridmap/HardwareQubits.hpp (6 hunks)
  • include/hybridmap/HybridAnimation.hpp (1 hunks)
  • include/hybridmap/HybridNeutralAtomMapper.hpp (3 hunks)
  • include/hybridmap/HybridSynthesisMapper.hpp (1 hunks)
  • include/hybridmap/Mapping.hpp (3 hunks)
  • include/hybridmap/MoveToAodConverter.hpp (5 hunks)
  • include/hybridmap/NeutralAtomArchitecture.hpp (4 hunks)
  • include/hybridmap/NeutralAtomDefinitions.hpp (2 hunks)
  • include/hybridmap/NeutralAtomLayer.hpp (2 hunks)
  • include/hybridmap/NeutralAtomScheduler.hpp (3 hunks)
  • include/hybridmap/NeutralAtomUtils.hpp (5 hunks)
  • python/mqt/qmap/hybrid_mapper.pyi (3 hunks)
  • src/hybridmap/HardwareQubits.cpp (7 hunks)
  • src/hybridmap/HybridNeutralAtomMapper.cpp (30 hunks)
  • src/hybridmap/HybridSynthesisMapper.cpp (1 hunks)
  • src/hybridmap/Mapping.cpp (1 hunks)
  • src/hybridmap/MoveToAodConverter.cpp (15 hunks)
  • src/hybridmap/NeutralAtomArchitecture.cpp (8 hunks)
  • src/hybridmap/NeutralAtomLayer.cpp (4 hunks)
  • src/hybridmap/NeutralAtomScheduler.cpp (4 hunks)
  • test/hybridmap/test_hybridmap.cpp (4 hunks)
  • test/python/hybrid_mapper/test_hybrid_synthesis.py (1 hunks)
🧰 Additional context used
🧠 Learnings (14)
📚 Learning: 2025-11-20T18:19:31.857Z
Learnt from: lsschmid
Repo: munich-quantum-toolkit/qmap PR: 832
File: src/hybridmap/HybridNeutralAtomMapper.cpp:1679-1715
Timestamp: 2025-11-20T18:19:31.857Z
Learning: In src/hybridmap/HybridNeutralAtomMapper.cpp, the compareSwapAndBridge and compareShuttlingAndFlyingAncilla methods intentionally use division by zero to produce infinity when distance reductions are zero or negative. This infinity value marks that option as highly unfavorable in cost comparisons, which is the intended behavior rather than a bug.

Applied to files:

  • src/hybridmap/HybridSynthesisMapper.cpp
  • include/hybridmap/HybridSynthesisMapper.hpp
  • src/hybridmap/NeutralAtomArchitecture.cpp
  • bindings/hybrid_mapper/hybrid_mapper.cpp
  • src/hybridmap/Mapping.cpp
  • src/hybridmap/NeutralAtomLayer.cpp
  • include/hybridmap/NeutralAtomDefinitions.hpp
  • include/hybridmap/NeutralAtomScheduler.hpp
  • src/hybridmap/HardwareQubits.cpp
  • src/hybridmap/HybridNeutralAtomMapper.cpp
  • include/hybridmap/Mapping.hpp
  • src/hybridmap/MoveToAodConverter.cpp
  • test/hybridmap/test_hybridmap.cpp
  • src/hybridmap/NeutralAtomScheduler.cpp
  • include/hybridmap/MoveToAodConverter.hpp
  • include/hybridmap/NeutralAtomArchitecture.hpp
  • include/hybridmap/HybridNeutralAtomMapper.hpp
  • include/hybridmap/NeutralAtomUtils.hpp
  • include/hybridmap/HardwareQubits.hpp
📚 Learning: 2025-11-27T08:52:06.428Z
Learnt from: lsschmid
Repo: munich-quantum-toolkit/qmap PR: 832
File: src/hybridmap/HybridNeutralAtomMapper.cpp:1379-1383
Timestamp: 2025-11-27T08:52:06.428Z
Learning: In src/hybridmap/HybridNeutralAtomMapper.cpp, the getBestMovePos function throws std::runtime_error when no move position is found (finalBestPos.coords is empty). This behavior is acceptable for current use cases since tight architectures are not a concern. A future improvement would be to fall back to SWAP-based strategies instead of throwing.

Applied to files:

  • src/hybridmap/HybridSynthesisMapper.cpp
  • include/hybridmap/HybridSynthesisMapper.hpp
  • src/hybridmap/NeutralAtomArchitecture.cpp
  • bindings/hybrid_mapper/hybrid_mapper.cpp
  • src/hybridmap/Mapping.cpp
  • src/hybridmap/NeutralAtomLayer.cpp
  • include/hybridmap/HybridAnimation.hpp
  • include/hybridmap/NeutralAtomDefinitions.hpp
  • include/hybridmap/NeutralAtomScheduler.hpp
  • src/hybridmap/HardwareQubits.cpp
  • src/hybridmap/HybridNeutralAtomMapper.cpp
  • include/hybridmap/Mapping.hpp
  • src/hybridmap/MoveToAodConverter.cpp
  • test/hybridmap/test_hybridmap.cpp
  • src/hybridmap/NeutralAtomScheduler.cpp
  • include/hybridmap/NeutralAtomLayer.hpp
  • include/hybridmap/MoveToAodConverter.hpp
  • include/hybridmap/NeutralAtomArchitecture.hpp
  • include/hybridmap/HybridNeutralAtomMapper.hpp
  • include/hybridmap/NeutralAtomUtils.hpp
  • include/hybridmap/HardwareQubits.hpp
📚 Learning: 2025-11-20T16:57:24.305Z
Learnt from: lsschmid
Repo: munich-quantum-toolkit/qmap PR: 832
File: test/hybridmap/test_hybridmap.cpp:200-287
Timestamp: 2025-11-20T16:57:24.305Z
Learning: In test/hybridmap/test_hybridmap.cpp, the LongShuttling test is designed as a stress test to catch rare edge cases that only occur after extended shuttling sequences, which is why it maps a long circuit without explicit assertions beyond ensuring no exceptions are thrown.

Applied to files:

  • src/hybridmap/HybridSynthesisMapper.cpp
  • test/python/hybrid_mapper/test_hybrid_synthesis.py
  • src/hybridmap/HybridNeutralAtomMapper.cpp
  • test/hybridmap/test_hybridmap.cpp
  • include/hybridmap/NeutralAtomUtils.hpp
📚 Learning: 2025-10-07T15:30:42.946Z
Learnt from: MatthiasReumann
Repo: munich-quantum-toolkit/core PR: 1237
File: mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h:219-231
Timestamp: 2025-10-07T15:30:42.946Z
Learning: In the Layout class for MLIR quantum routing (mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h), the swap method intentionally does NOT swap the hw fields in QubitInfo. This is correct because SSA values represent quantum states at fixed hardware locations, and only their program index associations change during a SWAP gate. The hw field indicates where an SSA value physically resides and remains constant.

Applied to files:

  • src/hybridmap/HybridSynthesisMapper.cpp
  • src/hybridmap/NeutralAtomArchitecture.cpp
  • src/hybridmap/Mapping.cpp
  • include/hybridmap/NeutralAtomDefinitions.hpp
  • src/hybridmap/HardwareQubits.cpp
  • src/hybridmap/HybridNeutralAtomMapper.cpp
  • include/hybridmap/Mapping.hpp
  • include/hybridmap/HybridNeutralAtomMapper.hpp
  • include/hybridmap/HardwareQubits.hpp
📚 Learning: 2025-10-09T04:30:29.071Z
Learnt from: MatthiasReumann
Repo: munich-quantum-toolkit/core PR: 1237
File: mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Scheduler.h:72-140
Timestamp: 2025-10-09T04:30:29.071Z
Learning: In the CrawlScheduler (mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Scheduler.h), the persistent `layoutCopy` is updated across all lookahead layers via `remapQubitValue` calls. After each two-qubit gate is scheduled in a layer, its input qubits are remapped to output qubits, ensuring the next layer's crawl starts from those outputs. This progressive remapping through SSA values prevents the same operation from being scheduled in multiple lookahead layers.

Applied to files:

  • include/hybridmap/HybridSynthesisMapper.hpp
  • src/hybridmap/NeutralAtomLayer.cpp
  • src/hybridmap/HybridNeutralAtomMapper.cpp
  • src/hybridmap/NeutralAtomScheduler.cpp
  • include/hybridmap/HybridNeutralAtomMapper.hpp
📚 Learning: 2025-12-04T06:59:40.314Z
Learnt from: MatthiasReumann
Repo: munich-quantum-toolkit/core PR: 1301
File: mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.cpp:84-85
Timestamp: 2025-12-04T06:59:40.314Z
Learning: In the MQTOpt MLIR routing passes (NaiveRoutingPassSC, AStarRoutingPassSC), the input IR is guaranteed to contain only 1-qubit and 2-qubit gates. All 3+-qubit gates must be decomposed before routing; otherwise the input IR is invalid. This invariant allows skipTwoQubitBlock in LayeredUnit.cpp to safely assert wires.size() == 2.

Applied to files:

  • src/hybridmap/NeutralAtomArchitecture.cpp
  • src/hybridmap/Mapping.cpp
  • src/hybridmap/NeutralAtomLayer.cpp
  • src/hybridmap/HardwareQubits.cpp
  • src/hybridmap/HybridNeutralAtomMapper.cpp
  • include/hybridmap/Mapping.hpp
  • test/hybridmap/test_hybridmap.cpp
  • include/hybridmap/NeutralAtomLayer.hpp
📚 Learning: 2025-11-01T15:57:31.153Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1283
File: src/qir/runtime/QIR.cpp:196-201
Timestamp: 2025-11-01T15:57:31.153Z
Learning: In the QIR runtime (src/qir/runtime/QIR.cpp), the PRX gate (__quantum__qis__prx__body) is an alias for the R gate (Phased X-Rotation) and should call runtime.apply<qc::R>(theta, phi, qubit), not runtime.apply<qc::RX>() which is a single-parameter rotation gate.

Applied to files:

  • src/hybridmap/NeutralAtomArchitecture.cpp
  • test/hybridmap/test_hybridmap.cpp
📚 Learning: 2025-10-09T13:20:11.483Z
Learnt from: DRovara
Repo: munich-quantum-toolkit/core PR: 1108
File: mlir/test/Dialect/MQTOpt/Transforms/lift-measurements.mlir:269-288
Timestamp: 2025-10-09T13:20:11.483Z
Learning: In the MQT MLIR dialect, the `rz` gate should not be included in the `DIAGONAL_GATES` set for the `ReplaceBasisStateControlsWithIfPattern` because its operator matrix does not have the required shape | 1 0 | / | 0 x | for the targets-as-controls optimization. It is only included in `LiftMeasurementsAboveGatesPatterns` where the matrix structure requirement differs.

Applied to files:

  • src/hybridmap/NeutralAtomArchitecture.cpp
  • src/hybridmap/HybridNeutralAtomMapper.cpp
📚 Learning: 2025-10-09T13:13:51.224Z
Learnt from: DRovara
Repo: munich-quantum-toolkit/core PR: 1108
File: mlir/lib/Dialect/MQTOpt/Transforms/ReplaceBasisStateControlsWithIfPattern.cpp:171-180
Timestamp: 2025-10-09T13:13:51.224Z
Learning: In MQT Core MLIR, UnitaryInterface operations guarantee 1-1 correspondence between input and output qubits in the same order. When cloning or modifying unitary operations (e.g., removing controls), this correspondence is maintained by construction, so yielding getAllInQubits() in else-branches matches the result types from the operation's outputs.

Applied to files:

  • src/hybridmap/NeutralAtomArchitecture.cpp
  • src/hybridmap/NeutralAtomLayer.cpp
  • src/hybridmap/HardwareQubits.cpp
  • src/hybridmap/HybridNeutralAtomMapper.cpp
  • include/hybridmap/Mapping.hpp
  • include/hybridmap/HardwareQubits.hpp
📚 Learning: 2025-11-05T09:23:46.540Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1287
File: src/qdmi/dd/Device.cpp:492-521
Timestamp: 2025-11-05T09:23:46.540Z
Learning: In the munich-quantum-toolkit/core repository, the `qasm3::Importer::imports()` function is backwards compatible with OpenQASM 2 programs. Therefore, it can be used to parse both QASM2 and QASM3 program formats without requiring separate importers for each version.

Applied to files:

  • bindings/hybrid_mapper/hybrid_mapper.cpp
  • python/mqt/qmap/hybrid_mapper.pyi
📚 Learning: 2025-12-02T07:37:46.860Z
Learnt from: MatthiasReumann
Repo: munich-quantum-toolkit/core PR: 1301
File: mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp:144-151
Timestamp: 2025-12-02T07:37:46.860Z
Learning: In MLIR transformation code (mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp and similar), when inserting operations before a target operation, prefer `rewriter.setInsertionPoint(op)` over `rewriter.setInsertionPointAfter(op->getPrevNode())`. The former is cleaner, avoids null pointer edge cases (when op is first in block), and is semantically clearer.

Applied to files:

  • src/hybridmap/HybridNeutralAtomMapper.cpp
  • src/hybridmap/MoveToAodConverter.cpp
  • include/hybridmap/NeutralAtomUtils.hpp
📚 Learning: 2025-10-07T15:58:19.247Z
Learnt from: MatthiasReumann
Repo: munich-quantum-toolkit/core PR: 1237
File: mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Planner.h:202-214
Timestamp: 2025-10-07T15:58:19.247Z
Learning: In the QMAP routing algorithm (mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Planner.h), the heuristic function h() intentionally uses a non-admissible heuristic that sums per-gate shortest-path distances. This is a deliberate design choice from the QMAP paper, where admissibility is dropped because locally optimal solutions can negatively affect the overall routing quality. This approach prioritizes global solution quality over guaranteed local optimality.

Applied to files:

  • src/hybridmap/HybridNeutralAtomMapper.cpp
📚 Learning: 2025-11-05T07:42:45.507Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1287
File: test/qdmi/dd/error_handling_test.cpp:118-194
Timestamp: 2025-11-05T07:42:45.507Z
Learning: In the munich-quantum-toolkit/core QDMI device API, session parameters can only be set before calling `device_session_init()`. Once a session is initialized, any attempt to set a parameter returns `QDMI_ERROR_BADSTATE`. Since `SessionGuard` (in test/qdmi/dd/helpers/test_utils.hpp) automatically initializes the session in its constructor, tests that need to verify session parameter setting behavior before initialization must allocate a separate uninitialized session rather than reusing the `SessionGuard`'s session.

Applied to files:

  • test/hybridmap/test_hybridmap.cpp
  • include/hybridmap/HybridNeutralAtomMapper.hpp
📚 Learning: 2025-11-24T10:19:41.147Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1326
File: python/mqt/core/__init__.py:22-22
Timestamp: 2025-11-24T10:19:41.147Z
Learning: In the munich-quantum-toolkit/core repository, Ruff is configured with 'ALL' rules enabled by default, and only specific rules are selectively disabled. When reviewing changes that enable previously-disabled rules (like PLC0415), noqa directives for those rules become necessary and should be retained.

Applied to files:

  • include/hybridmap/HybridNeutralAtomMapper.hpp
🧬 Code graph analysis (14)
src/hybridmap/HybridSynthesisMapper.cpp (2)
include/hybridmap/HybridSynthesisMapper.hpp (4)
  • synthesisSteps (130-131)
  • qc (56-56)
  • qc (137-137)
  • qc (143-143)
include/hybridmap/HybridNeutralAtomMapper.hpp (8)
  • qc (549-556)
  • qc (549-550)
  • qc (558-563)
  • qc (558-559)
  • qc (571-571)
  • qc (678-678)
  • op (134-134)
  • op (385-386)
include/hybridmap/HybridSynthesisMapper.hpp (3)
include/hybridmap/Mapping.hpp (4)
  • Mapping (61-61)
  • Mapping (66-70)
  • Mapping (66-66)
  • Mapping (81-107)
include/hybridmap/NeutralAtomArchitecture.hpp (15)
  • nodiscard (97-99)
  • nodiscard (103-103)
  • nodiscard (107-107)
  • nodiscard (111-111)
  • nodiscard (115-117)
  • nodiscard (121-123)
  • nodiscard (127-129)
  • nodiscard (133-135)
  • nodiscard (139-139)
  • nodiscard (161-166)
  • nodiscard (231-231)
  • nodiscard (236-238)
  • nodiscard (243-245)
  • maybe_unused (250-252)
  • maybe_unused (591-596)
include/hybridmap/NeutralAtomUtils.hpp (2)
  • maybe_unused (67-81)
  • maybe_unused (90-102)
test/python/hybrid_mapper/test_hybrid_synthesis.py (2)
noxfile.py (1)
  • qiskit (139-147)
python/mqt/qmap/hybrid_mapper.pyi (9)
  • NeutralAtomHybridArchitecture (50-79)
  • init_mapping (114-114)
  • evaluate_synthesis_steps (107-109)
  • append_with_mapping (103-103)
  • append_without_mapping (104-104)
  • save_mapped_qc_qasm (115-115)
  • convert_to_aod (106-106)
  • get_synthesized_qc (113-113)
  • get_circuit_adjacency_matrix (110-110)
src/hybridmap/NeutralAtomLayer.cpp (1)
include/hybridmap/NeutralAtomLayer.hpp (5)
  • qubitsToUpdate (57-57)
  • qubitsToUpdate (67-67)
  • qubitsToUpdate (76-76)
  • commutesWithAtQubit (138-139)
  • commuteAtQubit (150-151)
include/hybridmap/HybridAnimation.hpp (1)
src/hybridmap/HybridAnimation.cpp (2)
  • initPositions (30-57)
  • initPositions (30-32)
include/hybridmap/NeutralAtomScheduler.hpp (2)
include/hybridmap/NeutralAtomArchitecture.hpp (14)
  • nodiscard (97-99)
  • nodiscard (103-103)
  • nodiscard (107-107)
  • nodiscard (111-111)
  • nodiscard (115-117)
  • nodiscard (121-123)
  • nodiscard (127-129)
  • nodiscard (133-135)
  • nodiscard (139-139)
  • nodiscard (161-166)
  • nodiscard (231-231)
  • nodiscard (236-238)
  • nodiscard (243-245)
  • shuttlingSpeedFactor (584-584)
src/hybridmap/NeutralAtomArchitecture.cpp (2)
  • getAnimationMachine (265-319)
  • getAnimationMachine (265-266)
src/hybridmap/HardwareQubits.cpp (1)
include/hybridmap/HardwareQubits.hpp (11)
  • q1 (78-78)
  • q1 (154-154)
  • q1 (281-282)
  • q (300-300)
  • q (309-309)
  • hwQubit (179-179)
  • hwQubit (187-209)
  • hwQubit (187-187)
  • idx (167-167)
  • idx (317-317)
  • idx (325-325)
src/hybridmap/HybridNeutralAtomMapper.cpp (3)
include/hybridmap/HardwareQubits.hpp (11)
  • op (215-220)
  • op (215-215)
  • q (300-300)
  • q (309-309)
  • qubit (70-70)
  • qubit (228-228)
  • qubits (332-332)
  • qubits (363-363)
  • q1 (78-78)
  • q1 (154-154)
  • q1 (281-282)
include/hybridmap/Mapping.hpp (10)
  • op (199-204)
  • op (199-199)
  • qubit (113-115)
  • qubit (113-113)
  • qubit (124-124)
  • qubit (170-170)
  • qubit (187-187)
  • qubits (136-136)
  • qubits (152-152)
  • swap (213-213)
src/hybridmap/Mapping.cpp (2)
  • applySwap (27-42)
  • applySwap (27-27)
python/mqt/qmap/hybrid_mapper.pyi (3)
python/mqt/qmap/sc.pyi (3)
  • name (109-109)
  • name (173-173)
  • map (258-270)
test/hybridmap/test_hybridmap.cpp (1)
  • params (79-88)
bindings/sc/sc.cpp (2)
  • map (50-78)
  • map (51-52)
test/hybridmap/test_hybridmap.cpp (2)
include/hybridmap/NeutralAtomArchitecture.hpp (1)
  • NeutralAtomArchitecture (216-216)
include/hybridmap/HybridNeutralAtomMapper.hpp (7)
  • NeutralAtomMapper (447-447)
  • NeutralAtomMapper (448-469)
  • NeutralAtomMapper (448-449)
  • NeutralAtomMapper (470-472)
  • NeutralAtomMapper (470-471)
  • p (480-493)
  • p (480-480)
src/hybridmap/NeutralAtomScheduler.cpp (2)
include/hybridmap/HybridAnimation.hpp (2)
  • initHwPos (53-54)
  • op (94-95)
include/hybridmap/NeutralAtomArchitecture.hpp (5)
  • shuttlingSpeedFactor (584-584)
  • op (329-329)
  • op (335-335)
  • op (342-342)
  • c (418-418)
include/hybridmap/NeutralAtomArchitecture.hpp (1)
src/hybridmap/NeutralAtomArchitecture.cpp (2)
  • getAnimationMachine (265-319)
  • getAnimationMachine (265-266)
include/hybridmap/HybridNeutralAtomMapper.hpp (3)
include/hybridmap/HardwareQubits.hpp (5)
  • op (215-220)
  • op (215-215)
  • HardwareQubits (88-88)
  • HardwareQubits (100-143)
  • HardwareQubits (100-103)
include/hybridmap/Mapping.hpp (6)
  • op (199-204)
  • op (199-199)
  • Mapping (61-61)
  • Mapping (66-70)
  • Mapping (66-66)
  • Mapping (81-107)
test/hybridmap/test_hybridmap.cpp (5)
  • mapper (94-94)
  • mapper (227-227)
  • mapper (263-263)
  • mapper (272-272)
  • mapper (285-285)
include/hybridmap/NeutralAtomUtils.hpp (1)
src/hybridmap/NeutralAtomUtils.cpp (4)
  • computeBridgeCircuit (132-148)
  • computeBridgeCircuit (132-132)
  • computeGates (110-130)
  • computeGates (110-110)
🪛 GitHub Check: 🇨‌ Lint / 🚨 Lint
src/hybridmap/HybridNeutralAtomMapper.cpp

[warning] 32-32: src/hybridmap/HybridNeutralAtomMapper.cpp:32:1 [misc-include-cleaner]
included header iostream is not used directly


[warning] 31-31: src/hybridmap/HybridNeutralAtomMapper.cpp:31:1 [misc-include-cleaner]
included header iomanip is not used directly

⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: 🐍 Test (macos-15-intel) / 🐍 macos-15-intel
  • GitHub Check: 🐍 Test (windows-2022) / 🐍 windows-2022
🔇 Additional comments (79)
src/hybridmap/NeutralAtomArchitecture.cpp (5)

76-91: Fallback gate handling looks good.

The lambda ensureGateWithFallback properly validates that required gates (cz, h) exist in the timing/fidelity maps, falling back to "none" if available or throwing a clear error otherwise. This ensures the downstream SWAP and Bridge calculations won't fail due to missing entries.


115-132: Bridge gate precomputation bounds check properly implemented.

The bounds check using maxIdx = std::min({...}) and std::min<std::size_t>(10, maxIdx) addresses the previous concern about unconditional indexing. This ensures safe access to bridgeCircuits arrays.


265-271: Positive guard for shuttlingSpeedFactor is correct.

The validation throws with a clear message if shuttlingSpeedFactor <= 0, preventing division-by-zero and nonsensical negative speed factors.


334-348: Phase normalization and loop bounds are correct.

The iteration for (size_t i = 1; i < nQubits; ++i) safely handles the nQubits == 0 case, and the fmod-based phase normalization is clearer and more efficient than the previous loop.


376-392: Ancilla qubit blocking logic is correct.

The while loop properly maps ancilla qubits (which may be indexed beyond getNpositions()) back to their primary coordinate positions. Adding blocked indices at offsets i + getNpositions() and i + 2*getNpositions() correctly reserves space in the ancilla zones for the flying ancilla support mentioned in the PR objectives.

python/mqt/qmap/hybrid_mapper.pyi (3)

24-48: MapperParameters and MapperStats type definitions are well-structured.

The parameter and stats classes expose clear, typed attributes. The naming follows Python conventions (snake_case), and the types are appropriate for the data they represent.


81-98: HybridNAMapper API surface is well-defined.

The method naming clearly distinguishes QASM string returns (get_mapped_qc_qasm, get_mapped_qc_aod_qasm) from other accessors. The schedule method's parameter types (typing.SupportsFloat for shuttling_speed_factor) allow flexible numeric input from Python.


100-121: HybridSynthesisMapper stubs align with C++ API.

The synthesis mapper exposes a comprehensive API for synthesis workflow: evaluation (evaluate_synthesis_steps), appending (append_with_mapping, append_without_mapping), remapping (complete_remap), and I/O (save_*, get_*). Return types are consistent with HybridNAMapper.

test/python/hybrid_mapper/test_hybrid_synthesis.py (3)

25-34: Module-level circuit creation is acceptable for test setup.

Creating qc1 and qc2 at module level via load(qc_qiskit) is a common pattern for test fixtures. Since these are simple 3-qubit circuits used across multiple tests, this avoids redundant construction overhead.


70-96: Test properly uses tmp_path for file artifacts.

The test correctly uses pytest's tmp_path fixture for all output files, ensuring automatic cleanup and avoiding pollution of the source tree.


127-131: Keep-alive test validates object lifetime correctly.

This test ensures the HybridSynthesisMapper created via a helper function remains valid after the helper returns. The lack of explicit assertions is intentional—the test passes if no crash occurs, verifying correct object ownership across the Python/C++ boundary.

src/hybridmap/Mapping.cpp (2)

27-42: applySwap correctly uses const reference.

The signature change to const Swap& swap and the use of local const variables (q1, q2) improves const-correctness and avoids unnecessary copies.


117-187: Graph matching algorithm is correctly implemented.

The algorithm uses a greedy approach: starting from the highest-degree circuit qubit mapped to the most-connected hardware qubit, it propagates mappings by minimizing weighted distances to already-mapped neighbors. The implementation correctly handles edge cases (empty queue, no free hardware qubits) with appropriate exceptions.

src/hybridmap/HybridSynthesisMapper.cpp (4)

31-68: evaluateSynthesisSteps correctly handles edge cases.

The implementation properly addresses previous concerns:

  • Empty input guard returns early (lines 34-36)
  • Evaluation order is deterministic via separate fidelity variable (line 47)
  • alsoMap guard checks !candidates.empty() (line 64)
  • Logging uses consistent ++qcIndex at loop end (line 52)

70-80: evaluateSynthesisStep correctly preserves input.

Making a copy (auto qcCopy = qc) before calling tempMapper.map(qcCopy, mapping) ensures the original circuit is not mutated, addressing the previous const-correctness concern.


93-101: Verify whether synthesizedQc should contain mapped or unmapped operations.

appendWithMapping calls mapAppend(qc, mapping) which mutates qc (MCX→MCZ conversion, fusion, etc.), then clones the resulting operations into synthesizedQc. However, synthesizedQc is described as the "unmapped" circuit in the header.

If synthesizedQc is intended to hold the original unmapped circuit, you should clone qc before calling mapAppend:

 void HybridSynthesisMapper::appendWithMapping(qc::QuantumComputation& qc) {
   if (mappedQc.empty()) {
     initMapping(qc.getNqubits());
   }
+  // Clone original ops before mapping mutates qc
+  for (const auto& op : qc) {
+    synthesizedQc.emplace_back(op->clone());
+  }
   mapAppend(qc, mapping);
-  for (const auto& op : qc) {
-    synthesizedQc.emplace_back(op->clone());
-  }
 }

Please verify the intended semantics of synthesizedQc.


103-129: Adjacency matrix construction correctly fills symmetric entries.

The implementation now explicitly sets both adjMatrix(i, j) and adjMatrix(j, i) for each pair, ensuring the matrix is symmetric regardless of AdjacencyMatrix's internal storage model.

include/hybridmap/HybridSynthesisMapper.hpp (3)

66-69: Constructor correctly delegates to base class.

The explicit constructor properly initializes the NeutralAtomMapper base class with architecture and parameters.


91-94: completeRemap correctly preserves synthesizedQc.

Making a local copy before calling map() ensures the canonical unmapped circuit in synthesizedQc is not mutated, allowing completeRemap() to be called multiple times with different initial mappings.


130-149: Public API methods handle initialization gracefully.

Methods like evaluateSynthesisSteps and appendWith* implicitly initialize if needed (checking !initialized or mappedQc.empty()), while getCircuitAdjacencyMatrix throws if not initialized. This provides flexibility while preventing misuse.

src/hybridmap/NeutralAtomLayer.cpp (3)

42-68: Front-layer vs. lookahead branching logic is clear and well-structured.

The two code paths are cleanly separated:

  • Front-layer mode advances while operations commute with existing gates and candidates
  • Lookahead mode collects up to lookaheadDepth multi-qubit gates per qubit

The logic correctly stops advancing on the first non-commuting operation in front-layer mode.


129-135: LGTM! Free function for layer-wide commutation check.

The commutesWithAtQubit free function properly uses std::ranges::all_of to check commutation against all operations in a layer.


161-176: The current code using std::ranges::find for target membership checks is correct and cannot be changed to contains(). getControls() and getTargets() return different container types—controls is a set-like container with a contains() method, while targets is a vector-like container without one. Using std::ranges::find on targets is the appropriate idiom for vector membership checks.

Likely an incorrect or invalid review comment.

include/hybridmap/HybridAnimation.hpp (2)

24-33: Good documentation of lifetime contract.

The class docstring now explicitly documents that "The architecture reference passed to the constructor must remain valid for the lifetime of this object." This addresses the previous feedback about documenting the non-owning pointer lifetime requirement.


71-95: Well-documented opToNaViz contract.

The documentation now explicitly covers supported operation types (AOD load/store/move, multi-qubit gates, single-qubit gates) and states that other operations are not currently supported. This addresses prior feedback about clarifying the operation handling contract.

include/hybridmap/NeutralAtomDefinitions.hpp (1)

79-110: AtomMove struct with comparison operators looks good.

The struct now includes operator< using std::tie for lexicographic ordering, addressing the previous feedback. This enables use in ordered containers like std::set and std::map.

include/hybridmap/NeutralAtomLayer.hpp (1)

138-151: LGTM! Well-documented free functions for commutation checks.

The commutesWithAtQubit and commuteAtQubit free functions are clearly documented with their commutation rules, including handling of non-unitaries, identities, single-qubit gates, and two-qubit control/target patterns.

include/hybridmap/Mapping.hpp (2)

81-92: Good: Added validation for nQubits parameter.

The constructor now validates that nQubits does not exceed qc.getNqubits(), addressing the previous concern about potential out-of-bounds access in the Graph branch. The parameter is also now passed by reference, avoiding an unnecessary copy.


144-159: LGTM! New vector-based getHwQubits overload.

The new overload preserves input order, which is useful when the caller needs ordered results. Good use of reserve() to avoid reallocations.

include/hybridmap/NeutralAtomScheduler.hpp (2)

37-95: SchedulerResults and arch pointer usage look sound.

The extended SchedulerResults (AOD counters, ctor, toMap) and the switch from a reference member to a non-owning const NeutralAtomArchitecture* with a deleted default constructor cleanly address earlier UB risks around uninitialized architectures. The API surface (ctor + schedule return type) is consistent and backwards-compatible at the call sites.


142-183: Guarded animation saving avoids empty artifacts; behavior is consistent.

saveAnimationFiles now checks animation.empty() || animationMachine.empty() and logs a warning instead of blindly writing files, and the docstring matches the actual .naviz/.namachine outputs. This prevents confusing empty files when schedule(..., createAnimationCsv=true, ...) was not called, while intentionally requiring both artifacts to be present before writing.

src/hybridmap/HardwareQubits.cpp (2)

91-135: Shortest‑paths computation for bridges is correct and self‑limiting.

computeAllShortestPaths correctly uses BFS over getNearbyQubits to enumerate all simple paths of minimal length between two qubits and stops exploring longer paths once a shortest length is known. This is logically sound for bridge selection; the potential combinatorial blowup is acceptable in this narrow use (front‑layer gates only).


137-180: move() + getClosestQubit now maintain a consistent, safe hardware view.

The new move implementation keeps occupiedCoordinates, freeCoordinates, nearbyQubits, and hwToCoordIdx in sync and validates coordinates against arch->getNpositions(). Together with resetSwapDistances(), this ensures swap distances reflect the updated layout.

getClosestQubit no longer returns 0 as a silent default when no non‑ignored qubit exists; instead it tracks whether any candidate was found and throws a std::runtime_error if not. This resolves the earlier latent correctness bug where an invalid qubit could be selected and used downstream.

Also applies to: 297-318

src/hybridmap/HybridNeutralAtomMapper.cpp (5)

47-125: mapAppend and initial layering flow are consistent with new features.

The mapAppend pipeline (pre‑optimization, multi‑qubit‑gate detection, ancilla/flying‑ancilla register provisioning, DAG construction, initial gate mapping, and the front/lookahead layer loop) is structurally sound. Reusing mappedQc with conditional ancilla-register creation cleanly supports incremental mapping, and the multiQubitGates flag correctly disables bridge/flying‑ancilla paths when >2‑qubit ops are detected.

Also applies to: 127-138


187-235: Pass‑by application keeps logical↔hardware state coherent.

applyPassBy updates only the mapped circuit (mappedQc.move) and the per‑gate coordinate lists, leaving mapping and hardwareQubits untouched. That matches the intended semantics of pass‑by being an AOD‑level maneuver only. The use of the ancilla register [npositions, 2*npositions) and the restoration moves at the end keeps the qubit layout consistent.


548-638: Bridge selection and coordinate usage tracking now look robust.

findBestBridge + getShortestBridges correctly constrain bridges by maxBridgeDistance, use all shortest paths from HardwareQubits::computeAllShortestPaths, and break ties by summing usage counts from computeCurrentCoordUsages(). The latter now sizes coordUsages as 3 * arch->getNpositions() and accounts for front‑layer gates, recently mapped gates, and the latest blocked‑qubit set, which resolves the earlier risk of indexing beyond the logical register space.


639-703: Flying‑ancilla / pass‑by conversion logic matches intended semantics.

convertMoveCombToFlyingAncillaComb and convertMoveCombToPassByComb correctly filter moves to those touching the gate’s coordinates, respect the current set of flying ancillas and the usePassBy flag, and construct combinations that align with the later application routines (moving via FA register vs. pass‑by ancilla register). Aside from the indexing issue in applyFlyingAncilla itself, the construction of these combs is coherent.


705-736: Swap and move distance reduction helpers are consistent with cost model.

swapDistanceReduction and moveCombDistanceReduction both compute total distance reduction per layer by comparing pre‑ and post‑move getAllToAllSwapDistance / getAllToAllEuclideanDistance values, mutating a local copy of the qubit set/coord indices. Skipping swaps that touch both endpoints of a gate is intentional and keeps the model simple. The new helpers nicely factor out logic used by swapCost and the dynamic mapping heuristics.

Also applies to: 737-786

src/hybridmap/NeutralAtomScheduler.cpp (1)

34-66: Scheduler refactor and metric extensions look correct.

The reworked schedule correctly sizes coord‑indexed arrays as 3 * arch->getNpositions(), uses arch->getOpTime()/getOpFidelity() and getBlockedCoordIndices() for timing and Rydberg‑blocking, and tracks nCZs, nAodActivate, and nAodMove alongside idle and decoherence effects. The optional AnimationAtoms plus getAnimationMachine(shuttlingSpeedFactor) integrate cleanly with the new animation strings, and printSchedulerResults mirrors the extended metrics.

Also applies to: 78-181

bindings/hybrid_mapper/hybrid_mapper.cpp (3)

44-95: MapperParameters and MapperStats bindings are well‑structured.

The expanded MapperParameters and new MapperStats Python bindings cleanly expose all relevant tuning knobs and counters with clear, accurate docstrings. The default constructor plus def_readwrite fields match the C++ layout and make the parameter space discoverable from Python.


109-224: HybridNAMapper binding now has correct lifetimes and clearer I/O.

The HybridNAMapper ctor and set_parameters now use py::keep_alive to match the mapper’s non‑owning pointers to NeutralAtomArchitecture and MapperParameters, eliminating prior dangling‑pointer risks from temporaries. The mapping/scheduling interface (map, map_qasm_file, get_stats, get_mapped_qc_qasm, get_mapped_qc_aod_qasm, save_mapped_qc_aod_qasm, schedule) is coherent, and the animation bindings correctly describe .naviz/.namachine outputs and expose the .naviz content directly.


226-321: HybridSynthesisMapper Python API is comprehensive and consistent.

The new HybridSynthesisMapper bindings mirror the C++ API, including parameter lifetime management, synthesized/mapped QASM getters/savers, incremental append (with/without mapping), adjacency export, synthesis‑step evaluation, complete remap, and scheduling. The adjacency‑matrix lambda now pre‑sizes the std::vector<std::vector<int>> and uses size_t with proper includes, and the docstrings clearly distinguish circuit‑level adjacency from hardware connectivity. Overall, this provides a well‑designed Python surface for the hybrid synthesis workflow.

include/hybridmap/HardwareQubits.hpp (4)

44-52: LGTM: Protected member additions are appropriate.

The new protected members (nQubits, freeCoordinates, occupiedCoordinates) properly support the expanded functionality. The arch pointer defaulting to nullptr is acceptable given the explicit constructor requirement.


100-143: Constructor logic is well-structured.

The initialization correctly handles both trivial and random mappings, populating occupiedCoordinates during assignment and deriving freeCoordinates afterward. The assert for bounds checking is acceptable for internal library use.

One minor note: the assert only fires in debug builds. If this class could be constructed with invalid parameters in production, consider a runtime check.


187-209: Correctly fixed removeHwQubit implementation.

The implementation now properly:

  1. Captures the current coordinate before erasing mappings.
  2. Removes the coordinate from occupiedCoordinates.
  3. Adds it to freeCoordinates only if not already present.
  4. Invalidates swap distances and cleans up nearby qubit references.

This addresses the critical issues identified in past reviews.


167-170: Linear search in isMapped is acceptable for typical architectures.

Using std::ranges::find on a vector is O(n), but given neutral atom architectures typically have a limited number of positions, this should not be a performance concern. If architectures grow significantly larger, consider using std::set for occupiedCoordinates.

test/hybridmap/test_hybridmap.cpp (4)

91-122: Parameterized mapping test provides good coverage.

The MapCircuitsIdentity test exercises the mapper with various architecture, circuit, and parameter combinations. The assertions on qcMapped.size(), scheduleResults.totalFidelities, totalIdleTime, and totalExecutionTime provide reasonable coverage.


167-216: Output test comprehensively covers I/O and statistics.

The test properly:

  • Uses temporary directory for output files.
  • Verifies file existence and non-zero size.
  • Cleans up files after assertions.
  • Checks various output methods (getMappedQcQasm, getMappedQcAodQasm, getStats, getStatsMap, getAnimationViz).

This addresses previous review concerns about temp file handling.


220-277: Exception tests properly validate error conditions.

The tests correctly verify:

  • Circuit size exceeding hardware capacity throws.
  • Multiple flying ancillas (>1) throws at construction.
  • Shuttling weight > 0 without free coordinates throws.
  • Unmappable multi-qubit gates throw.

EXPECT_THROW with explicit std::runtime_error typing is appropriate.


279-290: LongShuttling stress test intentionally lacks assertions.

Per previous discussion, this test is designed to catch rare edge cases during extended shuttling sequences. The lack of explicit assertions is intentional—the test passes if no exception is thrown. Based on learnings from past review.

Consider adding a brief comment like // Stress test: exercises rare edge cases in extended shuttling sequences for future maintainers.

include/hybridmap/NeutralAtomUtils.hpp (5)

60-102: Parsing functions now have clear error messages.

The error messages now include the expected values (e.g., "trivial"/"0" or "random"/"1"), addressing past review feedback. The case-sensitive behavior is appropriately documented.


110-212: Direction and MoveVector structs are well-designed.

Both structs have clear semantics, appropriate const-correctness, and comprehensive documentation. The MoveVector methods (sameDirection, getLength, overlap, include) provide useful geometric predicates for move analysis.


235-252: FlyingAncillaComb and PassByComb now have safe default initialization.

The op pointers now default to nullptr, addressing the past review concern about uninitialized pointers. This prevents accidental use of indeterminate values.


259-325: MoveComb implementation is correct.

Key design choices:

  • Equality comparison intentionally ignores cost, op, and bestPos to focus on move sequence identity.
  • append methods reset cost to max() to force recomputation.
  • empty() and size() delegate correctly to the underlying vector.

406-433: BridgeCircuits precomputation is appropriate for expected use.

The eager precomputation of bridge circuits for lengths [3, maxLength) is acceptable given the author's confirmation that maxLength is typically below 5 in practice. For larger values, lazy initialization could be considered, but current design is sufficient.

include/hybridmap/HybridNeutralAtomMapper.hpp (7)

44-60: MapperParameters is well-structured with sensible defaults.

The parameter struct provides clear configuration options with reasonable default values. The limitShuttlingLayer = 10 default is documented here; ensure Python binding docstrings match if they claim "unlimited" (per past review feedback on bindings file).


62-71: MapperStats provides useful runtime counters.

The counters for different routing strategies (nSwaps, nBridges, nFAncillas, nMoves, nPassBy) enable performance analysis and debugging.


447-472: Constructor correctly stores parameters by value, fixing past lifetime issues.

The critical issues from past reviews are resolved:

  1. MapperParameters is now stored by value (parameters member), not as a pointer.
  2. Signed arithmetic (static_cast<int>) prevents unsigned underflow in the free-coords check.
  3. Both constructors validate constraints (shuttling/flying ancillas) before proceeding.

The delegation from the reference-based constructor to the pointer-based one is safe since &architecture yields a stable pointer.


480-493: setParameters now has consistent validation with constructor.

The method:

  1. Stores parameters by value (safe).
  2. Validates the shuttling constraint using signed arithmetic.
  3. Validates numFlyingAncillas <= 1 (addressing past review that this check was missing).
  4. Resets internal state after parameter update.

500-510: copyStateFrom now includes flyingAncillas per past review.

The method correctly copies all relevant state including flyingAncillas, addressing the previous omission that could have caused stale ancilla state issues.


558-563: Unused DAG construction removed per past review.

The map(qc, InitialMapping) overload now directly constructs the Mapping without the redundant DAG computation that was flagged in past reviews.


594-596: getMappedQc now returns const reference per past review.

Returning a const reference avoids the expensive deep copy that was previously performed when returning by value.

src/hybridmap/MoveToAodConverter.cpp (5)

69-81: convertOpToMove correctly handles multi-region qubit indices.

The method extracts load flags before normalizing qubit indices, ensuring correct interpretation of flying ancilla vs. data qubit moves. The while loops safely reduce indices to the valid range.


82-116: initFlyingAncillas correctly initializes flying ancilla positions.

The method properly:

  • Early returns if no ancillas are configured.
  • Computes coordinate positions including offsets based on architecture parameters.
  • Emits a single AodActivate operation to initialize all ancillas.

537-549: Iterator safety issue fixed per past review.

The target qubit ordering logic now safely inserts at pos instead of pos - 1, avoiding undefined behavior when pos == targetQubits.begin(). The fix maintains the intended "move away before used" ordering.


437-493: processMoves and processMovesFa have correct control flow.

The methods properly:

  • Check activation compatibility for both X and Y dimensions.
  • Fall back to creating a new move group when activation is impossible.
  • Separate flying ancilla moves (processMovesFa) from regular moves.

Note: These methods rely on canAddActivation, which has the unsigned underflow issue noted in the separate comment.


666-726: AOD operation generation handles activation/deactivation symmetry correctly.

The implementation:

  • Builds operations only for moves with the appropriate load flag.
  • Returns empty vector when no operations needed (lines 704-706).
  • Reverses operation order for deactivation to maintain correct sequence.
include/hybridmap/MoveToAodConverter.hpp (5)

13-29: LGTM!

The include list is clean and appropriately organized. The removal of <sys/types.h> and use of <cstdint> instead addresses the previous portability concerns.


31-41: LGTM!

The ActivationMergeType enum and MergeTypeXY alias are well-designed and properly documented. The enum class provides type safety, and the pair abstraction for per-dimension merge types is clean.


51-67: LGTM!

The AncillaAtom struct properly uses std::uint32_t for portable fixed-width integers, addressing the previous review feedback. The design cleanly separates coordinate and offset tracking for both original and dodged positions.


135-148: LGTM!

The AodActivationHelper constructor correctly stores the architecture as a pointer and properly deletes copy/move constructors. The pointer storage is appropriate for a helper class whose lifetime is managed by the parent MoveToAodConverter.


287-296: LGTM!

The assertion addresses the previous review concern about potential UB when both containers are empty. The logic correctly handles all three cases: only moves populated, only movesFa populated, or both populated.

include/hybridmap/NeutralAtomArchitecture.hpp (7)

82-92: LGTM!

The Properties constructor properly validates aodDist > 0 before computing nAodIntermediateLevels, addressing the previous concern about potential division by zero in getOffsetDistance(). The assertion that nAodIntermediateLevels >= 1 provides additional safety.


161-166: LGTM!

The tEff() implementation correctly handles edge cases. When both T1 and T2 are zero, it returns 0 to avoid division by zero. When only one is zero, the formula naturally returns 0, which is physically correct (if either decoherence mechanism is infinitely fast, effective time is zero).


184-185: LGTM!

The comment now correctly documents that bridge circuits are computed for distances 3-9, which matches the BridgeCircuits constructor logic (iterates from 3 to maxLength-1 = 9).


353-376: LGTM!

The diagnostic output now uses SPDLOG_WARN instead of std::cout, addressing the previous review feedback about avoiding console output in library code. The fallback behavior and potential exception from missing "none" entry are properly documented.


427-431: LGTM!

The assertion at line 429 addresses the previous review concern about potential out-of-bounds access. This provides debug-time safety while keeping release builds lean.


462-503: LGTM!

The new distance helper methods (getMoveCombEuclideanDistance, getFaEuclideanDistance, getPassByEuclideanDistance) provide clean abstractions for computing path lengths of different move combinations. The formulas correctly account for the geometric characteristics of each move type.


583-596: LGTM!

The animation export methods are well-designed. The getAnimationMachine implementation (per the relevant code snippet) properly validates that shuttlingSpeedFactor > 0 and throws a clear error otherwise. The file-writing wrapper is appropriately simple.

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: 10

Caution

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

⚠️ Outside diff range comments (2)
src/hybridmap/NeutralAtomLayer.cpp (1)

129-178: Commutation logic is comprehensive and correct.

The free functions implement reasonable commutation rules:

  • Non-unitaries never commute (line 139-141)
  • Single-qubit gates always commute with each other (lines 143-145)
  • Identity gates commute with everything (lines 147-149)
  • Operations not using the qubit commute (lines 152-156)
  • Two controlled gates commute at control qubit (lines 161-164)
  • Control and Z commute (lines 166-169)
  • Same gate type on same target commutes (lines 172-176)

Minor note: Line 172-174 uses std::ranges::find on getTargets() which returns a container that likely has a contains method like getControls() does at lines 161-162, 166-167. Consider using contains for consistency if available.

-  if (std::ranges::find(op1->getTargets(), qubit) != op1->getTargets().end() &&
-      std::ranges::find(op2->getTargets(), qubit) != op2->getTargets().end() &&
+  // If getTargets() supports contains():
+  if (op1->getTargets().contains(qubit) &&
+      op2->getTargets().contains(qubit) &&
       op1->getType() == op2->getType()) {
src/hybridmap/HardwareQubits.cpp (1)

224-242: Replace hwToCoordIdx.maxKey() with nQubits to include all hardware qubits

The loop condition uses hwToCoordIdx.maxKey() which excludes the last qubit from blocking analysis. Given the initialization pattern where keys range from 0 to nQubits-1, the loop should be for (uint32_t i = 0; i < nQubits; ++i) to match patterns used elsewhere (e.g., initTrivialSwapDistances).

♻️ Duplicate comments (16)
test/python/hybrid_mapper/test_hybrid_synthesis.py (2)

56-60: Extend fidelity bounds assertions to both elements.

The test checks bounds for best_circuit[0] but not best_circuit[1]. Since both are fidelity values, both should be validated.

     assert best_circuit[0] <= 1
     assert best_circuit[0] >= 0
+    assert best_circuit[1] <= 1
+    assert best_circuit[1] >= 0

109-111: Simplify symmetry check with NumPy.

The nested loop can be replaced with a single NumPy assertion for clarity.

-    for i in range(circ_size):
-        for j in range(circ_size):
-            assert adj_mat[i, j] == adj_mat[j, i]
+    assert np.array_equal(adj_mat, adj_mat.T), "Adjacency matrix should be symmetric"
include/hybridmap/HybridSynthesisMapper.hpp (2)

118-122: saveSynthesizedQc doesn't throw on file errors despite documentation.

The implementation opens a file and writes without enabling exceptions or checking stream state. If the file cannot be opened or written, errors will be silently ignored.

Either enable exceptions to match expected throwing behavior:

 [[maybe_unused]] void saveSynthesizedQc(const std::string& filename) const {
   std::ofstream ofs(filename);
+  ofs.exceptions(std::ios::failbit | std::ios::badbit);
   synthesizedQc.dumpOpenQASM(ofs, false);
   ofs.close();
 }

Or add explicit error checking:

 [[maybe_unused]] void saveSynthesizedQc(const std::string& filename) const {
   std::ofstream ofs(filename);
+  if (!ofs) {
+    throw std::ios_base::failure("Failed to open file: " + filename);
+  }
   synthesizedQc.dumpOpenQASM(ofs, false);
   ofs.close();
 }

77-85: Consider documenting single-use semantics or adding reset capability.

initMapping reinitializes mappedQc, synthesizedQc, and mapping but doesn't reset inherited base-class state (e.g., stats, mappedQcAOD). If instances are reused across multiple synthesis runs, stale state could leak.

Either:

  • Document that HybridSynthesisMapper is intended for single-run use, or
  • Add a base-class reset call in initMapping
src/hybridmap/HybridSynthesisMapper.cpp (1)

93-101: appendWithMapping mutates its input circuit.

The function takes qc by non-const reference and passes it to mapAppend, which runs optimization passes that modify the circuit. This may surprise callers who expect evaluation-like behavior.

Consider either:

  1. Taking const qc::QuantumComputation& and working on a local copy, or
  2. Documenting that the input may be modified
-void HybridSynthesisMapper::appendWithMapping(qc::QuantumComputation& qc) {
+void HybridSynthesisMapper::appendWithMapping(const qc::QuantumComputation& qc) {
+  auto localQc = qc;
   if (mappedQc.empty()) {
-    initMapping(qc.getNqubits());
+    initMapping(localQc.getNqubits());
   }
-  mapAppend(qc, mapping);
-  for (const auto& op : qc) {
+  mapAppend(localQc, mapping);
+  for (const auto& op : localQc) {
     synthesizedQc.emplace_back(op->clone());
   }
 }
src/hybridmap/NeutralAtomLayer.cpp (1)

31-37: initAllQubits assumes dag.size() equals number of qubits.

This builds allQubits as 0..dag.size()-1. This is correct only if dag is indexed by logical qubit and dag.size() matches the qubit count. Consider documenting this invariant or using an explicit qubit count source.

include/hybridmap/NeutralAtomDefinitions.hpp (1)

43-54: HwPositions deprecation could use [[deprecated]] attribute.

HwPositions is marked [[maybe_unused]] with a comment indicating deprecation. For stronger compiler enforcement, [[deprecated("...")]] would generate warnings when used.

include/hybridmap/NeutralAtomLayer.hpp (1)

87-99: Constructor is well-designed; consider reserving ends vector.

The constructor properly:

  • Moves the DAG to avoid copies
  • Initializes mode and depth parameters
  • Reserves space for iterators and candidates

Minor: ends is not reserved like iterators and candidates. For consistency and to avoid small reallocations:

    iterators.reserve(dag.size());
    candidates.reserve(dag.size());
+   ends.reserve(dag.size());
    for (auto& i : dag) {
src/hybridmap/NeutralAtomScheduler.cpp (1)

34-64: Still no global validation of shuttlingSpeedFactor for non‑animation runs

The schedule path multiplies AOD op times by shuttlingSpeedFactor unconditionally, but getAnimationMachine only enforces a positive factor when createAnimationCsv is true. For robustness, it would still be valuable to validate shuttlingSpeedFactor > 0 at the start of schedule() and throw a clear error otherwise, so non‑animation use cannot silently produce zero or negative durations.

src/hybridmap/HybridNeutralAtomMapper.cpp (2)

405-474: Out-of-bounds risk in applyFlyingAncilla when multiple flying-ancilla moves exist

applyFlyingAncilla uses a shared allCoords vector and an index i advanced by 2 per move:

auto allCoords = targetCoords;
allCoords.insert(allCoords.end(), controlCoords.begin(), controlCoords.end());
uint32_t i = 0;
for (const auto& passBy : faComb.moves) {
  ...
  mappedQc.cz(allCoords[i], ancQ1);
  ...
  i += 2;
}
...
i = 0;
for (const auto& passBy : faComb.moves) {
  ...
  mappedQc.cz(allCoords[i], ancQ1);
  i += 2;
}

For 2‑qubit gates, allCoords.size() is 2. If faComb.moves.size() ever becomes ≥ 2 (e.g., both gate qubits generate flying‑ancilla moves in convertMoveCombToFlyingAncillaComb), the second iteration accesses allCoords[2], which is out of range.

Even if current heuristics typically produce at most one flying‑ancilla move per gate, this assumption is not enforced here and any future change to move generation could trigger undefined behavior.

Consider adding a hard guard before the loops:

const auto allCoords = /* as now */;
if (faComb.moves.size() * 2 > allCoords.size()) {
  throw std::runtime_error(
      "Invalid flying-ancilla combination: too many moves for available coords");
}

or otherwise restructuring the indexing so each FlyingAncilla explicitly carries the data‑qubit coordinate it couples to instead of relying on this i += 2 pattern.


185-233: Pass-by implementation is consistent; consider avoiding the extra clone

applyPassBy correctly:

  • maps op targets/controls through Mapping and HardwareQubits to coordinates,
  • shuttles data into the ancilla coord band [npos, 2*npos),
  • rewrites the cloned op’s coords, and
  • moves atoms back after the gate.

You still pay for two clones (pbComb.op->clone() and then opCopy->clone() into mappedQc). If mappedQc.emplace_back can take ownership of a std::unique_ptr<qc::Operation>, you could move the first clone directly to avoid the second allocation/copy.

Not critical, but an easy micro-optimization on a hot path.

include/hybridmap/NeutralAtomArchitecture.hpp (2)

22-35: Potentially unused includes remain.

Past review flagged <array>, <cmath>, <iterator>, and <utility> as unused. These should be verified and removed if not needed to reduce compilation overhead.

#!/bin/bash
# Check usage of types from potentially unused headers
echo "=== Checking std::array usage ==="
rg -n "std::array" include/hybridmap/NeutralAtomArchitecture.hpp

echo "=== Checking cmath function usage ==="
rg -n "std::(sqrt|pow|abs|floor|ceil|round|sin|cos|exp|log|hypot)" include/hybridmap/NeutralAtomArchitecture.hpp

echo "=== Checking iterator usage ==="
rg -n "std::(iterator|advance|distance|next|prev)" include/hybridmap/NeutralAtomArchitecture.hpp

echo "=== Checking utility usage ==="
rg -n "std::(pair|make_pair|move|forward|swap|exchange)" include/hybridmap/NeutralAtomArchitecture.hpp

573-576: getVectorShuttlingTime still uses OpType::Move instead of OpType::AodMove.

This issue was flagged in a past review. The JSON configurations define both "move" and "aod_move" with potentially different values (e.g., rubidium_hybrid.json has "move": 0.55 vs "aod_move": 0.3). Using OpType::Move here while other shuttling calculations use OpType::AodMove causes inconsistent timing results.

   [[nodiscard]] qc::fp getVectorShuttlingTime(const MoveVector& v) const {
     return v.getLength() * getInterQubitDistance() /
-           getShuttlingTime(qc::OpType::Move);
+           getShuttlingTime(qc::OpType::AodMove);
   }
src/hybridmap/MoveToAodConverter.cpp (1)

321-325: Unsigned subtraction for delta can still underflow.

This issue was flagged in a past review. The code computes:

const auto start = static_cast<std::uint32_t>(...);
const auto end = static_cast<std::uint32_t>(...);
const auto delta = static_cast<qc::fp>(end - start);

When end < start, the unsigned subtraction wraps to a large positive value before the cast to qc::fp. This produces incorrect results in the merge-type comparisons at lines 363-364.

Apply this diff to use signed arithmetic:

   const auto start =
       static_cast<std::uint32_t>(dim == Dimension::X ? origin.x : origin.y);
   const auto end =
       static_cast<std::uint32_t>(dim == Dimension::X ? final.x : final.y);
-  const auto delta = static_cast<qc::fp>(end - start);
+  const auto delta = static_cast<qc::fp>(
+      static_cast<std::int32_t>(end) - static_cast<std::int32_t>(start));
include/hybridmap/HybridNeutralAtomMapper.hpp (1)

612-615: Consider adding I/O error handling.

The saveMappedQcQasm method doesn't verify that the file opened successfully. While this was flagged in past review, it appears not fully addressed.

   [[maybe_unused]] void saveMappedQcQasm(const std::string& filename) const {
     std::ofstream ofs(filename);
+    if (!ofs) {
+      throw std::runtime_error("Failed to open file: " + filename);
+    }
     mappedQc.dumpOpenQASM(ofs, false);
   }
include/hybridmap/MoveToAodConverter.hpp (1)

356-363: Loop index type mismatch with container size.

The loop uses std::uint32_t i but compares against flyingAncillas.getInitHwPos().size() which returns std::size_t. On 64-bit systems, this is a narrowing comparison. Additionally, i + 1 used in AncillaAtom construction may need explicit casting for strict compilers.

-    for (std::uint32_t i = 0; i < flyingAncillas.getInitHwPos().size(); ++i) {
+    for (std::size_t i = 0; i < flyingAncillas.getInitHwPos().size(); ++i) {
       const auto coord =
           flyingAncillas.getInitHwPos().at(i) + (2 * arch.getNpositions());
       const auto col = coord % arch.getNcolumns();
       const auto row = coord / arch.getNcolumns();
-      const AncillaAtom ancillaAtom({col, row}, {i + 1, i + 1});
+      const AncillaAtom ancillaAtom(
+          {static_cast<std::uint32_t>(col), static_cast<std::uint32_t>(row)},
+          {static_cast<std::uint32_t>(i + 1), static_cast<std::uint32_t>(i + 1)});
       ancillas.emplace_back(ancillaAtom);
     }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6858379 and ffa0787.

📒 Files selected for processing (26)
  • CHANGELOG.md (3 hunks)
  • UPGRADING.md (1 hunks)
  • bindings/hybrid_mapper/hybrid_mapper.cpp (4 hunks)
  • docs/na_hybrid.md (1 hunks)
  • include/hybridmap/HardwareQubits.hpp (6 hunks)
  • include/hybridmap/HybridAnimation.hpp (1 hunks)
  • include/hybridmap/HybridNeutralAtomMapper.hpp (3 hunks)
  • include/hybridmap/HybridSynthesisMapper.hpp (1 hunks)
  • include/hybridmap/Mapping.hpp (3 hunks)
  • include/hybridmap/MoveToAodConverter.hpp (5 hunks)
  • include/hybridmap/NeutralAtomArchitecture.hpp (4 hunks)
  • include/hybridmap/NeutralAtomDefinitions.hpp (2 hunks)
  • include/hybridmap/NeutralAtomLayer.hpp (2 hunks)
  • include/hybridmap/NeutralAtomScheduler.hpp (3 hunks)
  • include/hybridmap/NeutralAtomUtils.hpp (5 hunks)
  • python/mqt/qmap/hybrid_mapper.pyi (3 hunks)
  • src/hybridmap/HardwareQubits.cpp (7 hunks)
  • src/hybridmap/HybridNeutralAtomMapper.cpp (30 hunks)
  • src/hybridmap/HybridSynthesisMapper.cpp (1 hunks)
  • src/hybridmap/Mapping.cpp (1 hunks)
  • src/hybridmap/MoveToAodConverter.cpp (15 hunks)
  • src/hybridmap/NeutralAtomArchitecture.cpp (8 hunks)
  • src/hybridmap/NeutralAtomLayer.cpp (4 hunks)
  • src/hybridmap/NeutralAtomScheduler.cpp (4 hunks)
  • test/hybridmap/test_hybridmap.cpp (4 hunks)
  • test/python/hybrid_mapper/test_hybrid_synthesis.py (1 hunks)
🧰 Additional context used
🧠 Learnings (17)
📓 Common learnings
Learnt from: lsschmid
Repo: munich-quantum-toolkit/qmap PR: 832
File: src/hybridmap/HybridNeutralAtomMapper.cpp:1679-1715
Timestamp: 2025-11-20T18:19:31.857Z
Learning: In src/hybridmap/HybridNeutralAtomMapper.cpp, the compareSwapAndBridge and compareShuttlingAndFlyingAncilla methods intentionally use division by zero to produce infinity when distance reductions are zero or negative. This infinity value marks that option as highly unfavorable in cost comparisons, which is the intended behavior rather than a bug.
📚 Learning: 2025-10-14T14:37:38.047Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/yaqs PR: 212
File: CHANGELOG.md:12-15
Timestamp: 2025-10-14T14:37:38.047Z
Learning: In the munich-quantum-toolkit/yaqs project, changelog entries follow the template: "- $TITLE ([#$NUMBER]($URL)) ([**AUTHOR**](https://github.com/$AUTHOR))". Issue references should not be included in changelog entries; the PR number is sufficient for traceability.

Applied to files:

  • CHANGELOG.md
📚 Learning: 2025-12-01T11:00:40.342Z
Learnt from: flowerthrower
Repo: munich-quantum-toolkit/core-plugins-catalyst PR: 1
File: CHANGELOG.md:18-18
Timestamp: 2025-12-01T11:00:40.342Z
Learning: In the munich-quantum-toolkit/core-plugins-catalyst repository, the CHANGELOG.md intentionally references the parent MQT Core repository's release notes (https://github.com/munich-quantum-toolkit/core/releases) because the plugin repository is based on work performed in the parent repository.

Applied to files:

  • CHANGELOG.md
📚 Learning: 2025-11-20T18:19:31.857Z
Learnt from: lsschmid
Repo: munich-quantum-toolkit/qmap PR: 832
File: src/hybridmap/HybridNeutralAtomMapper.cpp:1679-1715
Timestamp: 2025-11-20T18:19:31.857Z
Learning: In src/hybridmap/HybridNeutralAtomMapper.cpp, the compareSwapAndBridge and compareShuttlingAndFlyingAncilla methods intentionally use division by zero to produce infinity when distance reductions are zero or negative. This infinity value marks that option as highly unfavorable in cost comparisons, which is the intended behavior rather than a bug.

Applied to files:

  • UPGRADING.md
  • src/hybridmap/Mapping.cpp
  • include/hybridmap/HybridSynthesisMapper.hpp
  • src/hybridmap/NeutralAtomArchitecture.cpp
  • src/hybridmap/NeutralAtomLayer.cpp
  • include/hybridmap/HybridAnimation.hpp
  • include/hybridmap/Mapping.hpp
  • bindings/hybrid_mapper/hybrid_mapper.cpp
  • src/hybridmap/HybridSynthesisMapper.cpp
  • include/hybridmap/HybridNeutralAtomMapper.hpp
  • src/hybridmap/MoveToAodConverter.cpp
  • src/hybridmap/HardwareQubits.cpp
  • include/hybridmap/HardwareQubits.hpp
  • src/hybridmap/HybridNeutralAtomMapper.cpp
  • test/hybridmap/test_hybridmap.cpp
  • include/hybridmap/NeutralAtomUtils.hpp
  • src/hybridmap/NeutralAtomScheduler.cpp
  • include/hybridmap/NeutralAtomArchitecture.hpp
  • include/hybridmap/MoveToAodConverter.hpp
  • include/hybridmap/NeutralAtomDefinitions.hpp
📚 Learning: 2025-11-20T16:57:24.305Z
Learnt from: lsschmid
Repo: munich-quantum-toolkit/qmap PR: 832
File: test/hybridmap/test_hybridmap.cpp:200-287
Timestamp: 2025-11-20T16:57:24.305Z
Learning: In test/hybridmap/test_hybridmap.cpp, the LongShuttling test is designed as a stress test to catch rare edge cases that only occur after extended shuttling sequences, which is why it maps a long circuit without explicit assertions beyond ensuring no exceptions are thrown.

Applied to files:

  • docs/na_hybrid.md
  • test/python/hybrid_mapper/test_hybrid_synthesis.py
  • src/hybridmap/Mapping.cpp
  • src/hybridmap/HybridSynthesisMapper.cpp
  • src/hybridmap/HybridNeutralAtomMapper.cpp
  • test/hybridmap/test_hybridmap.cpp
  • include/hybridmap/NeutralAtomUtils.hpp
📚 Learning: 2025-11-27T08:52:06.428Z
Learnt from: lsschmid
Repo: munich-quantum-toolkit/qmap PR: 832
File: src/hybridmap/HybridNeutralAtomMapper.cpp:1379-1383
Timestamp: 2025-11-27T08:52:06.428Z
Learning: In src/hybridmap/HybridNeutralAtomMapper.cpp, the getBestMovePos function throws std::runtime_error when no move position is found (finalBestPos.coords is empty). This behavior is acceptable for current use cases since tight architectures are not a concern. A future improvement would be to fall back to SWAP-based strategies instead of throwing.

Applied to files:

  • include/hybridmap/NeutralAtomScheduler.hpp
  • src/hybridmap/Mapping.cpp
  • include/hybridmap/HybridSynthesisMapper.hpp
  • src/hybridmap/NeutralAtomArchitecture.cpp
  • src/hybridmap/NeutralAtomLayer.cpp
  • include/hybridmap/NeutralAtomLayer.hpp
  • include/hybridmap/HybridAnimation.hpp
  • include/hybridmap/Mapping.hpp
  • bindings/hybrid_mapper/hybrid_mapper.cpp
  • src/hybridmap/HybridSynthesisMapper.cpp
  • include/hybridmap/HybridNeutralAtomMapper.hpp
  • src/hybridmap/MoveToAodConverter.cpp
  • src/hybridmap/HardwareQubits.cpp
  • include/hybridmap/HardwareQubits.hpp
  • src/hybridmap/HybridNeutralAtomMapper.cpp
  • test/hybridmap/test_hybridmap.cpp
  • include/hybridmap/NeutralAtomUtils.hpp
  • src/hybridmap/NeutralAtomScheduler.cpp
  • include/hybridmap/NeutralAtomArchitecture.hpp
  • include/hybridmap/MoveToAodConverter.hpp
  • include/hybridmap/NeutralAtomDefinitions.hpp
📚 Learning: 2025-10-07T15:30:42.946Z
Learnt from: MatthiasReumann
Repo: munich-quantum-toolkit/core PR: 1237
File: mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h:219-231
Timestamp: 2025-10-07T15:30:42.946Z
Learning: In the Layout class for MLIR quantum routing (mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h), the swap method intentionally does NOT swap the hw fields in QubitInfo. This is correct because SSA values represent quantum states at fixed hardware locations, and only their program index associations change during a SWAP gate. The hw field indicates where an SSA value physically resides and remains constant.

Applied to files:

  • src/hybridmap/Mapping.cpp
  • src/hybridmap/NeutralAtomArchitecture.cpp
  • include/hybridmap/Mapping.hpp
  • src/hybridmap/HybridSynthesisMapper.cpp
  • include/hybridmap/HybridNeutralAtomMapper.hpp
  • src/hybridmap/HardwareQubits.cpp
  • include/hybridmap/HardwareQubits.hpp
  • src/hybridmap/HybridNeutralAtomMapper.cpp
  • include/hybridmap/NeutralAtomDefinitions.hpp
📚 Learning: 2025-12-04T06:59:40.314Z
Learnt from: MatthiasReumann
Repo: munich-quantum-toolkit/core PR: 1301
File: mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.cpp:84-85
Timestamp: 2025-12-04T06:59:40.314Z
Learning: In the MQTOpt MLIR routing passes (NaiveRoutingPassSC, AStarRoutingPassSC), the input IR is guaranteed to contain only 1-qubit and 2-qubit gates. All 3+-qubit gates must be decomposed before routing; otherwise the input IR is invalid. This invariant allows skipTwoQubitBlock in LayeredUnit.cpp to safely assert wires.size() == 2.

Applied to files:

  • src/hybridmap/Mapping.cpp
  • src/hybridmap/NeutralAtomArchitecture.cpp
  • src/hybridmap/NeutralAtomLayer.cpp
  • include/hybridmap/NeutralAtomLayer.hpp
  • include/hybridmap/Mapping.hpp
  • src/hybridmap/HardwareQubits.cpp
  • src/hybridmap/HybridNeutralAtomMapper.cpp
  • test/hybridmap/test_hybridmap.cpp
📚 Learning: 2025-10-09T04:30:29.071Z
Learnt from: MatthiasReumann
Repo: munich-quantum-toolkit/core PR: 1237
File: mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Scheduler.h:72-140
Timestamp: 2025-10-09T04:30:29.071Z
Learning: In the CrawlScheduler (mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Scheduler.h), the persistent `layoutCopy` is updated across all lookahead layers via `remapQubitValue` calls. After each two-qubit gate is scheduled in a layer, its input qubits are remapped to output qubits, ensuring the next layer's crawl starts from those outputs. This progressive remapping through SSA values prevents the same operation from being scheduled in multiple lookahead layers.

Applied to files:

  • include/hybridmap/HybridSynthesisMapper.hpp
  • src/hybridmap/NeutralAtomLayer.cpp
  • include/hybridmap/HybridNeutralAtomMapper.hpp
  • src/hybridmap/HybridNeutralAtomMapper.cpp
  • src/hybridmap/NeutralAtomScheduler.cpp
📚 Learning: 2025-11-01T15:57:31.153Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1283
File: src/qir/runtime/QIR.cpp:196-201
Timestamp: 2025-11-01T15:57:31.153Z
Learning: In the QIR runtime (src/qir/runtime/QIR.cpp), the PRX gate (__quantum__qis__prx__body) is an alias for the R gate (Phased X-Rotation) and should call runtime.apply<qc::R>(theta, phi, qubit), not runtime.apply<qc::RX>() which is a single-parameter rotation gate.

Applied to files:

  • src/hybridmap/NeutralAtomArchitecture.cpp
  • test/hybridmap/test_hybridmap.cpp
📚 Learning: 2025-10-09T13:20:11.483Z
Learnt from: DRovara
Repo: munich-quantum-toolkit/core PR: 1108
File: mlir/test/Dialect/MQTOpt/Transforms/lift-measurements.mlir:269-288
Timestamp: 2025-10-09T13:20:11.483Z
Learning: In the MQT MLIR dialect, the `rz` gate should not be included in the `DIAGONAL_GATES` set for the `ReplaceBasisStateControlsWithIfPattern` because its operator matrix does not have the required shape | 1 0 | / | 0 x | for the targets-as-controls optimization. It is only included in `LiftMeasurementsAboveGatesPatterns` where the matrix structure requirement differs.

Applied to files:

  • src/hybridmap/NeutralAtomArchitecture.cpp
  • src/hybridmap/HybridNeutralAtomMapper.cpp
📚 Learning: 2025-10-09T13:13:51.224Z
Learnt from: DRovara
Repo: munich-quantum-toolkit/core PR: 1108
File: mlir/lib/Dialect/MQTOpt/Transforms/ReplaceBasisStateControlsWithIfPattern.cpp:171-180
Timestamp: 2025-10-09T13:13:51.224Z
Learning: In MQT Core MLIR, UnitaryInterface operations guarantee 1-1 correspondence between input and output qubits in the same order. When cloning or modifying unitary operations (e.g., removing controls), this correspondence is maintained by construction, so yielding getAllInQubits() in else-branches matches the result types from the operation's outputs.

Applied to files:

  • src/hybridmap/NeutralAtomArchitecture.cpp
  • src/hybridmap/NeutralAtomLayer.cpp
  • include/hybridmap/Mapping.hpp
  • include/hybridmap/HardwareQubits.hpp
  • src/hybridmap/HybridNeutralAtomMapper.cpp
📚 Learning: 2025-11-05T09:23:46.540Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1287
File: src/qdmi/dd/Device.cpp:492-521
Timestamp: 2025-11-05T09:23:46.540Z
Learning: In the munich-quantum-toolkit/core repository, the `qasm3::Importer::imports()` function is backwards compatible with OpenQASM 2 programs. Therefore, it can be used to parse both QASM2 and QASM3 program formats without requiring separate importers for each version.

Applied to files:

  • bindings/hybrid_mapper/hybrid_mapper.cpp
  • python/mqt/qmap/hybrid_mapper.pyi
📚 Learning: 2025-11-24T10:19:41.147Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1326
File: python/mqt/core/__init__.py:22-22
Timestamp: 2025-11-24T10:19:41.147Z
Learning: In the munich-quantum-toolkit/core repository, Ruff is configured with 'ALL' rules enabled by default, and only specific rules are selectively disabled. When reviewing changes that enable previously-disabled rules (like PLC0415), noqa directives for those rules become necessary and should be retained.

Applied to files:

  • include/hybridmap/HybridNeutralAtomMapper.hpp
📚 Learning: 2025-11-05T07:42:45.507Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1287
File: test/qdmi/dd/error_handling_test.cpp:118-194
Timestamp: 2025-11-05T07:42:45.507Z
Learning: In the munich-quantum-toolkit/core QDMI device API, session parameters can only be set before calling `device_session_init()`. Once a session is initialized, any attempt to set a parameter returns `QDMI_ERROR_BADSTATE`. Since `SessionGuard` (in test/qdmi/dd/helpers/test_utils.hpp) automatically initializes the session in its constructor, tests that need to verify session parameter setting behavior before initialization must allocate a separate uninitialized session rather than reusing the `SessionGuard`'s session.

Applied to files:

  • include/hybridmap/HybridNeutralAtomMapper.hpp
  • test/hybridmap/test_hybridmap.cpp
📚 Learning: 2025-12-02T07:37:46.860Z
Learnt from: MatthiasReumann
Repo: munich-quantum-toolkit/core PR: 1301
File: mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp:144-151
Timestamp: 2025-12-02T07:37:46.860Z
Learning: In MLIR transformation code (mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp and similar), when inserting operations before a target operation, prefer `rewriter.setInsertionPoint(op)` over `rewriter.setInsertionPointAfter(op->getPrevNode())`. The former is cleaner, avoids null pointer edge cases (when op is first in block), and is semantically clearer.

Applied to files:

  • src/hybridmap/MoveToAodConverter.cpp
  • src/hybridmap/HybridNeutralAtomMapper.cpp
  • include/hybridmap/NeutralAtomUtils.hpp
📚 Learning: 2025-10-07T15:58:19.247Z
Learnt from: MatthiasReumann
Repo: munich-quantum-toolkit/core PR: 1237
File: mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Planner.h:202-214
Timestamp: 2025-10-07T15:58:19.247Z
Learning: In the QMAP routing algorithm (mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Planner.h), the heuristic function h() intentionally uses a non-admissible heuristic that sums per-gate shortest-path distances. This is a deliberate design choice from the QMAP paper, where admissibility is dropped because locally optimal solutions can negatively affect the overall routing quality. This approach prioritizes global solution quality over guaranteed local optimality.

Applied to files:

  • src/hybridmap/HybridNeutralAtomMapper.cpp
🧬 Code graph analysis (8)
include/hybridmap/NeutralAtomScheduler.hpp (1)
include/hybridmap/HybridAnimation.hpp (1)
  • initHwPos (53-54)
test/python/hybrid_mapper/test_hybrid_synthesis.py (1)
python/mqt/qmap/hybrid_mapper.pyi (17)
  • HybridSynthesisMapper (101-121)
  • NeutralAtomHybridArchitecture (50-79)
  • init_mapping (114-114)
  • evaluate_synthesis_steps (107-109)
  • append_with_mapping (103-103)
  • append_without_mapping (104-104)
  • get_mapped_qc_qasm (85-85)
  • get_mapped_qc_qasm (111-111)
  • save_mapped_qc_qasm (115-115)
  • convert_to_aod (106-106)
  • get_mapped_qc_aod_qasm (86-86)
  • get_mapped_qc_aod_qasm (112-112)
  • save_mapped_qc_aod_qasm (91-91)
  • save_mapped_qc_aod_qasm (116-116)
  • get_synthesized_qc_qasm (113-113)
  • save_synthesized_qc_qasm (117-117)
  • get_circuit_adjacency_matrix (110-110)
src/hybridmap/NeutralAtomArchitecture.cpp (1)
include/hybridmap/NeutralAtomArchitecture.hpp (8)
  • Properties (69-69)
  • Properties (82-92)
  • NeutralAtomArchitecture (216-216)
  • interactionRadius (197-197)
  • op (329-329)
  • op (335-335)
  • op (342-342)
  • v (573-573)
src/hybridmap/NeutralAtomLayer.cpp (1)
include/hybridmap/NeutralAtomLayer.hpp (5)
  • qubitsToUpdate (57-57)
  • qubitsToUpdate (67-67)
  • qubitsToUpdate (76-76)
  • commutesWithAtQubit (138-139)
  • commuteAtQubit (150-151)
include/hybridmap/HybridNeutralAtomMapper.hpp (3)
include/hybridmap/HardwareQubits.hpp (5)
  • op (215-220)
  • op (215-215)
  • HardwareQubits (88-88)
  • HardwareQubits (100-143)
  • HardwareQubits (100-103)
test/hybridmap/test_hybridmap.cpp (5)
  • mapper (94-94)
  • mapper (227-227)
  • mapper (263-263)
  • mapper (272-272)
  • mapper (285-285)
include/hybridmap/NeutralAtomScheduler.hpp (1)
  • qc (136-140)
src/hybridmap/HybridNeutralAtomMapper.cpp (4)
include/hybridmap/HardwareQubits.hpp (13)
  • op (215-220)
  • op (215-215)
  • q (300-300)
  • q (309-309)
  • hwQubit (179-179)
  • hwQubit (187-209)
  • hwQubit (187-187)
  • qubit (70-70)
  • qubit (228-228)
  • qubits (332-332)
  • qubits (363-363)
  • coord (343-344)
  • coord (353-354)
include/hybridmap/Mapping.hpp (10)
  • op (199-204)
  • op (199-199)
  • qubit (113-115)
  • qubit (113-113)
  • qubit (124-124)
  • qubit (170-170)
  • qubit (187-187)
  • qubits (136-136)
  • qubits (152-152)
  • swap (213-213)
include/sc/Architecture.hpp (5)
  • control (427-428)
  • qubit (88-93)
  • qubit (88-90)
  • qubit (95-96)
  • qubit (101-101)
src/hybridmap/HardwareQubits.cpp (2)
  • move (137-180)
  • move (137-137)
test/hybridmap/test_hybridmap.cpp (2)
include/hybridmap/NeutralAtomArchitecture.hpp (1)
  • NeutralAtomArchitecture (216-216)
include/hybridmap/HybridNeutralAtomMapper.hpp (13)
  • qc (549-556)
  • qc (549-550)
  • qc (558-563)
  • qc (558-559)
  • qc (571-571)
  • qc (678-678)
  • NeutralAtomMapper (447-447)
  • NeutralAtomMapper (448-469)
  • NeutralAtomMapper (448-449)
  • NeutralAtomMapper (470-472)
  • NeutralAtomMapper (470-471)
  • p (480-493)
  • p (480-480)
include/hybridmap/MoveToAodConverter.hpp (2)
include/hybridmap/NeutralAtomArchitecture.hpp (17)
  • c (418-418)
  • nodiscard (97-99)
  • nodiscard (103-103)
  • nodiscard (107-107)
  • nodiscard (111-111)
  • nodiscard (115-117)
  • nodiscard (121-123)
  • nodiscard (127-129)
  • nodiscard (133-135)
  • nodiscard (139-139)
  • nodiscard (161-166)
  • nodiscard (231-231)
  • nodiscard (236-238)
  • nodiscard (243-245)
  • nodiscard (257-259)
  • nodiscard (264-264)
  • v (573-573)
src/hybridmap/HardwareQubits.cpp (2)
  • move (137-180)
  • move (137-137)
🪛 LanguageTool
CHANGELOG.md

[grammar] ~18-~18: Ensure spelling is correct
Context: ...m Compiler ([#847]) ([@ystade]) - ✨ Added extension to the hybrid routing mapper ...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[grammar] ~19-~19: Ensure spelling is correct
Context: ...ancillas ([#832]) ([@lsschmid]) - ✨ Added hybrid synthesis routing for iterative ...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

UPGRADING.md

[grammar] ~15-~15: Ensure spelling is correct
Context: ...uting. Additionally, we also introduce and extension to the Hybrid Neutral Atom Ma...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

docs/na_hybrid.md

[style] ~20-~20: The abbreviation “ASAP” (= as soon as possible) can sound harsh. Consider an alternative.
Context: ... capability-specific cost models and an ASAP scheduler that respects hardware constr...

(ASAP)


[grammar] ~50-~50: Please add a punctuation mark at the end of paragraph.
Context: ...s repository ships several ready-to-use examples ```{code-cell} ipython3 from pathlib ...

(PUNCTUATION_PARAGRAPH_END)


[style] ~227-~227: Consider using the typographical ellipsis character here instead.
Context: ...ile("path/to/circuit.qasm")and thenmapper.schedule(...)` as shown above. - Architectures: the...

(ELLIPSIS)


[grammar] ~234-~234: Please add a punctuation mark at the end of paragraph.
Context: ...irrors the unit test flow and shows how to - evaluate multiple candidate fragment...

(PUNCTUATION_PARAGRAPH_END)

⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: 🐍 Test (macos-14) / 🐍 macos-14
  • GitHub Check: 🐍 Test (macos-15-intel) / 🐍 macos-15-intel
  • GitHub Check: 🐍 Test (windows-2022) / 🐍 windows-2022
🔇 Additional comments (70)
CHANGELOG.md (1)

18-19: LGTM!

The changelog entries follow the project's established format with proper PR link and contributor attribution. The entries clearly describe the new hybrid mapping extension features (Bridge gates, Passby moves, Flying ancillas) and hybrid synthesis routing.

include/hybridmap/HardwareQubits.hpp (1)

187-209: LGTM!

The removeHwQubit implementation correctly captures the current coordinate before erasing mappings, properly updates both occupiedCoordinates and freeCoordinates, invalidates swap distances, and cleans up nearby qubit references. This addresses the issues flagged in previous reviews.

python/mqt/qmap/hybrid_mapper.pyi (2)

81-98: LGTM!

The HybridNAMapper type stubs are well-defined with clear method signatures. The *_qasm suffix on method names makes the return type (QASM strings) explicit, which addresses the earlier review feedback about consistency.


100-121: LGTM!

The HybridSynthesisMapper type stubs are consistent with HybridNAMapper and provide a complete API surface for synthesis-aware mapping workflows. Method naming conventions (e.g., get_*_qasm, save_*_qasm) clearly indicate return types and purposes.

UPGRADING.md (1)

17-21: LGTM!

The upgrade documentation clearly explains backward compatibility by specifying default parameter values (usePassBy=False, numFlyingAncillas=0, maxBridgeDistance=0) that recover previous behavior. The .naviz output capability is also documented.

include/hybridmap/NeutralAtomScheduler.hpp (4)

43-44: LGTM!

The SchedulerResults struct is properly extended with nAodActivate and nAodMove counters. The constructor initializes all new fields, and toMap() correctly exports them with appropriate type casting.

Also applies to: 58-64, 91-92


111-117: LGTM!

Deleting the default constructor and requiring explicit architecture initialization addresses the earlier review concern about potential null pointer dereferences in schedule(). The explicit constructor correctly stores the architecture pointer.


157-163: Docstring mentions .nastyle but implementation doesn't write it.

The documentation at line 159 states the method writes .naviz/.namachine artifacts, which matches the implementation. However, a past review noted that .nastyle was mentioned elsewhere. Please verify the docstring accurately reflects the actual outputs.


164-183: LGTM!

The saveAnimationFiles implementation includes appropriate guards with a warning when animation data is missing, and correctly derives output filenames from the input path. The early return when either animation string is empty prevents writing incomplete artifacts.

docs/na_hybrid.md (1)

1-285: Documentation looks comprehensive and well-structured.

The documentation covers the HyRoNA workflow end-to-end with clear examples for architecture loading, mapping, scheduling, animation export, parameter tuning, and the Hybrid Synthesis Mapper. Previous review feedback has been addressed (corrected ipython3 directive, clarified shuttling_weight semantics, limited QASM snippets, fixed AOD coordinate explanation, and aligned animation output description).

src/hybridmap/Mapping.cpp (2)

27-42: LGTM: applySwap correctly handles all mapping cases.

The refactored function properly handles the four cases:

  1. Both qubits mapped → swap their circuit qubit assignments
  2. Only q1 mapped → transfer mapping to q2
  3. Only q2 mapped → transfer mapping to q1
  4. Neither mapped → throw (invalid operation)

The const reference parameter and local const bindings improve clarity.


44-189: Graph matching algorithm is well-structured.

The implementation correctly:

  • Builds hardware and circuit adjacency graphs with weighted edges
  • Identifies center qubits by connectivity
  • Prioritizes circuit qubits by degree and total edge weight
  • Uses weighted distance minimization for placement decisions
  • Handles neighbor mapping to fill adjacent positions

The algorithm provides a reasonable greedy heuristic for initial placement.

test/python/hybrid_mapper/test_hybrid_synthesis.py (1)

1-131: Test coverage is solid for the HybridSynthesisMapper Python bindings.

The tests properly cover:

  • Synthesis step evaluation with multiple architectures
  • I/O operations (QASM export, AOD conversion, file saving) using tmp_path
  • Adjacency matrix retrieval and symmetry
  • Object lifetime with helper functions

Previous review feedback has been addressed (keyword argument for also_map, tmp_path fixture usage).

include/hybridmap/HybridSynthesisMapper.hpp (1)

32-150: Header is well-structured with clear documentation.

The API is well-documented with:

  • Clear class-level documentation explaining the bridging role
  • Per-method documentation with parameter and return descriptions
  • Appropriate use of [[nodiscard]] and [[maybe_unused]] attributes
  • Logical grouping of constructors, lifecycle methods, accessors, and operations

The completeRemap fix (using a copy) addresses the previous mutation concern.

src/hybridmap/HybridSynthesisMapper.cpp (3)

31-68: evaluateSynthesisSteps is well-implemented.

The function properly:

  • Guards against empty input (returns early)
  • Initializes on first use
  • Computes fidelity before emplacing (avoiding evaluation order issues)
  • Uses proper logging via spdlog
  • Guards alsoMap with !candidates.empty()

Previous review feedback has been addressed.


70-80: evaluateSynthesisStep correctly preserves input.

The function now makes a copy of qc before mapping, ensuring the original circuit is not mutated. This addresses the previous const-correctness concern.


103-129: Adjacency matrix implementation is correct.

The function:

  • Validates initialization state
  • Properly sets both (i,j) and (j,i) for symmetry
  • Explicitly sets diagonal to 0
  • Uses swap distance of 0 to determine adjacency
src/hybridmap/NeutralAtomLayer.cpp (4)

18-21: LGTM! Modern include update.

Replacing <iterator> with <cstddef> is appropriate since std::size_t is the primary utility needed here.


42-69: Front-layer vs lookahead logic is well-structured.

The branching between front-layer mode (commutation-based candidate collection) and lookahead mode (bounded multi-qubit gate collection) is clear. The commutation checks at lines 46-47 properly gate candidate addition.

One observation: in lookahead mode (lines 57-67), single-qubit gates are collected without limit between multi-qubit gates. This appears intentional for heuristic evaluation but may accumulate many candidates for circuits with long single-qubit sequences.


72-111: Candidate promotion logic is correct; consider iterator validity note.

The logic correctly:

  1. Checks if an operation is a candidate on all its qubits (lines 80-90)
  2. Promotes to gates and newGates (lines 92-93)
  3. Removes from other qubits' candidate lists (lines 95-101)
  4. Defers removal from the current qubit's list to avoid iterator invalidation (lines 106-110)

The use of std::ranges::find followed by erase at lines 99-100 assumes the element exists (since we just verified candidacy). This is safe given the preceding check.


114-125: Gate removal with ranges is clean.

The refactored removeGatesAndUpdate properly:

  1. Uses std::ranges::find for safe lookup
  2. Only erases if found (lines 118-119)
  3. Collects affected qubits for subsequent update

This is a good modernization from the previous iterator-based approach.

include/hybridmap/NeutralAtomDefinitions.hpp (4)

13-21: LGTM! Required includes are present.

The addition of <tuple> supports the std::tie usage in operator<, and the other includes properly support the new types.


29-36: Good documentation for CoordIndex and AdjacencyMatrix.

The doxygen comments clearly explain the purpose of CoordIndex as a physical coordinate slot index. The AdjacencyMatrix alias is appropriate for the symmetric matrix representation.


79-110: AtomMove struct is well-designed with proper ordering.

The struct provides:

  • Clear field documentation (c1/c2 for coordinates, load1/load2 for actions)
  • Proper equality operators
  • Strict weak ordering via std::tie for use in ordered containers

This addresses the previous review concern about missing operator<.


112-119: GateList and GateLists type aliases are appropriate.

These provide clear semantic names for operation pointer vectors and their collections, improving code readability throughout the codebase.

include/hybridmap/HybridAnimation.hpp (5)

24-33: Lifetime contract is now properly documented.

The @note at lines 31-32 clearly states that "The architecture reference passed to the constructor must remain valid for the lifetime of this object." This addresses the previous review concern about undocumented lifetime requirements.


34-40: Raw pointer storage is acceptable with documented contract.

The arch member stores a non-owning pointer initialized from a reference in the constructor. Given the explicit lifetime documentation, this design is appropriate for performance-sensitive code where the architecture is expected to outlive all animation helpers.


42-54: initPositions documentation is clear.

The protected helper is well-documented, explaining the coordinate initialization for both hardware qubits and flying ancillas, including the offset calculations based on AOD intermediate levels.


64-69: Constructor properly initializes architecture pointer.

The constructor takes a reference and stores its address, which is safe given the documented lifetime contract. The immediate call to initPositions ensures consistent state after construction.


71-95: opToNaViz documentation is comprehensive.

The docstring now explicitly covers:

  • AOD operations (Activate, Deactivate, Move)
  • Multi-qubit gates (grouped cz)
  • Single-qubit gates (rz visualization)
  • Explicitly states "Other operations are currently not supported"

This addresses the previous concern about undocumented operation handling.

include/hybridmap/NeutralAtomLayer.hpp (4)

24-31: Class documentation clearly explains the dual-mode design.

The updated docstring effectively communicates the two operational modes:

  1. Front layer: mutually commuting gates ready to execute
  2. Look-ahead layer: bounded depth of forthcoming multi-qubit gates

This provides good context for users of the class.


39-46: New state members support lookahead functionality.

The additions are appropriate:

  • ends: tracks DAG column end iterators for bounds checking
  • newGates: tracks gates added in the latest update (replacing mappedSingleQubitGates)
  • lookaheadDepth: configurable multi-qubit gate depth for lookahead
  • isFrontLayer: mode flag

The rename from mappedSingleQubitGates to newGates better reflects its actual purpose (all newly promoted gates, not just single-qubit).


105-112: Accessor methods are appropriately const-qualified.

Both getGates() and getNewGates() are marked [[nodiscard]] and const, correctly indicating they don't modify state and their return values should be used. Returning copies is reasonable for these small vectors.


128-151: Free function declarations are well-documented.

The namespace-level commutation helpers have clear documentation explaining:

  • Their purpose (checking commutation at a specific qubit)
  • The rules applied (non-unitaries, identities, single-qubit ops, control/target patterns)

Making these free functions rather than class members is appropriate since they operate on general GateList and qc::Operation* types.

src/hybridmap/NeutralAtomArchitecture.cpp (7)

76-91: Robust fallback mechanism for required gate entries.

The ensureGateWithFallback lambda elegantly handles missing gate entries by:

  1. Checking if the required gate exists
  2. Falling back to "none" if available
  3. Throwing with a clear error message if both are missing

This prevents silent failures and provides clear error messages during JSON loading.


107-132: SWAP and Bridge gate precomputation is safe and efficient.

The implementation correctly:

  • Computes SWAP timing from 3 CZ + 4 H gates (line 107-108)
  • Computes SWAP fidelity from gate fidelities raised to appropriate powers (lines 109-111)
  • Uses std::min across all bridge circuit vectors to prevent out-of-bounds access (lines 117-120)
  • Iterates only up to the safe maximum index

This addresses the previous review concern about bridge circuit index safety.


171-231: computeSwapDistances is well-implemented.

The algorithm:

  1. Computes diagonal distances within interaction radius
  2. Sorts by distance for greedy diagonal reduction
  3. Uses a greedy approach to minimize swap count

Note at lines 225-227: if no diagonal moves were found, swapDistance defaults to 1. The stored value is swapDistance - 1, meaning adjacent qubits within interaction radius get distance 0 (direct interaction). This appears intentional for the hybrid mapping model.


265-319: getAnimationMachine implementation is robust.

The method:

  • Validates shuttlingSpeedFactor > 0 to prevent division by zero (lines 267-271)
  • Generates complete NaViz machine configuration including movement, timing, distance, zones, and trap positions

The use of getGateTime("x") for rz/ry timing (lines 286-287) is acceptable per the developer's confirmation that all architectures define either "x" or "none".


333-348: getOpTime correctly handles multi-qubit and phase gates.

The implementation:

  • Uses safe iteration for (size_t i = 1; i < nQubits; ++i) avoiding unsigned underflow (lines 334-335)
  • Handles P/RZ gates with proper pi-normalized scaling using std::fmod (lines 342-347)

This addresses the previous critical concern about unsigned underflow when getNqubits() == 0.


359-365: getOpFidelity uses same safe loop pattern.

The loop at lines 360-361 mirrors the safe pattern from getOpTime, preventing underflow for operations with zero qubits.


368-396: getBlockedCoordIndices correctly handles ancilla register mapping.

The implementation:

  1. Maps ancilla qubits back to primary positions (lines 378-380)
  2. Computes blocked indices based on Euclidean distance and blocking factor
  3. Extends blocking to ancilla register offsets (lines 390-391)

The while loop at lines 378-380 handles multiple ancilla register levels by repeatedly subtracting getNpositions() until the coordinate is in the primary range. This is correct for the multi-level ancilla model.

src/hybridmap/HardwareQubits.cpp (1)

91-130: BFS-based computeAllShortestPaths and getClosestQubit behavior look correct

The new BFS over nearbyQubits correctly enumerates all simple shortest paths between two hardware qubits, and getClosestQubit now robustly throws when no non‑ignored qubit exists instead of silently returning 0. This fixes the earlier latent bug and gives callers a well‑defined “no candidate” signal while reusing the same distance metric infrastructure.

include/hybridmap/Mapping.hpp (1)

81-107: New Mapping constructor fixes prior safety and performance issues

Taking qc::QuantumComputation& instead of by-value and validating both qc.getNqubits() <= hwQubits.getNumQubits() and nQubits <= qc.getNqubits() removes the redundant circuit copy and closes the out‑of‑bounds hole noted earlier for mismatched nQubits. The Graph branch then safely indexes qubitIndices[i] only within the validated range.

src/hybridmap/HybridNeutralAtomMapper.cpp (1)

607-636: computeCurrentCoordUsages sizing and indexing now look safe

Sizing coordUsages as 3 * arch->getNpositions() and then indexing it with:

  • coordinates derived via hardwareQubits.getCoordIndex(mapping.getHwQubit(qubit)) (in‑range [0, nPositions)),
  • coord indices from mappedQc’s getUsedQubits() (which live in the 3‑register space), and
  • coord indices from lastBlockedQubits routed via getCoordIndex(qubit)

fixes the earlier mismatch where flying‑ancilla and ancilla coords could exceed the vector size. This eliminates the previous out‑of‑bounds risk.

test/hybridmap/test_hybridmap.cpp (3)

59-134: Mapper parametrization and identity-mapping test look solid

The new NeutralAtomMapperTestParams fixture wires all relevant MapperParameters (including initialCoordMapping and dynamicMappingWeight), runs mapping + convertToAod(), and then schedules the circuit, asserting sensible lower bounds on execution time and fidelities. This gives good coverage of the end‑to‑end hybrid mapping/scheduling path for multiple architectures and circuits.


136-216: Output test robustly exercises mapping, I/O, stats, and animation

NeutralAtomMapperTest.Output now:

  • writes mapped and AOD QASM to temp files and checks they exist and are non‑empty,
  • verifies stats and stats map are non‑negative,
  • confirms getInitHwPos() size matches “nqubits minus flying ancilla”,
  • schedules with animation enabled and asserts non‑empty animation plus valid .namachine/.naviz files.

Combined, this is a strong integration test for hybrid mapping, AOD conversion, scheduler, and NaViz export.


218-290: Exception tests nicely document and guard key failure modes

The new NeutralAtomMapperExceptions tests (insufficient qubits for circuit+ancillas, enforcing a single flying ancilla, no free coords for shuttling in ctor/setParameters, no multi‑qubit space, and the long‑shuttling stress case) clearly encode the expected failure behavior of the mapper API and will help catch regressions in edge conditions.

include/hybridmap/NeutralAtomUtils.hpp (3)

30-102: Configuration enums and parsers are clear and fail-fast

The introduction of InitialCoordinateMapping, InitialMapping, and MappingMethod plus the *_FromString helpers (with explicit, case‑sensitive accepted tokens and descriptive std::invalid_argument messages) makes mapper configuration much more explicit and robust than raw strings or ints.


214-268: Ancilla combo and move-combination structs are safer and more expressive now

Default‑initializing FlyingAncillaComb::op and PassByComb::op to nullptr, adding cost/op/bestPos to MoveComb, and providing helper ctors/append/size/empty all make the ancilla/pass‑by/move bookkeeping clearer while avoiding uninitialized pointer UB. This pairs well with the new higher‑level mapping heuristics that rely on these types.


400-468: BridgeCircuits precomputation matches bridge-gate usage assumptions

Precomputing bridge circuits and their H/CZ counts and depths for lengths in [3, maxLength) and storing them in bridgeCircuits/hs/czs/hDepth/czDepth aligns with the architecture’s bridge‑gate decomposition needs. As long as maxLength is chosen to cover the maximum supported bridge size (which is already constrained by maxBridgeDistance on the mapper side), this design keeps runtime bridge decomposition simple and fast.

bindings/hybrid_mapper/hybrid_mapper.cpp (5)

52-94: LGTM! Clean parameter binding structure.

The MapperParameters bindings are well-organized with descriptive docstrings for each field. The default constructor and def_readwrite bindings provide a clean Python interface.


96-107: LGTM! MapperStats provides useful mapping statistics.

Clear exposure of mapping statistics with appropriate docstrings for each counter.


170-176: Good fix: keep_alive added to set_parameters.

The py::keep_alive<1, 2>() directive on line 176 correctly keeps the MapperParameters object alive for the mapper's lifetime, addressing the previously identified dangling pointer risk.


286-300: Adjacency matrix export is clean and pre-sized.

Good implementation following past review suggestion to pre-size the matrix. The conversion from SymmetricMatrix to std::vector<std::vector<int>> is correct.


230-237: keep_alive directive for params is redundant but not problematic.

The py::keep_alive<1, 3>() directive is unnecessary because NeutralAtomMapper stores MapperParameters by value (member: MapperParameters parameters at line 109), not by reference or pointer. The constructor copies the parameter via member initialization (parameters(p) at line 450), so the temporary created from the default argument is safely copied before being destroyed. The binding is safe despite the default argument.

Likely an incorrect or invalid review comment.

include/hybridmap/HybridNeutralAtomMapper.hpp (4)

109-109: Good: MapperParameters now stored by value.

This change from const MapperParameters* to MapperParameters by value eliminates the dangling pointer risks identified in past reviews. The mapper now owns its configuration.


447-469: Constructor safety improvements look good.

The refactored constructor:

  1. Uses signed arithmetic for position checks (lines 454-456)
  2. Validates numFlyingAncillas constraint (lines 461-463)
  3. Stores parameters by value, eliminating lifetime issues

This addresses the critical issues identified in past reviews.


480-493: setParameters now properly validates and copies.

The method correctly:

  1. Copies parameters by value (line 481)
  2. Uses signed arithmetic for the shuttling check (lines 482-484)
  3. Validates numFlyingAncillas > 1 (lines 489-491)

This mirrors the constructor validation and addresses the past review concern about missing the flying ancilla check.


500-510: copyStateFrom now includes flyingAncillas.

Line 509 adds the missing flyingAncillas copy, addressing the past review concern about incomplete state copying.

src/hybridmap/MoveToAodConverter.cpp (4)

69-81: convertOpToMove handles flying ancilla coordinate mapping.

The logic correctly maps qubit indices that exceed getNpositions() back to valid coordinates while tracking the load1/load2 flags. The while loops handle multiple position wrapping.


82-116: initFlyingAncillas emits activation for ancilla positions.

The method constructs AOD activation operations for flying ancillas with their offset coordinates. The coordinate calculation appears correct, using the architecture's intermediate levels.


537-549: Iterator UB fix applied correctly.

The past review flagged targetQubits.insert(pos - 1, ...) as UB when pos == begin(). The fix now correctly inserts at pos (lines 546-547), avoiding the invalid iterator decrement.


437-476: processMoves cleanly separates standard moves from flying ancilla moves.

The method correctly handles activation merging and falls back to creating a new move group when merging is impossible. The separation of concerns is clear.

include/hybridmap/NeutralAtomArchitecture.hpp (4)

88-92: Good: Validation for AOD intermediate levels.

The assertions at lines 89 and 91 prevent division-by-zero in getOffsetDistance() by ensuring aodDist > 0 and nAodIntermediateLevels >= 1. This addresses a past review concern.


184-185: Bridge circuit limit now documented.

The comment now correctly states "distances (3-9)" matching the BridgeCircuits(10) initialization, addressing past review feedback about the misleading "<=5" comment.


353-359: Logging now uses spdlog instead of std::cout.

SPDLOG_WARN replaces direct std::cout output, which is appropriate for library code. The same pattern is applied to getGateAverageFidelity.


427-431: Bounds check added for getBridgeCircuit.

The assert(length < bridgeCircuits.bridgeCircuits.size()) prevents out-of-bounds access, addressing the past review concern.

include/hybridmap/MoveToAodConverter.hpp (3)

51-66: AncillaAtom::XAndY now uses standard types.

The struct correctly uses std::uint32_t instead of the non-portable uint type, and the <sys/types.h> include has been removed. This addresses past review feedback.


81-95: AodMove constructor properly initializes all fields.

The removal of the default constructor and use of a parameterized constructor ensures all members (init, load, offset, delta) are properly initialized, eliminating the uninitialized-member risk flagged in past review.


285-294: getFirstIdx now has defensive assertion.

The assert(!moves.empty() || !movesFa.empty()) at line 286 prevents UB from dereferencing .front() on empty containers, addressing the past review concern.

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

♻️ Duplicate comments (2)
UPGRADING.md (1)

15-15: Fix typo: "and extension" → "an extension".

This issue was previously flagged and remains unfixed.

Apply this diff:

-Additionally, we also introduce and extension to the Hybrid Neutral Atom Mapper (HyRoNA), which unifies gate-based routing (SWAP/BRIDGE) with atom shuttling, pass-by, and an optional flying ancilla to find the most suitable routing.
+Additionally, we also introduce an extension to the Hybrid Neutral Atom Mapper (HyRoNA), which unifies gate-based routing (SWAP/BRIDGE) with atom shuttling, pass-by, and an optional flying ancilla to find the most suitable routing.
include/hybridmap/HybridNeutralAtomMapper.hpp (1)

612-615: Add file I/O error checking for consistency.

While saveMappedQcAodQasm includes file open validation (lines 639-641), this method lacks similar error handling. For API consistency and robustness, add the same validation pattern.

Apply this diff:

 [[maybe_unused]] void saveMappedQcQasm(const std::string& filename) const {
   std::ofstream ofs(filename);
+  if (!ofs) {
+    throw std::runtime_error("Failed to open file: " + filename);
+  }
   mappedQc.dumpOpenQASM(ofs, false);
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ffa0787 and ecf97d2.

📒 Files selected for processing (3)
  • UPGRADING.md (1 hunks)
  • docs/na_hybrid.md (1 hunks)
  • include/hybridmap/HybridNeutralAtomMapper.hpp (3 hunks)
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: lsschmid
Repo: munich-quantum-toolkit/qmap PR: 832
File: src/hybridmap/HybridNeutralAtomMapper.cpp:1679-1715
Timestamp: 2025-11-20T18:19:31.857Z
Learning: In src/hybridmap/HybridNeutralAtomMapper.cpp, the compareSwapAndBridge and compareShuttlingAndFlyingAncilla methods intentionally use division by zero to produce infinity when distance reductions are zero or negative. This infinity value marks that option as highly unfavorable in cost comparisons, which is the intended behavior rather than a bug.
Learnt from: lsschmid
Repo: munich-quantum-toolkit/qmap PR: 832
File: src/hybridmap/HybridNeutralAtomMapper.cpp:1379-1383
Timestamp: 2025-11-27T08:52:06.428Z
Learning: In src/hybridmap/HybridNeutralAtomMapper.cpp, the getBestMovePos function throws std::runtime_error when no move position is found (finalBestPos.coords is empty). This behavior is acceptable for current use cases since tight architectures are not a concern. A future improvement would be to fall back to SWAP-based strategies instead of throwing.
📚 Learning: 2025-11-20T18:19:31.857Z
Learnt from: lsschmid
Repo: munich-quantum-toolkit/qmap PR: 832
File: src/hybridmap/HybridNeutralAtomMapper.cpp:1679-1715
Timestamp: 2025-11-20T18:19:31.857Z
Learning: In src/hybridmap/HybridNeutralAtomMapper.cpp, the compareSwapAndBridge and compareShuttlingAndFlyingAncilla methods intentionally use division by zero to produce infinity when distance reductions are zero or negative. This infinity value marks that option as highly unfavorable in cost comparisons, which is the intended behavior rather than a bug.

Applied to files:

  • UPGRADING.md
  • include/hybridmap/HybridNeutralAtomMapper.hpp
📚 Learning: 2025-11-27T08:52:06.428Z
Learnt from: lsschmid
Repo: munich-quantum-toolkit/qmap PR: 832
File: src/hybridmap/HybridNeutralAtomMapper.cpp:1379-1383
Timestamp: 2025-11-27T08:52:06.428Z
Learning: In src/hybridmap/HybridNeutralAtomMapper.cpp, the getBestMovePos function throws std::runtime_error when no move position is found (finalBestPos.coords is empty). This behavior is acceptable for current use cases since tight architectures are not a concern. A future improvement would be to fall back to SWAP-based strategies instead of throwing.

Applied to files:

  • include/hybridmap/HybridNeutralAtomMapper.hpp
📚 Learning: 2025-11-05T07:42:45.507Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1287
File: test/qdmi/dd/error_handling_test.cpp:118-194
Timestamp: 2025-11-05T07:42:45.507Z
Learning: In the munich-quantum-toolkit/core QDMI device API, session parameters can only be set before calling `device_session_init()`. Once a session is initialized, any attempt to set a parameter returns `QDMI_ERROR_BADSTATE`. Since `SessionGuard` (in test/qdmi/dd/helpers/test_utils.hpp) automatically initializes the session in its constructor, tests that need to verify session parameter setting behavior before initialization must allocate a separate uninitialized session rather than reusing the `SessionGuard`'s session.

Applied to files:

  • include/hybridmap/HybridNeutralAtomMapper.hpp
📚 Learning: 2025-11-24T10:19:41.147Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1326
File: python/mqt/core/__init__.py:22-22
Timestamp: 2025-11-24T10:19:41.147Z
Learning: In the munich-quantum-toolkit/core repository, Ruff is configured with 'ALL' rules enabled by default, and only specific rules are selectively disabled. When reviewing changes that enable previously-disabled rules (like PLC0415), noqa directives for those rules become necessary and should be retained.

Applied to files:

  • include/hybridmap/HybridNeutralAtomMapper.hpp
📚 Learning: 2025-10-07T15:30:42.946Z
Learnt from: MatthiasReumann
Repo: munich-quantum-toolkit/core PR: 1237
File: mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h:219-231
Timestamp: 2025-10-07T15:30:42.946Z
Learning: In the Layout class for MLIR quantum routing (mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h), the swap method intentionally does NOT swap the hw fields in QubitInfo. This is correct because SSA values represent quantum states at fixed hardware locations, and only their program index associations change during a SWAP gate. The hw field indicates where an SSA value physically resides and remains constant.

Applied to files:

  • include/hybridmap/HybridNeutralAtomMapper.hpp
📚 Learning: 2025-10-09T04:30:29.071Z
Learnt from: MatthiasReumann
Repo: munich-quantum-toolkit/core PR: 1237
File: mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Scheduler.h:72-140
Timestamp: 2025-10-09T04:30:29.071Z
Learning: In the CrawlScheduler (mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Scheduler.h), the persistent `layoutCopy` is updated across all lookahead layers via `remapQubitValue` calls. After each two-qubit gate is scheduled in a layer, its input qubits are remapped to output qubits, ensuring the next layer's crawl starts from those outputs. This progressive remapping through SSA values prevents the same operation from being scheduled in multiple lookahead layers.

Applied to files:

  • include/hybridmap/HybridNeutralAtomMapper.hpp
🧬 Code graph analysis (1)
include/hybridmap/HybridNeutralAtomMapper.hpp (2)
include/hybridmap/HardwareQubits.hpp (7)
  • op (215-220)
  • op (215-215)
  • qubits (332-332)
  • qubits (363-363)
  • HardwareQubits (88-88)
  • HardwareQubits (100-143)
  • HardwareQubits (100-103)
src/hybridmap/HybridNeutralAtomMapper.cpp (4)
  • mapAppend (45-123)
  • mapAppend (45-46)
  • convertToAod (168-183)
  • convertToAod (168-168)
🪛 LanguageTool
docs/na_hybrid.md

[style] ~20-~20: The abbreviation “ASAP” (= as soon as possible) can sound harsh. Consider an alternative.
Context: ... capability-specific cost models and an ASAP scheduler that respects hardware constr...

(ASAP)


[style] ~228-~228: Consider using the typographical ellipsis character here instead.
Context: ...ile("path/to/circuit.qasm")and thenmapper.schedule(...)` as shown above. - Architectures: the...

(ELLIPSIS)

🪛 markdownlint-cli2 (0.18.1)
docs/na_hybrid.md

8-8: First line in a file should be a top-level heading

(MD041, first-line-heading, first-line-h1)

🔇 Additional comments (11)
UPGRADING.md (1)

17-22: Content and structure are clear.

The documentation effectively describes the HyRoNA extension, new parameters, backwards compatibility, and optional .naviz output. The messaging aligns well with the PR objectives (issue #466).

include/hybridmap/HybridNeutralAtomMapper.hpp (10)

44-60: LGTM! MapperParameters structure is well-defined.

The runtime configuration parameters are clearly documented with sensible defaults. The previously flagged documentation mismatch for limitShuttlingLayer has been resolved.


65-71: LGTM! Clean statistics structure.

The MapperStats struct provides clear, documented counters for tracking mapping operations.


87-126: Excellent! Parameters now stored by value.

The refactor to store MapperParameters parameters by value (line 109) completely resolves the critical dangling pointer issues raised in previous reviews. This makes the API much safer for C++ and Python users.

Based on learnings, previous pointer-based storage allowed temporaries to dangle.


447-472: Excellent fixes! All critical constructor issues resolved.

The constructor implementation now correctly addresses all previously raised critical concerns:

  1. Default constructor deleted (line 447) prevents invalid mapper state.
  2. Parameters stored by value via copy construction (line 450) eliminates dangling pointer risks.
  3. Signed arithmetic for validation (lines 454-455) avoids unsigned wrap bugs.
  4. Flying ancilla validation enforced (lines 461-462).
  5. Delegating constructor (line 472) safely copies parameters.

This is a robust, safe implementation.


480-493: Excellent! setParameters now safe and complete.

The method correctly:

  1. Copies parameters by value (line 481) instead of storing a pointer.
  2. Validates constraints using signed arithmetic (lines 482-487).
  3. Checks flying ancilla limit (lines 489-491), which was missing in earlier versions.

All previously raised critical and major issues are resolved.


500-522: LGTM! State management methods are now complete and safe.

Both copyStateFrom and reset are correctly implemented:

  • copyStateFrom now includes flyingAncillas assignment (line 509), which was previously missing.
  • reset safely accesses parameters member (lines 518-521) without null dereference risk.

Based on learnings, these fixes address previously identified gaps.


549-571: LGTM! Map methods are clean and efficient.

The map overload (lines 558-563) no longer contains the redundant DAG construction that was flagged in previous reviews. The implementation efficiently delegates to the main mapping method.


594-596: Excellent optimization! Return by const reference.

Changing getMappedQc() to return by const reference (line 594) avoids expensive deep copies of large circuits, as recommended in previous reviews.


634-643: Good! File I/O error handling implemented.

The file open validation (lines 639-641) matches the documentation and provides clear error messages, as requested in previous reviews.


654-679: LGTM! Scheduling and animation methods are well-structured.

The schedule method correctly:

  • Performs lazy AOD conversion (lines 657-659).
  • Passes initial positions for both hardware qubits and flying ancillas (lines 660-662).
  • Exposes animation artifacts consistently (lines 669-679).

@lsschmid
Copy link
Collaborator Author

lsschmid commented Dec 6, 2025

@burgholzer I incorporated the suggested changes and fixed some more minor typos as suggested by CodeRabbit. I also added an entry to the Upgrading and ChangeLog file.

@lsschmid lsschmid requested a review from burgholzer December 6, 2025 18:35
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.

Nicely done 🙌🏼
Let's get this in! 🚀

I am waiting for Yannick's NA compiler changes to be merged before the next minor release. Should be soon.

@burgholzer burgholzer merged commit a6ab6e8 into main Dec 6, 2025
32 checks passed
@burgholzer burgholzer deleted the na-hybrid-merge-main branch December 6, 2025 18:59
@github-project-automation github-project-automation bot moved this from In Progress to Done in MQT Compilation Dec 6, 2025
@burgholzer burgholzer added the minor Changes leading to a minor version increase label Dec 6, 2025
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 documentation Improvements or additions to documentation enhancement Anything related to improvements of the existing library feature New feature or request minor Changes leading to a minor version increase python Anything related to Python code usability Anything related to usability

Projects

None yet

Development

Successfully merging this pull request may close these issues.

✨ Hybrid NA Mapper Documentation

4 participants