Skip to content

Commit cb21e93

Browse files
authored
✨ Add buffer size support to HybridSynthesisMapper (#885)
## Description This PR adds buffer functionality to the ZX hybrid synthesis mapper, enabling staged operation management before mapping to hardware. The changes introduce a bufferSize parameter and bufferedQc quantum computation to temporarily store operations, along with support for complete remapping and more flexible synthesis evaluation. These are the extension which will be presented at DATE 2026 ## Checklist: - [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.
2 parents 5b40525 + fe2a173 commit cb21e93

6 files changed

Lines changed: 286 additions & 110 deletions

File tree

bindings/hybrid_mapper/hybrid_mapper.cpp

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -228,10 +228,10 @@ PYBIND11_MODULE(MQT_QMAP_MODULE_NAME, m, py::mod_gil_not_used()) {
228228
"Neutral Atom Mapper that can evaluate different synthesis steps "
229229
"to choose the best one.")
230230
.def(py::init<const na::NeutralAtomArchitecture&,
231-
const na::MapperParameters&>(),
231+
const na::MapperParameters&, uint32_t>(),
232232
"Create Hybrid Synthesis Mapper with mapper parameters.",
233233
py::keep_alive<1, 2>(), py::keep_alive<1, 3>(), "arch"_a,
234-
"params"_a = na::MapperParameters())
234+
"params"_a = na::MapperParameters(), "buffer_size"_a = 0)
235235
.def("set_parameters", &na::HybridSynthesisMapper::setParameters,
236236
"Set the parameters for the Hybrid Synthesis Mapper.", "params"_a,
237237
py::keep_alive<1, 2>())
@@ -267,22 +267,15 @@ PYBIND11_MODULE(MQT_QMAP_MODULE_NAME, m, py::mod_gil_not_used()) {
267267
"Saves the synthesized circuit with all gates but not "
268268
"mapped to the hardware as qasm2 to a file.",
269269
"filename"_a)
270-
.def(
271-
"append_without_mapping",
272-
[](na::HybridSynthesisMapper& mapper, qc::QuantumComputation& qc) {
273-
mapper.appendWithoutMapping(qc);
274-
},
275-
"Appends the given QuantumComputation to the synthesized "
276-
"QuantumComputation without mapping it to the hardware.",
277-
"qc"_a)
278270
.def(
279271
"append_with_mapping",
280-
[](na::HybridSynthesisMapper& mapper, qc::QuantumComputation& qc) {
281-
mapper.appendWithMapping(qc);
272+
[](na::HybridSynthesisMapper& mapper, qc::QuantumComputation& qc,
273+
bool completeRemap) {
274+
mapper.appendWithMapping(qc, completeRemap);
282275
},
283276
"Appends the given QuantumComputation to the synthesized "
284277
"QuantumComputation and maps the gates to the hardware.",
285-
"qc"_a)
278+
"qc"_a, "complete_remap"_a = false)
286279
.def(
287280
"get_circuit_adjacency_matrix",
288281
[](const na::HybridSynthesisMapper& mapper) {
@@ -301,15 +294,20 @@ PYBIND11_MODULE(MQT_QMAP_MODULE_NAME, m, py::mod_gil_not_used()) {
301294
.def(
302295
"evaluate_synthesis_steps",
303296
[](na::HybridSynthesisMapper& mapper,
304-
std::vector<qc::QuantumComputation>& qcs, bool alsoMap) {
305-
return mapper.evaluateSynthesisSteps(qcs, alsoMap);
297+
std::vector<qc::QuantumComputation>& qcs, bool completeRemap,
298+
bool alsoMap) {
299+
return mapper.evaluateSynthesisSteps(qcs, completeRemap, alsoMap);
306300
},
307301
"Evaluates the synthesis steps proposed by the ZX extraction. "
308302
"Returns a list of fidelities of the mapped synthesis steps.",
309-
"synthesis_steps"_a, "also_map"_a = false)
310-
.def("complete_remap", &na::HybridSynthesisMapper::completeRemap,
311-
"Remaps the synthesized QuantumComputation to the hardware.",
312-
"initial_mapping"_a = na::InitialMapping::Identity)
303+
"synthesis_steps"_a, "complete_remap"_a = false, "also_map"_a = false)
304+
.def(
305+
"complete_remap",
306+
[](na::HybridSynthesisMapper& mapper, bool includeBuffer) {
307+
mapper.completeRemap(includeBuffer);
308+
},
309+
"Remaps the QuantumComputation to the hardware.",
310+
"include_buffer"_a = true)
313311
.def(
314312
"schedule",
315313
[](na::HybridSynthesisMapper& mapper, const bool verbose,

include/hybridmap/HybridSynthesisMapper.hpp

Lines changed: 116 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,13 @@
1717

1818
#include "HybridNeutralAtomMapper.hpp"
1919
#include "NeutralAtomArchitecture.hpp"
20-
#include "NeutralAtomUtils.hpp"
20+
#include "hybridmap/Mapping.hpp"
2121
#include "hybridmap/NeutralAtomDefinitions.hpp"
22+
#include "hybridmap/NeutralAtomScheduler.hpp"
2223
#include "ir/Definitions.hpp"
2324
#include "ir/QuantumComputation.hpp"
2425

25-
#include <cstddef>
26+
#include <cstdint>
2627
#include <fstream>
2728
#include <sstream>
2829
#include <stdexcept>
@@ -44,103 +45,187 @@ class HybridSynthesisMapper : public NeutralAtomMapper {
4445
using qcs = std::vector<qc::QuantumComputation>;
4546

4647
qc::QuantumComputation synthesizedQc;
48+
qc::QuantumComputation bufferedQc;
49+
uint32_t bufferSize;
50+
Mapping originalMapping;
4751
bool initialized = false;
4852

4953
/**
5054
* @brief Evaluate a single proposed synthesis step.
5155
* @details Effort considers swaps/shuttling and execution time estimated by
5256
* the mapper.
5357
* @param qc Proposed synthesis subcircuit.
58+
* @param completeRemap If `true`, evaluate by completely remapping the whole
59+
* circuit; if `false`, preserve the current mapping state.
5460
* @return Scalar cost/effort score for mapping qc.
5561
*/
56-
qc::fp evaluateSynthesisStep(qc::QuantumComputation& qc) const;
62+
qc::fp evaluateSynthesisStep(qc::QuantumComputation& qc,
63+
bool completeRemap = false) const;
5764

5865
public:
5966
// Constructors
6067
HybridSynthesisMapper() = delete;
6168
/**
62-
* @brief Construct with device and optional mapper parameters.
63-
* @param arch Neutral atom architecture.
64-
* @param params Optional mapper configuration parameters.
69+
* @brief Create a HybridSynthesisMapper configured for a neutral-atom device.
70+
*
71+
* @param arch Neutral-atom device architecture used for mapping and
72+
* scheduling.
73+
* @param params Optional mapper configuration parameters; defaults to
74+
* MapperParameters().
75+
* @param bufferSize Number of operations to hold in the internal buffer
76+
* before flushing to the synthesized circuit (0 disables buffering).
6577
*/
6678
explicit HybridSynthesisMapper(
6779
const NeutralAtomArchitecture& arch,
68-
const MapperParameters& params = MapperParameters())
69-
: NeutralAtomMapper(arch, params) {}
80+
const MapperParameters& params = MapperParameters(),
81+
const uint32_t bufferSize = 0)
82+
: NeutralAtomMapper(arch, params), bufferSize(bufferSize) {}
7083

7184
// Functions
7285

7386
/**
74-
* @brief Initialize synthesized and mapped circuits and mapping structures.
75-
* @param nQubits Number of logical qubits to synthesize.
87+
* @brief Initialize internal circuits and mapping state for a given number of
88+
* logical qubits.
89+
*
90+
* Sets up the synthesized and buffered quantum computations, the mapped
91+
* circuit storage, the logical-to-physical mapping, and records the original
92+
* mapping; marks the mapper as initialized.
93+
*
94+
* @param nQubits Number of logical qubits to prepare.
95+
* @throws std::runtime_error if `nQubits` exceeds the architecture's
96+
* available positions.
7697
*/
7798
void initMapping(const size_t nQubits) {
7899
if (nQubits > arch->getNpositions()) {
79100
throw std::runtime_error("Not enough qubits in architecture.");
80101
}
81102
mappedQc = qc::QuantumComputation(arch->getNpositions());
82103
synthesizedQc = qc::QuantumComputation(nQubits);
104+
bufferedQc = qc::QuantumComputation(nQubits);
83105
mapping = Mapping(nQubits);
84106
initialized = true;
107+
originalMapping = mapping;
85108
}
86109

87110
/**
88-
* @brief Complete a (re-)mapping of the synthesized circuit to hardware.
89-
* @param initMapping Initial mapping heuristic (defaults to Identity).
111+
* @brief Remap the synthesized circuit onto the stored original hardware
112+
* mapping.
113+
*
114+
* If `includeBuffer` is true, the remap uses the combined
115+
* synthesized-plus-buffered circuit returned by getSynthesizedQc(); otherwise
116+
* it remaps only the internal synthesizedQc (excluding buffered operations).
117+
* The result is mapped into originalMapping.
118+
*
119+
* @param includeBuffer When true, include buffered operations in the remap.
90120
*/
91-
void completeRemap(const InitialMapping initMapping = Identity) {
92-
auto qcCopy = synthesizedQc;
93-
map(qcCopy, initMapping);
121+
void completeRemap(const bool includeBuffer = true) {
122+
if (includeBuffer) {
123+
auto copyQC = getSynthesizedQc();
124+
map(copyQC, originalMapping);
125+
} else {
126+
auto temp = synthesizedQc;
127+
map(temp, originalMapping);
128+
}
129+
}
130+
131+
/**
132+
* @brief Appends buffered operations into the synthesized circuit, maps them,
133+
* and schedules the mapped circuit.
134+
*
135+
* Appends all operations currently held in the internal buffer to the
136+
* synthesized circuit, applies mapping for those appended operations, clears
137+
* the buffer, and then performs the scheduling pass using the base mapper.
138+
*
139+
* @param verboseArg Enable verbose scheduling output when true.
140+
* @param createAnimationCsv Emit scheduling animation CSV when true.
141+
* @param shuttlingSpeedFactor Factor to scale shuttling durations during
142+
* scheduling (1.0 = nominal speed).
143+
* @return SchedulerResults Results produced by the scheduling pass (timing
144+
* and placement outcomes).
145+
*/
146+
[[nodiscard]] SchedulerResults
147+
schedule(const bool verboseArg = false, const bool createAnimationCsv = false,
148+
const qc::fp shuttlingSpeedFactor = 1.0) {
149+
for (const auto& op : bufferedQc) {
150+
synthesizedQc.emplace_back(op->clone());
151+
}
152+
mapAppend(bufferedQc, this->mapping);
153+
bufferedQc.clear();
154+
return NeutralAtomMapper::schedule(verboseArg, createAnimationCsv,
155+
shuttlingSpeedFactor);
94156
}
95157

96158
/**
97-
* @brief Get the currently synthesized (unmapped) circuit.
98-
* @return Synthesized QuantumComputation.
159+
* @brief Return a combined view of the synthesized and buffered (unmapped)
160+
* circuit.
161+
*
162+
* The returned QuantumComputation contains all operations from the mapper's
163+
* synthesized circuit followed by any buffered operations, using the mapper's
164+
* current qubit count.
165+
*
166+
* @return qc::QuantumComputation A new QuantumComputation with synthesized
167+
* operations then buffered operations.
99168
*/
100169
[[nodiscard]] qc::QuantumComputation getSynthesizedQc() const {
101-
return synthesizedQc;
170+
qc::QuantumComputation qc(synthesizedQc.getNqubits());
171+
qc.reserve(synthesizedQc.size() + bufferedQc.size());
172+
for (const auto& op : synthesizedQc) {
173+
qc.emplace_back(op->clone());
174+
}
175+
for (const auto& op : bufferedQc) {
176+
qc.emplace_back(op->clone());
177+
}
178+
return qc;
102179
}
103180

104181
/**
105-
* @brief Export synthesized circuit as OpenQASM string.
106-
* @return QASM representation of the synthesized circuit.
182+
* @brief Produce the OpenQASM representation of the synthesized circuit.
183+
*
184+
* Returns an OpenQASM string for the current synthesized circuit view,
185+
* including any buffered operations not yet scheduled.
186+
*
187+
* @return std::string OpenQASM text describing the synthesized (and buffered)
188+
* circuit.
107189
*/
108190
[[nodiscard]] [[maybe_unused]] std::string getSynthesizedQcQASM() const {
109191
std::stringstream ss;
110-
synthesizedQc.dumpOpenQASM(ss, false);
192+
const auto copyQC = getSynthesizedQc();
193+
copyQC.dumpOpenQASM(ss, false);
111194
return ss.str();
112195
}
113196

114197
/**
115-
* @brief Save synthesized circuit as OpenQASM to a file.
116-
* @param filename Output filename.
198+
* @brief Write the current synthesized circuit (including buffered
199+
* operations) to a file in OpenQASM format.
200+
*
201+
* @param filename Path to the output file where the OpenQASM representation
202+
* will be written.
117203
*/
118204
[[maybe_unused]] void saveSynthesizedQc(const std::string& filename) const {
119205
std::ofstream ofs(filename);
120-
synthesizedQc.dumpOpenQASM(ofs, false);
206+
const auto copyQC = getSynthesizedQc();
207+
copyQC.dumpOpenQASM(ofs, false);
121208
ofs.close();
122209
}
123210

124211
/**
125212
* @brief Evaluate candidate synthesis steps and optionally map the best.
126213
* @param synthesisSteps Vector of candidate subcircuits.
214+
* @param completeRemap If true, completely remap before evaluation.
127215
* @param alsoMap If true, append and map the best candidate.
128216
* @return List of fidelity scores for mapped steps (order matches input).
129217
*/
130218
std::vector<qc::fp> evaluateSynthesisSteps(qcs& synthesisSteps,
219+
bool completeRemap = false,
131220
bool alsoMap = false);
132221

133-
/**
134-
* @brief Append gates without mapping (no SWAPs/shuttling inserted).
135-
* @param qc Subcircuit to append as-is.
136-
*/
137-
void appendWithoutMapping(const qc::QuantumComputation& qc);
138-
139222
/**
140223
* @brief Append and map a subcircuit to hardware (may insert moves/SWAPs).
141224
* @param qc Subcircuit to append and map.
225+
* @param completeRemap If true, completely remap before appending.
142226
*/
143-
void appendWithMapping(qc::QuantumComputation& qc);
227+
void appendWithMapping(qc::QuantumComputation& qc,
228+
bool completeRemap = false);
144229

145230
/**
146231
* @brief Get the current device adjacency (connectivity) matrix.

python/mqt/qmap/hybrid_mapper.pyi

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -99,21 +99,22 @@ class HybridNAMapper:
9999

100100
# noinspection DuplicatedCode
101101
class HybridSynthesisMapper:
102-
def __init__(self, arch: NeutralAtomHybridArchitecture, params: MapperParameters = ...) -> None: ...
103-
def append_with_mapping(self, qc: QuantumComputation) -> None: ...
104-
def append_without_mapping(self, qc: QuantumComputation) -> None: ...
105-
def complete_remap(self, initial_mapping: InitialCircuitMapping = ...) -> None: ...
102+
def __init__(
103+
self, arch: NeutralAtomHybridArchitecture, params: MapperParameters = ..., buffer_size: typing.SupportsInt = ...
104+
) -> None: ...
105+
def append_with_mapping(self, qc: QuantumComputation, complete_remap: bool = ...) -> None: ...
106+
def complete_remap(self, include_buffer: bool = ...) -> None: ...
106107
def convert_to_aod(self) -> None: ...
107108
def evaluate_synthesis_steps(
108-
self, synthesis_steps: list[QuantumComputation], also_map: bool = ...
109+
self, synthesis_steps: list[QuantumComputation], complete_remap: bool = ..., also_map: bool = ...
109110
) -> list[float]: ...
110111
def get_circuit_adjacency_matrix(self) -> list[list[int]]: ...
111-
def get_mapped_qc_qasm(self) -> str: ...
112112
def get_mapped_qc_aod_qasm(self) -> str: ...
113+
def get_mapped_qc_qasm(self) -> str: ...
113114
def get_synthesized_qc_qasm(self) -> str: ...
114115
def init_mapping(self, n_qubits: typing.SupportsInt) -> None: ...
115-
def save_mapped_qc_qasm(self, filename: str) -> None: ...
116116
def save_mapped_qc_aod_qasm(self, filename: str) -> None: ...
117+
def save_mapped_qc_qasm(self, filename: str) -> None: ...
117118
def save_synthesized_qc_qasm(self, filename: str) -> None: ...
118119
def schedule(
119120
self, verbose: bool = ..., create_animation_csv: bool = ..., shuttling_speed_factor: typing.SupportsFloat = ...

0 commit comments

Comments
 (0)