|
| 1 | +--- |
| 2 | +file_format: mystnb |
| 3 | +kernelspec: |
| 4 | + name: python3 |
| 5 | + number_source_lines: true |
| 6 | +--- |
| 7 | + |
| 8 | +```{code-cell} ipython3 |
| 9 | +:tags: [remove-cell] |
| 10 | +%config InlineBackend.figure_formats = ['svg'] |
| 11 | +``` |
| 12 | + |
| 13 | +<style>.widget-subarea{display:none;} /*hide widgets as they do not work with sphinx*/</style> |
| 14 | + |
| 15 | +# Hybrid Neutral Atom Routing and Mapping |
| 16 | + |
| 17 | +Neutral-atom (NA) processors combine long-range, native multi-qubit interactions with high-fidelity atom transport. |
| 18 | +HyRoNA, the hybrid mapper in MQT QMAP, exploits both capabilities: it adaptively mixes gate-based routing (SWAP/BRIDGE) |
| 19 | +with atom transport to minimize latency and error. It pairs interaction-aware initial placement with fast, |
| 20 | +capability-specific cost models and an ASAP scheduler that respects hardware constraints, and it emits hardware-native |
| 21 | +programs plus optional animation files for visualization. |
| 22 | + |
| 23 | +Below, we show how to use the Hybrid NA Mapper from Python on a small GHZ example and how to tune a few key parameters. |
| 24 | + |
| 25 | +## Example: GHZ state on a hybrid NA architecture |
| 26 | + |
| 27 | +In this example, we prepare an 8-qubit GHZ state (similar to the [zoned compiler](na_zoned_compiler.md)) and map it to a hybrid NA architecture. |
| 28 | + |
| 29 | +```{code-cell} ipython3 |
| 30 | +
|
| 31 | +from qiskit import QuantumCircuit |
| 32 | +# Build a compact GHZ(8) circuit (tree-like to keep depth small) |
| 33 | +
|
| 34 | +qc = QuantumCircuit(8) |
| 35 | +qc.h(0) |
| 36 | +qc.cx(0, 4) |
| 37 | +qc.cx(0, 2) |
| 38 | +qc.cx(4, 6) |
| 39 | +qc.cx(0, 1) |
| 40 | +qc.cx(2, 3) |
| 41 | +qc.cx(4, 5) |
| 42 | +qc.cx(6, 7) |
| 43 | +
|
| 44 | +qc.draw(output="mpl") |
| 45 | +
|
| 46 | +``` |
| 47 | + |
| 48 | +### Load a hybrid NA architecture |
| 49 | + |
| 50 | +The hybrid mapper expects an architecture specification in JSON. |
| 51 | +This repository ships several ready-to-use examples. |
| 52 | + |
| 53 | +```{code-cell} ipython3 |
| 54 | +
|
| 55 | +from pathlib import Path |
| 56 | +import tempfile |
| 57 | +import os |
| 58 | +from mqt.qmap.hybrid_mapper import NeutralAtomHybridArchitecture |
| 59 | +
|
| 60 | +# Create a minimal architecture from an in-code JSON string and instantiate |
| 61 | +# the NeutralAtomHybridArchitecture using a temporary file. The mapper's |
| 62 | +# constructor expects a filename, so we write the JSON to a temp file first. |
| 63 | +arch_json = '''{ |
| 64 | + "name": "example arch", |
| 65 | + "properties": { |
| 66 | + "nRows": 5, |
| 67 | + "nColumns": 5, |
| 68 | + "nAods": 1, |
| 69 | + "nAodCoordinates": 1, |
| 70 | + "interQubitDistance": 10, |
| 71 | + "minimalAodDistance": 0.1, |
| 72 | + "interactionRadius": 1, |
| 73 | + "blockingFactor": 1 |
| 74 | + }, |
| 75 | + "parameters": { |
| 76 | + "nQubits": 10, |
| 77 | + "gateTimes": { |
| 78 | + "none": 0.5 |
| 79 | + }, |
| 80 | + "gateAverageFidelities": { |
| 81 | + "none": 0.999 |
| 82 | + }, |
| 83 | + "decoherenceTimes": { |
| 84 | + "t1": 100000000, |
| 85 | + "t2": 1500000 |
| 86 | + }, |
| 87 | + "shuttlingTimes": { |
| 88 | + "move": 0.55, |
| 89 | + "aod_move": 0.55, |
| 90 | + "aod_activate": 20, |
| 91 | + "aod_deactivate": 20 |
| 92 | + }, |
| 93 | + "shuttlingAverageFidelities": { |
| 94 | + "move": 1, |
| 95 | + "aod_move": 1, |
| 96 | + "aod_activate": 1, |
| 97 | + "aod_deactivate": 1 |
| 98 | + } |
| 99 | + } |
| 100 | +}''' |
| 101 | +
|
| 102 | +with tempfile.NamedTemporaryFile(mode="w", delete=False, suffix=".json") as _tmp: |
| 103 | + _tmp.write(arch_json) |
| 104 | + tmp_name = _tmp.name |
| 105 | +
|
| 106 | +arch = NeutralAtomHybridArchitecture(tmp_name) |
| 107 | +# Clean up the temporary file |
| 108 | +os.unlink(tmp_name) |
| 109 | +
|
| 110 | +print(f"Loaded architecture: {arch.name} with {arch.num_qubits} qubits.") |
| 111 | +
|
| 112 | +``` |
| 113 | + |
| 114 | +### Map with HyRoNA |
| 115 | + |
| 116 | +Mapping translates the algorithm to hardware-native operations using a combination of routing with SWAP/BRIDGE gates and |
| 117 | +atom moves. What combination is used depends on the architecture capabilities and the mapper parameters. |
| 118 | +Here, we show two examples: first, we only use gate-based routing, then we let the mapper decide freely. |
| 119 | + |
| 120 | +```{code-cell} ipython3 |
| 121 | +from mqt.core import load |
| 122 | +from mqt.qmap.hybrid_mapper import HybridNAMapper, MapperParameters |
| 123 | +
|
| 124 | +# Optional: tweak parameters (defaults are sensible for most cases) |
| 125 | +params_shuttling = MapperParameters() |
| 126 | +params_shuttling.gate_weight = 1.0 |
| 127 | +params_shuttling.shuttling_weight = 0.0 # disables atom moves |
| 128 | +
|
| 129 | +
|
| 130 | +mapper = HybridNAMapper(arch, params=params_shuttling) |
| 131 | +
|
| 132 | +# Convert the Qiskit circuit to an MQT QuantumComputation and map it |
| 133 | +circ = load(qc) |
| 134 | +mapper.map(circ) # optionally: mapper.map(circ, initial_mapping=InitialCircuitMapping.identity) |
| 135 | +
|
| 136 | +# Retrieve mapping statistics |
| 137 | +mapper.get_stats() |
| 138 | +``` |
| 139 | + |
| 140 | +Note, how we set `shuttling_weight` zero to disallow atom moves and only use SWAP gates for routing. |
| 141 | + |
| 142 | +The idea of the hybrid mapper is to mix both capabilities or to automatically select the best one. |
| 143 | +We now re-run the mapping with default parameters that allow both SWAPs and atom moves. |
| 144 | + |
| 145 | +```{code-cell} ipython3 |
| 146 | +params_default = MapperParameters() |
| 147 | +
|
| 148 | +mapper.set_parameters(params_default) |
| 149 | +
|
| 150 | +mapper.map(circ) |
| 151 | +mapper.get_stats() |
| 152 | +``` |
| 153 | + |
| 154 | +Now the mapper uses atom moves only as they are the better option on this architecture where moves are unit fidelity and |
| 155 | +gates are noisy. |
| 156 | + |
| 157 | +### Schedule the mapped circuit |
| 158 | + |
| 159 | +Scheduling orders the mapped operations as-soon-as-possible while respecting hardware constraints. |
| 160 | + |
| 161 | +```{code-cell} ipython3 |
| 162 | +# Schedule; set create_animation_csv=True to generate visualization data |
| 163 | +results = mapper.schedule(verbose=False, create_animation_csv=False) |
| 164 | +
|
| 165 | +results |
| 166 | +``` |
| 167 | + |
| 168 | +You can retrieve the mapped scheduled circuit (extended QASM2) and, if desired, the variant with explicit AOD movements equivalent to the operations done on the hardware. |
| 169 | + |
| 170 | +```{code-cell} ipython3 |
| 171 | +
|
| 172 | +mapped_qasm = mapper.get_mapped_qc_qasm() |
| 173 | +# Print a snippet of the mapped QASM |
| 174 | +print("\n... Mapped QASM snippet ...\n" + |
| 175 | + "\n".join(mapped_qasm.splitlines()[:30])) |
| 176 | +
|
| 177 | +# AOD-annotated variant (hardware-native moves) |
| 178 | +mapped_aod_qasm = mapper.get_mapped_qc_aod_qasm() |
| 179 | +print("\n... AOD (converted) snippet ...\n" + "\n".join(mapped_aod_qasm.splitlines()[:30]) + "\n...") |
| 180 | +``` |
| 181 | + |
| 182 | +Here, the `q` register corresponds to the 5x5=25 coordinates of the architecture and no longer to the circuit qubits. |
| 183 | +The other registers are used for temporary storage and AOD control. |
| 184 | + |
| 185 | +The second variant shows explicit AOD movements that correspond to the atom moves done on hardware. |
| 186 | +Here, the AODs can be activated, moved, and deactivated to shuttle atoms around. |
| 187 | +The first entry corresponds to the x-direction and the second to the y-direction; in each pair, the two numbers denote start and end coordinates. |
| 188 | + |
| 189 | +### Export animation files (optional) |
| 190 | + |
| 191 | +HyRoNA can write animation output files that can be visualized with [MQT NAViz](https://github.com/munich-quantum-toolkit/naviz). |
| 192 | +Typically one has to accelerate the shuttling speed for better visualization by setting `shuttling_speed_factor`. |
| 193 | + |
| 194 | +```{code-cell} ipython3 |
| 195 | +# Re-run scheduling with animation output enabled |
| 196 | +_ = mapper.schedule(verbose=False, create_animation_csv=True, shuttling_speed_factor=100) |
| 197 | +
|
| 198 | +# Save the files; the method writes `.naviz` and `.namachine` files |
| 199 | +# next to the given base name |
| 200 | +mapper.save_animation_files("ghz8_hyrona_animation") |
| 201 | +``` |
| 202 | + |
| 203 | +This creates `.namachine` and `.naviz` files which can then be imported into |
| 204 | +MQT NAViz for visualization. |
| 205 | + |
| 206 | + |
| 207 | + |
| 208 | +## Tuning the mapper |
| 209 | + |
| 210 | +HyRoNA exposes a concise set of parameters via `MapperParameters`: |
| 211 | + |
| 212 | +- lookahead_depth: limited lookahead to peek at near-future layers |
| 213 | +- lookahead_weight_swaps / lookahead_weight_moves: balance gate-routing vs. atom motion during lookahead |
| 214 | +- decay: decreases the incentive for repeatedly blocking the same qubits |
| 215 | +- gate_weight / shuttling_weight / shuttling_time_weight: cost-model weights for gates vs. transport |
| 216 | +- dynamic_mapping_weight: bias for enabling dynamic re-mapping (SWAP/MOVE) when beneficial |
| 217 | +- max_bridge_distance: limit for BRIDGE operations; use to avoid long-range chains |
| 218 | +- initial_coord_mapping: strategy for hardware coordinate initialization |
| 219 | + |
| 220 | +For more details, please check the source code documentation of `MapperParameters`. |
| 221 | + |
| 222 | +## Tips |
| 223 | + |
| 224 | +- Initial mapping: you can explicitly select the initial circuit-to-hardware mapping for `map` or `map_qasm_file` using |
| 225 | + `initial_mapping=InitialCircuitMapping.identity` or `InitialCircuitMapping.graph`. |
| 226 | + |
| 227 | +- QASM input: instead of building a circuit in Qiskit, you can call `mapper.map_qasm_file("path/to/circuit.qasm")` and |
| 228 | + then `mapper.schedule(...)` as shown above. |
| 229 | + |
| 230 | +- Architectures: the examples `rubidium_hybrid.json`, `rubidium_gate.json`, and `rubidium_shuttling.json` in |
| 231 | + `architectures/` cover different capability profiles and are a good starting point |
| 232 | + |
| 233 | +## Hybrid Synthesis Mapper |
| 234 | + |
| 235 | +The Hybrid Synthesis Mapper helps you compare and stitch together alternative circuit fragments while keeping track of the current mapping on the NA device. |
| 236 | +Below is a compact example that mirrors the unit test flow and shows how to: |
| 237 | + |
| 238 | +- evaluate multiple candidate fragments and pick the best, |
| 239 | +- append fragments, |
| 240 | +- and retrieve mapped QASM as well as the AOD-annotated variant. |
| 241 | + |
| 242 | +First, we create the `HybridSynthesisMapper` and evaluate two candidate fragments (here, both are the same GHZ circuit as above for simplicity). |
| 243 | + |
| 244 | +```{code-cell} ipython3 |
| 245 | +from mqt.qmap.hybrid_mapper import HybridSynthesisMapper |
| 246 | +
|
| 247 | +# Reuse the architecture `arch` and GHZ circuit `qc` defined above. |
| 248 | +synth = HybridSynthesisMapper(arch) |
| 249 | +synth.init_mapping(qc.num_qubits) |
| 250 | +
|
| 251 | +fidelities = synth.evaluate_synthesis_steps([circ, circ], also_map = False) |
| 252 | +print("Fidelities of subcircuits:", fidelities ) |
| 253 | +``` |
| 254 | + |
| 255 | +The fidelity return indicates how well the subcircuit can be appended to the circuit mapped to the hardware. |
| 256 | + |
| 257 | +You can then build up a larger program by appending fragments. |
| 258 | + |
| 259 | +```{code-cell} ipython3 |
| 260 | +synth.append_with_mapping(circ) |
| 261 | +``` |
| 262 | + |
| 263 | +This appends the circuit and maps it directly. This can be repeated for multiple fragments to always choose the best one. |
| 264 | + |
| 265 | +Similar to the normal Hybrid Mapper, you can retrieve the mapped circuit and the AOD-annotated variant: |
| 266 | + |
| 267 | +```{code-cell} ipython3 |
| 268 | +
|
| 269 | +# Retrieve mapped circuit (extended QASM2) |
| 270 | +qasm_mapped = synth.get_mapped_qc_qasm() |
| 271 | +print("\n... Mapped QASM snippet ...\n" + |
| 272 | + "\n".join(qasm_mapped.splitlines()[:30]) + |
| 273 | + "\n...") |
| 274 | +``` |
| 275 | + |
| 276 | +One can also access the circuits which is constructed step-by-step in a unmapped state (e.g. to use a different mapper). |
| 277 | + |
| 278 | +```{code-cell} ipython3 |
| 279 | +qasm_synth = synth.get_synthesized_qc_qasm() |
| 280 | +print("\n... Synthesized QASM snippet ...\n" + |
| 281 | + "\n".join(qasm_synth.splitlines()[:30]) + |
| 282 | + "\n...") |
| 283 | +``` |
| 284 | + |
| 285 | +Here this corresponds simply to the input GHZ circuit. |
| 286 | + |
| 287 | +This workflow lets you mix candidate-generation (synthesis) with hardware-aware mapping and scheduling, while keeping a single source of truth for mapping state across fragments. |
0 commit comments