Skip to content

Commit fb60e20

Browse files
authored
♻️ Merge Placement and Routing into Layout Synthesis (#713)
## Description This PR prepares the structure of the neutral atom compiler for upcoming projects, where the placement and routing stage will be performed in one step. This one step will be called *layout synthesis*. For the current implementation that splits both steps into separate stages, it provides a `PlaceAndRouteSynthesizer` that allows building a `LayoutSynthesizer` from separate `Placement` and `Routing` steps. ## Checklist: <!--- This checklist serves as a reminder of a couple of things that ensure your pull request will be merged swiftly. --> - [x] The pull request only contains commits that are focused and relevant to this change. - [x] I have added appropriate tests that cover the new/changed functionality. - [x] I have updated the documentation to reflect these changes. - [x] I have added entries to the changelog for any noteworthy additions, changes, fixes, or removals. - [x] I have added migration instructions to the upgrade guide (if needed). - [x] The changes follow the project's style guidelines and introduce no new warnings. - [x] The changes are fully tested and pass the CI checks. - [x] I have reviewed my own code changes.
1 parent 822e763 commit fb60e20

18 files changed

Lines changed: 294 additions & 83 deletions

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ _If you are upgrading: please see [`UPGRADING.md`](UPGRADING.md#320)._
2323

2424
### Changed
2525

26+
- **Breaking**: ♻️ Neutral Atom Compiler: Merge Placement and Routing stage into a Layout Synthesis stage ([#713]) ([**@ystade**])
2627
- ✨ Expose enums to Python via `pybind11`'s new (`enum.Enum`-compatible) `py::native_enum` ([#715]) ([**@denialhaag**])
2728
- ♻️ Restructure the Python code to introduce modules ([#665]) ([**@denialhaag**])
2829
- ♻️ Restructure the C++ code for the Python bindings to mirror the introduced Python modules ([#665]) ([**@denialhaag**])
@@ -113,6 +114,7 @@ _📚 Refer to the [GitHub Release Notes] for previous changelogs._
113114
[#717]: https://github.com/munich-quantum-toolkit/qmap/pull/717
114115
[#715]: https://github.com/munich-quantum-toolkit/qmap/pull/715
115116
[#714]: https://github.com/munich-quantum-toolkit/qmap/pull/714
117+
[#713]: https://github.com/munich-quantum-toolkit/qmap/pull/713
116118
[#712]: https://github.com/munich-quantum-toolkit/qmap/pull/712
117119
[#694]: https://github.com/munich-quantum-toolkit/qmap/pull/694
118120
[#665]: https://github.com/munich-quantum-toolkit/qmap/pull/665

UPGRADING.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@ All Python enums (e.g., `sc.Method`) are now exposed via `pybind11`'s new `py::n
1414
As a result, the enums can no longer be initialized using a string.
1515
Instead of `Method("exact")` or `"exact"`, use `Method.exact`.
1616

17+
This release restructures the neutral atom compiler which has consequences for its configuration and the reporting of statistics.
18+
The placement and routing stages have been merged into a single layout synthesis stage.
19+
There is a new `PlaceAndRouteSynthesizer` that combines the previously separate placement and routing stages.
20+
Consequently, the configuration for the placement and routing stages must now be wrapped in a configuration for the layout synthesis stage when using the C++ API.
21+
The Python API did not change in this regard.
22+
Furthermore, when reporting the statistics of the neutral atom compiler, the statistics for placement and routing are now reported as part of the layout synthesis statistics.
23+
The latter affects both the C++ and Python APIs.
24+
1725
## [3.2.0]
1826

1927
With this release, the Python package has been restructured.

bindings/na/zoned/zoned.cpp

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@
1212
#include "na/zoned/Architecture.hpp"
1313
#include "na/zoned/Compiler.hpp"
1414
#include "na/zoned/code_generator/CodeGenerator.hpp"
15-
#include "na/zoned/placer/AStarPlacer.hpp"
16-
#include "na/zoned/placer/VertexMatchingPlacer.hpp"
15+
#include "na/zoned/layout_synthesizer/PlaceAndRouteSynthesizer.hpp"
16+
#include "na/zoned/layout_synthesizer/placer/AStarPlacer.hpp"
17+
#include "na/zoned/layout_synthesizer/placer/VertexMatchingPlacer.hpp"
1718

1819
#include <cstddef>
1920
// The header <nlohmann/json.hpp> is used, but clang-tidy confuses it with the
@@ -50,7 +51,8 @@ PYBIND11_MODULE(MQT_QMAP_MODULE_NAME, m, py::mod_gil_not_used()) {
5051
-> na::zoned::RoutingAgnosticCompiler {
5152
na::zoned::RoutingAgnosticCompiler::Config config;
5253
config.logLevel = spdlog::level::from_str(logLevel);
53-
config.placerConfig = {useWindow, windowSize, dynamicPlacement};
54+
config.layoutSynthesizerConfig.placerConfig = {useWindow, windowSize,
55+
dynamicPlacement};
5456
config.codeGeneratorConfig = {parkingOffset, warnUnsupportedGates};
5557
return {arch, config};
5658
}),
@@ -93,9 +95,10 @@ PYBIND11_MODULE(MQT_QMAP_MODULE_NAME, m, py::mod_gil_not_used()) {
9395
-> na::zoned::RoutingAwareCompiler {
9496
na::zoned::RoutingAwareCompiler::Config config;
9597
config.logLevel = spdlog::level::from_str(logLevel);
96-
config.placerConfig = {useWindow, windowMinWidth, windowRatio,
97-
windowShare, deepeningFactor, deepeningValue,
98-
lookaheadFactor, reuseLevel, maxNodes};
98+
config.layoutSynthesizerConfig.placerConfig = {
99+
useWindow, windowMinWidth, windowRatio,
100+
windowShare, deepeningFactor, deepeningValue,
101+
lookaheadFactor, reuseLevel, maxNodes};
99102
config.codeGeneratorConfig = {parkingOffset, warnUnsupportedGates};
100103
return {arch, config};
101104
}),

include/na/zoned/Compiler.hpp

Lines changed: 67 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,12 @@
1414
#include "code_generator/CodeGenerator.hpp"
1515
#include "ir/QuantumComputation.hpp"
1616
#include "ir/operations/Operation.hpp"
17+
#include "layout_synthesizer/PlaceAndRouteSynthesizer.hpp"
18+
#include "layout_synthesizer/placer/AStarPlacer.hpp"
19+
#include "layout_synthesizer/placer/VertexMatchingPlacer.hpp"
20+
#include "layout_synthesizer/router/IndependentSetRouter.hpp"
1721
#include "na/NAComputation.hpp"
18-
#include "placer/AStarPlacer.hpp"
19-
#include "placer/VertexMatchingPlacer.hpp"
2022
#include "reuse_analyzer/VertexMatchingReuseAnalyzer.hpp"
21-
#include "router/IndependentSetRouter.hpp"
2223
#include "scheduler/ASAPScheduler.hpp"
2324

2425
#include <cassert>
@@ -43,11 +44,10 @@ namespace na::zoned {
4344
* compiler and setting them at runtime.
4445
*/
4546
template <class ConcreteType, class Scheduler, class ReuseAnalyzer,
46-
class Placer, class Router, class CodeGenerator>
47+
class LayoutSynthesizer, class CodeGenerator>
4748
class Compiler : protected Scheduler,
4849
protected ReuseAnalyzer,
49-
protected Placer,
50-
protected Router,
50+
protected LayoutSynthesizer,
5151
protected CodeGenerator {
5252
friend ConcreteType;
5353

@@ -61,33 +61,33 @@ class Compiler : protected Scheduler,
6161
typename Scheduler::Config schedulerConfig{};
6262
/// Configuration for the reuse analyzer
6363
typename ReuseAnalyzer::Config reuseAnalyzerConfig{};
64-
/// Configuration for the placer
65-
typename Placer::Config placerConfig{};
66-
/// Configuration for the router
67-
typename Router::Config routerConfig{};
64+
/// Configuration for the layout synthesizer
65+
typename LayoutSynthesizer::Config layoutSynthesizerConfig{};
6866
/// Configuration for the code generator
6967
typename CodeGenerator::Config codeGeneratorConfig{};
7068
/// Log level for the compiler
7169
spdlog::level::level_enum logLevel = spdlog::level::info;
7270
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Config, schedulerConfig,
7371
reuseAnalyzerConfig,
74-
placerConfig, routerConfig,
72+
layoutSynthesizerConfig,
7573
codeGeneratorConfig, logLevel);
7674
};
7775
/**
7876
* Collection of statistics collected during the compilation process for the
7977
* different components.
8078
*/
8179
struct Statistics {
82-
int64_t schedulingTime; ///< Time taken for scheduling in us
83-
int64_t reuseAnalysisTime; ///< Time taken for reuse analysis in us
84-
int64_t placementTime; ///< Time taken for placement in us
85-
int64_t routingTime; ///< Time taken for routing in us
86-
int64_t codeGenerationTime; ///< Time taken for code generation in us
87-
int64_t totalTime; ///< Total time taken for the compilation in us
80+
int64_t schedulingTime; ///< Time taken for scheduling in us
81+
int64_t reuseAnalysisTime; ///< Time taken for reuse analysis in us
82+
/// Statistics collected during layout synthesis.
83+
typename LayoutSynthesizer::Statistics layoutSynthesizerStatistics;
84+
int64_t layoutSynthesisTime; ///< Time taken for layout synthesis in us
85+
int64_t codeGenerationTime; ///< Time taken for code generation in us
86+
int64_t totalTime; ///< Total time taken for the compilation in us
8887
NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(Statistics, schedulingTime,
8988
reuseAnalysisTime,
90-
placementTime, routingTime,
89+
layoutSynthesizerStatistics,
90+
layoutSynthesisTime,
9191
codeGenerationTime,
9292
totalTime);
9393
};
@@ -107,8 +107,7 @@ class Compiler : protected Scheduler,
107107
Compiler(const Architecture& architecture, const Config& config)
108108
: Scheduler(architecture, config.schedulerConfig),
109109
ReuseAnalyzer(architecture, config.reuseAnalyzerConfig),
110-
Placer(architecture, config.placerConfig),
111-
Router(architecture, config.routerConfig),
110+
LayoutSynthesizer(architecture, config.layoutSynthesizerConfig),
112111
CodeGenerator(architecture, config.codeGeneratorConfig),
113112
architecture_(architecture), config_(config) {
114113
spdlog::set_level(config.logLevel);
@@ -166,9 +165,20 @@ class Compiler : protected Scheduler,
166165
const auto& schedule = SELF.schedule(qComp);
167166
const auto& singleQubitGateLayers = schedule.first;
168167
const auto& twoQubitGateLayers = schedule.second;
168+
const auto& schedulingEnd = std::chrono::system_clock::now();
169+
const auto& reuseQubits = SELF.analyzeReuse(twoQubitGateLayers);
170+
const auto& reuseAnalysisEnd = std::chrono::system_clock::now();
171+
const auto& [placement, routing] = LayoutSynthesizer::synthesize(
172+
qComp.getNqubits(), twoQubitGateLayers, reuseQubits);
173+
const auto& layoutSynthesisEnd = std::chrono::system_clock::now();
174+
NAComputation code =
175+
SELF.generate(singleQubitGateLayers, placement, routing);
176+
const auto& codeGenerationEnd = std::chrono::system_clock::now();
177+
assert(code.validate().first);
178+
169179
statistics_.schedulingTime =
170-
std::chrono::duration_cast<std::chrono::microseconds>(
171-
std::chrono::system_clock::now() - schedulingStart)
180+
std::chrono::duration_cast<std::chrono::microseconds>(schedulingEnd -
181+
schedulingStart)
172182
.count();
173183
SPDLOG_INFO("Time for scheduling: {}us", statistics_.schedulingTime);
174184
#if SPDLOG_ACTIVE_LEVEL <= SPDLOG_LEVEL_DEBUG
@@ -193,46 +203,28 @@ class Compiler : protected Scheduler,
193203
avg, max);
194204
}
195205
#endif // SPDLOG_ACTIVE_LEVEL <= SPDLOG_LEVEL_DEBUG
196-
197-
const auto& reuseAnalysisStart = std::chrono::system_clock::now();
198-
const auto& reuseQubits = SELF.analyzeReuse(twoQubitGateLayers);
199206
statistics_.reuseAnalysisTime =
200-
std::chrono::duration_cast<std::chrono::microseconds>(
201-
std::chrono::system_clock::now() - reuseAnalysisStart)
207+
std::chrono::duration_cast<std::chrono::microseconds>(reuseAnalysisEnd -
208+
schedulingEnd)
202209
.count();
203210
SPDLOG_INFO("Time for reuse analysis: {}us", statistics_.reuseAnalysisTime);
204-
205-
const auto& placementStart = std::chrono::system_clock::now();
206-
const auto& placement =
207-
SELF.place(qComp.getNqubits(), twoQubitGateLayers, reuseQubits);
208-
statistics_.placementTime =
211+
statistics_.layoutSynthesisTime =
209212
std::chrono::duration_cast<std::chrono::microseconds>(
210-
std::chrono::system_clock::now() - placementStart)
213+
layoutSynthesisEnd - reuseAnalysisEnd)
211214
.count();
212-
SPDLOG_INFO("Time for placement: {}us", statistics_.placementTime);
213-
214-
const auto& routingStart = std::chrono::system_clock::now();
215-
const auto& routing = SELF.route(placement);
216-
statistics_.routingTime =
217-
std::chrono::duration_cast<std::chrono::microseconds>(
218-
std::chrono::system_clock::now() - routingStart)
219-
.count();
220-
SPDLOG_INFO("Time for routing: {}us", statistics_.routingTime);
221-
222-
const auto& codeGenerationStart = std::chrono::system_clock::now();
223-
NAComputation code =
224-
SELF.generate(singleQubitGateLayers, placement, routing);
225-
assert(code.validate().first);
215+
statistics_.layoutSynthesizerStatistics =
216+
SELF.getLayoutSynthesisStatistics();
217+
SPDLOG_INFO("Time for layout synthesis: {}us",
218+
statistics_.layoutSynthesisTime);
226219
statistics_.codeGenerationTime =
227220
std::chrono::duration_cast<std::chrono::microseconds>(
228-
std::chrono::system_clock::now() - codeGenerationStart)
221+
codeGenerationEnd - layoutSynthesisEnd)
229222
.count();
230223
SPDLOG_INFO("Time for code generation: {}us",
231224
statistics_.codeGenerationTime);
232-
233225
statistics_.totalTime =
234226
std::chrono::duration_cast<std::chrono::microseconds>(
235-
std::chrono::system_clock::now() - schedulingStart)
227+
codeGenerationEnd - schedulingStart)
236228
.count();
237229
SPDLOG_INFO("Total time: {}us", statistics_.totalTime);
238230
return code;
@@ -243,10 +235,21 @@ class Compiler : protected Scheduler,
243235
}
244236
};
245237

238+
class RoutingAgnosticSynthesizer
239+
: public PlaceAndRouteSynthesizer<RoutingAgnosticSynthesizer,
240+
VertexMatchingPlacer,
241+
IndependentSetRouter> {
242+
public:
243+
RoutingAgnosticSynthesizer(const Architecture& architecture,
244+
const Config& config)
245+
: PlaceAndRouteSynthesizer(architecture, config) {}
246+
explicit RoutingAgnosticSynthesizer(const Architecture& architecture)
247+
: PlaceAndRouteSynthesizer(architecture) {}
248+
};
246249
class RoutingAgnosticCompiler final
247250
: public Compiler<RoutingAgnosticCompiler, ASAPScheduler,
248-
VertexMatchingReuseAnalyzer, VertexMatchingPlacer,
249-
IndependentSetRouter, CodeGenerator> {
251+
VertexMatchingReuseAnalyzer, RoutingAgnosticSynthesizer,
252+
CodeGenerator> {
250253
public:
251254
RoutingAgnosticCompiler(const Architecture& architecture,
252255
const Config& config)
@@ -255,10 +258,20 @@ class RoutingAgnosticCompiler final
255258
: Compiler(architecture) {}
256259
};
257260

261+
class RoutingAwareSynthesizer
262+
: public PlaceAndRouteSynthesizer<RoutingAwareSynthesizer, AStarPlacer,
263+
IndependentSetRouter> {
264+
public:
265+
RoutingAwareSynthesizer(const Architecture& architecture,
266+
const Config& config)
267+
: PlaceAndRouteSynthesizer(architecture, config) {}
268+
explicit RoutingAwareSynthesizer(const Architecture& architecture)
269+
: PlaceAndRouteSynthesizer(architecture) {}
270+
};
258271
class RoutingAwareCompiler final
259272
: public Compiler<RoutingAwareCompiler, ASAPScheduler,
260-
VertexMatchingReuseAnalyzer, AStarPlacer,
261-
IndependentSetRouter, CodeGenerator> {
273+
VertexMatchingReuseAnalyzer, RoutingAwareSynthesizer,
274+
CodeGenerator> {
262275
public:
263276
RoutingAwareCompiler(const Architecture& architecture, const Config& config)
264277
: Compiler(architecture, config) {}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright (c) 2023 - 2025 Chair for Design Automation, TUM
3+
* Copyright (c) 2025 Munich Quantum Software Company GmbH
4+
* All rights reserved.
5+
*
6+
* SPDX-License-Identifier: MIT
7+
*
8+
* Licensed under the MIT License
9+
*/
10+
11+
#pragma once
12+
13+
#include "na/zoned/Types.hpp"
14+
15+
#include <unordered_set>
16+
#include <vector>
17+
18+
namespace na::zoned {
19+
/**
20+
* The Abstract Base Class for the Layout Synthesizer of the MQT's Zoned Neutral
21+
* Atom Compiler.
22+
*/
23+
class LayoutSynthesizerBase {
24+
public:
25+
virtual ~LayoutSynthesizerBase() = default;
26+
/**
27+
* Collection of the placement and routing results.
28+
*/
29+
struct Layout {
30+
std::vector<Placement> placement; ///< The placement of the qubits
31+
std::vector<Routing> routing; ///< The routing of the qubits
32+
};
33+
/**
34+
* This function defines the interface of the layout synthesizer.
35+
* @param nQubits is the number of qubits in the quantum computation.
36+
* @param twoQubitGateLayers is a vector of two-qubit gate layers,
37+
* where each layer contains the two-qubit gates to be placed.
38+
* @param reuseQubits is a vector of qubit sets that can be reused
39+
* between layers.
40+
* @returns A Layout object containing the placement and routing results.
41+
*/
42+
[[nodiscard]] virtual auto
43+
synthesize(size_t nQubits,
44+
const std::vector<TwoQubitGateLayer>& twoQubitGateLayers,
45+
const std::vector<std::unordered_set<qc::Qubit>>& reuseQubits)
46+
-> Layout = 0;
47+
};
48+
} // namespace na::zoned

0 commit comments

Comments
 (0)