Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ _If you are upgrading: please see [`UPGRADING.md`](UPGRADING.md#320)._

### Changed

- **Breaking**: ♻️ Neutral Atom Compiler: Merge Placement and Routing stage into a Layout Synthesis stage ([#713]) ([**@ystade**])
- ✨ Expose enums to Python via `pybind11`'s new (`enum.Enum`-compatible) `py::native_enum` ([#715]) ([**@denialhaag**])
- ♻️ Restructure the Python code to introduce modules ([#665]) ([**@denialhaag**])
- ♻️ Restructure the C++ code for the Python bindings to mirror the introduced Python modules ([#665]) ([**@denialhaag**])
Expand Down Expand Up @@ -108,6 +109,7 @@ _📚 Refer to the [GitHub Release Notes] for previous changelogs._

[#715]: https://github.com/munich-quantum-toolkit/qmap/pull/715
[#714]: https://github.com/munich-quantum-toolkit/qmap/pull/714
[#713]: https://github.com/munich-quantum-toolkit/qmap/pull/713
[#712]: https://github.com/munich-quantum-toolkit/qmap/pull/712
[#694]: https://github.com/munich-quantum-toolkit/qmap/pull/694
[#665]: https://github.com/munich-quantum-toolkit/qmap/pull/665
Expand Down
8 changes: 8 additions & 0 deletions UPGRADING.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ All Python enums (e.g., `sc.Method`) are now exposed via `pybind11`'s new `py::n
As a result, the enums can no longer be initialized using a string.
Instead of `Method("exact")` or `"exact"`, use `Method.exact`.

This release restructures the neutral atom compiler which has consequences for its configuration and the reporting of statistics.
The placement and routing stages have been merged into a single layout synthesis stage.
There is a new `PlaceAndRouteSynthesizer` that combines the previously separate placement and routing stages.
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.
The Python API did not change in this regard.
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.
The latter affects both the C++ and Python APIs.

## [3.2.0]

With this release, the Python package has been restructured.
Expand Down
15 changes: 9 additions & 6 deletions bindings/na/zoned/zoned.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@
#include "na/zoned/Architecture.hpp"
#include "na/zoned/Compiler.hpp"
#include "na/zoned/code_generator/CodeGenerator.hpp"
#include "na/zoned/placer/AStarPlacer.hpp"
#include "na/zoned/placer/VertexMatchingPlacer.hpp"
#include "na/zoned/layout_synthesizer/PlaceAndRouteSynthesizer.hpp"
#include "na/zoned/layout_synthesizer/placer/AStarPlacer.hpp"
#include "na/zoned/layout_synthesizer/placer/VertexMatchingPlacer.hpp"

#include <cstddef>
// The header <nlohmann/json.hpp> is used, but clang-tidy confuses it with the
Expand Down Expand Up @@ -50,7 +51,8 @@ PYBIND11_MODULE(MQT_QMAP_MODULE_NAME, m, py::mod_gil_not_used()) {
-> na::zoned::RoutingAgnosticCompiler {
na::zoned::RoutingAgnosticCompiler::Config config;
config.logLevel = spdlog::level::from_str(logLevel);
config.placerConfig = {useWindow, windowSize, dynamicPlacement};
config.layoutSynthesizerConfig.placerConfig = {useWindow, windowSize,
dynamicPlacement};
config.codeGeneratorConfig = {parkingOffset, warnUnsupportedGates};
return {arch, config};
}),
Expand Down Expand Up @@ -93,9 +95,10 @@ PYBIND11_MODULE(MQT_QMAP_MODULE_NAME, m, py::mod_gil_not_used()) {
-> na::zoned::RoutingAwareCompiler {
na::zoned::RoutingAwareCompiler::Config config;
config.logLevel = spdlog::level::from_str(logLevel);
config.placerConfig = {useWindow, windowMinWidth, windowRatio,
windowShare, deepeningFactor, deepeningValue,
lookaheadFactor, reuseLevel, maxNodes};
config.layoutSynthesizerConfig.placerConfig = {
useWindow, windowMinWidth, windowRatio,
windowShare, deepeningFactor, deepeningValue,
lookaheadFactor, reuseLevel, maxNodes};
config.codeGeneratorConfig = {parkingOffset, warnUnsupportedGates};
return {arch, config};
}),
Expand Down
121 changes: 67 additions & 54 deletions include/na/zoned/Compiler.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@
#include "code_generator/CodeGenerator.hpp"
#include "ir/QuantumComputation.hpp"
#include "ir/operations/Operation.hpp"
#include "layout_synthesizer/PlaceAndRouteSynthesizer.hpp"
#include "layout_synthesizer/placer/AStarPlacer.hpp"
#include "layout_synthesizer/placer/VertexMatchingPlacer.hpp"
#include "layout_synthesizer/router/IndependentSetRouter.hpp"
#include "na/NAComputation.hpp"
#include "placer/AStarPlacer.hpp"
#include "placer/VertexMatchingPlacer.hpp"
#include "reuse_analyzer/VertexMatchingReuseAnalyzer.hpp"
#include "router/IndependentSetRouter.hpp"
#include "scheduler/ASAPScheduler.hpp"

#include <cassert>
Expand All @@ -43,11 +44,10 @@ namespace na::zoned {
* compiler and setting them at runtime.
*/
template <class ConcreteType, class Scheduler, class ReuseAnalyzer,
class Placer, class Router, class CodeGenerator>
class LayoutSynthesizer, class CodeGenerator>
class Compiler : protected Scheduler,
protected ReuseAnalyzer,
protected Placer,
protected Router,
protected LayoutSynthesizer,
protected CodeGenerator {
friend ConcreteType;

Expand All @@ -61,33 +61,33 @@ class Compiler : protected Scheduler,
typename Scheduler::Config schedulerConfig{};
/// Configuration for the reuse analyzer
typename ReuseAnalyzer::Config reuseAnalyzerConfig{};
/// Configuration for the placer
typename Placer::Config placerConfig{};
/// Configuration for the router
typename Router::Config routerConfig{};
/// Configuration for the layout synthesizer
typename LayoutSynthesizer::Config layoutSynthesizerConfig{};
/// Configuration for the code generator
typename CodeGenerator::Config codeGeneratorConfig{};
/// Log level for the compiler
spdlog::level::level_enum logLevel = spdlog::level::info;
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Config, schedulerConfig,
reuseAnalyzerConfig,
placerConfig, routerConfig,
layoutSynthesizerConfig,
codeGeneratorConfig, logLevel);
};
/**
* Collection of statistics collected during the compilation process for the
* different components.
*/
struct Statistics {
int64_t schedulingTime; ///< Time taken for scheduling in us
int64_t reuseAnalysisTime; ///< Time taken for reuse analysis in us
int64_t placementTime; ///< Time taken for placement in us
int64_t routingTime; ///< Time taken for routing in us
int64_t codeGenerationTime; ///< Time taken for code generation in us
int64_t totalTime; ///< Total time taken for the compilation in us
int64_t schedulingTime; ///< Time taken for scheduling in us
int64_t reuseAnalysisTime; ///< Time taken for reuse analysis in us
/// Statistics collected during layout synthesis.
typename LayoutSynthesizer::Statistics layoutSynthesizerStatistics;
int64_t layoutSynthesisTime; ///< Time taken for layout synthesis in us
int64_t codeGenerationTime; ///< Time taken for code generation in us
int64_t totalTime; ///< Total time taken for the compilation in us
NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(Statistics, schedulingTime,
reuseAnalysisTime,
placementTime, routingTime,
layoutSynthesizerStatistics,
layoutSynthesisTime,
codeGenerationTime,
totalTime);
};
Expand All @@ -107,8 +107,7 @@ class Compiler : protected Scheduler,
Compiler(const Architecture& architecture, const Config& config)
: Scheduler(architecture, config.schedulerConfig),
ReuseAnalyzer(architecture, config.reuseAnalyzerConfig),
Placer(architecture, config.placerConfig),
Router(architecture, config.routerConfig),
LayoutSynthesizer(architecture, config.layoutSynthesizerConfig),
CodeGenerator(architecture, config.codeGeneratorConfig),
architecture_(architecture), config_(config) {
spdlog::set_level(config.logLevel);
Expand Down Expand Up @@ -166,9 +165,20 @@ class Compiler : protected Scheduler,
const auto& schedule = SELF.schedule(qComp);
const auto& singleQubitGateLayers = schedule.first;
const auto& twoQubitGateLayers = schedule.second;
const auto& schedulingEnd = std::chrono::system_clock::now();
const auto& reuseQubits = SELF.analyzeReuse(twoQubitGateLayers);
const auto& reuseAnalysisEnd = std::chrono::system_clock::now();
const auto& [placement, routing] = LayoutSynthesizer::synthesize(
qComp.getNqubits(), twoQubitGateLayers, reuseQubits);
const auto& layoutSynthesisEnd = std::chrono::system_clock::now();
NAComputation code =
SELF.generate(singleQubitGateLayers, placement, routing);
const auto& codeGenerationEnd = std::chrono::system_clock::now();
assert(code.validate().first);

statistics_.schedulingTime =
std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::system_clock::now() - schedulingStart)
std::chrono::duration_cast<std::chrono::microseconds>(schedulingEnd -
schedulingStart)
.count();
SPDLOG_INFO("Time for scheduling: {}us", statistics_.schedulingTime);
#if SPDLOG_ACTIVE_LEVEL <= SPDLOG_LEVEL_DEBUG
Expand All @@ -193,46 +203,28 @@ class Compiler : protected Scheduler,
avg, max);
}
#endif // SPDLOG_ACTIVE_LEVEL <= SPDLOG_LEVEL_DEBUG

const auto& reuseAnalysisStart = std::chrono::system_clock::now();
const auto& reuseQubits = SELF.analyzeReuse(twoQubitGateLayers);
statistics_.reuseAnalysisTime =
std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::system_clock::now() - reuseAnalysisStart)
std::chrono::duration_cast<std::chrono::microseconds>(reuseAnalysisEnd -
schedulingEnd)
.count();
SPDLOG_INFO("Time for reuse analysis: {}us", statistics_.reuseAnalysisTime);

const auto& placementStart = std::chrono::system_clock::now();
const auto& placement =
SELF.place(qComp.getNqubits(), twoQubitGateLayers, reuseQubits);
statistics_.placementTime =
statistics_.layoutSynthesisTime =
std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::system_clock::now() - placementStart)
layoutSynthesisEnd - reuseAnalysisEnd)
.count();
SPDLOG_INFO("Time for placement: {}us", statistics_.placementTime);

const auto& routingStart = std::chrono::system_clock::now();
const auto& routing = SELF.route(placement);
statistics_.routingTime =
std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::system_clock::now() - routingStart)
.count();
SPDLOG_INFO("Time for routing: {}us", statistics_.routingTime);

const auto& codeGenerationStart = std::chrono::system_clock::now();
NAComputation code =
SELF.generate(singleQubitGateLayers, placement, routing);
assert(code.validate().first);
statistics_.layoutSynthesizerStatistics =
SELF.getLayoutSynthesisStatistics();
SPDLOG_INFO("Time for layout synthesis: {}us",
statistics_.layoutSynthesisTime);
statistics_.codeGenerationTime =
std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::system_clock::now() - codeGenerationStart)
codeGenerationEnd - layoutSynthesisEnd)
.count();
SPDLOG_INFO("Time for code generation: {}us",
statistics_.codeGenerationTime);

statistics_.totalTime =
std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::system_clock::now() - schedulingStart)
codeGenerationEnd - schedulingStart)
.count();
SPDLOG_INFO("Total time: {}us", statistics_.totalTime);
return code;
Expand All @@ -243,10 +235,21 @@ class Compiler : protected Scheduler,
}
};

class RoutingAgnosticSynthesizer
: public PlaceAndRouteSynthesizer<RoutingAgnosticSynthesizer,
VertexMatchingPlacer,
IndependentSetRouter> {
public:
RoutingAgnosticSynthesizer(const Architecture& architecture,
const Config& config)
: PlaceAndRouteSynthesizer(architecture, config) {}
explicit RoutingAgnosticSynthesizer(const Architecture& architecture)
: PlaceAndRouteSynthesizer(architecture) {}
};
class RoutingAgnosticCompiler final
: public Compiler<RoutingAgnosticCompiler, ASAPScheduler,
VertexMatchingReuseAnalyzer, VertexMatchingPlacer,
IndependentSetRouter, CodeGenerator> {
VertexMatchingReuseAnalyzer, RoutingAgnosticSynthesizer,
CodeGenerator> {
public:
RoutingAgnosticCompiler(const Architecture& architecture,
const Config& config)
Expand All @@ -255,10 +258,20 @@ class RoutingAgnosticCompiler final
: Compiler(architecture) {}
};

class RoutingAwareSynthesizer
: public PlaceAndRouteSynthesizer<RoutingAwareSynthesizer, AStarPlacer,
IndependentSetRouter> {
public:
RoutingAwareSynthesizer(const Architecture& architecture,
const Config& config)
: PlaceAndRouteSynthesizer(architecture, config) {}
explicit RoutingAwareSynthesizer(const Architecture& architecture)
: PlaceAndRouteSynthesizer(architecture) {}
};
class RoutingAwareCompiler final
: public Compiler<RoutingAwareCompiler, ASAPScheduler,
VertexMatchingReuseAnalyzer, AStarPlacer,
IndependentSetRouter, CodeGenerator> {
VertexMatchingReuseAnalyzer, RoutingAwareSynthesizer,
CodeGenerator> {
public:
RoutingAwareCompiler(const Architecture& architecture, const Config& config)
: Compiler(architecture, config) {}
Expand Down
48 changes: 48 additions & 0 deletions include/na/zoned/layout_synthesizer/LayoutSynthesizerBase.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright (c) 2023 - 2025 Chair for Design Automation, TUM
* Copyright (c) 2025 Munich Quantum Software Company GmbH
* All rights reserved.
*
* SPDX-License-Identifier: MIT
*
* Licensed under the MIT License
*/

#pragma once

#include "na/zoned/Types.hpp"

#include <unordered_set>
#include <vector>

namespace na::zoned {
/**
* The Abstract Base Class for the Layout Synthesizer of the MQT's Zoned Neutral
* Atom Compiler.
*/
class LayoutSynthesizerBase {
public:
virtual ~LayoutSynthesizerBase() = default;
/**
* Collection of the placement and routing results.
*/
struct Layout {
std::vector<Placement> placement; ///< The placement of the qubits
std::vector<Routing> routing; ///< The routing of the qubits
};
/**
* This function defines the interface of the layout synthesizer.
* @param nQubits is the number of qubits in the quantum computation.
* @param twoQubitGateLayers is a vector of two-qubit gate layers,
* where each layer contains the two-qubit gates to be placed.
* @param reuseQubits is a vector of qubit sets that can be reused
* between layers.
* @returns A Layout object containing the placement and routing results.
*/
[[nodiscard]] virtual auto
synthesize(size_t nQubits,
const std::vector<TwoQubitGateLayer>& twoQubitGateLayers,
const std::vector<std::unordered_set<qc::Qubit>>& reuseQubits)
-> Layout = 0;
};
} // namespace na::zoned
Loading
Loading