From 960d1a4823ce26776d7f71228398bab6d1764477 Mon Sep 17 00:00:00 2001 From: lsschmid <117631861+lsschmid@users.noreply.github.com> Date: Thu, 30 May 2024 10:42:51 +0200 Subject: [PATCH 001/394] =?UTF-8?q?=E2=9C=A8=20Added=20HybridSynthesisMapp?= =?UTF-8?q?er=20for=20ZX=20extraction=20+=20mapping?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridSynthesisMapper.hpp | 39 +++++++++++++++++++++ src/CMakeLists.txt | 4 ++- src/hybridmap/HybridSynthesisMapper.cpp | 4 +++ 3 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 include/hybridmap/HybridSynthesisMapper.hpp create mode 100644 src/hybridmap/HybridSynthesisMapper.cpp diff --git a/include/hybridmap/HybridSynthesisMapper.hpp b/include/hybridmap/HybridSynthesisMapper.hpp new file mode 100644 index 000000000..e6928cbb7 --- /dev/null +++ b/include/hybridmap/HybridSynthesisMapper.hpp @@ -0,0 +1,39 @@ +// +// This file is part of the MQT QMAP library released under the MIT license. +// See README.md or go to https://github.com/cda-tum/qmap for more information. +// + +#pragma once + +#include "HybridNeutralAtomMapper.hpp" +#include "NeutralAtomArchitecture.hpp" +#include "NeutralAtomUtils.hpp" + +namespace qc { + +/** + * @brief Class to manage information exchange between the neutral atom mapper + * and the ZX extraction. + * @deteils This class is derived from the HybridNeutralAtomMapper and stores + * all the information about the neutral atom hardware and the current status of + * the mapping. The ZX (or another synthesis algorithm) can propose different + * possible next synthesis steps, which are then evaluated by the + * HybridNeutralAtomMapper regarding the "effort" to map this synthesis step. It + * also provides additional functionality to exchange information between the ZX + * and the HybridNeutralAtomMapper. + * + */ +class HybridSynthesisMapper : private NeutralAtomMapper { +public: + // Constructors + HybridSynthesisMapper() = delete; + explicit HybridSynthesisMapper(const NeutralAtomMapper& neutralAtomMapper) + : NeutralAtomMapper(neutralAtomMapper) {} + explicit HybridSynthesisMapper( + const NeutralAtomArchitecture& arch, + InitialCoordinateMapping initialCoordinateMapping = + InitialCoordinateMapping::Trivial, + const MapperParameters& params = MapperParameters()) + : NeutralAtomMapper(arch, initialCoordinateMapping, params) {} +}; +} // namespace qc diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e0a12bbe3..420e1a497 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -87,6 +87,7 @@ macro(ADD_HYBRIDMAP_LIBRARY libname srcfile) ${PROJECT_SOURCE_DIR}/include/${libname}/NeutralAtomDefinitions.hpp ${PROJECT_SOURCE_DIR}/include/${libname}/MoveToAodConverter.hpp ${PROJECT_SOURCE_DIR}/include/${libname}/NeutralAtomLayer.hpp + ${PROJECT_SOURCE_DIR}/include/${libname}/HybridSynthesisMapper.hpp ${PROJECT_SOURCE_DIR}/include/${libname}/HybridAnimation.hpp hybridmap/NeutralAtomUtils.cpp hybridmap/NeutralAtomScheduler.cpp @@ -95,7 +96,8 @@ macro(ADD_HYBRIDMAP_LIBRARY libname srcfile) hybridmap/Mapping.cpp hybridmap/MoveToAodConverter.cpp hybridmap/HybridAnimation.cpp - hybridmap/NeutralAtomLayer.cpp) + hybridmap/NeutralAtomLayer.cpp + hybridmap/HybridSynthesisMapper.cpp) add_internal_library(${lib}) endmacro() diff --git a/src/hybridmap/HybridSynthesisMapper.cpp b/src/hybridmap/HybridSynthesisMapper.cpp new file mode 100644 index 000000000..b580c7876 --- /dev/null +++ b/src/hybridmap/HybridSynthesisMapper.cpp @@ -0,0 +1,4 @@ +// +// This file is part of the MQT QMAP library released under the MIT license. +// See README.md or go to https://github.com/cda-tum/qmap for more information. +// From b834cd1ca748cd1cf182ab163623ebe292beee66 Mon Sep 17 00:00:00 2001 From: lsschmid <117631861+lsschmid@users.noreply.github.com> Date: Thu, 30 May 2024 12:22:09 +0200 Subject: [PATCH 002/394] =?UTF-8?q?=E2=9C=A8=20Added=20AdjacencyMatrix=20d?= =?UTF-8?q?efinition?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/NeutralAtomDefinitions.hpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/include/hybridmap/NeutralAtomDefinitions.hpp b/include/hybridmap/NeutralAtomDefinitions.hpp index d253ac089..f49b6c399 100644 --- a/include/hybridmap/NeutralAtomDefinitions.hpp +++ b/include/hybridmap/NeutralAtomDefinitions.hpp @@ -15,8 +15,9 @@ namespace na { // A CoordIndex corresponds to node in the SLM grid, where an atom can be placed // (or not). -using CoordIndex = std::uint32_t; -using CoordIndices = std::vector; +using CoordIndex = std::uint32_t; +using CoordIndices = std::vector; +using AdjacencyMatrix = std::vector>; // A HwQubit corresponds to an atom in the neutral atom architecture. It can be // used as qubit or not and occupies a certain position in the architecture. using HwQubit = uint32_t; From 0a674193ccdb1c167f881198d445baf692e4b797 Mon Sep 17 00:00:00 2001 From: lsschmid <117631861+lsschmid@users.noreply.github.com> Date: Thu, 30 May 2024 12:23:59 +0200 Subject: [PATCH 003/394] =?UTF-8?q?=E2=9C=A8=20Added=20function=20definiti?= =?UTF-8?q?ons=20of=20necessary=20functions.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridSynthesisMapper.hpp | 44 +++++++++++++++++++++ src/hybridmap/HybridSynthesisMapper.cpp | 25 ++++++++++++ 2 files changed, 69 insertions(+) diff --git a/include/hybridmap/HybridSynthesisMapper.hpp b/include/hybridmap/HybridSynthesisMapper.hpp index e6928cbb7..bb6c5d38f 100644 --- a/include/hybridmap/HybridSynthesisMapper.hpp +++ b/include/hybridmap/HybridSynthesisMapper.hpp @@ -8,6 +8,11 @@ #include "HybridNeutralAtomMapper.hpp" #include "NeutralAtomArchitecture.hpp" #include "NeutralAtomUtils.hpp" +#include "QuantumComputation.hpp" +#include "hybridmap/NeutralAtomDefinitions.hpp" + +#include +#include namespace qc { @@ -24,6 +29,10 @@ namespace qc { * */ class HybridSynthesisMapper : private NeutralAtomMapper { + using qcs = std::vector; + + AdjacencyMatrix adjacencyMatrix; + public: // Constructors HybridSynthesisMapper() = delete; @@ -35,5 +44,40 @@ class HybridSynthesisMapper : private NeutralAtomMapper { InitialCoordinateMapping::Trivial, const MapperParameters& params = MapperParameters()) : NeutralAtomMapper(arch, initialCoordinateMapping, params) {} + + // Functions + + /** + * @brief Remaps the whole circuit again starting from the initial mapping. + * @param initialMapping The initial mapping to be used. + * @return The remapped circuit. + */ + QuantumComputation + completelyRemap(InitialMapping initialMapping = InitialMapping::Identity); + + /** + * @brief Evaluates the synthesis steps proposed by the ZX extraction. + * @param synthesisSteps The synthesis steps proposed by the ZX extraction. + * @param directlyMap If true, the synthesis steps are directly mapped to the + * hardware. + * @return The index of the synthesis step with the lowest effort. + */ + uint32_t evaluateSynthesisSteps(qcs& synthesisSteps, + bool directlyMap = false); + + /** + * @brief Directly maps the given QuantumComputation to the hardware by + * inserting SWAP gates or shuttling move operations. + * @param qc The gates (QuantumComputation) to be mapped. + */ + void directlyMap(const QuantumComputation& qc); + + /** + * @brief Returns the current adjacency matrix of the neutral atom hardware. + * @return The current adjacency matrix of the neutral atom hardware. + */ + [[nodiscard]] AdjacencyMatrix getAdjacencyMatrix() const { + return adjacencyMatrix; + } }; } // namespace qc diff --git a/src/hybridmap/HybridSynthesisMapper.cpp b/src/hybridmap/HybridSynthesisMapper.cpp index b580c7876..47fdf6812 100644 --- a/src/hybridmap/HybridSynthesisMapper.cpp +++ b/src/hybridmap/HybridSynthesisMapper.cpp @@ -2,3 +2,28 @@ // This file is part of the MQT QMAP library released under the MIT license. // See README.md or go to https://github.com/cda-tum/qmap for more information. // +#include "hybridmap/HybridSynthesisMapper.hpp" + +#include "CircuitOptimizer.hpp" +#include "operations/OpType.hpp" + +#include + +namespace qc { + +QuantumComputation +HybridSynthesisMapper::completelyRemap(InitialMapping initialMapping) { + // copy mapped circuit, removing SWAPs and MOVEs + QuantumComputation newCirc = this->mappedQc; + CircuitOptimizer::removeOpTypes(newCirc, {OpType::SWAP, OpType::Move}); + this->map(newCirc, initialMapping, false); + return this->mappedQc; +} + +uint32_t HybridSynthesisMapper::evaluateSynthesisSteps(qcs& synthesisSteps, + bool directlyMap) { + return 0; +} +void HybridSynthesisMapper::directlyMap(const QuantumComputation& qc) {} + +} // namespace qc From 189510ce1386184af87f47c512c8e72d42633df5 Mon Sep 17 00:00:00 2001 From: lsschmid <117631861+lsschmid@users.noreply.github.com> Date: Mon, 3 Jun 2024 12:35:28 +0200 Subject: [PATCH 004/394] =?UTF-8?q?=F0=9F=8E=A8=20Fixed=20changed=20namesp?= =?UTF-8?q?ace?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridSynthesisMapper.hpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/include/hybridmap/HybridSynthesisMapper.hpp b/include/hybridmap/HybridSynthesisMapper.hpp index bb6c5d38f..40b8dbf59 100644 --- a/include/hybridmap/HybridSynthesisMapper.hpp +++ b/include/hybridmap/HybridSynthesisMapper.hpp @@ -14,7 +14,7 @@ #include #include -namespace qc { +namespace na { /** * @brief Class to manage information exchange between the neutral atom mapper @@ -29,7 +29,7 @@ namespace qc { * */ class HybridSynthesisMapper : private NeutralAtomMapper { - using qcs = std::vector; + using qcs = std::vector; AdjacencyMatrix adjacencyMatrix; @@ -52,7 +52,7 @@ class HybridSynthesisMapper : private NeutralAtomMapper { * @param initialMapping The initial mapping to be used. * @return The remapped circuit. */ - QuantumComputation + qc::QuantumComputation completelyRemap(InitialMapping initialMapping = InitialMapping::Identity); /** @@ -70,7 +70,7 @@ class HybridSynthesisMapper : private NeutralAtomMapper { * inserting SWAP gates or shuttling move operations. * @param qc The gates (QuantumComputation) to be mapped. */ - void directlyMap(const QuantumComputation& qc); + void directlyMap(const qc::QuantumComputation& qc); /** * @brief Returns the current adjacency matrix of the neutral atom hardware. @@ -80,4 +80,4 @@ class HybridSynthesisMapper : private NeutralAtomMapper { return adjacencyMatrix; } }; -} // namespace qc +} // namespace na From fffb4da6476cf5c4011c0c8eb86095097a736dfc Mon Sep 17 00:00:00 2001 From: lsschmid <117631861+lsschmid@users.noreply.github.com> Date: Mon, 3 Jun 2024 12:36:45 +0200 Subject: [PATCH 005/394] =?UTF-8?q?=F0=9F=8E=A8=20Fixed=20changed=20constr?= =?UTF-8?q?uctors=20of=20Mapper?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridSynthesisMapper.hpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/include/hybridmap/HybridSynthesisMapper.hpp b/include/hybridmap/HybridSynthesisMapper.hpp index 40b8dbf59..bab224fe8 100644 --- a/include/hybridmap/HybridSynthesisMapper.hpp +++ b/include/hybridmap/HybridSynthesisMapper.hpp @@ -36,14 +36,10 @@ class HybridSynthesisMapper : private NeutralAtomMapper { public: // Constructors HybridSynthesisMapper() = delete; - explicit HybridSynthesisMapper(const NeutralAtomMapper& neutralAtomMapper) - : NeutralAtomMapper(neutralAtomMapper) {} explicit HybridSynthesisMapper( const NeutralAtomArchitecture& arch, - InitialCoordinateMapping initialCoordinateMapping = - InitialCoordinateMapping::Trivial, - const MapperParameters& params = MapperParameters()) - : NeutralAtomMapper(arch, initialCoordinateMapping, params) {} + const MapperParameters& params = MapperParameters()) + : NeutralAtomMapper(arch, params) {} // Functions From fb31d3a2d9080e131de79cf1bab9a98dc1446cb9 Mon Sep 17 00:00:00 2001 From: lsschmid <117631861+lsschmid@users.noreply.github.com> Date: Tue, 4 Jun 2024 10:03:36 +0200 Subject: [PATCH 006/394] =?UTF-8?q?=F0=9F=8E=A8=20Fixed=20moving=20problem?= =?UTF-8?q?s.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- extern/mqt-core | 2 +- src/hybridmap/HybridSynthesisMapper.cpp | 17 ++++++++++------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/extern/mqt-core b/extern/mqt-core index 6ab9ffa3e..b5a58ff80 160000 --- a/extern/mqt-core +++ b/extern/mqt-core @@ -1 +1 @@ -Subproject commit 6ab9ffa3e5e99514090bc57f13a5c7bd4d5d6102 +Subproject commit b5a58ff80617323e8043232068cafee6f414ecaf diff --git a/src/hybridmap/HybridSynthesisMapper.cpp b/src/hybridmap/HybridSynthesisMapper.cpp index 47fdf6812..1b2977b96 100644 --- a/src/hybridmap/HybridSynthesisMapper.cpp +++ b/src/hybridmap/HybridSynthesisMapper.cpp @@ -5,18 +5,21 @@ #include "hybridmap/HybridSynthesisMapper.hpp" #include "CircuitOptimizer.hpp" +#include "QuantumComputation.hpp" +#include "hybridmap/NeutralAtomUtils.hpp" #include "operations/OpType.hpp" #include -namespace qc { +namespace na { -QuantumComputation +qc::QuantumComputation HybridSynthesisMapper::completelyRemap(InitialMapping initialMapping) { // copy mapped circuit, removing SWAPs and MOVEs - QuantumComputation newCirc = this->mappedQc; - CircuitOptimizer::removeOpTypes(newCirc, {OpType::SWAP, OpType::Move}); - this->map(newCirc, initialMapping, false); + qc::QuantumComputation newCirc = this->mappedQc; + qc::CircuitOptimizer::removeOperation( + newCirc, {qc::OpType::SWAP, qc::OpType::Move}, 2); + this->map(newCirc, initialMapping); return this->mappedQc; } @@ -24,6 +27,6 @@ uint32_t HybridSynthesisMapper::evaluateSynthesisSteps(qcs& synthesisSteps, bool directlyMap) { return 0; } -void HybridSynthesisMapper::directlyMap(const QuantumComputation& qc) {} +void HybridSynthesisMapper::directlyMap(const qc::QuantumComputation& qc) {} -} // namespace qc +} // namespace na From a8a440867ae1ad4d0cee96ac527278140c7dd0df Mon Sep 17 00:00:00 2001 From: lsschmid <117631861+lsschmid@users.noreply.github.com> Date: Tue, 4 Jun 2024 10:11:54 +0200 Subject: [PATCH 007/394] =?UTF-8?q?=F0=9F=8E=A8=20Use=20bool=20for=20Adjac?= =?UTF-8?q?ency=20Matrix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/NeutralAtomDefinitions.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/hybridmap/NeutralAtomDefinitions.hpp b/include/hybridmap/NeutralAtomDefinitions.hpp index f49b6c399..ca85a77c6 100644 --- a/include/hybridmap/NeutralAtomDefinitions.hpp +++ b/include/hybridmap/NeutralAtomDefinitions.hpp @@ -17,7 +17,7 @@ namespace na { // (or not). using CoordIndex = std::uint32_t; using CoordIndices = std::vector; -using AdjacencyMatrix = std::vector>; +using AdjacencyMatrix = std::vector>; // A HwQubit corresponds to an atom in the neutral atom architecture. It can be // used as qubit or not and occupies a certain position in the architecture. using HwQubit = uint32_t; From 5ceec6a2d64b07620ca839b5e33f346c3aa80c64 Mon Sep 17 00:00:00 2001 From: lsschmid <117631861+lsschmid@users.noreply.github.com> Date: Tue, 4 Jun 2024 10:12:29 +0200 Subject: [PATCH 008/394] =?UTF-8?q?=E2=9C=A8=20Added=20completely=20remap?= =?UTF-8?q?=20function?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridSynthesisMapper.hpp | 3 ++- src/hybridmap/HybridSynthesisMapper.cpp | 6 +----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/include/hybridmap/HybridSynthesisMapper.hpp b/include/hybridmap/HybridSynthesisMapper.hpp index bab224fe8..d7e6fe67d 100644 --- a/include/hybridmap/HybridSynthesisMapper.hpp +++ b/include/hybridmap/HybridSynthesisMapper.hpp @@ -31,7 +31,8 @@ namespace na { class HybridSynthesisMapper : private NeutralAtomMapper { using qcs = std::vector; - AdjacencyMatrix adjacencyMatrix; + AdjacencyMatrix adjacencyMatrix; + qc::QuantumComputation unmappedQc; public: // Constructors diff --git a/src/hybridmap/HybridSynthesisMapper.cpp b/src/hybridmap/HybridSynthesisMapper.cpp index 1b2977b96..afabb463a 100644 --- a/src/hybridmap/HybridSynthesisMapper.cpp +++ b/src/hybridmap/HybridSynthesisMapper.cpp @@ -15,11 +15,7 @@ namespace na { qc::QuantumComputation HybridSynthesisMapper::completelyRemap(InitialMapping initialMapping) { - // copy mapped circuit, removing SWAPs and MOVEs - qc::QuantumComputation newCirc = this->mappedQc; - qc::CircuitOptimizer::removeOperation( - newCirc, {qc::OpType::SWAP, qc::OpType::Move}, 2); - this->map(newCirc, initialMapping); + this->map(unmappedQc, initialMapping); return this->mappedQc; } From 1a7437a82846e19b08537ef681e79b2e4f43de4a Mon Sep 17 00:00:00 2001 From: lsschmid <117631861+lsschmid@users.noreply.github.com> Date: Tue, 4 Jun 2024 10:42:52 +0200 Subject: [PATCH 009/394] =?UTF-8?q?=E2=9C=A8=20Added=20return=20adjacency?= =?UTF-8?q?=20matrix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridSynthesisMapper.hpp | 5 +---- include/hybridmap/NeutralAtomDefinitions.hpp | 3 ++- src/hybridmap/HybridSynthesisMapper.cpp | 19 +++++++++++++++++++ 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/include/hybridmap/HybridSynthesisMapper.hpp b/include/hybridmap/HybridSynthesisMapper.hpp index d7e6fe67d..37c70af7b 100644 --- a/include/hybridmap/HybridSynthesisMapper.hpp +++ b/include/hybridmap/HybridSynthesisMapper.hpp @@ -31,7 +31,6 @@ namespace na { class HybridSynthesisMapper : private NeutralAtomMapper { using qcs = std::vector; - AdjacencyMatrix adjacencyMatrix; qc::QuantumComputation unmappedQc; public: @@ -73,8 +72,6 @@ class HybridSynthesisMapper : private NeutralAtomMapper { * @brief Returns the current adjacency matrix of the neutral atom hardware. * @return The current adjacency matrix of the neutral atom hardware. */ - [[nodiscard]] AdjacencyMatrix getAdjacencyMatrix() const { - return adjacencyMatrix; - } + [[nodiscard]] AdjacencyMatrix getAdjacencyMatrix() const; }; } // namespace na diff --git a/include/hybridmap/NeutralAtomDefinitions.hpp b/include/hybridmap/NeutralAtomDefinitions.hpp index ca85a77c6..00feb9c9a 100644 --- a/include/hybridmap/NeutralAtomDefinitions.hpp +++ b/include/hybridmap/NeutralAtomDefinitions.hpp @@ -6,6 +6,7 @@ #pragma once #include "Definitions.hpp" +#include "datastructures/SymmetricMatrix.hpp" #include #include @@ -17,7 +18,7 @@ namespace na { // (or not). using CoordIndex = std::uint32_t; using CoordIndices = std::vector; -using AdjacencyMatrix = std::vector>; +using AdjacencyMatrix = SymmetricMatrix; // A HwQubit corresponds to an atom in the neutral atom architecture. It can be // used as qubit or not and occupies a certain position in the architecture. using HwQubit = uint32_t; diff --git a/src/hybridmap/HybridSynthesisMapper.cpp b/src/hybridmap/HybridSynthesisMapper.cpp index afabb463a..b64ea3114 100644 --- a/src/hybridmap/HybridSynthesisMapper.cpp +++ b/src/hybridmap/HybridSynthesisMapper.cpp @@ -4,6 +4,7 @@ // #include "hybridmap/HybridSynthesisMapper.hpp" +#include "/private/var/folders/rg/sbn50h9d07577zx7p_kslps80000gn/T/clion-clang-tidy/NeutralAtomDefinitions.hpp" #include "CircuitOptimizer.hpp" #include "QuantumComputation.hpp" #include "hybridmap/NeutralAtomUtils.hpp" @@ -25,4 +26,22 @@ uint32_t HybridSynthesisMapper::evaluateSynthesisSteps(qcs& synthesisSteps, } void HybridSynthesisMapper::directlyMap(const qc::QuantumComputation& qc) {} +AdjacencyMatrix HybridSynthesisMapper::getAdjacencyMatrix() const { + auto numCircQubits = mappedQc.getNqubits(); + AdjacencyMatrix adjMatrix(numCircQubits); + + for (uint32_t i = 0; i < numCircQubits; ++i) { + for (uint32_t j = 0; j < i; ++j) { + auto mappedI = this->mapping.getHwQubit(i); + auto mappedJ = this->mapping.getHwQubit(j); + if (this->arch.getSwapDistance(mappedI, mappedJ) == 0) { + adjMatrix(i, j) = 1; + } else { + adjMatrix(i, j) = 0; + } + } + } + return adjMatrix; +} + } // namespace na From 9eb08a3f662e8f659f1cf7d5bd07af34a912b437 Mon Sep 17 00:00:00 2001 From: lsschmid <117631861+lsschmid@users.noreply.github.com> Date: Tue, 4 Jun 2024 10:51:02 +0200 Subject: [PATCH 010/394] =?UTF-8?q?=E2=9C=A8=20Added=20directMap=20functio?= =?UTF-8?q?n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridSynthesisMapper.hpp | 2 +- src/hybridmap/HybridSynthesisMapper.cpp | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/include/hybridmap/HybridSynthesisMapper.hpp b/include/hybridmap/HybridSynthesisMapper.hpp index 37c70af7b..98af904d0 100644 --- a/include/hybridmap/HybridSynthesisMapper.hpp +++ b/include/hybridmap/HybridSynthesisMapper.hpp @@ -62,7 +62,7 @@ class HybridSynthesisMapper : private NeutralAtomMapper { bool directlyMap = false); /** - * @brief Directly maps the given QuantumComputation to the hardware by + * @brief Directly maps the given QuantumComputation to the hardware NOT * inserting SWAP gates or shuttling move operations. * @param qc The gates (QuantumComputation) to be mapped. */ diff --git a/src/hybridmap/HybridSynthesisMapper.cpp b/src/hybridmap/HybridSynthesisMapper.cpp index b64ea3114..f5545ede4 100644 --- a/src/hybridmap/HybridSynthesisMapper.cpp +++ b/src/hybridmap/HybridSynthesisMapper.cpp @@ -4,11 +4,10 @@ // #include "hybridmap/HybridSynthesisMapper.hpp" -#include "/private/var/folders/rg/sbn50h9d07577zx7p_kslps80000gn/T/clion-clang-tidy/NeutralAtomDefinitions.hpp" #include "CircuitOptimizer.hpp" #include "QuantumComputation.hpp" +#include "hybridmap/NeutralAtomDefinitions.hpp" #include "hybridmap/NeutralAtomUtils.hpp" -#include "operations/OpType.hpp" #include @@ -24,7 +23,11 @@ uint32_t HybridSynthesisMapper::evaluateSynthesisSteps(qcs& synthesisSteps, bool directlyMap) { return 0; } -void HybridSynthesisMapper::directlyMap(const qc::QuantumComputation& qc) {} +void HybridSynthesisMapper::directlyMap(const qc::QuantumComputation& qc) { + for (const auto& op : qc) { + this->mapGate(op.get()); + } +} AdjacencyMatrix HybridSynthesisMapper::getAdjacencyMatrix() const { auto numCircQubits = mappedQc.getNqubits(); From 117e199f68afd05907317b9b41957aedc0aa6cc9 Mon Sep 17 00:00:00 2001 From: lsschmid <117631861+lsschmid@users.noreply.github.com> Date: Tue, 4 Jun 2024 11:07:05 +0200 Subject: [PATCH 011/394] =?UTF-8?q?=E2=9C=A8=20Added=20draft=20for=20synth?= =?UTF-8?q?esis=20circuit=20evaluation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridSynthesisMapper.hpp | 16 ++++++++++-- src/hybridmap/HybridSynthesisMapper.cpp | 27 ++++++++++++++++++--- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/include/hybridmap/HybridSynthesisMapper.hpp b/include/hybridmap/HybridSynthesisMapper.hpp index 98af904d0..23f3c8792 100644 --- a/include/hybridmap/HybridSynthesisMapper.hpp +++ b/include/hybridmap/HybridSynthesisMapper.hpp @@ -5,12 +5,14 @@ #pragma once +#include "Definitions.hpp" #include "HybridNeutralAtomMapper.hpp" #include "NeutralAtomArchitecture.hpp" #include "NeutralAtomUtils.hpp" #include "QuantumComputation.hpp" #include "hybridmap/NeutralAtomDefinitions.hpp" +#include #include #include @@ -58,8 +60,18 @@ class HybridSynthesisMapper : private NeutralAtomMapper { * hardware. * @return The index of the synthesis step with the lowest effort. */ - uint32_t evaluateSynthesisSteps(qcs& synthesisSteps, - bool directlyMap = false); + size_t evaluateSynthesisSteps(const qcs& synthesisSteps, + bool directlyMap = false); + + /** + * @brief Evaluates a single synthesis step proposed by the ZX extraction. + * @details The effort is calculated by the NeutralAtomMapper, taking into + * account the number of SWAP gates or shuttling moves and the time needed to + * execute the mapped synthesis step. + * @param qc The synthesis step to be evaluated. + * @return The cost/effort to map the synthesis step. + */ + qc::fp evaluateSynthesisStep(const qc::QuantumComputation& qc); /** * @brief Directly maps the given QuantumComputation to the hardware NOT diff --git a/src/hybridmap/HybridSynthesisMapper.cpp b/src/hybridmap/HybridSynthesisMapper.cpp index f5545ede4..c00a9b37a 100644 --- a/src/hybridmap/HybridSynthesisMapper.cpp +++ b/src/hybridmap/HybridSynthesisMapper.cpp @@ -4,12 +4,17 @@ // #include "hybridmap/HybridSynthesisMapper.hpp" -#include "CircuitOptimizer.hpp" +#include "Definitions.hpp" #include "QuantumComputation.hpp" #include "hybridmap/NeutralAtomDefinitions.hpp" #include "hybridmap/NeutralAtomUtils.hpp" +#include +#include #include +#include +#include +#include namespace na { @@ -19,10 +24,26 @@ HybridSynthesisMapper::completelyRemap(InitialMapping initialMapping) { return this->mappedQc; } -uint32_t HybridSynthesisMapper::evaluateSynthesisSteps(qcs& synthesisSteps, - bool directlyMap) { +size_t HybridSynthesisMapper::evaluateSynthesisSteps(const qcs& synthesisSteps, + bool directlyMap) { + std::vector> costs; + for (const auto& qc : synthesisSteps) { + costs.emplace_back(qc, this->evaluateSynthesisStep(qc)); + } + const auto bestQc = std::min_element( + costs.begin(), costs.end(), + [](const auto& a, const auto& b) { return a.second < b.second; }); + if (directlyMap) { + this->directlyMap(bestQc->first); + } + return static_cast(std::distance(costs.begin(), bestQc)); +} + +qc::fp +HybridSynthesisMapper::evaluateSynthesisStep(const qc::QuantumComputation& qc) { return 0; } + void HybridSynthesisMapper::directlyMap(const qc::QuantumComputation& qc) { for (const auto& op : qc) { this->mapGate(op.get()); From 819b234b50d48f17963e66ae7000feb3a55b81ae Mon Sep 17 00:00:00 2001 From: lsschmid <117631861+lsschmid@users.noreply.github.com> Date: Tue, 4 Jun 2024 12:38:58 +0200 Subject: [PATCH 012/394] =?UTF-8?q?=F0=9F=8E=A8=20Changed=20architecture?= =?UTF-8?q?=20from=20const=20ref=20member=20to=20pointer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridNeutralAtomMapper.hpp | 8 +- include/hybridmap/MoveToAodConverter.hpp | 4 +- src/hybridmap/HybridNeutralAtomMapper.cpp | 91 ++++++++++--------- src/hybridmap/HybridSynthesisMapper.cpp | 2 +- src/hybridmap/MoveToAodConverter.cpp | 12 +-- 5 files changed, 60 insertions(+), 57 deletions(-) diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index 197017c54..9f9db90e0 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -65,7 +65,7 @@ struct MapperParameters { class NeutralAtomMapper { protected: // The considered architecture - const NeutralAtomArchitecture& arch; + const NeutralAtomArchitecture* arch; // The mapped quantum circuit qc::QuantumComputation mappedQc; // The mapped quantum circuit converted to AOD movements @@ -361,7 +361,7 @@ class NeutralAtomMapper { NeutralAtomMapper(NeutralAtomMapper&&) = delete; explicit NeutralAtomMapper(const NeutralAtomArchitecture& architecture, const MapperParameters& p = MapperParameters()) - : arch(architecture), mappedQc(architecture.getNpositions()), + : arch(&architecture), mappedQc(architecture.getNpositions()), mappedQcAOD(architecture.getNpositions()), scheduler(architecture), parameters(p), hardwareQubits(architecture, parameters.initialMapping, parameters.seed) { @@ -378,7 +378,7 @@ class NeutralAtomMapper { */ void setParameters(const MapperParameters& p) { this->parameters = p; - if (arch.getNpositions() - arch.getNqubits() < 1) { + if (arch->getNpositions() - arch->getNqubits() < 1) { this->parameters.gateWeight = 1; this->parameters.shuttlingWeight = 0; } @@ -390,7 +390,7 @@ class NeutralAtomMapper { */ void reset() { hardwareQubits = - HardwareQubits(arch, parameters.initialMapping, parameters.seed); + HardwareQubits(*arch, parameters.initialMapping, parameters.seed); } // Methods diff --git a/include/hybridmap/MoveToAodConverter.hpp b/include/hybridmap/MoveToAodConverter.hpp index 7cd111b1c..c0ee46455 100644 --- a/include/hybridmap/MoveToAodConverter.hpp +++ b/include/hybridmap/MoveToAodConverter.hpp @@ -100,7 +100,7 @@ class MoveToAodConverter { // AODScheduler // NeutralAtomArchitecture to call necessary hardware information - const NeutralAtomArchitecture& arch; + const NeutralAtomArchitecture* arch; std::vector allActivations; // Differentiate between loading and unloading qc::OpType type; @@ -111,7 +111,7 @@ class MoveToAodConverter { AodActivationHelper(AodActivationHelper&&) = delete; AodActivationHelper(const NeutralAtomArchitecture& architecture, qc::OpType opType) - : arch(architecture), type(opType) {} + : arch(&architecture), type(opType) {} // Methods diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index cbe99bc91..e5c5ed6f7 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -35,7 +35,7 @@ namespace na { qc::QuantumComputation NeutralAtomMapper::map(qc::QuantumComputation& qc, InitialMapping initialMapping) { - mappedQc = qc::QuantumComputation(arch.getNpositions()); + mappedQc = qc::QuantumComputation(arch->getNpositions()); nMoves = 0; nSwaps = 0; qc::CircuitOptimizer::replaceMCXWithMCZ(qc); @@ -56,13 +56,13 @@ qc::QuantumComputation NeutralAtomMapper::map(qc::QuantumComputation& qc, lookaheadLayer.initLayerOffset(frontLayer.getIteratorOffset()); // Checks - if (dag.size() > arch.getNqubits()) { + if (dag.size() > arch->getNqubits()) { throw std::runtime_error("More qubits in circuit than in architecture"); } // precompute exponential decay weights - this->decayWeights.reserve(this->arch.getNcolumns()); - for (uint32_t i = this->arch.getNcolumns(); i > 0; --i) { + this->decayWeights.reserve(this->arch->getNcolumns()); + for (uint32_t i = this->arch->getNcolumns(); i > 0; --i) { this->decayWeights.emplace_back(std::exp(-this->parameters.decay * i)); } @@ -149,7 +149,7 @@ NeutralAtomMapper::convertToAod(qc::QuantumComputation& qc) { qc::CircuitOptimizer::singleQubitGateFusion(qc); qc::CircuitOptimizer::flattenOperations(qc); // decompose AOD moves - MoveToAodConverter aodScheduler(arch); + MoveToAodConverter aodScheduler(*arch); mappedQcAOD = aodScheduler.schedule(qc); if (this->parameters.verbose) { std::cout << "nMoveGroups: " << aodScheduler.getNMoveGroups() << '\n'; @@ -272,7 +272,7 @@ void NeutralAtomMapper::updateMappingSwap(Swap swap) { // save to lastSwaps this->lastBlockedQubits.emplace_back( this->hardwareQubits.getBlockedQubits({swap.first, swap.second})); - if (this->lastBlockedQubits.size() > this->arch.getNcolumns()) { + if (this->lastBlockedQubits.size() > this->arch->getNcolumns()) { this->lastBlockedQubits.pop_front(); } this->mapping.applySwap(swap); @@ -846,7 +846,7 @@ qc::fp NeutralAtomMapper::moveCostPerLayer(const AtomMove& move, continue; } auto hwQubit = this->mapping.getHwQubit(qubit); - auto dist = this->arch.getEuclidianDistance( + auto dist = this->arch->getEuclidianDistance( this->hardwareQubits.getCoordIndex(hwQubit), this->hardwareQubits.getCoordIndex(toMoveHwQubit)); distanceBefore += dist; @@ -857,7 +857,7 @@ qc::fp NeutralAtomMapper::moveCostPerLayer(const AtomMove& move, continue; } auto hwQubit = this->mapping.getHwQubit(qubit); - auto dist = this->arch.getEuclidianDistance( + auto dist = this->arch->getEuclidianDistance( this->hardwareQubits.getCoordIndex(hwQubit), move.second); distanceAfter += dist; } @@ -870,50 +870,51 @@ qc::fp NeutralAtomMapper::moveCostPerLayer(const AtomMove& move, qc::fp NeutralAtomMapper::parallelMoveCost(const AtomMove& move) { qc::fp parallelCost = 0; - auto moveVector = this->arch.getVector(move.first, move.second); + auto moveVector = this->arch->getVector(move.first, move.second); std::vector lastEndingCoords; if (this->lastMoves.empty()) { - parallelCost += arch.getVectorShuttlingTime(moveVector); + parallelCost += arch->getVectorShuttlingTime(moveVector); } for (const auto& lastMove : this->lastMoves) { lastEndingCoords.emplace_back(lastMove.second); // decide of shuttling can be done in parallel - auto lastMoveVector = this->arch.getVector(lastMove.first, lastMove.second); + auto lastMoveVector = + this->arch->getVector(lastMove.first, lastMove.second); if (moveVector.overlap(lastMoveVector)) { if (moveVector.direction != lastMoveVector.direction) { - parallelCost += arch.getVectorShuttlingTime(moveVector); + parallelCost += arch->getVectorShuttlingTime(moveVector); } else { // check if move can be done in parallel if (moveVector.include(lastMoveVector)) { - parallelCost += arch.getVectorShuttlingTime(moveVector); + parallelCost += arch->getVectorShuttlingTime(moveVector); } } } } // check if in same row/column like last moves // then can may be loaded in parallel - auto moveCoordInit = this->arch.getCoordinate(move.first); - auto moveCoordEnd = this->arch.getCoordinate(move.second); - parallelCost += arch.getShuttlingTime(qc::OpType::AodActivate) + - arch.getShuttlingTime(qc::OpType::AodDeactivate); + auto moveCoordInit = this->arch->getCoordinate(move.first); + auto moveCoordEnd = this->arch->getCoordinate(move.second); + parallelCost += arch->getShuttlingTime(qc::OpType::AodActivate) + + arch->getShuttlingTime(qc::OpType::AodDeactivate); for (const auto& lastMove : this->lastMoves) { - auto lastMoveCoordInit = this->arch.getCoordinate(lastMove.first); - auto lastMoveCoordEnd = this->arch.getCoordinate(lastMove.second); + auto lastMoveCoordInit = this->arch->getCoordinate(lastMove.first); + auto lastMoveCoordEnd = this->arch->getCoordinate(lastMove.second); if (moveCoordInit.x == lastMoveCoordInit.x || moveCoordInit.y == lastMoveCoordInit.y) { - parallelCost -= arch.getShuttlingTime(qc::OpType::AodActivate); + parallelCost -= arch->getShuttlingTime(qc::OpType::AodActivate); } if (moveCoordEnd.x == lastMoveCoordEnd.x || moveCoordEnd.y == lastMoveCoordEnd.y) { - parallelCost -= arch.getShuttlingTime(qc::OpType::AodDeactivate); + parallelCost -= arch->getShuttlingTime(qc::OpType::AodDeactivate); } } // check if move can use AOD atom from last moves // if (std::find(lastEndingCoords.begin(), lastEndingCoords.end(), // move.first) == // lastEndingCoords.end()) { - // parallelCost += arch.getShuttlingTime(qc::OpType::AodActivate) + - // arch.getShuttlingTime(qc::OpType::AodDeactivate); + // parallelCost += arch->getShuttlingTime(qc::OpType::AodActivate) + + // arch->getShuttlingTime(qc::OpType::AodDeactivate); // } return parallelCost; } @@ -929,14 +930,15 @@ NeutralAtomMapper::getMovePositionRec(MultiQubitMovePos currentPos, return {}; } - auto nearbyCoords = this->arch.getNearbyCoordinates(currentPos.coords.back()); + auto nearbyCoords = + this->arch->getNearbyCoordinates(currentPos.coords.back()); // filter out coords that have a SWAP distance unequal to 0 to any of the // current qubits. Also sort out coords that are already in the vector std::vector filteredNearbyCoords; for (const auto& coord : nearbyCoords) { bool valid = true; for (const auto& qubit : currentPos.coords) { - if (this->arch.getSwapDistance(qubit, coord) != 0 || coord == qubit) { + if (this->arch->getSwapDistance(qubit, coord) != 0 || coord == qubit) { valid = false; break; } @@ -1074,7 +1076,7 @@ CoordIndices NeutralAtomMapper::getBestMovePos(const CoordIndices& gateCoords) { if (!bestPos.coords.empty()) { nMovesGate = std::min(nMovesGate, bestPos.nMoves); } - for (const auto& nearbyCoord : this->arch.getNearbyCoordinates(coord)) { + for (const auto& nearbyCoord : this->arch->getNearbyCoordinates(coord)) { if (std::find(visited.begin(), visited.end(), nearbyCoord) == visited.end()) { q.push(nearbyCoord); @@ -1160,7 +1162,7 @@ NeutralAtomMapper::getMoveAwayCombinations(CoordIndex startCoord, CoordIndex targetCoord, const CoordIndices& excludedCoords) { MoveCombs moveCombinations; - auto const originalVector = this->arch.getVector(startCoord, targetCoord); + auto const originalVector = this->arch->getVector(startCoord, targetCoord); auto const originalDirection = originalVector.direction; // Find move away target in the same direction as the original move auto moveAwayTargets = this->hardwareQubits.findClosestFreeCoord( @@ -1210,7 +1212,7 @@ NeutralAtomMapper::estimateNumSwapGates(const qc::Operation* opPointer) { minNumSwaps += this->hardwareQubits.getSwapDistance(q1, q2, false); } } - const qc::fp minTime = minNumSwaps * this->arch.getGateTime("swap"); + const qc::fp minTime = minNumSwaps * this->arch->getGateTime("swap"); return {minNumSwaps, minTime}; } @@ -1245,18 +1247,18 @@ NeutralAtomMapper::estimateNumMove(const qc::Operation* opPointer) { continue; } if (nearbyFreeIt != nearbyFreeCoords.end()) { - totalTime += this->arch.getVectorShuttlingTime( - this->arch.getVector(otherCoord, *nearbyFreeIt)); - totalTime += this->arch.getShuttlingTime(qc::OpType::AodActivate) + - this->arch.getShuttlingTime(qc::OpType::AodDeactivate); + totalTime += this->arch->getVectorShuttlingTime( + this->arch->getVector(otherCoord, *nearbyFreeIt)); + totalTime += this->arch->getShuttlingTime(qc::OpType::AodActivate) + + this->arch->getShuttlingTime(qc::OpType::AodDeactivate); nearbyFreeIt++; totalMoves++; } else if (nearbyOccIt != nearbyOccupiedCoords.end()) { - totalTime += 2 * this->arch.getVectorShuttlingTime( - this->arch.getVector(otherCoord, *nearbyOccIt)); + totalTime += 2 * this->arch->getVectorShuttlingTime( + this->arch->getVector(otherCoord, *nearbyOccIt)); totalTime += - 2 * (this->arch.getShuttlingTime(qc::OpType::AodActivate) + - this->arch.getShuttlingTime(qc::OpType::AodDeactivate)); + 2 * (this->arch->getShuttlingTime(qc::OpType::AodActivate) + + this->arch->getShuttlingTime(qc::OpType::AodDeactivate)); nearbyOccIt++; totalMoves += 2; } else { @@ -1286,16 +1288,17 @@ bool NeutralAtomMapper::swapGateBetter(const qc::Operation* opPointer) { } auto [minMoves, minTimeMoves] = estimateNumMove(opPointer); auto fidSwaps = - std::exp(-minTimeSwaps * this->arch.getNqubits() / - this->arch.getDecoherenceTime()) * - std::pow(this->arch.getGateAverageFidelity("swap"), minNumSwaps); + std::exp(-minTimeSwaps * this->arch->getNqubits() / + this->arch->getDecoherenceTime()) * + std::pow(this->arch->getGateAverageFidelity("swap"), minNumSwaps); auto fidMoves = - std::exp(-minTimeMoves * this->arch.getNqubits() / - this->arch.getDecoherenceTime()) * + std::exp(-minTimeMoves * this->arch->getNqubits() / + this->arch->getDecoherenceTime()) * std::pow( - this->arch.getShuttlingAverageFidelity(qc::OpType::AodMove) * - this->arch.getShuttlingAverageFidelity(qc::OpType::AodActivate) * - this->arch.getShuttlingAverageFidelity(qc::OpType::AodDeactivate), + this->arch->getShuttlingAverageFidelity(qc::OpType::AodMove) * + this->arch->getShuttlingAverageFidelity(qc::OpType::AodActivate) * + this->arch->getShuttlingAverageFidelity( + qc::OpType::AodDeactivate), minMoves); return fidSwaps * parameters.gateWeight > diff --git a/src/hybridmap/HybridSynthesisMapper.cpp b/src/hybridmap/HybridSynthesisMapper.cpp index c00a9b37a..f41f746cf 100644 --- a/src/hybridmap/HybridSynthesisMapper.cpp +++ b/src/hybridmap/HybridSynthesisMapper.cpp @@ -58,7 +58,7 @@ AdjacencyMatrix HybridSynthesisMapper::getAdjacencyMatrix() const { for (uint32_t j = 0; j < i; ++j) { auto mappedI = this->mapping.getHwQubit(i); auto mappedJ = this->mapping.getHwQubit(j); - if (this->arch.getSwapDistance(mappedI, mappedJ) == 0) { + if (this->arch->getSwapDistance(mappedI, mappedJ) == 0) { adjMatrix(i, j) = 1; } else { adjMatrix(i, j) = 0; diff --git a/src/hybridmap/MoveToAodConverter.cpp b/src/hybridmap/MoveToAodConverter.cpp index 6e4cbb2b5..4d5b4e28b 100644 --- a/src/hybridmap/MoveToAodConverter.cpp +++ b/src/hybridmap/MoveToAodConverter.cpp @@ -462,15 +462,15 @@ bool MoveToAodConverter::AodActivationHelper::checkIntermediateSpaceAtInit( } if (aodMoves.empty()) { return getMaxOffsetAtInit(dim, neighborX, sign) < - arch.getNAodIntermediateLevels(); + arch->getNAodIntermediateLevels(); } if (aodMovesNeighbor.empty()) { return getMaxOffsetAtInit(dim, init, sign) < - arch.getNAodIntermediateLevels(); + arch->getNAodIntermediateLevels(); } return getMaxOffsetAtInit(dim, init, sign) + getMaxOffsetAtInit(dim, neighborX, sign) < - arch.getNAodIntermediateLevels(); + arch->getNAodIntermediateLevels(); } void MoveToAodConverter::AodActivationHelper::mergeActivationDim( @@ -526,9 +526,9 @@ MoveToAodConverter::AodActivationHelper::getAodOperation( std::vector initOperations; std::vector offsetOperations; - auto d = this->arch.getInterQubitDistance(); - auto interD = this->arch.getInterQubitDistance() / - this->arch.getNAodIntermediateLevels(); + auto d = this->arch->getInterQubitDistance(); + auto interD = this->arch->getInterQubitDistance() / + this->arch->getNAodIntermediateLevels(); for (const auto& aodMove : activation.activateXs) { initOperations.emplace_back(Dimension::X, From 3a860477bf08053c196c98c52671ba0443a10769 Mon Sep 17 00:00:00 2001 From: lsschmid <117631861+lsschmid@users.noreply.github.com> Date: Tue, 4 Jun 2024 13:10:55 +0200 Subject: [PATCH 013/394] =?UTF-8?q?=E2=9C=A8=20Added=20initialization=20of?= =?UTF-8?q?=20Synthesis=20Mapper?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridSynthesisMapper.hpp | 14 +++++++++++++- src/hybridmap/HybridSynthesisMapper.cpp | 2 +- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/include/hybridmap/HybridSynthesisMapper.hpp b/include/hybridmap/HybridSynthesisMapper.hpp index 23f3c8792..740f49d69 100644 --- a/include/hybridmap/HybridSynthesisMapper.hpp +++ b/include/hybridmap/HybridSynthesisMapper.hpp @@ -45,6 +45,18 @@ class HybridSynthesisMapper : private NeutralAtomMapper { // Functions + /** + * @brief Initializes the mapping with the given number of qubits and the + * initial mapping. + * @param nQubits The number of qubits to be mapped. + * @param initialMapping The initial mapping to be used. + */ + void initMapping(size_t nQubits, + InitialMapping initialMapping = InitialMapping::Identity) { + mappedQc = qc::QuantumComputation(nQubits); + mapping = Mapping(nQubits, initialMapping); + } + /** * @brief Remaps the whole circuit again starting from the initial mapping. * @param initialMapping The initial mapping to be used. @@ -84,6 +96,6 @@ class HybridSynthesisMapper : private NeutralAtomMapper { * @brief Returns the current adjacency matrix of the neutral atom hardware. * @return The current adjacency matrix of the neutral atom hardware. */ - [[nodiscard]] AdjacencyMatrix getAdjacencyMatrix() const; + [[nodiscard]] AdjacencyMatrix getCircuitAdjacencyMatrix() const; }; } // namespace na diff --git a/src/hybridmap/HybridSynthesisMapper.cpp b/src/hybridmap/HybridSynthesisMapper.cpp index f41f746cf..424b58d55 100644 --- a/src/hybridmap/HybridSynthesisMapper.cpp +++ b/src/hybridmap/HybridSynthesisMapper.cpp @@ -50,7 +50,7 @@ void HybridSynthesisMapper::directlyMap(const qc::QuantumComputation& qc) { } } -AdjacencyMatrix HybridSynthesisMapper::getAdjacencyMatrix() const { +AdjacencyMatrix HybridSynthesisMapper::getCircuitAdjacencyMatrix() const { auto numCircQubits = mappedQc.getNqubits(); AdjacencyMatrix adjMatrix(numCircQubits); From 8cd97b886fbf31a23d6e5b8f6447dbfc7d4c5603 Mon Sep 17 00:00:00 2001 From: lsschmid <117631861+lsschmid@users.noreply.github.com> Date: Tue, 4 Jun 2024 13:11:21 +0200 Subject: [PATCH 014/394] =?UTF-8?q?=E2=9C=85=20added=20first=20basic=20tes?= =?UTF-8?q?t=20for=20adjacency=20matrix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/CMakeLists.txt | 3 +- test/hybridmap/test_hybrid_synthesis_map.cpp | 53 ++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 test/hybridmap/test_hybrid_synthesis_map.cpp diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 5c16c6d2c..1321f3bc4 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -31,5 +31,6 @@ if(TARGET MQT::QMapHybrid) file(COPY ${PROJECT_SOURCE_DIR}/test/hybridmap/circuits DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) package_add_test_with_working_dir( ${PROJECT_NAME}-hybridmap-test MQT::QMapHybrid ${CMAKE_CURRENT_BINARY_DIR} - ${CMAKE_CURRENT_SOURCE_DIR}/hybridmap/test_hybridmap.cpp) + ${CMAKE_CURRENT_SOURCE_DIR}/hybridmap/test_hybridmap.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/hybridmap/test_hybrid_synthesis_map.cpp) endif() diff --git a/test/hybridmap/test_hybrid_synthesis_map.cpp b/test/hybridmap/test_hybrid_synthesis_map.cpp new file mode 100644 index 000000000..e9c73114e --- /dev/null +++ b/test/hybridmap/test_hybrid_synthesis_map.cpp @@ -0,0 +1,53 @@ +// +// This file is part of the MQT QMAP library released under the MIT license. +// See README.md or go to https://github.com/cda-tum/qmap for more information. +// + +#include "QuantumComputation.hpp" +#include "hybridmap/HybridNeutralAtomMapper.hpp" +#include "hybridmap/HybridSynthesisMapper.hpp" +#include "hybridmap/NeutralAtomArchitecture.hpp" + +#include +#include +#include +#include + +namespace na { +class TestHybridSynthesisMapper : public ::testing::TestWithParam { +protected: + std::string testArchitecturePath = "architectures/"; + std::vector circuits; + + void SetUp() override { + testArchitecturePath += GetParam() + ".json"; + qc::QuantumComputation qc1(3); + qc1.x(0); + qc1.cx(0, 1); + qc1.cx(1, 2); + circuits.push_back(qc1); + + qc::QuantumComputation qc2(3); + qc2.move(0, 2); + qc2.x(0); + circuits.push_back(qc2); + } + + // Test the HybridSynthesisMapper class +}; + +TEST_P(TestHybridSynthesisMapper, AdjaencyMatrix) { + auto arch = NeutralAtomArchitecture(testArchitecturePath); + auto mapper = HybridSynthesisMapper(arch); + mapper.initMapping(3); + auto adjMatrix = mapper.getCircuitAdjacencyMatrix(); + EXPECT_EQ(adjMatrix.size(), 3); + EXPECT_TRUE(adjMatrix(0, 2) == 0 || adjMatrix(0, 2) == 1); +} + +INSTANTIATE_TEST_SUITE_P(HybridSynthesisMapperTestSuite, + TestHybridSynthesisMapper, + ::testing::Values("rubidium", "rubidium_hybrid", + "rubidium_shuttling")); + +} // namespace na From a8859931f08d3be96808a78fb789738058e93b13 Mon Sep 17 00:00:00 2001 From: lsschmid <117631861+lsschmid@users.noreply.github.com> Date: Tue, 4 Jun 2024 13:36:52 +0200 Subject: [PATCH 015/394] =?UTF-8?q?=F0=9F=8E=A8=20Refactored=20get/init=20?= =?UTF-8?q?of=20mapped/synthesized=20circuit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridSynthesisMapper.hpp | 31 +++++++++++---- src/hybridmap/HybridSynthesisMapper.cpp | 7 +--- test/hybridmap/test_hybrid_synthesis_map.cpp | 42 ++++++++++++++++++-- 3 files changed, 63 insertions(+), 17 deletions(-) diff --git a/include/hybridmap/HybridSynthesisMapper.hpp b/include/hybridmap/HybridSynthesisMapper.hpp index 740f49d69..054a8dad7 100644 --- a/include/hybridmap/HybridSynthesisMapper.hpp +++ b/include/hybridmap/HybridSynthesisMapper.hpp @@ -33,7 +33,7 @@ namespace na { class HybridSynthesisMapper : private NeutralAtomMapper { using qcs = std::vector; - qc::QuantumComputation unmappedQc; + qc::QuantumComputation synthesizedQc; public: // Constructors @@ -53,17 +53,32 @@ class HybridSynthesisMapper : private NeutralAtomMapper { */ void initMapping(size_t nQubits, InitialMapping initialMapping = InitialMapping::Identity) { - mappedQc = qc::QuantumComputation(nQubits); - mapping = Mapping(nQubits, initialMapping); + mappedQc = qc::QuantumComputation(arch->getNpositions()); + synthesizedQc = qc::QuantumComputation(nQubits); + mapping = Mapping(nQubits, initialMapping); } /** - * @brief Remaps the whole circuit again starting from the initial mapping. - * @param initialMapping The initial mapping to be used. - * @return The remapped circuit. + * @brief Returns the mapped QuantumComputation. + * @return The mapped QuantumComputation. + */ + [[nodiscard]] qc::QuantumComputation + getMappedQc(bool completeRemap = true, + InitialMapping initMapping = InitialMapping::Identity) { + if (completeRemap) { + return this->map(synthesizedQc, initMapping); + } + return mappedQc; + } + + /** + * @brief Returns the synthesized QuantumComputation with all gates but not + * mapped to the hardware. + * @return The synthesized QuantumComputation. */ - qc::QuantumComputation - completelyRemap(InitialMapping initialMapping = InitialMapping::Identity); + [[nodiscard]] qc::QuantumComputation getSynthesizedQc() const { + return this->synthesizedQc; + } /** * @brief Evaluates the synthesis steps proposed by the ZX extraction. diff --git a/src/hybridmap/HybridSynthesisMapper.cpp b/src/hybridmap/HybridSynthesisMapper.cpp index 424b58d55..c81c5cdf4 100644 --- a/src/hybridmap/HybridSynthesisMapper.cpp +++ b/src/hybridmap/HybridSynthesisMapper.cpp @@ -18,12 +18,6 @@ namespace na { -qc::QuantumComputation -HybridSynthesisMapper::completelyRemap(InitialMapping initialMapping) { - this->map(unmappedQc, initialMapping); - return this->mappedQc; -} - size_t HybridSynthesisMapper::evaluateSynthesisSteps(const qcs& synthesisSteps, bool directlyMap) { std::vector> costs; @@ -46,6 +40,7 @@ HybridSynthesisMapper::evaluateSynthesisStep(const qc::QuantumComputation& qc) { void HybridSynthesisMapper::directlyMap(const qc::QuantumComputation& qc) { for (const auto& op : qc) { + this->synthesizedQc.emplace_back(op->clone()); this->mapGate(op.get()); } } diff --git a/test/hybridmap/test_hybrid_synthesis_map.cpp b/test/hybridmap/test_hybrid_synthesis_map.cpp index e9c73114e..3f647abc2 100644 --- a/test/hybridmap/test_hybrid_synthesis_map.cpp +++ b/test/hybridmap/test_hybrid_synthesis_map.cpp @@ -14,7 +14,8 @@ #include namespace na { -class TestHybridSynthesisMapper : public ::testing::TestWithParam { +class TestParametrizedHybridSynthesisMapper + : public ::testing::TestWithParam { protected: std::string testArchitecturePath = "architectures/"; std::vector circuits; @@ -36,7 +37,7 @@ class TestHybridSynthesisMapper : public ::testing::TestWithParam { // Test the HybridSynthesisMapper class }; -TEST_P(TestHybridSynthesisMapper, AdjaencyMatrix) { +TEST_P(TestParametrizedHybridSynthesisMapper, AdjaencyMatrix) { auto arch = NeutralAtomArchitecture(testArchitecturePath); auto mapper = HybridSynthesisMapper(arch); mapper.initMapping(3); @@ -46,8 +47,43 @@ TEST_P(TestHybridSynthesisMapper, AdjaencyMatrix) { } INSTANTIATE_TEST_SUITE_P(HybridSynthesisMapperTestSuite, - TestHybridSynthesisMapper, + TestParametrizedHybridSynthesisMapper, ::testing::Values("rubidium", "rubidium_hybrid", "rubidium_shuttling")); +class TestHybridSynthesisMapper : public ::testing::Test { +protected: + NeutralAtomArchitecture arch = + NeutralAtomArchitecture("architectures/rubidium.json"); + HybridSynthesisMapper mapper = HybridSynthesisMapper(arch); + qc::QuantumComputation qc; + + void SetUp() override { + qc = qc::QuantumComputation(3); + qc.x(0); + qc.cx(0, 1); + qc.cx(1, 2); + + mapper.initMapping(3); + } +}; + +TEST_F(TestHybridSynthesisMapper, DirectlyMap) { + mapper.directlyMap(qc); + auto synthesizedQc = mapper.getSynthesizedQc(); + EXPECT_EQ(synthesizedQc.getNqubits(), 3); + EXPECT_EQ(synthesizedQc.getNops(), 3); +} + +TEST_F(TestHybridSynthesisMapper, completelyRemap) { + mapper.directlyMap(qc); + mapper.directlyMap(qc); + auto mappedQc = mapper.getMappedQc(false); + EXPECT_EQ(mappedQc.getNqubits(), arch.getNpositions()); + EXPECT_GE(mappedQc.getNops(), 3); + auto mappedQcRemapped = mapper.getMappedQc(); + EXPECT_EQ(mappedQcRemapped.getNqubits(), arch.getNpositions()); + EXPECT_GE(mappedQcRemapped.getNops(), 3); +} + } // namespace na From ed10c87a022e74313a3e866a0da5d7a7326fabdd Mon Sep 17 00:00:00 2001 From: lsschmid <117631861+lsschmid@users.noreply.github.com> Date: Tue, 4 Jun 2024 13:41:30 +0200 Subject: [PATCH 016/394] =?UTF-8?q?=F0=9F=8E=A8=20renamed=20directlyMap?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridSynthesisMapper.hpp | 2 +- src/hybridmap/HybridSynthesisMapper.cpp | 5 +++-- test/hybridmap/test_hybrid_synthesis_map.cpp | 6 +++--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/include/hybridmap/HybridSynthesisMapper.hpp b/include/hybridmap/HybridSynthesisMapper.hpp index 054a8dad7..bc35ce71e 100644 --- a/include/hybridmap/HybridSynthesisMapper.hpp +++ b/include/hybridmap/HybridSynthesisMapper.hpp @@ -105,7 +105,7 @@ class HybridSynthesisMapper : private NeutralAtomMapper { * inserting SWAP gates or shuttling move operations. * @param qc The gates (QuantumComputation) to be mapped. */ - void directlyMap(const qc::QuantumComputation& qc); + void appendWithoutMapping(const qc::QuantumComputation& qc); /** * @brief Returns the current adjacency matrix of the neutral atom hardware. diff --git a/src/hybridmap/HybridSynthesisMapper.cpp b/src/hybridmap/HybridSynthesisMapper.cpp index c81c5cdf4..91ccd5229 100644 --- a/src/hybridmap/HybridSynthesisMapper.cpp +++ b/src/hybridmap/HybridSynthesisMapper.cpp @@ -28,7 +28,7 @@ size_t HybridSynthesisMapper::evaluateSynthesisSteps(const qcs& synthesisSteps, costs.begin(), costs.end(), [](const auto& a, const auto& b) { return a.second < b.second; }); if (directlyMap) { - this->directlyMap(bestQc->first); + this->appendWithoutMapping(bestQc->first); } return static_cast(std::distance(costs.begin(), bestQc)); } @@ -38,7 +38,8 @@ HybridSynthesisMapper::evaluateSynthesisStep(const qc::QuantumComputation& qc) { return 0; } -void HybridSynthesisMapper::directlyMap(const qc::QuantumComputation& qc) { +void HybridSynthesisMapper::appendWithoutMapping( + const qc::QuantumComputation& qc) { for (const auto& op : qc) { this->synthesizedQc.emplace_back(op->clone()); this->mapGate(op.get()); diff --git a/test/hybridmap/test_hybrid_synthesis_map.cpp b/test/hybridmap/test_hybrid_synthesis_map.cpp index 3f647abc2..a450f402e 100644 --- a/test/hybridmap/test_hybrid_synthesis_map.cpp +++ b/test/hybridmap/test_hybrid_synthesis_map.cpp @@ -69,15 +69,15 @@ class TestHybridSynthesisMapper : public ::testing::Test { }; TEST_F(TestHybridSynthesisMapper, DirectlyMap) { - mapper.directlyMap(qc); + mapper.appendWithoutMapping(qc); auto synthesizedQc = mapper.getSynthesizedQc(); EXPECT_EQ(synthesizedQc.getNqubits(), 3); EXPECT_EQ(synthesizedQc.getNops(), 3); } TEST_F(TestHybridSynthesisMapper, completelyRemap) { - mapper.directlyMap(qc); - mapper.directlyMap(qc); + mapper.appendWithoutMapping(qc); + mapper.appendWithoutMapping(qc); auto mappedQc = mapper.getMappedQc(false); EXPECT_EQ(mappedQc.getNqubits(), arch.getNpositions()); EXPECT_GE(mappedQc.getNops(), 3); From f8c68c2dd47a11eac0f36fe51416b0d6c2aa37fb Mon Sep 17 00:00:00 2001 From: lsschmid <117631861+lsschmid@users.noreply.github.com> Date: Tue, 4 Jun 2024 16:33:35 +0200 Subject: [PATCH 017/394] =?UTF-8?q?=F0=9F=8E=A8=20changed=20mapper=20param?= =?UTF-8?q?eter=20to=20const=20pointer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridNeutralAtomMapper.hpp | 29 ++++++++-------- src/hybridmap/HybridNeutralAtomMapper.cpp | 34 +++++++++---------- src/python/bindings.cpp | 7 ++-- 3 files changed, 35 insertions(+), 35 deletions(-) diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index 9f9db90e0..2bb37a867 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -15,6 +15,7 @@ #include "hybridmap/NeutralAtomScheduler.hpp" #include "hybridmap/NeutralAtomUtils.hpp" #include "operations/Operation.hpp" +#include "utils.hpp" #include #include @@ -85,7 +86,7 @@ class NeutralAtomMapper { // The minimal weight for any multi-qubit gate qc::fp twoQubitSwapWeight = 1; // The runtime parameters of the mapper - MapperParameters parameters; + const MapperParameters* parameters; // The qubits that are blocked by the last swap std::deque> lastBlockedQubits; // The last moves that have been executed @@ -363,12 +364,13 @@ class NeutralAtomMapper { const MapperParameters& p = MapperParameters()) : arch(&architecture), mappedQc(architecture.getNpositions()), mappedQcAOD(architecture.getNpositions()), scheduler(architecture), - parameters(p), hardwareQubits(architecture, parameters.initialMapping, - parameters.seed) { + parameters(&p), hardwareQubits(architecture, parameters->initialMapping, + parameters->seed) { // need at least on free coordinate to shuttle - if (architecture.getNpositions() - architecture.getNqubits() < 1) { - this->parameters.gateWeight = 1; - this->parameters.shuttlingWeight = 0; + if (architecture.getNpositions() - architecture.getNqubits() < 1 && + p.shuttlingWeight > 0) { + throw QMAPException("No free coordinates for shuttling but shuttling " + "weight is greater than 0."); } }; @@ -377,10 +379,11 @@ class NeutralAtomMapper { * @param p The runtime parameters of the mapper */ void setParameters(const MapperParameters& p) { - this->parameters = p; - if (arch->getNpositions() - arch->getNqubits() < 1) { - this->parameters.gateWeight = 1; - this->parameters.shuttlingWeight = 0; + this->parameters = &p; + if (arch->getNpositions() - arch->getNqubits() < 1 && + p.shuttlingWeight > 0) { + throw QMAPException("No free coordinates for shuttling but shuttling " + "weight is greater than 0."); } this->reset(); } @@ -390,7 +393,7 @@ class NeutralAtomMapper { */ void reset() { hardwareQubits = - HardwareQubits(*arch, parameters.initialMapping, parameters.seed); + HardwareQubits(*arch, parameters->initialMapping, parameters->seed); } // Methods @@ -430,9 +433,7 @@ class NeutralAtomMapper { * hardware qubits */ [[maybe_unused]] void mapAndConvert(qc::QuantumComputation& qc, - InitialMapping initialMapping, - bool printInfo) { - this->parameters.verbose = printInfo; + InitialMapping initialMapping) { map(qc, initialMapping); convertToAod(this->mappedQc); } diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index e5c5ed6f7..2d8b2eda0 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -63,7 +63,7 @@ qc::QuantumComputation NeutralAtomMapper::map(qc::QuantumComputation& qc, // precompute exponential decay weights this->decayWeights.reserve(this->arch->getNcolumns()); for (uint32_t i = this->arch->getNcolumns(); i > 0; --i) { - this->decayWeights.emplace_back(std::exp(-this->parameters.decay * i)); + this->decayWeights.emplace_back(std::exp(-this->parameters->decay * i)); } auto i = 0; @@ -79,7 +79,7 @@ qc::QuantumComputation NeutralAtomMapper::map(qc::QuantumComputation& qc, GateList gatesToExecute; while (gatesToExecute.empty()) { ++i; - if (this->parameters.verbose) { + if (this->parameters->verbose) { std::cout << "iteration " << i << '\n'; } auto bestSwap = findBestSwap(lastSwap); @@ -90,7 +90,7 @@ qc::QuantumComputation NeutralAtomMapper::map(qc::QuantumComputation& qc, mapAllPossibleGates(frontLayer); lookaheadLayer.initLayerOffset(frontLayer.getIteratorOffset()); reassignGatesToLayers(frontLayer.getGates(), lookaheadLayer.getGates()); - if (this->parameters.verbose) { + if (this->parameters->verbose) { printLayers(); } } @@ -99,7 +99,7 @@ qc::QuantumComputation NeutralAtomMapper::map(qc::QuantumComputation& qc, GateList gatesToExecute; while (gatesToExecute.empty()) { ++i; - if (this->parameters.verbose) { + if (this->parameters->verbose) { std::cout << "iteration " << i << '\n'; } auto bestMove = findBestAtomMove(); @@ -109,12 +109,12 @@ qc::QuantumComputation NeutralAtomMapper::map(qc::QuantumComputation& qc, mapAllPossibleGates(frontLayer); lookaheadLayer.initLayerOffset(frontLayer.getIteratorOffset()); reassignGatesToLayers(frontLayer.getGates(), lookaheadLayer.getGates()); - if (this->parameters.verbose) { + if (this->parameters->verbose) { printLayers(); } } } - if (this->parameters.verbose) { + if (this->parameters->verbose) { std::cout << "nSwaps: " << nSwaps << '\n'; std::cout << "nMoves: " << nMoves << '\n'; } @@ -151,7 +151,7 @@ NeutralAtomMapper::convertToAod(qc::QuantumComputation& qc) { // decompose AOD moves MoveToAodConverter aodScheduler(*arch); mappedQcAOD = aodScheduler.schedule(qc); - if (this->parameters.verbose) { + if (this->parameters->verbose) { std::cout << "nMoveGroups: " << aodScheduler.getNMoveGroups() << '\n'; } return mappedQcAOD; @@ -192,7 +192,7 @@ void NeutralAtomMapper::mapGate(const qc::Operation* op) { return; } this->executedCommutingGates.emplace_back(op); - if (this->parameters.verbose) { + if (this->parameters->verbose) { std::cout << "mapped " << op->getName() << " "; for (auto qubit : op->getUsedQubits()) { std::cout << qubit << " "; @@ -280,7 +280,7 @@ void NeutralAtomMapper::updateMappingSwap(Swap swap) { auto idxFirst = this->hardwareQubits.getCoordIndex(swap.first); auto idxSecond = this->hardwareQubits.getCoordIndex(swap.second); this->mappedQc.swap(idxFirst, idxSecond); - if (this->parameters.verbose) { + if (this->parameters->verbose) { std::cout << "swapped " << swap.first << " " << swap.second; std::cout << " logical qubits: "; if (this->mapping.isMapped(swap.first)) { @@ -305,7 +305,7 @@ void NeutralAtomMapper::updateMappingMove(AtomMove move) { mappedQc.move(move.first, move.second); auto toMoveHwQubit = this->hardwareQubits.getHwQubit(move.first); this->hardwareQubits.move(toMoveHwQubit, move.second); - if (this->parameters.verbose) { + if (this->parameters->verbose) { std::cout << "moved " << move.first << " to " << move.second; if (this->mapping.isMapped(toMoveHwQubit)) { std::cout << " logical qubit: " @@ -398,10 +398,10 @@ qc::fp NeutralAtomMapper::swapCost( swapCostPerLayer(swap, swapCloseByLookahead, swapExactLookahead) / static_cast(this->lookaheadLayerGate.size()); } - auto cost = parameters.lookaheadWeightSwaps * distanceChangeLookahead + + auto cost = parameters->lookaheadWeightSwaps * distanceChangeLookahead + distanceChangeFront; // compute the last time one of the swap qubits was used - if (this->parameters.decay != 0) { + if (this->parameters->decay != 0) { uint32_t idxLastUsed = 0; for (uint32_t i = 0; i < this->lastBlockedQubits.size(); ++i) { if (this->lastBlockedQubits[i].find(swap.first) != @@ -431,7 +431,7 @@ NeutralAtomMapper::initSwaps(const GateList& layer) { } else { // for multi-qubit gates, find the best position around the gate qubits auto bestPos = getBestMultiQubitPosition(gate); - if (this->parameters.verbose) { + if (this->parameters->verbose) { std::cout << "bestPos: "; for (auto qubit : bestPos) { std::cout << qubit << " "; @@ -816,10 +816,10 @@ qc::fp NeutralAtomMapper::moveCost(const AtomMove& move) { auto lookaheadCost = moveCostPerLayer(move, this->lookaheadLayerShuttling) / static_cast(this->lookaheadLayerShuttling.size()); - cost += parameters.lookaheadWeightMoves * lookaheadCost; + cost += parameters->lookaheadWeightMoves * lookaheadCost; } if (!this->lastMoves.empty()) { - auto parallelCost = parameters.shuttlingTimeWeight * + auto parallelCost = parameters->shuttlingTimeWeight * parallelMoveCost(move) / static_cast(this->lastMoves.size()) / static_cast(this->frontLayerShuttling.size()); @@ -1301,8 +1301,8 @@ bool NeutralAtomMapper::swapGateBetter(const qc::Operation* opPointer) { qc::OpType::AodDeactivate), minMoves); - return fidSwaps * parameters.gateWeight > - fidMoves * parameters.shuttlingWeight; + return fidSwaps * parameters->gateWeight > + fidMoves * parameters->shuttlingWeight; } } // namespace na diff --git a/src/python/bindings.cpp b/src/python/bindings.cpp index b6d95fd17..1a8176727 100644 --- a/src/python/bindings.cpp +++ b/src/python/bindings.cpp @@ -857,14 +857,13 @@ PYBIND11_MODULE(pyqmap, m) { .def( "map", [](na::NeutralAtomMapper& mapper, const py::object& circ, - na::InitialMapping initialMapping, bool verbose) { + na::InitialMapping initialMapping) { qc::QuantumComputation qc{}; loadQC(qc, circ); - mapper.mapAndConvert(qc, initialMapping, verbose); + mapper.mapAndConvert(qc, initialMapping); }, "Map a quantum circuit to the neutral atom quantum computer", - "circ"_a, "initial_mapping"_a = na::InitialMapping::Identity, - "verbose"_a = false) + "circ"_a, "initial_mapping"_a = na::InitialMapping::Identity) .def( "map_qasm_file", [](na::NeutralAtomMapper& mapper, const std::string& filename, From 1c86fa1c2c7de29e9fea745aec4c1f9c6820e7fa Mon Sep 17 00:00:00 2001 From: lsschmid <117631861+lsschmid@users.noreply.github.com> Date: Wed, 5 Jun 2024 12:34:42 +0200 Subject: [PATCH 018/394] =?UTF-8?q?=F0=9F=8E=A8=20changed=20mapper=20membe?= =?UTF-8?q?rs=20all=20to=20pointer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HardwareQubits.hpp | 3 ++- include/hybridmap/HybridNeutralAtomMapper.hpp | 22 ++++++++++++++----- include/hybridmap/NeutralAtomScheduler.hpp | 8 +++---- src/hybridmap/NeutralAtomScheduler.cpp | 20 ++++++++--------- 4 files changed, 32 insertions(+), 21 deletions(-) diff --git a/include/hybridmap/HardwareQubits.hpp b/include/hybridmap/HardwareQubits.hpp index 5a834e168..ab7fbf9c9 100644 --- a/include/hybridmap/HardwareQubits.hpp +++ b/include/hybridmap/HardwareQubits.hpp @@ -35,7 +35,7 @@ namespace na { */ class HardwareQubits { protected: - const NeutralAtomArchitecture* arch; + const NeutralAtomArchitecture* arch = nullptr; qc::Permutation hwToCoordIdx; SymmetricMatrix swapDistances; std::map nearbyQubits; @@ -81,6 +81,7 @@ class HardwareQubits { public: // Constructors + HardwareQubits() = default; HardwareQubits(const NeutralAtomArchitecture& architecture, InitialCoordinateMapping initialCoordinateMapping, uint32_t seed) diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index 2bb37a867..7a0a946aa 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -66,7 +66,7 @@ struct MapperParameters { class NeutralAtomMapper { protected: // The considered architecture - const NeutralAtomArchitecture* arch; + const NeutralAtomArchitecture* arch = nullptr; // The mapped quantum circuit qc::QuantumComputation mappedQc; // The mapped quantum circuit converted to AOD movements @@ -86,7 +86,7 @@ class NeutralAtomMapper { // The minimal weight for any multi-qubit gate qc::fp twoQubitSwapWeight = 1; // The runtime parameters of the mapper - const MapperParameters* parameters; + const MapperParameters* parameters = nullptr; // The qubits that are blocked by the last swap std::deque> lastBlockedQubits; // The last moves that have been executed @@ -357,9 +357,7 @@ class NeutralAtomMapper { public: // Constructors - [[maybe_unused]] NeutralAtomMapper(const NeutralAtomMapper&) = delete; - NeutralAtomMapper& operator=(const NeutralAtomMapper&) = delete; - NeutralAtomMapper(NeutralAtomMapper&&) = delete; + NeutralAtomMapper() = default; explicit NeutralAtomMapper(const NeutralAtomArchitecture& architecture, const MapperParameters& p = MapperParameters()) : arch(&architecture), mappedQc(architecture.getNpositions()), @@ -388,6 +386,20 @@ class NeutralAtomMapper { this->reset(); } + /** + * @brief Loads the current mapping and hardware qubits. + * @param newMapping The new mapping to be loaded + * @param newHwQubits The new hardware qubits to be loaded + */ + void loadCurrentMapping(const Mapping& newMapping, + const HardwareQubits& newHwQubits) { + this->mapping = newMapping; + this->hardwareQubits = newHwQubits; + } + + Mapping& getMapping() { return mapping; } + HardwareQubits& getHardwareQubits() { return hardwareQubits; } + /** * @brief Resets the mapper and the hardware qubits. */ diff --git a/include/hybridmap/NeutralAtomScheduler.hpp b/include/hybridmap/NeutralAtomScheduler.hpp index a5cd87c08..60ce056ef 100644 --- a/include/hybridmap/NeutralAtomScheduler.hpp +++ b/include/hybridmap/NeutralAtomScheduler.hpp @@ -71,17 +71,15 @@ struct SchedulerResults { */ class NeutralAtomScheduler { protected: - const NeutralAtomArchitecture& arch; + const NeutralAtomArchitecture* arch = nullptr; std::string animationCsv; std::string animationArchitectureCsv; public: // Constructor - NeutralAtomScheduler() = delete; - NeutralAtomScheduler(const NeutralAtomScheduler&) = delete; - NeutralAtomScheduler(NeutralAtomScheduler&&) = delete; + NeutralAtomScheduler() = default; explicit NeutralAtomScheduler(const NeutralAtomArchitecture& architecture) - : arch(architecture) {} + : arch(&architecture) {} /** * @brief Schedules the given quantum circuit on the neutral atom architecture diff --git a/src/hybridmap/NeutralAtomScheduler.cpp b/src/hybridmap/NeutralAtomScheduler.cpp index 03b2458b2..4885f6041 100644 --- a/src/hybridmap/NeutralAtomScheduler.cpp +++ b/src/hybridmap/NeutralAtomScheduler.cpp @@ -34,18 +34,18 @@ na::NeutralAtomScheduler::schedule(const qc::QuantumComputation& qc, std::cout << "\n* schedule start!\n"; } - std::vector totalExecutionTimes(arch.getNpositions(), 0); + std::vector totalExecutionTimes(arch->getNpositions(), 0); // saves for each coord the time slots that are blocked by a multi qubit gate std::vector>> rydbergBlockedQubitsTimes( - arch.getNpositions(), std::deque>()); + arch->getNpositions(), std::deque>()); qc::fp aodLastBlockedTime = 0; qc::fp totalGateTime = 0; qc::fp totalGateFidelities = 1; - AnimationAtoms animationAtoms(initHwPos, arch); + AnimationAtoms animationAtoms(initHwPos, *arch); if (createAnimationCsv) { animationCsv += animationAtoms.getInitString(); - animationArchitectureCsv = arch.getAnimationCsv(); + animationArchitectureCsv = arch->getAnimationCsv(); } int index = 0; @@ -63,12 +63,12 @@ na::NeutralAtomScheduler::schedule(const qc::QuantumComputation& qc, } auto qubits = op->getUsedQubits(); - auto opTime = arch.getOpTime(op.get()); + auto opTime = arch->getOpTime(op.get()); if (op->getType() == qc::AodMove || op->getType() == qc::AodActivate || op->getType() == qc::AodDeactivate) { opTime *= shuttlingSpeedFactor; } - auto opFidelity = arch.getOpFidelity(op.get()); + auto opFidelity = arch->getOpFidelity(op.get()); // DEBUG info if (verbose) { @@ -91,7 +91,7 @@ na::NeutralAtomScheduler::schedule(const qc::QuantumComputation& qc, aodLastBlockedTime = maxTime + opTime; } else if (qubits.size() > 1) { // multi qubit gates -> take into consideration blocking - auto rydbergBlockedQubits = arch.getBlockedCoordIndices(op.get()); + auto rydbergBlockedQubits = arch->getBlockedCoordIndices(op.get()); // get max execution time over all blocked qubits bool rydbergBlocked = true; while (rydbergBlocked) { @@ -154,7 +154,7 @@ na::NeutralAtomScheduler::schedule(const qc::QuantumComputation& qc, // update animation if (createAnimationCsv) { animationCsv += - animationAtoms.createCsvOp(op, maxTime, maxTime + opTime, arch); + animationAtoms.createCsvOp(op, maxTime, maxTime + opTime, *arch); } } if (verbose) { @@ -165,10 +165,10 @@ na::NeutralAtomScheduler::schedule(const qc::QuantumComputation& qc, const auto maxExecutionTime = *std::max_element(totalExecutionTimes.begin(), totalExecutionTimes.end()); const auto totalIdleTime = - maxExecutionTime * arch.getNqubits() - totalGateTime; + maxExecutionTime * arch->getNqubits() - totalGateTime; const auto totalFidelities = totalGateFidelities * - std::exp(-totalIdleTime / arch.getDecoherenceTime()); + std::exp(-totalIdleTime / arch->getDecoherenceTime()); if (createAnimationCsv) { animationCsv += animationAtoms.getEndString(maxExecutionTime); From beaf4977592da629a0b7b6a4391afed0f0e60637 Mon Sep 17 00:00:00 2001 From: lsschmid <117631861+lsschmid@users.noreply.github.com> Date: Wed, 5 Jun 2024 12:35:00 +0200 Subject: [PATCH 019/394] =?UTF-8?q?=E2=9C=85=20Updated=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/hybridmap/test_hybridmap.cpp | 61 +++++++++++++++++++++---------- 1 file changed, 42 insertions(+), 19 deletions(-) diff --git a/test/hybridmap/test_hybridmap.cpp b/test/hybridmap/test_hybridmap.cpp index 8d2e7a066..007b38d51 100644 --- a/test/hybridmap/test_hybridmap.cpp +++ b/test/hybridmap/test_hybridmap.cpp @@ -50,7 +50,7 @@ INSTANTIATE_TEST_SUITE_P(NeutralAtomArchitectureTestSuite, NeutralAtomArchitectureTest, ::testing::Values("rubidium", "rubidium_hybrid", "rubidium_shuttling")); -class NeutralAtomMapperTest +class NeutralAtomMapperTestParams // parameters are architecture, circuit, gateWeight, shuttlingWeight, // lookAheadWeight, initialCoordinateMapping : public ::testing::TestWithParam< @@ -80,7 +80,7 @@ class NeutralAtomMapperTest } }; -TEST_P(NeutralAtomMapperTest, MapCircuitsIdentity) { +TEST_P(NeutralAtomMapperTestParams, MapCircuitsIdentity) { auto arch = na::NeutralAtomArchitecture(testArchitecturePath); na::InitialMapping const initialMapping = na::InitialMapping::Identity; na::NeutralAtomMapper mapper(arch); @@ -108,7 +108,7 @@ TEST_P(NeutralAtomMapperTest, MapCircuitsIdentity) { } INSTANTIATE_TEST_SUITE_P( - NeutralAtomMapperTestSuite, NeutralAtomMapperTest, + NeutralAtomMapperTestSuite, NeutralAtomMapperTestParams, ::testing::Combine( ::testing::Values("rubidium", "rubidium_hybrid", "rubidium_shuttling"), ::testing::Values("dj_nativegates_rigetti_qiskit_opt3_10", "modulo_2", @@ -120,25 +120,34 @@ INSTANTIATE_TEST_SUITE_P( ::testing::Values(na::InitialCoordinateMapping::Trivial, na::InitialCoordinateMapping::Random))); -TEST(NeutralAtomMapperTest, Output) { - auto arch = - na::NeutralAtomArchitecture("architectures/rubidium_shuttling.json"); +class NeutralAtomMapperTest : public ::testing::Test { +protected: + std::string testArchitecturePath = "architectures/rubidium_shuttling.json"; + const na::NeutralAtomArchitecture arch = + na::NeutralAtomArchitecture(testArchitecturePath); na::InitialMapping const initialMapping = na::InitialMapping::Identity; - na::NeutralAtomMapper mapper(arch); na::MapperParameters mapperParameters; - mapperParameters.initialMapping = na::InitialCoordinateMapping::Trivial; - mapperParameters.lookaheadWeightSwaps = 0.1; - mapperParameters.lookaheadWeightMoves = 0.1; - mapperParameters.decay = 0; - mapperParameters.shuttlingTimeWeight = 0.1; - mapperParameters.gateWeight = 1; - mapperParameters.shuttlingWeight = 0; - mapperParameters.seed = 43; - mapperParameters.verbose = true; - mapper.setParameters(mapperParameters); + na::NeutralAtomMapper mapper; + qc::QuantumComputation qc; + + void SetUp() override { + mapper = na::NeutralAtomMapper(arch); + mapperParameters.initialMapping = na::InitialCoordinateMapping::Trivial; + mapperParameters.lookaheadWeightSwaps = 0.1; + mapperParameters.lookaheadWeightMoves = 0.1; + mapperParameters.decay = 0; + mapperParameters.shuttlingTimeWeight = 0.1; + mapperParameters.gateWeight = 1; + mapperParameters.shuttlingWeight = 0; + mapperParameters.seed = 43; + mapperParameters.verbose = true; + mapper.setParameters(mapperParameters); + qc = qc::QuantumComputation( + "circuits/dj_nativegates_rigetti_qiskit_opt3_10.qasm"); + } +}; - qc::QuantumComputation qc( - "circuits/dj_nativegates_rigetti_qiskit_opt3_10.qasm"); +TEST_F(NeutralAtomMapperTest, Output) { auto qcMapped = mapper.map(qc, initialMapping); qcMapped.dumpOpenQASM(std::cout, false); @@ -151,3 +160,17 @@ TEST(NeutralAtomMapperTest, Output) { ASSERT_GT(scheduleResults.totalFidelities, 0); } + +TEST_F(NeutralAtomMapperTest, MappingLoading) { + const auto qcMapped = mapper.map(qc, initialMapping); + na::NeutralAtomMapper mapper2(arch); + mapper2.loadCurrentMapping(mapper.getMapping(), mapper.getHardwareQubits()); + for (std::uint32_t i = 0; i < qc.getNqubits(); i++) { + ASSERT_EQ(mapper.getMapping().getHwQubit(i), + mapper2.getMapping().getHwQubit(i)); + } + for (std::uint32_t i = 0; i < qcMapped.getNqubits(); i++) { + ASSERT_EQ(mapper.getHardwareQubits().isMapped(i), + mapper2.getHardwareQubits().isMapped(i)); + } +} From 7d9f911e927563b71c2f515abb59198f0f045cf2 Mon Sep 17 00:00:00 2001 From: lsschmid <117631861+lsschmid@users.noreply.github.com> Date: Wed, 5 Jun 2024 12:47:11 +0200 Subject: [PATCH 020/394] =?UTF-8?q?=E2=9C=85=20Added=20constructor=20with?= =?UTF-8?q?=20pointers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridNeutralAtomMapper.hpp | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index 7a0a946aa..6a0fd5e69 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -358,19 +358,18 @@ class NeutralAtomMapper { public: // Constructors NeutralAtomMapper() = default; - explicit NeutralAtomMapper(const NeutralAtomArchitecture& architecture, - const MapperParameters& p = MapperParameters()) - : arch(&architecture), mappedQc(architecture.getNpositions()), - mappedQcAOD(architecture.getNpositions()), scheduler(architecture), - parameters(&p), hardwareQubits(architecture, parameters->initialMapping, - parameters->seed) { - // need at least on free coordinate to shuttle - if (architecture.getNpositions() - architecture.getNqubits() < 1 && - p.shuttlingWeight > 0) { + NeutralAtomMapper(const NeutralAtomArchitecture* architecture, + const MapperParameters* p = nullptr) + : arch(architecture), scheduler(*architecture), parameters(p) { + if (arch->getNpositions() - arch->getNqubits() < 1 && + p->shuttlingWeight > 0) { throw QMAPException("No free coordinates for shuttling but shuttling " "weight is greater than 0."); } }; + explicit NeutralAtomMapper(const NeutralAtomArchitecture& architecture, + const MapperParameters& p = MapperParameters()) + : NeutralAtomMapper(&architecture, &p) {} /** * @brief Sets the runtime parameters of the mapper. From be843c324b72b4d328cb35afbac95d87913415b6 Mon Sep 17 00:00:00 2001 From: lsschmid <117631861+lsschmid@users.noreply.github.com> Date: Wed, 5 Jun 2024 13:02:38 +0200 Subject: [PATCH 021/394] =?UTF-8?q?=F0=9F=8E=A8=20Changed=20loading/settin?= =?UTF-8?q?g=20of=20initial=20mappings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridNeutralAtomMapper.hpp | 16 +++++++++------- src/hybridmap/HybridNeutralAtomMapper.cpp | 7 +++---- test/hybridmap/test_hybridmap.cpp | 6 +----- 3 files changed, 13 insertions(+), 16 deletions(-) diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index 6a0fd5e69..8bc1bdc1b 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -358,8 +358,8 @@ class NeutralAtomMapper { public: // Constructors NeutralAtomMapper() = default; - NeutralAtomMapper(const NeutralAtomArchitecture* architecture, - const MapperParameters* p = nullptr) + explicit NeutralAtomMapper(const NeutralAtomArchitecture* architecture, + const MapperParameters* p = nullptr) : arch(architecture), scheduler(*architecture), parameters(p) { if (arch->getNpositions() - arch->getNqubits() < 1 && p->shuttlingWeight > 0) { @@ -390,13 +390,10 @@ class NeutralAtomMapper { * @param newMapping The new mapping to be loaded * @param newHwQubits The new hardware qubits to be loaded */ - void loadCurrentMapping(const Mapping& newMapping, - const HardwareQubits& newHwQubits) { - this->mapping = newMapping; + void loadHwQubits(const HardwareQubits& newHwQubits) { this->hardwareQubits = newHwQubits; } - Mapping& getMapping() { return mapping; } HardwareQubits& getHardwareQubits() { return hardwareQubits; } /** @@ -434,7 +431,12 @@ class NeutralAtomMapper { * operations */ qc::QuantumComputation map(qc::QuantumComputation& qc, - InitialMapping initialMapping); + Mapping initialMapping); + + qc::QuantumComputation map(qc::QuantumComputation& qc, + InitialMapping initialMapping) { + return map(qc, Mapping(qc.getNqubits(), initialMapping)); + } /** * @brief Maps the given quantum circuit to the given architecture and diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 2d8b2eda0..3bfa81202 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -34,7 +34,9 @@ namespace na { qc::QuantumComputation NeutralAtomMapper::map(qc::QuantumComputation& qc, - InitialMapping initialMapping) { + Mapping initialMapping) { + mapping = initialMapping; + mappedQc = qc::QuantumComputation(arch->getNpositions()); nMoves = 0; nSwaps = 0; @@ -45,9 +47,6 @@ qc::QuantumComputation NeutralAtomMapper::map(qc::QuantumComputation& qc, auto dag = qc::CircuitOptimizer::constructDAG(qc); - // init mapping - this->mapping = Mapping(qc.getNqubits(), initialMapping); - // init layers NeutralAtomLayer frontLayer(dag); frontLayer.initLayerOffset(); diff --git a/test/hybridmap/test_hybridmap.cpp b/test/hybridmap/test_hybridmap.cpp index 007b38d51..a429f1127 100644 --- a/test/hybridmap/test_hybridmap.cpp +++ b/test/hybridmap/test_hybridmap.cpp @@ -164,11 +164,7 @@ TEST_F(NeutralAtomMapperTest, Output) { TEST_F(NeutralAtomMapperTest, MappingLoading) { const auto qcMapped = mapper.map(qc, initialMapping); na::NeutralAtomMapper mapper2(arch); - mapper2.loadCurrentMapping(mapper.getMapping(), mapper.getHardwareQubits()); - for (std::uint32_t i = 0; i < qc.getNqubits(); i++) { - ASSERT_EQ(mapper.getMapping().getHwQubit(i), - mapper2.getMapping().getHwQubit(i)); - } + mapper2.loadHwQubits(mapper.getHardwareQubits()); for (std::uint32_t i = 0; i < qcMapped.getNqubits(); i++) { ASSERT_EQ(mapper.getHardwareQubits().isMapped(i), mapper2.getHardwareQubits().isMapped(i)); From 67202252b0d66b25439afea95c3e494c857e1ef4 Mon Sep 17 00:00:00 2001 From: lsschmid <117631861+lsschmid@users.noreply.github.com> Date: Wed, 5 Jun 2024 13:08:16 +0200 Subject: [PATCH 022/394] =?UTF-8?q?=E2=9C=A8=20added=20Synthesis=20evaluat?= =?UTF-8?q?ion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridSynthesisMapper.hpp | 6 ++---- src/hybridmap/HybridSynthesisMapper.cpp | 16 ++++++++++------ 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/include/hybridmap/HybridSynthesisMapper.hpp b/include/hybridmap/HybridSynthesisMapper.hpp index bc35ce71e..d0b46f463 100644 --- a/include/hybridmap/HybridSynthesisMapper.hpp +++ b/include/hybridmap/HybridSynthesisMapper.hpp @@ -13,7 +13,6 @@ #include "hybridmap/NeutralAtomDefinitions.hpp" #include -#include #include namespace na { @@ -87,8 +86,7 @@ class HybridSynthesisMapper : private NeutralAtomMapper { * hardware. * @return The index of the synthesis step with the lowest effort. */ - size_t evaluateSynthesisSteps(const qcs& synthesisSteps, - bool directlyMap = false); + size_t evaluateSynthesisSteps(qcs& synthesisSteps, bool directlyMap = false); /** * @brief Evaluates a single synthesis step proposed by the ZX extraction. @@ -98,7 +96,7 @@ class HybridSynthesisMapper : private NeutralAtomMapper { * @param qc The synthesis step to be evaluated. * @return The cost/effort to map the synthesis step. */ - qc::fp evaluateSynthesisStep(const qc::QuantumComputation& qc); + qc::fp evaluateSynthesisStep(qc::QuantumComputation& qc); /** * @brief Directly maps the given QuantumComputation to the hardware NOT diff --git a/src/hybridmap/HybridSynthesisMapper.cpp b/src/hybridmap/HybridSynthesisMapper.cpp index 91ccd5229..7504ac3da 100644 --- a/src/hybridmap/HybridSynthesisMapper.cpp +++ b/src/hybridmap/HybridSynthesisMapper.cpp @@ -6,8 +6,8 @@ #include "Definitions.hpp" #include "QuantumComputation.hpp" +#include "hybridmap/HybridNeutralAtomMapper.hpp" #include "hybridmap/NeutralAtomDefinitions.hpp" -#include "hybridmap/NeutralAtomUtils.hpp" #include #include @@ -18,10 +18,10 @@ namespace na { -size_t HybridSynthesisMapper::evaluateSynthesisSteps(const qcs& synthesisSteps, - bool directlyMap) { +size_t HybridSynthesisMapper::evaluateSynthesisSteps(qcs& synthesisSteps, + bool directlyMap) { std::vector> costs; - for (const auto& qc : synthesisSteps) { + for (auto& qc : synthesisSteps) { costs.emplace_back(qc, this->evaluateSynthesisStep(qc)); } const auto bestQc = std::min_element( @@ -34,8 +34,12 @@ size_t HybridSynthesisMapper::evaluateSynthesisSteps(const qcs& synthesisSteps, } qc::fp -HybridSynthesisMapper::evaluateSynthesisStep(const qc::QuantumComputation& qc) { - return 0; +HybridSynthesisMapper::evaluateSynthesisStep(qc::QuantumComputation& qc) { + NeutralAtomMapper tempMapper(arch, parameters); + tempMapper.loadHwQubits(hardwareQubits); + const auto mappedQc = tempMapper.map(qc, mapping); + const auto results = tempMapper.schedule(); + return results.totalFidelities; } void HybridSynthesisMapper::appendWithoutMapping( From d4513aeba733dba3b2ff7ff68dfea56925e81f21 Mon Sep 17 00:00:00 2001 From: lsschmid <117631861+lsschmid@users.noreply.github.com> Date: Wed, 5 Jun 2024 13:40:53 +0200 Subject: [PATCH 023/394] =?UTF-8?q?=F0=9F=90=9B=20Fixed=20problems=20when?= =?UTF-8?q?=20initializing=20Mapper?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridNeutralAtomMapper.hpp | 3 ++- src/hybridmap/HybridNeutralAtomMapper.cpp | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index 8bc1bdc1b..673c36d91 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -360,7 +360,8 @@ class NeutralAtomMapper { NeutralAtomMapper() = default; explicit NeutralAtomMapper(const NeutralAtomArchitecture* architecture, const MapperParameters* p = nullptr) - : arch(architecture), scheduler(*architecture), parameters(p) { + : arch(architecture), scheduler(*architecture), parameters(p), + hardwareQubits(*arch, p->initialMapping, p->seed) { if (arch->getNpositions() - arch->getNqubits() < 1 && p->shuttlingWeight > 0) { throw QMAPException("No free coordinates for shuttling but shuttling " diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 3bfa81202..3c4751ef8 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -8,6 +8,7 @@ #include "CircuitOptimizer.hpp" #include "Definitions.hpp" #include "QuantumComputation.hpp" +#include "hybridmap/Mapping.hpp" #include "hybridmap/MoveToAodConverter.hpp" #include "hybridmap/NeutralAtomDefinitions.hpp" #include "hybridmap/NeutralAtomLayer.hpp" @@ -35,7 +36,7 @@ namespace na { qc::QuantumComputation NeutralAtomMapper::map(qc::QuantumComputation& qc, Mapping initialMapping) { - mapping = initialMapping; + mapping = std::move(initialMapping); mappedQc = qc::QuantumComputation(arch->getNpositions()); nMoves = 0; From 0ca81bf3cda6a64e1ea791e35512e15102ebf96d Mon Sep 17 00:00:00 2001 From: lsschmid <117631861+lsschmid@users.noreply.github.com> Date: Wed, 5 Jun 2024 13:41:44 +0200 Subject: [PATCH 024/394] =?UTF-8?q?=F0=9F=8E=A8=20made=20subtask=20private?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridSynthesisMapper.hpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/include/hybridmap/HybridSynthesisMapper.hpp b/include/hybridmap/HybridSynthesisMapper.hpp index d0b46f463..13ae64acd 100644 --- a/include/hybridmap/HybridSynthesisMapper.hpp +++ b/include/hybridmap/HybridSynthesisMapper.hpp @@ -34,6 +34,16 @@ class HybridSynthesisMapper : private NeutralAtomMapper { qc::QuantumComputation synthesizedQc; + /** + * @brief Evaluates a single synthesis step proposed by the ZX extraction. + * @details The effort is calculated by the NeutralAtomMapper, taking into + * account the number of SWAP gates or shuttling moves and the time needed to + * execute the mapped synthesis step. + * @param qc The synthesis step to be evaluated. + * @return The cost/effort to map the synthesis step. + */ + qc::fp evaluateSynthesisStep(qc::QuantumComputation& qc); + public: // Constructors HybridSynthesisMapper() = delete; @@ -88,16 +98,6 @@ class HybridSynthesisMapper : private NeutralAtomMapper { */ size_t evaluateSynthesisSteps(qcs& synthesisSteps, bool directlyMap = false); - /** - * @brief Evaluates a single synthesis step proposed by the ZX extraction. - * @details The effort is calculated by the NeutralAtomMapper, taking into - * account the number of SWAP gates or shuttling moves and the time needed to - * execute the mapped synthesis step. - * @param qc The synthesis step to be evaluated. - * @return The cost/effort to map the synthesis step. - */ - qc::fp evaluateSynthesisStep(qc::QuantumComputation& qc); - /** * @brief Directly maps the given QuantumComputation to the hardware NOT * inserting SWAP gates or shuttling move operations. From 1891e672b4b5e2a1f344f71100dcaea15bc181ba Mon Sep 17 00:00:00 2001 From: lsschmid <117631861+lsschmid@users.noreply.github.com> Date: Wed, 5 Jun 2024 13:41:59 +0200 Subject: [PATCH 025/394] =?UTF-8?q?=F0=9F=8E=A8=20fixed=20missing=20aod=20?= =?UTF-8?q?convertion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridSynthesisMapper.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/hybridmap/HybridSynthesisMapper.cpp b/src/hybridmap/HybridSynthesisMapper.cpp index 7504ac3da..c1f9430e8 100644 --- a/src/hybridmap/HybridSynthesisMapper.cpp +++ b/src/hybridmap/HybridSynthesisMapper.cpp @@ -24,7 +24,7 @@ size_t HybridSynthesisMapper::evaluateSynthesisSteps(qcs& synthesisSteps, for (auto& qc : synthesisSteps) { costs.emplace_back(qc, this->evaluateSynthesisStep(qc)); } - const auto bestQc = std::min_element( + const auto bestQc = std::max_element( costs.begin(), costs.end(), [](const auto& a, const auto& b) { return a.second < b.second; }); if (directlyMap) { @@ -37,8 +37,9 @@ qc::fp HybridSynthesisMapper::evaluateSynthesisStep(qc::QuantumComputation& qc) { NeutralAtomMapper tempMapper(arch, parameters); tempMapper.loadHwQubits(hardwareQubits); - const auto mappedQc = tempMapper.map(qc, mapping); - const auto results = tempMapper.schedule(); + auto mappedQc = tempMapper.map(qc, mapping); + const auto mappedQCAOD = tempMapper.convertToAod(mappedQc); + const auto results = tempMapper.schedule(); return results.totalFidelities; } @@ -51,7 +52,7 @@ void HybridSynthesisMapper::appendWithoutMapping( } AdjacencyMatrix HybridSynthesisMapper::getCircuitAdjacencyMatrix() const { - auto numCircQubits = mappedQc.getNqubits(); + auto numCircQubits = synthesizedQc.getNqubits(); AdjacencyMatrix adjMatrix(numCircQubits); for (uint32_t i = 0; i < numCircQubits; ++i) { From 7b0009aa3748c55ce69b27e8369d74c5013c1ce7 Mon Sep 17 00:00:00 2001 From: lsschmid <117631861+lsschmid@users.noreply.github.com> Date: Wed, 5 Jun 2024 13:42:23 +0200 Subject: [PATCH 026/394] =?UTF-8?q?=E2=9C=85=20Added=20evaluation=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/hybridmap/test_hybrid_synthesis_map.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/test/hybridmap/test_hybrid_synthesis_map.cpp b/test/hybridmap/test_hybrid_synthesis_map.cpp index a450f402e..24e1ceeea 100644 --- a/test/hybridmap/test_hybrid_synthesis_map.cpp +++ b/test/hybridmap/test_hybrid_synthesis_map.cpp @@ -4,7 +4,6 @@ // #include "QuantumComputation.hpp" -#include "hybridmap/HybridNeutralAtomMapper.hpp" #include "hybridmap/HybridSynthesisMapper.hpp" #include "hybridmap/NeutralAtomArchitecture.hpp" @@ -46,6 +45,14 @@ TEST_P(TestParametrizedHybridSynthesisMapper, AdjaencyMatrix) { EXPECT_TRUE(adjMatrix(0, 2) == 0 || adjMatrix(0, 2) == 1); } +TEST_P(TestParametrizedHybridSynthesisMapper, EvaluateSynthesisStep) { + auto arch = NeutralAtomArchitecture(testArchitecturePath); + auto mapper = HybridSynthesisMapper(arch); + mapper.initMapping(3); + auto best = mapper.evaluateSynthesisSteps(circuits, false); + EXPECT_GE(best, 0); +} + INSTANTIATE_TEST_SUITE_P(HybridSynthesisMapperTestSuite, TestParametrizedHybridSynthesisMapper, ::testing::Values("rubidium", "rubidium_hybrid", From bb4d5a33773cbfaee5fbb5b4c5d0ca567b19be89 Mon Sep 17 00:00:00 2001 From: lsschmid <117631861+lsschmid@users.noreply.github.com> Date: Wed, 5 Jun 2024 13:58:58 +0200 Subject: [PATCH 027/394] =?UTF-8?q?=F0=9F=8E=A8=20Changed=20state=20transf?= =?UTF-8?q?er=20to=20new=20mapper?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridNeutralAtomMapper.hpp | 19 +++++++++++-------- include/hybridmap/HybridSynthesisMapper.hpp | 11 +++++++++-- src/hybridmap/HybridSynthesisMapper.cpp | 11 +++++++---- test/hybridmap/test_hybridmap.cpp | 10 ---------- 4 files changed, 27 insertions(+), 24 deletions(-) diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index 673c36d91..b5a871ce7 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -387,16 +387,19 @@ class NeutralAtomMapper { } /** - * @brief Loads the current mapping and hardware qubits. - * @param newMapping The new mapping to be loaded - * @param newHwQubits The new hardware qubits to be loaded - */ - void loadHwQubits(const HardwareQubits& newHwQubits) { - this->hardwareQubits = newHwQubits; + * @brief Copies the state from the given mapper. + * @param mapper The mapper to copy the state from + */ + void copyStateFrom(const NeutralAtomMapper& mapper) { + this->arch = mapper.arch; + this->parameters = mapper.parameters; + this->mapping = mapper.mapping; + this->hardwareQubits = mapper.hardwareQubits; + this->lastMoves = mapper.lastMoves; + this->lastBlockedQubits = mapper.lastBlockedQubits; + this->scheduler = mapper.scheduler; } - HardwareQubits& getHardwareQubits() { return hardwareQubits; } - /** * @brief Resets the mapper and the hardware qubits. */ diff --git a/include/hybridmap/HybridSynthesisMapper.hpp b/include/hybridmap/HybridSynthesisMapper.hpp index 13ae64acd..4ec19f0d8 100644 --- a/include/hybridmap/HybridSynthesisMapper.hpp +++ b/include/hybridmap/HybridSynthesisMapper.hpp @@ -92,11 +92,11 @@ class HybridSynthesisMapper : private NeutralAtomMapper { /** * @brief Evaluates the synthesis steps proposed by the ZX extraction. * @param synthesisSteps The synthesis steps proposed by the ZX extraction. - * @param directlyMap If true, the synthesis steps are directly mapped to the + * @param alsoMap If true, the synthesis steps are directly mapped to the * hardware. * @return The index of the synthesis step with the lowest effort. */ - size_t evaluateSynthesisSteps(qcs& synthesisSteps, bool directlyMap = false); + size_t evaluateSynthesisSteps(qcs& synthesisSteps, bool alsoMap = false); /** * @brief Directly maps the given QuantumComputation to the hardware NOT @@ -105,6 +105,13 @@ class HybridSynthesisMapper : private NeutralAtomMapper { */ void appendWithoutMapping(const qc::QuantumComputation& qc); + /** + * @brief Appends the given QuantumComputation to the synthesized + * QuantumComputation and maps the gates to the hardware. + * @param qc The gates (QuantumComputation) to be appended and mapped. + */ + void appendWithMapping(const qc::QuantumComputation& qc); + /** * @brief Returns the current adjacency matrix of the neutral atom hardware. * @return The current adjacency matrix of the neutral atom hardware. diff --git a/src/hybridmap/HybridSynthesisMapper.cpp b/src/hybridmap/HybridSynthesisMapper.cpp index c1f9430e8..6d7da63b2 100644 --- a/src/hybridmap/HybridSynthesisMapper.cpp +++ b/src/hybridmap/HybridSynthesisMapper.cpp @@ -19,7 +19,7 @@ namespace na { size_t HybridSynthesisMapper::evaluateSynthesisSteps(qcs& synthesisSteps, - bool directlyMap) { + bool alsoMap) { std::vector> costs; for (auto& qc : synthesisSteps) { costs.emplace_back(qc, this->evaluateSynthesisStep(qc)); @@ -27,7 +27,7 @@ size_t HybridSynthesisMapper::evaluateSynthesisSteps(qcs& synthesisSteps, const auto bestQc = std::max_element( costs.begin(), costs.end(), [](const auto& a, const auto& b) { return a.second < b.second; }); - if (directlyMap) { + if (alsoMap) { this->appendWithoutMapping(bestQc->first); } return static_cast(std::distance(costs.begin(), bestQc)); @@ -35,8 +35,8 @@ size_t HybridSynthesisMapper::evaluateSynthesisSteps(qcs& synthesisSteps, qc::fp HybridSynthesisMapper::evaluateSynthesisStep(qc::QuantumComputation& qc) { - NeutralAtomMapper tempMapper(arch, parameters); - tempMapper.loadHwQubits(hardwareQubits); + NeutralAtomMapper tempMapper; + tempMapper.copyStateFrom(*this); auto mappedQc = tempMapper.map(qc, mapping); const auto mappedQCAOD = tempMapper.convertToAod(mappedQc); const auto results = tempMapper.schedule(); @@ -51,6 +51,9 @@ void HybridSynthesisMapper::appendWithoutMapping( } } +void HybridSynthesisMapper::appendWithMapping( + const qc::QuantumComputation& qc) {} + AdjacencyMatrix HybridSynthesisMapper::getCircuitAdjacencyMatrix() const { auto numCircQubits = synthesizedQc.getNqubits(); AdjacencyMatrix adjMatrix(numCircQubits); diff --git a/test/hybridmap/test_hybridmap.cpp b/test/hybridmap/test_hybridmap.cpp index a429f1127..6a7b475ba 100644 --- a/test/hybridmap/test_hybridmap.cpp +++ b/test/hybridmap/test_hybridmap.cpp @@ -160,13 +160,3 @@ TEST_F(NeutralAtomMapperTest, Output) { ASSERT_GT(scheduleResults.totalFidelities, 0); } - -TEST_F(NeutralAtomMapperTest, MappingLoading) { - const auto qcMapped = mapper.map(qc, initialMapping); - na::NeutralAtomMapper mapper2(arch); - mapper2.loadHwQubits(mapper.getHardwareQubits()); - for (std::uint32_t i = 0; i < qcMapped.getNqubits(); i++) { - ASSERT_EQ(mapper.getHardwareQubits().isMapped(i), - mapper2.getHardwareQubits().isMapped(i)); - } -} From 40cda1bb911517bf7568d2b16d426d2fc9bb492b Mon Sep 17 00:00:00 2001 From: lsschmid <117631861+lsschmid@users.noreply.github.com> Date: Wed, 5 Jun 2024 14:04:28 +0200 Subject: [PATCH 028/394] =?UTF-8?q?=E2=9C=A8=20added=20possibility=20of=20?= =?UTF-8?q?appending=20map=20circuits=20to=20mapper?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridNeutralAtomMapper.hpp | 16 +++++++++++++++- src/hybridmap/HybridNeutralAtomMapper.cpp | 8 ++------ 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index b5a871ce7..125415efa 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -435,13 +435,27 @@ class NeutralAtomMapper { * operations */ qc::QuantumComputation map(qc::QuantumComputation& qc, - Mapping initialMapping); + Mapping initialMapping) { + mappedQc = qc::QuantumComputation(arch->getNpositions()); + nMoves = 0; + nSwaps = 0; + mapAppend(qc, std::move(initialMapping)); + return mappedQc; + } qc::QuantumComputation map(qc::QuantumComputation& qc, InitialMapping initialMapping) { return map(qc, Mapping(qc.getNqubits(), initialMapping)); } + /** + * @brief Appends the given quantum circuit to the mapped quantum circuit. + * @param qc The quantum circuit to be mapped + * @param initialMapping The initial mapping of the circuit qubits to the + * hardware qubits + */ + void mapAppend(qc::QuantumComputation& qc, Mapping initialMapping); + /** * @brief Maps the given quantum circuit to the given architecture and * converts it to the AOD level. diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 3c4751ef8..92d6a6298 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -34,13 +34,10 @@ #include namespace na { -qc::QuantumComputation NeutralAtomMapper::map(qc::QuantumComputation& qc, - Mapping initialMapping) { +void NeutralAtomMapper::mapAppend(qc::QuantumComputation& qc, + Mapping initialMapping) { mapping = std::move(initialMapping); - mappedQc = qc::QuantumComputation(arch->getNpositions()); - nMoves = 0; - nSwaps = 0; qc::CircuitOptimizer::replaceMCXWithMCZ(qc); qc::CircuitOptimizer::singleQubitGateFusion(qc); qc::CircuitOptimizer::flattenOperations(qc); @@ -118,7 +115,6 @@ qc::QuantumComputation NeutralAtomMapper::map(qc::QuantumComputation& qc, std::cout << "nSwaps: " << nSwaps << '\n'; std::cout << "nMoves: " << nMoves << '\n'; } - return mappedQc; } void NeutralAtomMapper::mapAllPossibleGates(NeutralAtomLayer& layer) { From 1de4014240f07295f09f75c02080bd6d6e29a929 Mon Sep 17 00:00:00 2001 From: lsschmid <117631861+lsschmid@users.noreply.github.com> Date: Wed, 5 Jun 2024 14:18:19 +0200 Subject: [PATCH 029/394] =?UTF-8?q?=E2=9C=A8=20added=20possibility=20of=20?= =?UTF-8?q?appending=20map=20circuits=20to=20synthesis=20mapper?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridSynthesisMapper.hpp | 2 +- src/hybridmap/HybridSynthesisMapper.cpp | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/include/hybridmap/HybridSynthesisMapper.hpp b/include/hybridmap/HybridSynthesisMapper.hpp index 4ec19f0d8..ae53d203e 100644 --- a/include/hybridmap/HybridSynthesisMapper.hpp +++ b/include/hybridmap/HybridSynthesisMapper.hpp @@ -110,7 +110,7 @@ class HybridSynthesisMapper : private NeutralAtomMapper { * QuantumComputation and maps the gates to the hardware. * @param qc The gates (QuantumComputation) to be appended and mapped. */ - void appendWithMapping(const qc::QuantumComputation& qc); + void appendWithMapping(qc::QuantumComputation& qc); /** * @brief Returns the current adjacency matrix of the neutral atom hardware. diff --git a/src/hybridmap/HybridSynthesisMapper.cpp b/src/hybridmap/HybridSynthesisMapper.cpp index 6d7da63b2..7982317db 100644 --- a/src/hybridmap/HybridSynthesisMapper.cpp +++ b/src/hybridmap/HybridSynthesisMapper.cpp @@ -51,8 +51,12 @@ void HybridSynthesisMapper::appendWithoutMapping( } } -void HybridSynthesisMapper::appendWithMapping( - const qc::QuantumComputation& qc) {} +void HybridSynthesisMapper::appendWithMapping(qc::QuantumComputation& qc) { + mapAppend(qc, this->mapping); + for (const auto& op : qc) { + this->synthesizedQc.emplace_back(op->clone()); + } +} AdjacencyMatrix HybridSynthesisMapper::getCircuitAdjacencyMatrix() const { auto numCircQubits = synthesizedQc.getNqubits(); From 7021508a81f275ca9c462a864478090bbe5fbd86 Mon Sep 17 00:00:00 2001 From: lsschmid <117631861+lsschmid@users.noreply.github.com> Date: Wed, 5 Jun 2024 14:18:35 +0200 Subject: [PATCH 030/394] =?UTF-8?q?=E2=9C=85=20added=20test=20for=20append?= =?UTF-8?q?ing=20circuit=20with=20mapping?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/hybridmap/test_hybrid_synthesis_map.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/hybridmap/test_hybrid_synthesis_map.cpp b/test/hybridmap/test_hybrid_synthesis_map.cpp index 24e1ceeea..248003bda 100644 --- a/test/hybridmap/test_hybrid_synthesis_map.cpp +++ b/test/hybridmap/test_hybrid_synthesis_map.cpp @@ -93,4 +93,11 @@ TEST_F(TestHybridSynthesisMapper, completelyRemap) { EXPECT_GE(mappedQcRemapped.getNops(), 3); } +TEST_F(TestHybridSynthesisMapper, MapAppend) { + mapper.appendWithMapping(qc); + auto synthesizedQc = mapper.getSynthesizedQc(); + EXPECT_EQ(synthesizedQc.getNqubits(), 3); + EXPECT_GE(synthesizedQc.getNops(), 3); +} + } // namespace na From c28eac13daf24bf78347f766bff4bf90fa4ec2cd Mon Sep 17 00:00:00 2001 From: lsschmid <117631861+lsschmid@users.noreply.github.com> Date: Wed, 5 Jun 2024 14:25:46 +0200 Subject: [PATCH 031/394] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20update=20MQT=20Cor?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- extern/mqt-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/mqt-core b/extern/mqt-core index b5a58ff80..86d93b5b6 160000 --- a/extern/mqt-core +++ b/extern/mqt-core @@ -1 +1 @@ -Subproject commit b5a58ff80617323e8043232068cafee6f414ecaf +Subproject commit 86d93b5b60a39a335f16b9d0529a08e54350b698 From 9ada1284264afbf430fe4fb827607de76cfca300 Mon Sep 17 00:00:00 2001 From: lsschmid <117631861+lsschmid@users.noreply.github.com> Date: Wed, 5 Jun 2024 20:25:57 +0200 Subject: [PATCH 032/394] =?UTF-8?q?=F0=9F=8E=A8=20Restructured=20convert?= =?UTF-8?q?=20to=20AOD=20and=20return=20process=20+=20names?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridNeutralAtomMapper.hpp | 28 +++++++++++++------ src/hybridmap/HybridNeutralAtomMapper.cpp | 13 ++++----- src/python/bindings.cpp | 8 +++--- test/hybridmap/test_hybridmap.cpp | 6 ++-- 4 files changed, 32 insertions(+), 23 deletions(-) diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index 125415efa..68c6305f7 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -466,14 +466,20 @@ class NeutralAtomMapper { [[maybe_unused]] void mapAndConvert(qc::QuantumComputation& qc, InitialMapping initialMapping) { map(qc, initialMapping); - convertToAod(this->mappedQc); + convertToAod(); } + /** + * @brief Returns the mapped quantum circuit. + * @return The mapped quantum circuit + */ + [[nodiscard]] qc::QuantumComputation getMappedQc() const { return mappedQc; } + /** * @brief Prints the mapped circuits as an extended OpenQASM string. * @return The mapped quantum circuit with abstract SWAP gates and MOVE */ - [[maybe_unused]] std::string getMappedQc() { + [[maybe_unused]] std::string getMappedQcQasm() { std::stringstream ss; this->mappedQc.dumpOpenQASM(ss, false); return ss.str(); @@ -483,17 +489,24 @@ class NeutralAtomMapper { * @brief Saves the mapped quantum circuit to a file. * @param filename The name of the file to save the mapped quantum circuit to */ - [[maybe_unused]] void saveMappedQc(const std::string& filename) { + [[maybe_unused]] void saveMappedQcQasm(const std::string& filename) { std::ofstream ofs(filename); this->mappedQc.dumpOpenQASM(ofs, false); } + /** + * @brief Returns the mapped quantum circuit with AOD operations. + */ + [[nodiscard]] qc::QuantumComputation getMappedQcAod() const { + return mappedQcAOD; + } + /** * @brief Prints the mapped circuit with AOD operations as an extended * OpenQASM * @return The mapped quantum circuit with native AOD operations */ - [[maybe_unused]] std::string getMappedQcAOD() { + [[maybe_unused]] std::string getMappedQcAodQasm() { std::stringstream ss; this->mappedQcAOD.dumpOpenQASM(ss, false); return ss.str(); @@ -504,7 +517,7 @@ class NeutralAtomMapper { * @param filename The name of the file to save the mapped quantum circuit * with AOD operations to */ - [[maybe_unused]] void saveMappedQcAOD(const std::string& filename) { + [[maybe_unused]] void saveMappedQcAodQasm(const std::string& filename) { std::ofstream ofs(filename); this->mappedQcAOD.dumpOpenQASM(ofs, false); } @@ -550,11 +563,8 @@ class NeutralAtomMapper { * @details SWAP gates are decomposed into CX gates. Then CnX gates are * decomposed into CnZ gates. Move operations are combined if possible and * then converted into native AOD operations. - * @param qc The already mapped quantum circuit with abstract SWAP gates and - * MOVE operations - * @return The mapped quantum circuit with native AOD operations */ - qc::QuantumComputation convertToAod(qc::QuantumComputation& qc); + qc::QuantumComputation convertToAod(); [[maybe_unused]] [[nodiscard]] std::map getInitHwPos() const { diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 92d6a6298..62eaf52b9 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -137,16 +137,15 @@ void NeutralAtomMapper::mapAllPossibleGates(NeutralAtomLayer& layer) { } } -qc::QuantumComputation -NeutralAtomMapper::convertToAod(qc::QuantumComputation& qc) { +qc::QuantumComputation NeutralAtomMapper::convertToAod() { // decompose SWAP gates - qc::CircuitOptimizer::decomposeSWAP(qc, false); - qc::CircuitOptimizer::replaceMCXWithMCZ(qc); - qc::CircuitOptimizer::singleQubitGateFusion(qc); - qc::CircuitOptimizer::flattenOperations(qc); + qc::CircuitOptimizer::decomposeSWAP(mappedQc, false); + qc::CircuitOptimizer::replaceMCXWithMCZ(mappedQc); + qc::CircuitOptimizer::singleQubitGateFusion(mappedQc); + qc::CircuitOptimizer::flattenOperations(mappedQc); // decompose AOD moves MoveToAodConverter aodScheduler(*arch); - mappedQcAOD = aodScheduler.schedule(qc); + mappedQcAOD = aodScheduler.schedule(mappedQc); if (this->parameters->verbose) { std::cout << "nMoveGroups: " << aodScheduler.getNMoveGroups() << '\n'; } diff --git a/src/python/bindings.cpp b/src/python/bindings.cpp index 1a8176727..0ecd9033d 100644 --- a/src/python/bindings.cpp +++ b/src/python/bindings.cpp @@ -873,15 +873,15 @@ PYBIND11_MODULE(pyqmap, m) { }, "Map a quantum circuit to the neutral atom quantum computer", "filename"_a, "initial_mapping"_a = na::InitialMapping::Identity) - .def("get_mapped_qc", &na::NeutralAtomMapper::getMappedQc, + .def("get_mapped_qc", &na::NeutralAtomMapper::getMappedQcQasm, "Returns the mapped circuit as an extended qasm2 string") - .def("save_mapped_qc", &na::NeutralAtomMapper::saveMappedQc, + .def("save_mapped_qc", &na::NeutralAtomMapper::saveMappedQcQasm, "Saves the mapped circuit as an extended qasm2 string to a file", "filename"_a) - .def("get_mapped_qc_aod", &na::NeutralAtomMapper::getMappedQcAOD, + .def("get_mapped_qc_aod", &na::NeutralAtomMapper::getMappedQcAodQasm, "Returns the mapped circuit as an extended qasm2 string with native " "AOD movements") - .def("save_mapped_qc_aod", &na::NeutralAtomMapper::saveMappedQcAOD, + .def("save_mapped_qc_aod", &na::NeutralAtomMapper::saveMappedQcAodQasm, "Saves the mapped circuit as an extended qasm2 string with native " "AOD movements to a file", "filename"_a) diff --git a/test/hybridmap/test_hybridmap.cpp b/test/hybridmap/test_hybridmap.cpp index 6a7b475ba..6b61699a0 100644 --- a/test/hybridmap/test_hybridmap.cpp +++ b/test/hybridmap/test_hybridmap.cpp @@ -97,8 +97,8 @@ TEST_P(NeutralAtomMapperTestParams, MapCircuitsIdentity) { mapper.setParameters(mapperParameters); qc::QuantumComputation qc(testQcPath); - auto qcMapped = mapper.map(qc, initialMapping); - auto qcAodMapped = mapper.convertToAod(qcMapped); + auto qcMapped = mapper.map(qc, initialMapping); + mapper.convertToAod(); auto scheduleResults = mapper.schedule(true, true); @@ -152,7 +152,7 @@ TEST_F(NeutralAtomMapperTest, Output) { qcMapped.dumpOpenQASM(std::cout, false); - auto qcAodMapped = mapper.convertToAod(qcMapped); + auto qcAodMapped = mapper.convertToAod(); qcAodMapped.dumpOpenQASM(std::cout, false); auto scheduleResults = mapper.schedule(true, true); From df04ac25c3a0d6932762f5edb8d2cec09f7a46c3 Mon Sep 17 00:00:00 2001 From: lsschmid <117631861+lsschmid@users.noreply.github.com> Date: Wed, 5 Jun 2024 20:26:43 +0200 Subject: [PATCH 033/394] =?UTF-8?q?=F0=9F=8E=A8=20update=20Synthesis=20map?= =?UTF-8?q?per?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridSynthesisMapper.hpp | 33 +++++++++++++++----- src/hybridmap/HybridSynthesisMapper.cpp | 6 ++-- test/hybridmap/test_hybrid_synthesis_map.cpp | 2 +- 3 files changed, 30 insertions(+), 11 deletions(-) diff --git a/include/hybridmap/HybridSynthesisMapper.hpp b/include/hybridmap/HybridSynthesisMapper.hpp index ae53d203e..97b4f4309 100644 --- a/include/hybridmap/HybridSynthesisMapper.hpp +++ b/include/hybridmap/HybridSynthesisMapper.hpp @@ -13,6 +13,9 @@ #include "hybridmap/NeutralAtomDefinitions.hpp" #include +#include +#include +#include #include namespace na { @@ -29,7 +32,7 @@ namespace na { * and the HybridNeutralAtomMapper. * */ -class HybridSynthesisMapper : private NeutralAtomMapper { +class HybridSynthesisMapper : public NeutralAtomMapper { using qcs = std::vector; qc::QuantumComputation synthesizedQc; @@ -72,12 +75,8 @@ class HybridSynthesisMapper : private NeutralAtomMapper { * @return The mapped QuantumComputation. */ [[nodiscard]] qc::QuantumComputation - getMappedQc(bool completeRemap = true, - InitialMapping initMapping = InitialMapping::Identity) { - if (completeRemap) { - return this->map(synthesizedQc, initMapping); - } - return mappedQc; + completeRemap(InitialMapping initMapping = InitialMapping::Identity) { + return this->map(synthesizedQc, initMapping); } /** @@ -89,6 +88,26 @@ class HybridSynthesisMapper : private NeutralAtomMapper { return this->synthesizedQc; } + /** + * @brief Returns the synthesized QuantumComputation as a string. + * @return The synthesized QuantumComputation as a string. + */ + [[maybe_unused]] std::string getSynthesizedQcQASM() { + std::stringstream ss; + synthesizedQc.dumpOpenQASM(ss, false); + return ss.str(); + } + + /** + * @brief Saves the synthesized QuantumComputation to the given file. + * @param filename The file to save the synthesized QuantumComputation to. + */ + [[maybe_unused]] void saveSynthesizedQc(const std::string& filename) { + std::ofstream ofs(filename); + synthesizedQc.dumpOpenQASM(ofs, false); + ofs.close(); + } + /** * @brief Evaluates the synthesis steps proposed by the ZX extraction. * @param synthesisSteps The synthesis steps proposed by the ZX extraction. diff --git a/src/hybridmap/HybridSynthesisMapper.cpp b/src/hybridmap/HybridSynthesisMapper.cpp index 7982317db..4a5352383 100644 --- a/src/hybridmap/HybridSynthesisMapper.cpp +++ b/src/hybridmap/HybridSynthesisMapper.cpp @@ -37,9 +37,9 @@ qc::fp HybridSynthesisMapper::evaluateSynthesisStep(qc::QuantumComputation& qc) { NeutralAtomMapper tempMapper; tempMapper.copyStateFrom(*this); - auto mappedQc = tempMapper.map(qc, mapping); - const auto mappedQCAOD = tempMapper.convertToAod(mappedQc); - const auto results = tempMapper.schedule(); + auto mappedQc = tempMapper.map(qc, mapping); + tempMapper.convertToAod(); + const auto results = tempMapper.schedule(); return results.totalFidelities; } diff --git a/test/hybridmap/test_hybrid_synthesis_map.cpp b/test/hybridmap/test_hybrid_synthesis_map.cpp index 248003bda..40279e04f 100644 --- a/test/hybridmap/test_hybrid_synthesis_map.cpp +++ b/test/hybridmap/test_hybrid_synthesis_map.cpp @@ -85,7 +85,7 @@ TEST_F(TestHybridSynthesisMapper, DirectlyMap) { TEST_F(TestHybridSynthesisMapper, completelyRemap) { mapper.appendWithoutMapping(qc); mapper.appendWithoutMapping(qc); - auto mappedQc = mapper.getMappedQc(false); + auto mappedQc = mapper.getMappedQc(); EXPECT_EQ(mappedQc.getNqubits(), arch.getNpositions()); EXPECT_GE(mappedQc.getNops(), 3); auto mappedQcRemapped = mapper.getMappedQc(); From 3cdae9ca65f16416f2ffa30432a387c9ef01f689 Mon Sep 17 00:00:00 2001 From: lsschmid <117631861+lsschmid@users.noreply.github.com> Date: Wed, 5 Jun 2024 20:59:26 +0200 Subject: [PATCH 034/394] =?UTF-8?q?=E2=9C=A8=20added=20python=20bindings?= =?UTF-8?q?=20for=20synthesis=20mapper?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mqt/qmap/__init__.py | 2 + src/mqt/qmap/pyqmap.pyi | 25 +++++++++ src/python/bindings.cpp | 113 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 139 insertions(+), 1 deletion(-) diff --git a/src/mqt/qmap/__init__.py b/src/mqt/qmap/__init__.py index e3a5827e3..3df849267 100644 --- a/src/mqt/qmap/__init__.py +++ b/src/mqt/qmap/__init__.py @@ -31,6 +31,7 @@ Heuristic, HybridMapperParameters, HybridNAMapper, + HybridSynthesisMapper, InitialCircuitMapping, InitialCoordinateMapping, InitialLayout, @@ -59,6 +60,7 @@ "Heuristic", "HybridMapperParameters", "HybridNAMapper", + "HybridSynthesisMapper", "InitialCircuitMapping", "InitialCoordinateMapping", "InitialLayout", diff --git a/src/mqt/qmap/pyqmap.pyi b/src/mqt/qmap/pyqmap.pyi index 34efe68bb..2d1b6b5f4 100644 --- a/src/mqt/qmap/pyqmap.pyi +++ b/src/mqt/qmap/pyqmap.pyi @@ -631,3 +631,28 @@ class NeutralAtomHybridArchitecture: def nqubits(self) -> int: ... @property def nrows(self) -> int: ... + +class HybridSynthesisMapper: + def __init__(self, arch: NeutralAtomHybridArchitecture, params: HybridMapperParameters = ...) -> None: ... + def append_with_mapping(self, qc: object) -> None: ... + def append_without_mapping(self, qc: object) -> None: ... + def convert_to_aod(self) -> QuantumComputation: ... + @overload + def evaluate_synthesis_steps(self, synthesis_steps: list[QuantumComputation], also_map: bool = ...) -> int: ... + @overload + def evaluate_synthesis_steps(self, synthesis_steps: list[object], also_map: bool = ...) -> int: ... + def get_animation_csv(self) -> str: ... + def get_circuit_adjacency_matrix(self) -> list[list[int]]: ... + def get_init_hw_pos(self) -> dict[int, int]: ... + def get_mapped_qc(self) -> str: ... + def get_mapped_qc_aod(self) -> str: ... + def get_synthesized_qc(self) -> str: ... + def init_mapping(self, n_qubits: int, initial_mapping: InitialCircuitMapping = ...) -> None: ... + def save_animation_csv(self, filename: str) -> None: ... + def save_mapped_qc(self, filename: str) -> None: ... + def save_mapped_qc_aod(self, filename: str) -> None: ... + def save_synthesized_qc(self, filename: str) -> None: ... + def schedule( + self, verbose: bool = ..., create_animation_csv: bool = ..., shuttling_speed_factor: float = ... + ) -> dict[str, float]: ... + def set_parameters(self, params: HybridMapperParameters) -> None: ... diff --git a/src/python/bindings.cpp b/src/python/bindings.cpp index 0ecd9033d..377cbe18b 100644 --- a/src/python/bindings.cpp +++ b/src/python/bindings.cpp @@ -7,6 +7,7 @@ #include "exact/ExactMapper.hpp" #include "heuristic/HeuristicMapper.hpp" #include "hybridmap/HybridNeutralAtomMapper.hpp" +#include "hybridmap/HybridSynthesisMapper.hpp" #include "hybridmap/NeutralAtomScheduler.hpp" #include "nlohmann/json.hpp" #include "pybind11/pybind11.h" @@ -14,6 +15,8 @@ #include "pybind11_json/pybind11_json.hpp" #include "python/qiskit/QuantumCircuit.hpp" #include "string" +#include "utility" +#include "vector" namespace py = pybind11; using namespace pybind11::literals; @@ -869,7 +872,7 @@ PYBIND11_MODULE(pyqmap, m) { [](na::NeutralAtomMapper& mapper, const std::string& filename, na::InitialMapping initialMapping) { qc::QuantumComputation qc(filename); - mapper.map(qc, initialMapping); + mapper.mapAndConvert(qc, initialMapping); }, "Map a quantum circuit to the neutral atom quantum computer", "filename"_a, "initial_mapping"_a = na::InitialMapping::Identity) @@ -899,4 +902,112 @@ PYBIND11_MODULE(pyqmap, m) { "Returns the animation csv string") .def("save_animation_csv", &na::NeutralAtomMapper::saveAnimationCsv, "Saves the animation csv string to a file", "filename"_a); + + py::class_( + m, "HybridSynthesisMapper", + "Neutral Atom Mapper that can evaluate different synthesis steps " + "to choose the best one.") + .def(py::init(), + "Create Hybrid Synthesis Mapper with mapper parameters", "arch"_a, + "params"_a = na::MapperParameters()) + .def("set_parameters", &na::HybridSynthesisMapper::setParameters, + "Set the parameters for the Hybrid Synthesis Mapper", "params"_a) + .def("init_mapping", &na::HybridSynthesisMapper::initMapping, + "Initializes the mapping with the given number of qubits and the " + "initial mapping", + "n_qubits"_a, "initial_mapping"_a = na::InitialMapping::Identity) + .def( + "get_init_hw_pos", &na::HybridSynthesisMapper::getInitHwPos, + "Get the initial hardware positions, required to create an animation") + .def("get_mapped_qc", &na::HybridSynthesisMapper::getMappedQcQasm, + "Returns the mapped QuantumComputation") + .def("save_mapped_qc", &na::HybridSynthesisMapper::saveMappedQcQasm, + "Saves the mapped QuantumComputation to a file", "filename"_a) + .def( + "convert_to_aod", &na::HybridSynthesisMapper::convertToAod, + "Converts the mapped QuantumComputation to a QuantumComputation with " + "native AOD movements") + .def("get_mapped_qc_aod", &na::HybridSynthesisMapper::getMappedQcAodQasm, + "Returns the mapped QuantumComputation with native AOD movements") + .def("save_mapped_qc_aod", + &na::HybridSynthesisMapper::saveMappedQcAodQasm, + "Saves the mapped QuantumComputation with native AOD movements to a " + "file", + "filename"_a) + .def("get_synthesized_qc", + &na::HybridSynthesisMapper::getSynthesizedQcQASM, + "Returns the synthesized QuantumComputation with all gates but not " + "mapped to the hardware.") + .def("save_synthesized_qc", &na::HybridSynthesisMapper::saveSynthesizedQc, + "Saves the synthesized QuantumComputation with all gates but not " + "mapped to the hardware to a file", + "filename"_a) + .def("evaluate_synthesis_steps", + &na::HybridSynthesisMapper::evaluateSynthesisSteps, + "Evaluates the synthesis steps proposed by the ZX extraction. " + "Returns index of the best synthesis step.", + "synthesis_steps"_a, "also_map"_a = false) + .def( + "append_without_mapping", + [](na::HybridSynthesisMapper& mapper, const py::object& circ) { + qc::QuantumComputation qc{}; + loadQC(qc, circ); + mapper.appendWithoutMapping(qc); + }, + "Directly maps the given QuantumComputation to the hardware NOT " + "inserting SWAP gates or shuttling move operations.", + "qc"_a) + .def( + "append_with_mapping", + [](na::HybridSynthesisMapper& mapper, const py::object& circ) { + qc::QuantumComputation qc{}; + loadQC(qc, circ); + mapper.appendWithMapping(qc); + }, + "Appends the given QuantumComputation to the synthesized " + "QuantumComputation and maps the gates to the hardware.", + "qc"_a) + .def( + "get_circuit_adjacency_matrix", + [](na::HybridSynthesisMapper& mapper) { + const auto symmAdjMatrix = mapper.getCircuitAdjacencyMatrix(); + std::vector> adjMatrix = {}; + for (size_t i = 0; i < symmAdjMatrix.size(); ++i) { + adjMatrix.emplace_back(); + for (size_t j = 0; j < symmAdjMatrix.size(); ++j) { + adjMatrix[i].emplace_back(symmAdjMatrix(i, j)); + } + } + return adjMatrix; + }, + "Returns the current adjacency matrix of the neutral atom hardware.") + .def( + "evaluate_synthesis_steps", + [](na::HybridSynthesisMapper& mapper, + const std::vector& circs, bool alsoMap) { + std::vector qcs; + for (const auto& circ : circs) { + qc::QuantumComputation qc{}; + loadQC(qc, circ); + qcs.push_back(qc); + } + return mapper.evaluateSynthesisSteps(qcs, alsoMap); + }, + "Evaluates the synthesis steps proposed by the ZX extraction. " + "Returns index of the best synthesis step.", + "synthesis_steps"_a, "also_map"_a = false) + .def( + "schedule", + [](na::HybridSynthesisMapper& mapper, bool verbose, + bool create_animation_csv, double shuttling_speed_factor) { + auto results = mapper.schedule(verbose, create_animation_csv, + shuttling_speed_factor); + return results.toMap(); + }, + "Schedule the mapped circuit", "verbose"_a = false, + "create_animation_csv"_a = false, "shuttling_speed_factor"_a = 1.0) + .def("get_animation_csv", &na::HybridSynthesisMapper::getAnimationCsv, + "Returns the animation csv string") + .def("save_animation_csv", &na::HybridSynthesisMapper::saveAnimationCsv, + "Saves the animation csv string to a file", "filename"_a); } From 98062e3042dd3c9270403c886d5069f7348b4aeb Mon Sep 17 00:00:00 2001 From: lsschmid <117631861+lsschmid@users.noreply.github.com> Date: Wed, 5 Jun 2024 20:59:44 +0200 Subject: [PATCH 035/394] =?UTF-8?q?=E2=9C=85=20added=20python=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/python/test_hybrid_synthesis.py | 94 ++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 test/python/test_hybrid_synthesis.py diff --git a/test/python/test_hybrid_synthesis.py b/test/python/test_hybrid_synthesis.py new file mode 100644 index 000000000..2cfdf5f35 --- /dev/null +++ b/test/python/test_hybrid_synthesis.py @@ -0,0 +1,94 @@ +"""Test the hybrid Neutral Atom synthesis mapping.""" + +from __future__ import annotations + +from pathlib import Path + +import numpy as np +import pytest +from qiskit import QuantumCircuit + +from mqt.qmap import HybridSynthesisMapper, NeutralAtomHybridArchitecture + +arch_dir = Path(__file__).parent.parent / "hybridmap" / "architectures" +circuit_dir = Path(__file__).parent.parent / "hybridmap" / "circuits" + +qc1 = QuantumCircuit(3) +qc1.h(0) +qc1.cx(0, 1) +qc1.cx(1, 2) + +qc2 = QuantumCircuit(3) +qc2.cx(0, 2) +qc2.cx(1, 2) + + +@pytest.mark.parametrize( + "arch_filename", + [ + "rubidium.json", + "rubidium_hybrid.json", + "rubidium_shuttling.json", + ], +) +def test_hybrid_synthesis(arch_filename: str) -> None: + """Test the hybrid Neutral Atom synthesis mapper evaluation of different circuits.""" + arch = NeutralAtomHybridArchitecture(str(arch_dir / arch_filename)) + + synthesis_mapper = HybridSynthesisMapper(arch) + synthesis_mapper.init_mapping(3) + best_circuit = synthesis_mapper.evaluate_synthesis_steps([qc1, qc2], True) + + assert best_circuit is not None + + +@pytest.mark.parametrize( + "arch_filename", + [ + "rubidium.json", + "rubidium_hybrid.json", + "rubidium_shuttling.json", + ], +) +def test_hybrid_synthesis_input_output(arch_filename: str) -> None: + """Test printing and saving the produced circuits.""" + arch = NeutralAtomHybridArchitecture(str(arch_dir / arch_filename)) + synthesis_mapper = HybridSynthesisMapper(arch) + synthesis_mapper.init_mapping(3) + + synthesis_mapper.append_with_mapping(qc1) + synthesis_mapper.append_without_mapping(qc2) + + qasm = synthesis_mapper.get_mapped_qc() + assert qasm is not None + + filename_mapped = Path(__file__).parent / f"{arch_filename}_mapped.qasm" + synthesis_mapper.save_mapped_qc(str(filename_mapped)) + + synthesis_mapper.convert_to_aod() + qasm_aod = synthesis_mapper.get_mapped_qc_aod() + assert qasm_aod is not None + + filename_mapped_aod = Path(__file__).parent / f"{arch_filename}_mapped_aod.qasm" + synthesis_mapper.save_mapped_qc_aod(str(filename_mapped_aod)) + + qasm_synth = synthesis_mapper.get_synthesized_qc() + assert qasm_synth is not None + + filename_synth = Path(__file__).parent / f"{arch_filename}_synthesized.qasm" + synthesis_mapper.save_synthesized_qc(str(filename_synth)) + + +def test_adjacency_matrix() -> None: + """Test the adjacency matrix of the hybrid Neutral Atom synthesis mapper.""" + arch = NeutralAtomHybridArchitecture(str(arch_dir / "rubidium.json")) + synthesis_mapper = HybridSynthesisMapper(arch) + circ_size = 3 + synthesis_mapper.init_mapping(circ_size) + synthesis_mapper.append_with_mapping(qc1) + adj_mat = np.array(synthesis_mapper.get_circuit_adjacency_matrix()) + assert adj_mat is not None + assert adj_mat.shape == (circ_size, circ_size) + for i in range(circ_size): + for j in range(circ_size): + assert adj_mat[i, j] == adj_mat[j, i] From b077a0ceec02fb165cfb36d73f405e5e5e2714b0 Mon Sep 17 00:00:00 2001 From: lsschmid <117631861+lsschmid@users.noreply.github.com> Date: Wed, 5 Jun 2024 21:00:05 +0200 Subject: [PATCH 036/394] =?UTF-8?q?=F0=9F=8E=A8=20added=20help=20if=20init?= =?UTF-8?q?=20was=20forgotten?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridSynthesisMapper.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/hybridmap/HybridSynthesisMapper.cpp b/src/hybridmap/HybridSynthesisMapper.cpp index 4a5352383..630714812 100644 --- a/src/hybridmap/HybridSynthesisMapper.cpp +++ b/src/hybridmap/HybridSynthesisMapper.cpp @@ -52,6 +52,9 @@ void HybridSynthesisMapper::appendWithoutMapping( } void HybridSynthesisMapper::appendWithMapping(qc::QuantumComputation& qc) { + if (mappedQc.empty()) { + initMapping(qc.getNqubits()); + } mapAppend(qc, this->mapping); for (const auto& op : qc) { this->synthesizedQc.emplace_back(op->clone()); From 8778523977ea154f2a328c2043db735228b17ae0 Mon Sep 17 00:00:00 2001 From: lsschmid <117631861+lsschmid@users.noreply.github.com> Date: Thu, 6 Jun 2024 07:46:29 +0200 Subject: [PATCH 037/394] =?UTF-8?q?=F0=9F=90=9B=20fixed=20wrong=20paramete?= =?UTF-8?q?r=20python=20binding?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/python/bindings.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/python/bindings.cpp b/src/python/bindings.cpp index 377cbe18b..f523e9025 100644 --- a/src/python/bindings.cpp +++ b/src/python/bindings.cpp @@ -907,9 +907,10 @@ PYBIND11_MODULE(pyqmap, m) { m, "HybridSynthesisMapper", "Neutral Atom Mapper that can evaluate different synthesis steps " "to choose the best one.") - .def(py::init(), - "Create Hybrid Synthesis Mapper with mapper parameters", "arch"_a, - "params"_a = na::MapperParameters()) + .def( + py::init(), + "Create Hybrid Synthesis Mapper with mapper parameters", "arch"_a, + "params"_a = na::MapperParameters()) .def("set_parameters", &na::HybridSynthesisMapper::setParameters, "Set the parameters for the Hybrid Synthesis Mapper", "params"_a) .def("init_mapping", &na::HybridSynthesisMapper::initMapping, From 719d1d331e5466a5cd7cba101160168da8350029 Mon Sep 17 00:00:00 2001 From: lsschmid <117631861+lsschmid@users.noreply.github.com> Date: Thu, 6 Jun 2024 08:04:02 +0200 Subject: [PATCH 038/394] =?UTF-8?q?=E2=9C=A8=20added=20some=20verbosity?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridSynthesisMapper.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/hybridmap/HybridSynthesisMapper.cpp b/src/hybridmap/HybridSynthesisMapper.cpp index 630714812..8ddbff814 100644 --- a/src/hybridmap/HybridSynthesisMapper.cpp +++ b/src/hybridmap/HybridSynthesisMapper.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -21,8 +22,16 @@ namespace na { size_t HybridSynthesisMapper::evaluateSynthesisSteps(qcs& synthesisSteps, bool alsoMap) { std::vector> costs; + size_t qcIndex = 0; for (auto& qc : synthesisSteps) { + if (this->parameters->verbose) { + std::cout << "Evaluating synthesis step number " << qcIndex++ << "\n"; + } costs.emplace_back(qc, this->evaluateSynthesisStep(qc)); + if (this->parameters->verbose) { + std::cout << "Fidelity: " << costs.back().second << "\n"; + } + qcIndex++; } const auto bestQc = std::max_element( costs.begin(), costs.end(), From fa4281e29f25094bf5ea0d8b29e30dc1053ab932 Mon Sep 17 00:00:00 2001 From: lsschmid <117631861+lsschmid@users.noreply.github.com> Date: Thu, 6 Jun 2024 08:10:39 +0200 Subject: [PATCH 039/394] =?UTF-8?q?=F0=9F=8E=A8=20minor=20modifications=20?= =?UTF-8?q?on=20hybrid=20mapper?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridNeutralAtomMapper.hpp | 7 ++++--- src/hybridmap/HybridNeutralAtomMapper.cpp | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index 68c6305f7..2424f0b2a 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -436,9 +436,10 @@ class NeutralAtomMapper { */ qc::QuantumComputation map(qc::QuantumComputation& qc, Mapping initialMapping) { - mappedQc = qc::QuantumComputation(arch->getNpositions()); - nMoves = 0; - nSwaps = 0; + mappedQc = qc::QuantumComputation(arch->getNpositions()); + mappedQcAOD = qc::QuantumComputation(arch->getNpositions()); + nMoves = 0; + nSwaps = 0; mapAppend(qc, std::move(initialMapping)); return mappedQc; } diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 62eaf52b9..d7c9e0c3c 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -112,8 +112,8 @@ void NeutralAtomMapper::mapAppend(qc::QuantumComputation& qc, } } if (this->parameters->verbose) { - std::cout << "nSwaps: " << nSwaps << '\n'; - std::cout << "nMoves: " << nMoves << '\n'; + std::cout << "Total nSwaps: " << nSwaps << '\n'; + std::cout << "Total nMoves: " << nMoves << '\n'; } } From 158864616fbae194cb4d7114955c3fec8fae0926 Mon Sep 17 00:00:00 2001 From: lsschmid <117631861+lsschmid@users.noreply.github.com> Date: Thu, 6 Jun 2024 08:10:53 +0200 Subject: [PATCH 040/394] =?UTF-8?q?=E2=9C=A8=20add=20complete=20remap=20to?= =?UTF-8?q?=20bindings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridSynthesisMapper.hpp | 5 ++--- src/python/bindings.cpp | 3 +++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/include/hybridmap/HybridSynthesisMapper.hpp b/include/hybridmap/HybridSynthesisMapper.hpp index 97b4f4309..28bf0c9f9 100644 --- a/include/hybridmap/HybridSynthesisMapper.hpp +++ b/include/hybridmap/HybridSynthesisMapper.hpp @@ -74,9 +74,8 @@ class HybridSynthesisMapper : public NeutralAtomMapper { * @brief Returns the mapped QuantumComputation. * @return The mapped QuantumComputation. */ - [[nodiscard]] qc::QuantumComputation - completeRemap(InitialMapping initMapping = InitialMapping::Identity) { - return this->map(synthesizedQc, initMapping); + void completeRemap(InitialMapping initMapping = InitialMapping::Identity) { + this->map(synthesizedQc, initMapping); } /** diff --git a/src/python/bindings.cpp b/src/python/bindings.cpp index f523e9025..c40379649 100644 --- a/src/python/bindings.cpp +++ b/src/python/bindings.cpp @@ -997,6 +997,9 @@ PYBIND11_MODULE(pyqmap, m) { "Evaluates the synthesis steps proposed by the ZX extraction. " "Returns index of the best synthesis step.", "synthesis_steps"_a, "also_map"_a = false) + .def("complete_remap", &na::HybridSynthesisMapper::completeRemap, + "Remaps the QuantumComputation to the hardware.", + "initial_mapping"_a = na::InitialMapping::Identity) .def( "schedule", [](na::HybridSynthesisMapper& mapper, bool verbose, From b41364f99fead61da94a91c6fa03f9f0741bc9c0 Mon Sep 17 00:00:00 2001 From: lsschmid <117631861+lsschmid@users.noreply.github.com> Date: Fri, 14 Jun 2024 12:52:50 +0200 Subject: [PATCH 041/394] =?UTF-8?q?=F0=9F=90=9B=20Fixed=20python=20binding?= =?UTF-8?q?s=20keeping=20arch=20alive?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/python/bindings.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/python/bindings.cpp b/src/python/bindings.cpp index c40379649..e048bc6c5 100644 --- a/src/python/bindings.cpp +++ b/src/python/bindings.cpp @@ -850,7 +850,8 @@ PYBIND11_MODULE(pyqmap, m) { "Neutral Atom Hybrid Mapper that can use both SWAP gates and AOD " "movements to map a quantum circuit to a neutral atom quantum computer.") .def(py::init(), - "Create Hybrid NA Mapper with mapper parameters", "arch"_a, + "Create Hybrid NA Mapper with mapper parameters", + py::keep_alive<1, 2>(), py::keep_alive<1, 3>(), "arch"_a, "params"_a = na::MapperParameters()) .def("set_parameters", &na::NeutralAtomMapper::setParameters, "Set the parameters for the Hybrid NA Mapper", "params"_a) From fb97f509fc873198943591ee18145c7ac240773e Mon Sep 17 00:00:00 2001 From: lsschmid <117631861+lsschmid@users.noreply.github.com> Date: Fri, 14 Jun 2024 12:53:15 +0200 Subject: [PATCH 042/394] =?UTF-8?q?=E2=9C=85=20added=20test=20for=20keepin?= =?UTF-8?q?g=20python=20arch=20alive?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/python/test_hybridmap.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/test/python/test_hybridmap.py b/test/python/test_hybridmap.py index 5b42b1f0a..8be2e0d63 100644 --- a/test/python/test_hybridmap.py +++ b/test/python/test_hybridmap.py @@ -57,3 +57,24 @@ def test_hybrid_na_mapper( assert results["totalExecutionTime"] > 0 assert results["totalIdleTime"] > 0 assert results["totalFidelities"] > 0 + + +def _nested_mapper_create() -> HybridNAMapper: + """Create a nested Neutral Atom hybrid architecture.""" + arch = NeutralAtomHybridArchitecture(str(arch_dir / "rubidium.json")) + params = HybridMapperParameters() + return HybridNAMapper(arch, params=params) + + +def test_keep_alive() -> None: + """Test the keep alive feature of the python bindings.""" + mapper = _nested_mapper_create() + + qc = QuantumCircuit.from_qasm_file(str(circuit_dir / "dj_nativegates_rigetti_qiskit_opt3_10.qasm")) + + mapper.map(qc) + results = mapper.schedule(create_animation_csv=False) + + assert results["totalExecutionTime"] > 0 + assert results["totalIdleTime"] > 0 + assert results["totalFidelities"] > 0 From 2437827eecfb39f92b4ad895fce1c07401d44f11 Mon Sep 17 00:00:00 2001 From: lsschmid <117631861+lsschmid@users.noreply.github.com> Date: Sat, 15 Jun 2024 22:13:28 +0200 Subject: [PATCH 043/394] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20update=20mqt-core?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- extern/mqt-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/mqt-core b/extern/mqt-core index 0e4ff9e05..c1960381b 160000 --- a/extern/mqt-core +++ b/extern/mqt-core @@ -1 +1 @@ -Subproject commit 0e4ff9e0521886449027b252c65913e1afa863b0 +Subproject commit c1960381bcfa799b67fc176cb75bfc44a5ad7d03 From 97d99443268c367cf75478118b03c3927323e018 Mon Sep 17 00:00:00 2001 From: lsschmid <117631861+lsschmid@users.noreply.github.com> Date: Wed, 26 Jun 2024 14:03:18 -0700 Subject: [PATCH 044/394] =?UTF-8?q?=F0=9F=90=9B=20Fixed=20circuit=20not=20?= =?UTF-8?q?being=20mapped=20correctly.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridSynthesisMapper.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hybridmap/HybridSynthesisMapper.cpp b/src/hybridmap/HybridSynthesisMapper.cpp index 8ddbff814..95d034940 100644 --- a/src/hybridmap/HybridSynthesisMapper.cpp +++ b/src/hybridmap/HybridSynthesisMapper.cpp @@ -37,7 +37,7 @@ size_t HybridSynthesisMapper::evaluateSynthesisSteps(qcs& synthesisSteps, costs.begin(), costs.end(), [](const auto& a, const auto& b) { return a.second < b.second; }); if (alsoMap) { - this->appendWithoutMapping(bestQc->first); + this->appendWithMapping(bestQc->first); } return static_cast(std::distance(costs.begin(), bestQc)); } From 4599775549be69539df52333cb4783e976ad06e6 Mon Sep 17 00:00:00 2001 From: lsschmid <117631861+lsschmid@users.noreply.github.com> Date: Wed, 7 Aug 2024 18:46:38 +0200 Subject: [PATCH 045/394] =?UTF-8?q?=F0=9F=90=9B=20added=20keep=20alive=20f?= =?UTF-8?q?or=20Synthesis=20Mapper?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/python/bindings.cpp | 3 ++- test/python/test_hybrid_synthesis.py | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/python/bindings.cpp b/src/python/bindings.cpp index e048bc6c5..304c6bae8 100644 --- a/src/python/bindings.cpp +++ b/src/python/bindings.cpp @@ -910,7 +910,8 @@ PYBIND11_MODULE(pyqmap, m) { "to choose the best one.") .def( py::init(), - "Create Hybrid Synthesis Mapper with mapper parameters", "arch"_a, + "Create Hybrid Synthesis Mapper with mapper parameters", + py::keep_alive<1, 2>(), py::keep_alive<1, 3>(), "arch"_a, "params"_a = na::MapperParameters()) .def("set_parameters", &na::HybridSynthesisMapper::setParameters, "Set the parameters for the Hybrid Synthesis Mapper", "params"_a) diff --git a/test/python/test_hybrid_synthesis.py b/test/python/test_hybrid_synthesis.py index 2cfdf5f35..117203932 100644 --- a/test/python/test_hybrid_synthesis.py +++ b/test/python/test_hybrid_synthesis.py @@ -92,3 +92,20 @@ def test_adjacency_matrix() -> None: for i in range(circ_size): for j in range(circ_size): assert adj_mat[i, j] == adj_mat[j, i] + + +def help_create_arch(arch_filename: str) -> NeutralAtomHybridArchitecture: + """Helper function to create a hybrid Neutral Atom architecture.""" + return NeutralAtomHybridArchitecture(str(arch_dir / arch_filename)) + +def help_create_mapper(arch_filename: str) -> HybridSynthesisMapper: + """Helper function to create a hybrid synthesis mapper.""" + arch = help_create_arch(arch_filename) + synthesis_mapper = HybridSynthesisMapper(arch) + synthesis_mapper.init_mapping(3) + return synthesis_mapper +def test_keep_alive() -> None: + """Test the keep alive functionality of the hybrid Neutral Atom synthesis mapper.""" + synthesis_mapper = help_create_mapper("rubidium.json") + synthesis_mapper.append_with_mapping(qc1) + _ = synthesis_mapper.get_circuit_adjacency_matrix() From da7ec98b7b01fe7d05a5e73670d007817ec30441 Mon Sep 17 00:00:00 2001 From: lsschmid <117631861+lsschmid@users.noreply.github.com> Date: Tue, 27 Aug 2024 11:03:19 +0200 Subject: [PATCH 046/394] =?UTF-8?q?=F0=9F=90=9B=20fixed=20missing=20conver?= =?UTF-8?q?tion=20to=20AOD=20movements?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridNeutralAtomMapper.hpp | 9 +++++++++ include/hybridmap/HybridSynthesisMapper.hpp | 1 + 2 files changed, 10 insertions(+) diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index 2424f0b2a..251965949 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -508,6 +508,9 @@ class NeutralAtomMapper { * @return The mapped quantum circuit with native AOD operations */ [[maybe_unused]] std::string getMappedQcAodQasm() { + if (this->mappedQcAOD.empty()) { + this->convertToAod(); + } std::stringstream ss; this->mappedQcAOD.dumpOpenQASM(ss, false); return ss.str(); @@ -519,6 +522,9 @@ class NeutralAtomMapper { * with AOD operations to */ [[maybe_unused]] void saveMappedQcAodQasm(const std::string& filename) { + if (this->mappedQcAOD.empty()) { + this->convertToAod(); + } std::ofstream ofs(filename); this->mappedQcAOD.dumpOpenQASM(ofs, false); } @@ -538,6 +544,9 @@ class NeutralAtomMapper { [[maybe_unused]] SchedulerResults schedule(bool verboseArg = false, bool createAnimationCsv = false, qc::fp shuttlingSpeedFactor = 1.0) { + if (mappedQcAOD.empty()) { + convertToAod(); + } return scheduler.schedule(mappedQcAOD, hardwareQubits.getInitHwPos(), verboseArg, createAnimationCsv, shuttlingSpeedFactor); diff --git a/include/hybridmap/HybridSynthesisMapper.hpp b/include/hybridmap/HybridSynthesisMapper.hpp index 28bf0c9f9..bdc74e308 100644 --- a/include/hybridmap/HybridSynthesisMapper.hpp +++ b/include/hybridmap/HybridSynthesisMapper.hpp @@ -76,6 +76,7 @@ class HybridSynthesisMapper : public NeutralAtomMapper { */ void completeRemap(InitialMapping initMapping = InitialMapping::Identity) { this->map(synthesizedQc, initMapping); + this->convertToAod(); } /** From fb993ecd0bda62c2f223d35ed60e98c7ed4e965b Mon Sep 17 00:00:00 2001 From: lsschmid <117631861+lsschmid@users.noreply.github.com> Date: Tue, 27 Aug 2024 14:32:00 +0200 Subject: [PATCH 047/394] =?UTF-8?q?=E2=9C=A8=20added=20get=20function=20fo?= =?UTF-8?q?r=20maximum=20size=20of=20multi-qubit=20gates=20with=20Python?= =?UTF-8?q?=20bindings.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridSynthesisMapper.hpp | 8 +++++++ include/hybridmap/NeutralAtomArchitecture.hpp | 22 +++++++++++++++++++ src/python/bindings.cpp | 4 +++- 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/include/hybridmap/HybridSynthesisMapper.hpp b/include/hybridmap/HybridSynthesisMapper.hpp index bdc74e308..cb4c2b6c8 100644 --- a/include/hybridmap/HybridSynthesisMapper.hpp +++ b/include/hybridmap/HybridSynthesisMapper.hpp @@ -136,5 +136,13 @@ class HybridSynthesisMapper : public NeutralAtomMapper { * @return The current adjacency matrix of the neutral atom hardware. */ [[nodiscard]] AdjacencyMatrix getCircuitAdjacencyMatrix() const; + + /** + * @brief Returns the maximum gate size of the neutral atom hardware. + * @return The maximum gate size of the neutral atom hardware. + */ + [[nodiscard]] size_t getMaxGateSize() const { + return this->arch->getMaxGateSize(); + } }; } // namespace na diff --git a/include/hybridmap/NeutralAtomArchitecture.hpp b/include/hybridmap/NeutralAtomArchitecture.hpp index 51d1abe8f..2207f1fba 100644 --- a/include/hybridmap/NeutralAtomArchitecture.hpp +++ b/include/hybridmap/NeutralAtomArchitecture.hpp @@ -441,6 +441,28 @@ class NeutralAtomArchitecture { */ [[nodiscard]] std::vector getNN(CoordIndex idx) const; + /** + * @brief Get the maximum gate size for multi-qubit size. Gets derived + * from the interaction radius. + * @return The maximum gate size for multi-qubit size + */ + [[nodiscard]] size_t getMaxGateSize() const { + size_t maxGateSize = 0; + const auto intRad = getInteractionRadius(); + const auto xMax = static_cast(intRad); + auto y = static_cast(intRad); + size_t x = 0; + while (x <= xMax) { + if (static_cast(x * x + y * y) > intRad * intRad) { + y--; + } else { + maxGateSize += y + 1; + x++; + } + } + return maxGateSize; + } + // MoveVector functions /** * @brief Get the MoveVector between two coordinate indices diff --git a/src/python/bindings.cpp b/src/python/bindings.cpp index 304c6bae8..1e0add313 100644 --- a/src/python/bindings.cpp +++ b/src/python/bindings.cpp @@ -1015,5 +1015,7 @@ PYBIND11_MODULE(pyqmap, m) { .def("get_animation_csv", &na::HybridSynthesisMapper::getAnimationCsv, "Returns the animation csv string") .def("save_animation_csv", &na::HybridSynthesisMapper::saveAnimationCsv, - "Saves the animation csv string to a file", "filename"_a); + "Saves the animation csv string to a file", "filename"_a) + .def("get_max_gate_size", &na::HybridSynthesisMapper::getMaxGateSize, + "Returns the maximum gate size of the neutral atom hardware"); } From eaf89199f8b52fdb4bf3fd3f94981b12c4514925 Mon Sep 17 00:00:00 2001 From: lsschmid <117631861+lsschmid@users.noreply.github.com> Date: Tue, 27 Aug 2024 14:49:35 +0200 Subject: [PATCH 048/394] =?UTF-8?q?=E2=9C=A8=20Changed=20HybridSynthesisMa?= =?UTF-8?q?pper=20to=20return=20a=20list=20of=20fidelities=20instead=20of?= =?UTF-8?q?=20list=20index.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridSynthesisMapper.hpp | 7 +++--- src/hybridmap/HybridSynthesisMapper.cpp | 28 +++++++++++++-------- src/python/bindings.cpp | 2 +- 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/include/hybridmap/HybridSynthesisMapper.hpp b/include/hybridmap/HybridSynthesisMapper.hpp index cb4c2b6c8..fe314fca3 100644 --- a/include/hybridmap/HybridSynthesisMapper.hpp +++ b/include/hybridmap/HybridSynthesisMapper.hpp @@ -111,11 +111,12 @@ class HybridSynthesisMapper : public NeutralAtomMapper { /** * @brief Evaluates the synthesis steps proposed by the ZX extraction. * @param synthesisSteps The synthesis steps proposed by the ZX extraction. - * @param alsoMap If true, the synthesis steps are directly mapped to the + * @param alsoMap If true, the best synthesis step is directly mapped to the * hardware. - * @return The index of the synthesis step with the lowest effort. + * @return Returns a list of fidelities of the mapped synthesis steps. */ - size_t evaluateSynthesisSteps(qcs& synthesisSteps, bool alsoMap = false); + std::vector evaluateSynthesisSteps(qcs& synthesisSteps, + bool alsoMap = false); /** * @brief Directly maps the given QuantumComputation to the hardware NOT diff --git a/src/hybridmap/HybridSynthesisMapper.cpp b/src/hybridmap/HybridSynthesisMapper.cpp index 95d034940..c068be746 100644 --- a/src/hybridmap/HybridSynthesisMapper.cpp +++ b/src/hybridmap/HybridSynthesisMapper.cpp @@ -19,27 +19,35 @@ namespace na { -size_t HybridSynthesisMapper::evaluateSynthesisSteps(qcs& synthesisSteps, - bool alsoMap) { - std::vector> costs; +std::vector +HybridSynthesisMapper::evaluateSynthesisSteps(qcs& synthesisSteps, + bool alsoMap) { + std::vector> candidates; size_t qcIndex = 0; for (auto& qc : synthesisSteps) { if (this->parameters->verbose) { std::cout << "Evaluating synthesis step number " << qcIndex++ << "\n"; } - costs.emplace_back(qc, this->evaluateSynthesisStep(qc)); + candidates.emplace_back(qc, this->evaluateSynthesisStep(qc)); if (this->parameters->verbose) { - std::cout << "Fidelity: " << costs.back().second << "\n"; + std::cout << "Fidelity: " << candidates.back().second << "\n"; } qcIndex++; } - const auto bestQc = std::max_element( - costs.begin(), costs.end(), - [](const auto& a, const auto& b) { return a.second < b.second; }); + std::vector fidelities; + size_t bestIndex = 0; + qc::fp bestFidelity = 0; + for (size_t i = 0; i < candidates.size(); ++i) { + fidelities.push_back(candidates[i].second); + if (candidates[i].second > bestFidelity) { + bestFidelity = candidates[i].second; + bestIndex = i; + } + } if (alsoMap) { - this->appendWithMapping(bestQc->first); + this->appendWithMapping(candidates[bestIndex].first); } - return static_cast(std::distance(costs.begin(), bestQc)); + return fidelities; } qc::fp diff --git a/src/python/bindings.cpp b/src/python/bindings.cpp index 1e0add313..57733e89c 100644 --- a/src/python/bindings.cpp +++ b/src/python/bindings.cpp @@ -948,7 +948,7 @@ PYBIND11_MODULE(pyqmap, m) { .def("evaluate_synthesis_steps", &na::HybridSynthesisMapper::evaluateSynthesisSteps, "Evaluates the synthesis steps proposed by the ZX extraction. " - "Returns index of the best synthesis step.", + "Returns a list of fidelities of the mapped synthesis steps.", "synthesis_steps"_a, "also_map"_a = false) .def( "append_without_mapping", From 469389ae00066d2e40013eb97f99571fc14386d8 Mon Sep 17 00:00:00 2001 From: lsschmid <117631861+lsschmid@users.noreply.github.com> Date: Tue, 27 Aug 2024 15:02:19 +0200 Subject: [PATCH 049/394] =?UTF-8?q?=E2=9C=A8=20added=20linear=20time=20sca?= =?UTF-8?q?ling=20of=20phase=20gates.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/NeutralAtomArchitecture.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/hybridmap/NeutralAtomArchitecture.cpp b/src/hybridmap/NeutralAtomArchitecture.cpp index 135a2260e..dbec93cc5 100644 --- a/src/hybridmap/NeutralAtomArchitecture.cpp +++ b/src/hybridmap/NeutralAtomArchitecture.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -233,6 +234,16 @@ qc::fp NeutralAtomArchitecture::getOpTime(const qc::Operation* op) const { for (size_t i = 0; i < op->getNcontrols(); ++i) { opName += "c"; } + if (op->getType() == qc::OpType::P) { + // use time of theta = pi and linearly scale + opName += "z"; + auto param = abs(op->getParameter().back()); + const auto pi = 3.14159265358979323846; + while (param > pi) { + param = abs(param - 2 * pi); + } + return getGateTime(opName) * param / 3.14159265358979323846; + } opName += op->getName(); return getGateTime(opName); } From 1facbd8f39f33f68998078ed0400e1a6aa26e1a1 Mon Sep 17 00:00:00 2001 From: lsschmid <117631861+lsschmid@users.noreply.github.com> Date: Thu, 29 Aug 2024 08:41:23 +0200 Subject: [PATCH 050/394] =?UTF-8?q?=E2=9C=85=20updated=20test=20of=20evalu?= =?UTF-8?q?ateSynthesisSteps?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/hybridmap/test_hybrid_synthesis_map.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/hybridmap/test_hybrid_synthesis_map.cpp b/test/hybridmap/test_hybrid_synthesis_map.cpp index 40279e04f..db931a339 100644 --- a/test/hybridmap/test_hybrid_synthesis_map.cpp +++ b/test/hybridmap/test_hybrid_synthesis_map.cpp @@ -50,7 +50,9 @@ TEST_P(TestParametrizedHybridSynthesisMapper, EvaluateSynthesisStep) { auto mapper = HybridSynthesisMapper(arch); mapper.initMapping(3); auto best = mapper.evaluateSynthesisSteps(circuits, false); - EXPECT_GE(best, 0); + EXPECT_EQ(best.size(), 2); + EXPECT_GE(best[0], 0); + EXPECT_GE(best[1], 0); } INSTANTIATE_TEST_SUITE_P(HybridSynthesisMapperTestSuite, From 7dd65599170ca4ae343f28cb274895048133992e Mon Sep 17 00:00:00 2001 From: lsschmid <117631861+lsschmid@users.noreply.github.com> Date: Thu, 29 Aug 2024 19:10:06 +0200 Subject: [PATCH 051/394] =?UTF-8?q?=F0=9F=90=9B=20fixed=20bug=20which=20re?= =?UTF-8?q?sulted=20in=20mapping=20only=20a=20small=20part=20of=20the=20ci?= =?UTF-8?q?rcuit=20or=20omitting=20gates.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridNeutralAtomMapper.hpp | 5 +- include/hybridmap/NeutralAtomDefinitions.hpp | 3 +- include/hybridmap/NeutralAtomLayer.hpp | 37 +++++----- include/hybridmap/NeutralAtomUtils.hpp | 13 ++++ src/hybridmap/HybridNeutralAtomMapper.cpp | 42 ++++++------ src/hybridmap/HybridSynthesisMapper.cpp | 2 - src/hybridmap/NeutralAtomLayer.cpp | 68 +++++-------------- 7 files changed, 74 insertions(+), 96 deletions(-) diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index 251965949..dafea8965 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -41,7 +41,7 @@ struct MapperParameters { qc::fp gateWeight = 1; qc::fp shuttlingWeight = 1; uint32_t seed = 0; - bool verbose = false; + bool verbose = true; InitialCoordinateMapping initialMapping = InitialCoordinateMapping::Trivial; }; @@ -113,7 +113,8 @@ class NeutralAtomMapper { * can be mapped. * @param layer The layer to map all possible gates for */ - void mapAllPossibleGates(NeutralAtomLayer& layer); + void mapAllPossibleGates(NeutralAtomLayer& frontLayer, + NeutralAtomLayer& lookaheadLayer); /** * @brief Returns all gates that can be executed now * @param gates The gates to be checked diff --git a/include/hybridmap/NeutralAtomDefinitions.hpp b/include/hybridmap/NeutralAtomDefinitions.hpp index 00feb9c9a..3aa11a5f5 100644 --- a/include/hybridmap/NeutralAtomDefinitions.hpp +++ b/include/hybridmap/NeutralAtomDefinitions.hpp @@ -39,6 +39,7 @@ using SwapDistance = int32_t; using AtomMove = std::pair; // Used to represent operations -using GateList = std::vector; +using GateList = std::vector; +using GateLists = std::vector; } // namespace na diff --git a/include/hybridmap/NeutralAtomLayer.hpp b/include/hybridmap/NeutralAtomLayer.hpp index 2c0f7635a..51f566e9b 100644 --- a/include/hybridmap/NeutralAtomLayer.hpp +++ b/include/hybridmap/NeutralAtomLayer.hpp @@ -24,11 +24,13 @@ namespace na { class NeutralAtomLayer { protected: - qc::DAG dag; - qc::DAGIterators iterators; - GateList gates; - GateList mappedSingleQubitGates; - std::vector candidates; + qc::DAG dag; + qc::DAGIterators iterators; + qc::DAGIterators ends; + GateList gates; + GateList newGates; + GateList mappedSingleQubitGates; + GateLists candidates; /** * @brief Updates the gates for the given qubits @@ -38,6 +40,7 @@ class NeutralAtomLayer { * @param commuteWith Gates the new gates should commute with */ void updateByQubits(const std::set& qubitsToUpdate); + /** * @brief Updates the candidates for the given qubits */ @@ -48,14 +51,6 @@ class NeutralAtomLayer { */ void candidatesToGates(const std::set& qubitsToUpdate); - // Commutation checks - static bool commutesWithAtQubit(const GateList& layer, - const qc::Operation* opPointer, - const qc::Qubit& qubit); - static bool commuteAtQubit(const qc::Operation* opPointer1, - const qc::Operation* opPointer2, - const qc::Qubit& qubit); - public: // Constructor explicit NeutralAtomLayer(qc::DAG graph) : dag(std::move(graph)) { @@ -64,6 +59,7 @@ class NeutralAtomLayer { for (auto& i : dag) { auto it = i.begin(); iterators.emplace_back(it); + ends.emplace_back(i.end()); candidates.emplace_back(); } } @@ -73,17 +69,16 @@ class NeutralAtomLayer { * @return The current layer of gates */ GateList getGates() { return gates; } + GateList getNewGates() { return newGates; } /** - * @brief Returns a vector of the iterator indices + * @brief Returns a vector of the iterator indices for debugging * @return A copy of the current iterator indices */ std::vector getIteratorOffset(); /** - * @brief Initializes the layer by updating all qubits starting from the - * iterators - * @param The iterator offset to start from + * @brief Initializes the layer by updating all qubits starting */ - void initLayerOffset(const std::vector& iteratorOffset = {}); + void initAllQubits(); /** * @brief Removes the provided gates from the current layer and update the * the layer depending on the qubits of the gates. @@ -91,6 +86,7 @@ class NeutralAtomLayer { * @param commuteWith Gates the new gates should commute with */ void removeGatesAndUpdate(const GateList& gatesToRemove); + /** * @brief Returns the mapped single qubit gates * @return The mapped single qubit gates @@ -98,4 +94,9 @@ class NeutralAtomLayer { GateList getMappedSingleQubitGates() { return mappedSingleQubitGates; } }; +// Commutation checks +bool commutesWithAtQubit(const GateList& layer, const qc::Operation* opPointer, + const qc::Qubit& qubit); +bool commuteAtQubit(const qc::Operation* opPointer1, + const qc::Operation* opPointer2, const qc::Qubit& qubit); } // namespace na diff --git a/include/hybridmap/NeutralAtomUtils.hpp b/include/hybridmap/NeutralAtomUtils.hpp index a1ddf6fc0..386e83c06 100644 --- a/include/hybridmap/NeutralAtomUtils.hpp +++ b/include/hybridmap/NeutralAtomUtils.hpp @@ -20,6 +20,19 @@ namespace na { +class NeutralAtomException : public std::runtime_error { + std::string msg; + +public: + explicit NeutralAtomException(std::string m) + : std::runtime_error("Neutral Atom Mapper Exception"), msg(std::move(m)) { + } + + [[nodiscard]] const char* what() const noexcept override { + return msg.c_str(); + } +}; + // Enums for the different initial mappings strategies enum InitialCoordinateMapping : uint8_t { Trivial, Random }; enum InitialMapping : uint8_t { Identity }; diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 2942d4480..7ef2a5fdf 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -46,11 +46,12 @@ void NeutralAtomMapper::mapAppend(qc::QuantumComputation& qc, auto dag = qc::CircuitOptimizer::constructDAG(qc); // init layers - NeutralAtomLayer frontLayer(dag); - frontLayer.initLayerOffset(); - mapAllPossibleGates(frontLayer); NeutralAtomLayer lookaheadLayer(dag); - lookaheadLayer.initLayerOffset(frontLayer.getIteratorOffset()); + lookaheadLayer.initAllQubits(); + NeutralAtomLayer frontLayer(dag); + frontLayer.initAllQubits(); + lookaheadLayer.removeGatesAndUpdate(frontLayer.getGates()); + mapAllPossibleGates(frontLayer, lookaheadLayer); // Checks if (dag.size() > arch->getNqubits()) { @@ -84,8 +85,7 @@ void NeutralAtomMapper::mapAppend(qc::QuantumComputation& qc, updateMappingSwap(bestSwap); gatesToExecute = getExecutableGates(frontLayer.getGates()); } - mapAllPossibleGates(frontLayer); - lookaheadLayer.initLayerOffset(frontLayer.getIteratorOffset()); + mapAllPossibleGates(frontLayer, lookaheadLayer); reassignGatesToLayers(frontLayer.getGates(), lookaheadLayer.getGates()); if (this->parameters->verbose) { printLayers(); @@ -103,8 +103,7 @@ void NeutralAtomMapper::mapAppend(qc::QuantumComputation& qc, updateMappingMove(bestMove); gatesToExecute = getExecutableGates(frontLayer.getGates()); } - mapAllPossibleGates(frontLayer); - lookaheadLayer.initLayerOffset(frontLayer.getIteratorOffset()); + mapAllPossibleGates(frontLayer, lookaheadLayer); reassignGatesToLayers(frontLayer.getGates(), lookaheadLayer.getGates()); if (this->parameters->verbose) { printLayers(); @@ -117,23 +116,16 @@ void NeutralAtomMapper::mapAppend(qc::QuantumComputation& qc, } } -void NeutralAtomMapper::mapAllPossibleGates(NeutralAtomLayer& layer) { - // map single qubit gates - for (const auto* opPointer : layer.getMappedSingleQubitGates()) { - mapGate(opPointer); - } - layer.removeGatesAndUpdate({}); - // check and map multi qubit gates - auto executableGates = getExecutableGates(layer.getGates()); +void NeutralAtomMapper::mapAllPossibleGates(NeutralAtomLayer& frontLayer, + NeutralAtomLayer& lookaheadLayer) { + auto executableGates = getExecutableGates(frontLayer.getGates()); while (!executableGates.empty()) { - for (const auto* opPointer : layer.getMappedSingleQubitGates()) { - mapGate(opPointer); - } for (const auto* opPointer : executableGates) { mapGate(opPointer); } - layer.removeGatesAndUpdate(executableGates); - executableGates = getExecutableGates(layer.getGates()); + frontLayer.removeGatesAndUpdate(executableGates); + lookaheadLayer.removeGatesAndUpdate(frontLayer.getNewGates()); + executableGates = getExecutableGates(frontLayer.getGates()); } } @@ -158,6 +150,9 @@ void NeutralAtomMapper::reassignGatesToLayers(const GateList& frontGates, this->frontLayerGate.clear(); this->frontLayerShuttling.clear(); for (const auto& gate : frontGates) { + if (gate->getNqubits() == 1) { + continue; + } if (swapGateBetter(gate)) { this->frontLayerGate.emplace_back(gate); } else { @@ -168,6 +163,9 @@ void NeutralAtomMapper::reassignGatesToLayers(const GateList& frontGates, this->lookaheadLayerGate.clear(); this->lookaheadLayerShuttling.clear(); for (const auto& gate : lookaheadGates) { + if (gate->getNqubits() == 1) { + continue; + } if (swapGateBetter(gate)) { this->lookaheadLayerGate.emplace_back(gate); } else { @@ -255,7 +253,7 @@ void NeutralAtomMapper::printLayers() { GateList NeutralAtomMapper::getExecutableGates(const GateList& gates) { GateList executableGates; for (const auto* opPointer : gates) { - if (isExecutable(opPointer)) { + if (opPointer->getNqubits() == 1 || isExecutable(opPointer)) { executableGates.emplace_back(opPointer); } } diff --git a/src/hybridmap/HybridSynthesisMapper.cpp b/src/hybridmap/HybridSynthesisMapper.cpp index c068be746..607b9f9f6 100644 --- a/src/hybridmap/HybridSynthesisMapper.cpp +++ b/src/hybridmap/HybridSynthesisMapper.cpp @@ -9,11 +9,9 @@ #include "hybridmap/HybridNeutralAtomMapper.hpp" #include "hybridmap/NeutralAtomDefinitions.hpp" -#include #include #include #include -#include #include #include diff --git a/src/hybridmap/NeutralAtomLayer.cpp b/src/hybridmap/NeutralAtomLayer.cpp index 20f3cb602..f55bed6dd 100644 --- a/src/hybridmap/NeutralAtomLayer.cpp +++ b/src/hybridmap/NeutralAtomLayer.cpp @@ -34,23 +34,7 @@ std::vector NeutralAtomLayer::getIteratorOffset() { return offset; } -void NeutralAtomLayer::initLayerOffset( - const std::vector& iteratorOffset) { - this->gates.clear(); - for (auto& candidate : this->candidates) { - candidate.clear(); - } - this->mappedSingleQubitGates.clear(); - // if iteratorOffset is empty, set all iterators to begin - if (iteratorOffset.empty()) { - for (uint32_t i = 0; i < this->dag.size(); ++i) { - this->iterators[i] = this->dag[i].begin(); - } - } else { - for (uint32_t i = 0; i < this->dag.size(); ++i) { - this->iterators[i] = this->dag[i].begin() + iteratorOffset[i]; - } - } +void NeutralAtomLayer::initAllQubits() { std::set allQubits; for (uint32_t i = 0; i < this->dag.size(); ++i) { allQubits.emplace(i); @@ -61,29 +45,15 @@ void NeutralAtomLayer::initLayerOffset( void NeutralAtomLayer::updateCandidatesByQubits( const std::set& qubitsToUpdate) { for (const auto& qubit : qubitsToUpdate) { - auto tempIter = iterators[qubit]; - while (tempIter < this->dag[qubit].end()) { - auto* op = (*tempIter)->get(); - if (op->getUsedQubits().size() == 1) { - mappedSingleQubitGates.emplace_back(op); - this->iterators[qubit]++; - tempIter++; + while (iterators[qubit] < ends[qubit]) { + auto* op = (*iterators[qubit])->get(); + // check if operation commutes with gates and candidates + auto commutes = commutesWithAtQubit(gates, op, qubit) && + commutesWithAtQubit(candidates[qubit], op, qubit); + if (commutes) { + candidates[qubit].emplace_back(op); + iterators[qubit]++; } else { - // continue if following gates commute - bool commutes = true; - while (commutes && tempIter < this->dag[qubit].end()) { - auto* nextOp = (*tempIter)->get(); - commutes = commutesWithAtQubit(gates, nextOp, qubit) && - commutesWithAtQubit(candidates[qubit], nextOp, qubit); - if (commutes) { - if (nextOp->getUsedQubits().size() == 1) { - mappedSingleQubitGates.emplace_back(nextOp); - } else { // not executable but commutes - candidates[qubit].emplace_back(nextOp); - } - } - tempIter++; - } break; } } @@ -92,7 +62,9 @@ void NeutralAtomLayer::updateCandidatesByQubits( void NeutralAtomLayer::candidatesToGates( const std::set& qubitsToUpdate) { + newGates.clear(); for (const auto& qubit : qubitsToUpdate) { + // operations moved from candidates to gates have to be removed afterward std::vector toRemove; for (const auto* opPointer : candidates[qubit]) { // check if gate is candidate for all qubits it uses @@ -108,7 +80,8 @@ void NeutralAtomLayer::candidatesToGates( } } if (inFrontLayer) { - this->gates.emplace_back(opPointer); + gates.emplace_back(opPointer); + newGates.emplace_back(opPointer); // remove from candidacy of other qubits for (const auto& opQubit : opPointer->getUsedQubits()) { if (qubit == opQubit) { @@ -119,7 +92,6 @@ void NeutralAtomLayer::candidatesToGates( opPointer)); } - // save to remove from candidacy of this qubit toRemove.emplace_back(opPointer); } } @@ -133,7 +105,6 @@ void NeutralAtomLayer::candidatesToGates( } void NeutralAtomLayer::removeGatesAndUpdate(const GateList& gatesToRemove) { - this->mappedSingleQubitGates.clear(); std::set qubitsToUpdate; for (const auto& gate : gatesToRemove) { if (std::find(gates.begin(), gates.end(), gate) != gates.end()) { @@ -142,26 +113,21 @@ void NeutralAtomLayer::removeGatesAndUpdate(const GateList& gatesToRemove) { qubitsToUpdate.insert(usedQubits.begin(), usedQubits.end()); } } - for (const auto& qubit : qubitsToUpdate) { - ++this->iterators[qubit]; - } updateByQubits(qubitsToUpdate); } // Commutation -bool NeutralAtomLayer::commutesWithAtQubit(const GateList& layer, - const qc::Operation* opPointer, - const qc::Qubit& qubit) { +bool commutesWithAtQubit(const GateList& layer, const qc::Operation* opPointer, + const qc::Qubit& qubit) { return std::all_of(layer.begin(), layer.end(), [&opPointer, &qubit](const auto& frontOpPointer) { return commuteAtQubit(opPointer, frontOpPointer, qubit); }); } -bool NeutralAtomLayer::commuteAtQubit(const qc::Operation* op1, - const qc::Operation* op2, - const qc::Qubit& qubit) { +bool commuteAtQubit(const qc::Operation* op1, const qc::Operation* op2, + const qc::Qubit& qubit) { if (op1->isNonUnitaryOperation() || op2->isNonUnitaryOperation()) { return false; } From 4fe41df17e8eff5f29245f600388ac21ef8d99d6 Mon Sep 17 00:00:00 2001 From: lsschmid <117631861+lsschmid@users.noreply.github.com> Date: Thu, 29 Aug 2024 19:10:51 +0200 Subject: [PATCH 052/394] =?UTF-8?q?=E2=9C=85=20Added=20check=20in=20test?= =?UTF-8?q?=20to=20avoid=20early=20stop=20in=20mapping.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/hybridmap/test_hybridmap.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/test/hybridmap/test_hybridmap.cpp b/test/hybridmap/test_hybridmap.cpp index e7a57752e..7b891b968 100644 --- a/test/hybridmap/test_hybridmap.cpp +++ b/test/hybridmap/test_hybridmap.cpp @@ -98,6 +98,7 @@ TEST_P(NeutralAtomMapperTestParams, MapCircuitsIdentity) { qc::QuantumComputation qc(testQcPath); auto qcMapped = mapper.map(qc, initialMapping); + ASSERT_GT(qcMapped.size(), qc.size()); mapper.convertToAod(); auto scheduleResults = mapper.schedule(true, true); From 0dd1af241d9336564beaadb11516b4cb2ee3f1f4 Mon Sep 17 00:00:00 2001 From: lsschmid <117631861+lsschmid@users.noreply.github.com> Date: Wed, 4 Sep 2024 15:14:21 +0200 Subject: [PATCH 053/394] =?UTF-8?q?=F0=9F=90=9B=20Fixed=20handling=20of=20?= =?UTF-8?q?incoming=20SWAPS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/NeutralAtomLayer.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/hybridmap/NeutralAtomLayer.cpp b/src/hybridmap/NeutralAtomLayer.cpp index f55bed6dd..ca3dd42b4 100644 --- a/src/hybridmap/NeutralAtomLayer.cpp +++ b/src/hybridmap/NeutralAtomLayer.cpp @@ -163,6 +163,12 @@ bool commuteAtQubit(const qc::Operation* op1, const qc::Operation* op2, return true; } + // Swaps never commute + if (op1->getType() == qc::OpType::SWAP || + op2->getType() == qc::OpType::SWAP) { + return false; + } + // check targets if (std::find(op1->getTargets().begin(), op1->getTargets().end(), qubit) != op1->getTargets().end() && From f4871177fca72e30af6da46fbd80bf0b77802a89 Mon Sep 17 00:00:00 2001 From: lsschmid <117631861+lsschmid@users.noreply.github.com> Date: Wed, 4 Sep 2024 15:16:53 +0200 Subject: [PATCH 054/394] =?UTF-8?q?=F0=9F=90=9B=20Removed=20unnecessary=20?= =?UTF-8?q?check?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridNeutralAtomMapper.hpp | 2 -- src/hybridmap/HybridNeutralAtomMapper.cpp | 10 ---------- 2 files changed, 12 deletions(-) diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index dafea8965..1b37eb2c5 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -73,8 +73,6 @@ class NeutralAtomMapper { qc::QuantumComputation mappedQcAOD; // The scheduler to schedule the mapped quantum circuit NeutralAtomScheduler scheduler; - // The gates that have been executed - std::vector executedCommutingGates; // Gates in the front layer to be executed with swap gates GateList frontLayerGate; // Gates in the front layer to be executed with move operations diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 7ef2a5fdf..769771c48 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -175,16 +175,6 @@ void NeutralAtomMapper::reassignGatesToLayers(const GateList& frontGates, } void NeutralAtomMapper::mapGate(const qc::Operation* op) { - if (op->getType() == qc::OpType::I) { - return; - } - // Safety check - if (std::find(this->executedCommutingGates.begin(), - this->executedCommutingGates.end(), - op) != this->executedCommutingGates.end()) { - return; - } - this->executedCommutingGates.emplace_back(op); if (this->parameters->verbose) { std::cout << "mapped " << op->getName() << " "; for (auto qubit : op->getUsedQubits()) { From c28c1e3431f19244ae4f2a237292d9b3fe697338 Mon Sep 17 00:00:00 2001 From: lsschmid <117631861+lsschmid@users.noreply.github.com> Date: Wed, 4 Sep 2024 20:32:40 +0200 Subject: [PATCH 055/394] =?UTF-8?q?=F0=9F=90=9B=20Added=20Backup=20option?= =?UTF-8?q?=20when=20looking=20for=20MovePosition.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridNeutralAtomMapper.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 769771c48..5be9198a6 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -1054,6 +1054,10 @@ CoordIndices NeutralAtomMapper::getBestMovePos(const CoordIndices& gateCoords) { if (!bestPos.coords.empty() && bestPos.nMoves <= minMoves) { return bestPos.coords; } + if (!bestPos.coords.empty() && bestPos.nMoves < nMovesGate) { + nMovesGate = bestPos.nMoves; + finalBestPos = bestPos; + } // min not yet reached, check nearby if (!bestPos.coords.empty()) { @@ -1066,8 +1070,11 @@ CoordIndices NeutralAtomMapper::getBestMovePos(const CoordIndices& gateCoords) { } } } - throw qc::QFRException( - "No move position found (check if enough free coords are available)"); + if (finalBestPos.coords.empty()) { + throw qc::QFRException( + "No move position found (check if enough free coords are available)"); + } + return finalBestPos.coords; } MoveCombs From 143e0dc6e9389d33c6a2503e2c42df14a3a7917c Mon Sep 17 00:00:00 2001 From: lsschmid <117631861+lsschmid@users.noreply.github.com> Date: Thu, 31 Oct 2024 09:24:59 +0100 Subject: [PATCH 056/394] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20run?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridNeutralAtomMapper.hpp | 12 +++++++----- include/hybridmap/HybridSynthesisMapper.hpp | 10 +++++----- src/hybridmap/HybridNeutralAtomMapper.cpp | 1 - src/hybridmap/HybridSynthesisMapper.cpp | 12 ++++++------ test/hybridmap/test_hybrid_synthesis_map.cpp | 10 +++++----- test/python/test_hybrid_synthesis.py | 3 +++ 6 files changed, 26 insertions(+), 22 deletions(-) diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index 1aa9f21e0..add50f66f 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -7,7 +7,6 @@ #include "Definitions.hpp" #include "NeutralAtomLayer.hpp" -#include "QuantumComputation.hpp" #include "hybridmap/HardwareQubits.hpp" #include "hybridmap/Mapping.hpp" #include "hybridmap/NeutralAtomArchitecture.hpp" @@ -24,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -363,8 +363,9 @@ class NeutralAtomMapper { hardwareQubits(*arch, p->initialMapping, p->seed) { if (arch->getNpositions() - arch->getNqubits() < 1 && p->shuttlingWeight > 0) { - throw QMAPException("No free coordinates for shuttling but shuttling " - "weight is greater than 0."); + throw std::runtime_error( + "No free coordinates for shuttling but shuttling " + "weight is greater than 0."); } }; explicit NeutralAtomMapper(const NeutralAtomArchitecture& architecture, @@ -379,8 +380,9 @@ class NeutralAtomMapper { this->parameters = &p; if (arch->getNpositions() - arch->getNqubits() < 1 && p.shuttlingWeight > 0) { - throw QMAPException("No free coordinates for shuttling but shuttling " - "weight is greater than 0."); + throw std::runtime_error( + "No free coordinates for shuttling but shuttling " + "weight is greater than 0."); } this->reset(); } diff --git a/include/hybridmap/HybridSynthesisMapper.hpp b/include/hybridmap/HybridSynthesisMapper.hpp index fe314fca3..4c4ccdd27 100644 --- a/include/hybridmap/HybridSynthesisMapper.hpp +++ b/include/hybridmap/HybridSynthesisMapper.hpp @@ -9,8 +9,8 @@ #include "HybridNeutralAtomMapper.hpp" #include "NeutralAtomArchitecture.hpp" #include "NeutralAtomUtils.hpp" -#include "QuantumComputation.hpp" #include "hybridmap/NeutralAtomDefinitions.hpp" +#include "ir/QuantumComputation.hpp" #include #include @@ -52,7 +52,7 @@ class HybridSynthesisMapper : public NeutralAtomMapper { HybridSynthesisMapper() = delete; explicit HybridSynthesisMapper( const NeutralAtomArchitecture& arch, - const MapperParameters& params = MapperParameters()) + const MapperParameters& params = MapperParameters()) : NeutralAtomMapper(arch, params) {} // Functions @@ -63,11 +63,11 @@ class HybridSynthesisMapper : public NeutralAtomMapper { * @param nQubits The number of qubits to be mapped. * @param initialMapping The initial mapping to be used. */ - void initMapping(size_t nQubits, + void initMapping(size_t nQubits, InitialMapping initialMapping = InitialMapping::Identity) { - mappedQc = qc::QuantumComputation(arch->getNpositions()); + mappedQc = qc::QuantumComputation(arch->getNpositions()); synthesizedQc = qc::QuantumComputation(nQubits); - mapping = Mapping(nQubits, initialMapping); + mapping = Mapping(nQubits, initialMapping); } /** diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 2f74c7246..cb88f2049 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -6,7 +6,6 @@ #include "hybridmap/HybridNeutralAtomMapper.hpp" #include "Definitions.hpp" -#include "QuantumComputation.hpp" #include "circuit_optimizer/CircuitOptimizer.hpp" #include "hybridmap/Mapping.hpp" #include "hybridmap/MoveToAodConverter.hpp" diff --git a/src/hybridmap/HybridSynthesisMapper.cpp b/src/hybridmap/HybridSynthesisMapper.cpp index 607b9f9f6..f8cc64793 100644 --- a/src/hybridmap/HybridSynthesisMapper.cpp +++ b/src/hybridmap/HybridSynthesisMapper.cpp @@ -5,9 +5,9 @@ #include "hybridmap/HybridSynthesisMapper.hpp" #include "Definitions.hpp" -#include "QuantumComputation.hpp" #include "hybridmap/HybridNeutralAtomMapper.hpp" #include "hybridmap/NeutralAtomDefinitions.hpp" +#include "ir/QuantumComputation.hpp" #include #include @@ -21,7 +21,7 @@ std::vector HybridSynthesisMapper::evaluateSynthesisSteps(qcs& synthesisSteps, bool alsoMap) { std::vector> candidates; - size_t qcIndex = 0; + size_t qcIndex = 0; for (auto& qc : synthesisSteps) { if (this->parameters->verbose) { std::cout << "Evaluating synthesis step number " << qcIndex++ << "\n"; @@ -33,13 +33,13 @@ HybridSynthesisMapper::evaluateSynthesisSteps(qcs& synthesisSteps, qcIndex++; } std::vector fidelities; - size_t bestIndex = 0; - qc::fp bestFidelity = 0; + size_t bestIndex = 0; + qc::fp bestFidelity = 0; for (size_t i = 0; i < candidates.size(); ++i) { fidelities.push_back(candidates[i].second); if (candidates[i].second > bestFidelity) { bestFidelity = candidates[i].second; - bestIndex = i; + bestIndex = i; } } if (alsoMap) { @@ -77,7 +77,7 @@ void HybridSynthesisMapper::appendWithMapping(qc::QuantumComputation& qc) { } AdjacencyMatrix HybridSynthesisMapper::getCircuitAdjacencyMatrix() const { - auto numCircQubits = synthesizedQc.getNqubits(); + auto numCircQubits = synthesizedQc.getNqubits(); AdjacencyMatrix adjMatrix(numCircQubits); for (uint32_t i = 0; i < numCircQubits; ++i) { diff --git a/test/hybridmap/test_hybrid_synthesis_map.cpp b/test/hybridmap/test_hybrid_synthesis_map.cpp index db931a339..b2eec0435 100644 --- a/test/hybridmap/test_hybrid_synthesis_map.cpp +++ b/test/hybridmap/test_hybrid_synthesis_map.cpp @@ -3,9 +3,9 @@ // See README.md or go to https://github.com/cda-tum/qmap for more information. // -#include "QuantumComputation.hpp" #include "hybridmap/HybridSynthesisMapper.hpp" #include "hybridmap/NeutralAtomArchitecture.hpp" +#include "ir/QuantumComputation.hpp" #include #include @@ -16,7 +16,7 @@ namespace na { class TestParametrizedHybridSynthesisMapper : public ::testing::TestWithParam { protected: - std::string testArchitecturePath = "architectures/"; + std::string testArchitecturePath = "architectures/"; std::vector circuits; void SetUp() override { @@ -37,7 +37,7 @@ class TestParametrizedHybridSynthesisMapper }; TEST_P(TestParametrizedHybridSynthesisMapper, AdjaencyMatrix) { - auto arch = NeutralAtomArchitecture(testArchitecturePath); + auto arch = NeutralAtomArchitecture(testArchitecturePath); auto mapper = HybridSynthesisMapper(arch); mapper.initMapping(3); auto adjMatrix = mapper.getCircuitAdjacencyMatrix(); @@ -46,7 +46,7 @@ TEST_P(TestParametrizedHybridSynthesisMapper, AdjaencyMatrix) { } TEST_P(TestParametrizedHybridSynthesisMapper, EvaluateSynthesisStep) { - auto arch = NeutralAtomArchitecture(testArchitecturePath); + auto arch = NeutralAtomArchitecture(testArchitecturePath); auto mapper = HybridSynthesisMapper(arch); mapper.initMapping(3); auto best = mapper.evaluateSynthesisSteps(circuits, false); @@ -64,7 +64,7 @@ class TestHybridSynthesisMapper : public ::testing::Test { protected: NeutralAtomArchitecture arch = NeutralAtomArchitecture("architectures/rubidium.json"); - HybridSynthesisMapper mapper = HybridSynthesisMapper(arch); + HybridSynthesisMapper mapper = HybridSynthesisMapper(arch); qc::QuantumComputation qc; void SetUp() override { diff --git a/test/python/test_hybrid_synthesis.py b/test/python/test_hybrid_synthesis.py index 117203932..488a69eb0 100644 --- a/test/python/test_hybrid_synthesis.py +++ b/test/python/test_hybrid_synthesis.py @@ -98,12 +98,15 @@ def help_create_arch(arch_filename: str) -> NeutralAtomHybridArchitecture: """Helper function to create a hybrid Neutral Atom architecture.""" return NeutralAtomHybridArchitecture(str(arch_dir / arch_filename)) + def help_create_mapper(arch_filename: str) -> HybridSynthesisMapper: """Helper function to create a hybrid synthesis mapper.""" arch = help_create_arch(arch_filename) synthesis_mapper = HybridSynthesisMapper(arch) synthesis_mapper.init_mapping(3) return synthesis_mapper + + def test_keep_alive() -> None: """Test the keep alive functionality of the hybrid Neutral Atom synthesis mapper.""" synthesis_mapper = help_create_mapper("rubidium.json") From ed853defb6ab4a835e16c367414db6b2754fd463 Mon Sep 17 00:00:00 2001 From: sunghyepark Date: Thu, 12 Sep 2024 07:12:41 +0200 Subject: [PATCH 057/394] =?UTF-8?q?=F0=9F=93=A6=20add=20initial=20mapping,?= =?UTF-8?q?=20bridge,=20and=20flying=20ancilla?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmake/ExternalDependencies.cmake | 2 +- include/hybridmap/HardwareQubits.hpp | 36 +- include/hybridmap/HybridNeutralAtomMapper.hpp | 68 +- include/hybridmap/MoveToAodConverter.hpp | 14 +- include/hybridmap/NeutralAtomDefinitions.hpp | 12 + include/hybridmap/NeutralAtomUtils.hpp | 5 +- src/hybridmap/HardwareQubits.cpp | 41 + src/hybridmap/HybridNeutralAtomMapper.cpp | 772 +++++++++++++++++- src/hybridmap/MoveToAodConverter.cpp | 5 + .../{rubidium.json => rubidium_gate.json} | 0 test/hybridmap/main.cpp | 162 ++++ 11 files changed, 1090 insertions(+), 27 deletions(-) rename test/hybridmap/architectures/{rubidium.json => rubidium_gate.json} (100%) create mode 100644 test/hybridmap/main.cpp diff --git a/cmake/ExternalDependencies.cmake b/cmake/ExternalDependencies.cmake index 90f29e58e..d2be1ef1b 100644 --- a/cmake/ExternalDependencies.cmake +++ b/cmake/ExternalDependencies.cmake @@ -29,7 +29,7 @@ endif() # cmake-format: off set(MQT_CORE_VERSION 2.6.1 CACHE STRING "MQT Core version") -set(MQT_CORE_REV "41eea72cdbefbd86ba06f76dc41a911950dd3081" +set(MQT_CORE_REV "na-hybrid-extension" CACHE STRING "MQT Core identifier (tag, branch or commit hash)") set(MQT_CORE_REPO_OWNER "cda-tum" CACHE STRING "MQT Core repository owner (change when using a fork)") diff --git a/include/hybridmap/HardwareQubits.hpp b/include/hybridmap/HardwareQubits.hpp index 27ff91160..66a98f5f2 100644 --- a/include/hybridmap/HardwareQubits.hpp +++ b/include/hybridmap/HardwareQubits.hpp @@ -83,7 +83,8 @@ class HardwareQubits { // Constructors HardwareQubits(const NeutralAtomArchitecture& architecture, InitialCoordinateMapping initialCoordinateMapping, - uint32_t seed) + const std::vector& qubitIndices, + const std::vector& hwIndices, uint32_t seed) : arch(&architecture), swapDistances(architecture.getNqubits()) { switch (initialCoordinateMapping) { case Trivial: @@ -92,6 +93,33 @@ class HardwareQubits { } initTrivialSwapDistances(); break; + case Graph: { + if (qubitIndices.empty()) { + for (uint32_t i = 0; i < architecture.getNqubits(); ++i) { + hwToCoordIdx.emplace(i, i); + } + initTrivialSwapDistances(); + } else { + int hwIndex = 0; + for (uint32_t i = 0; i < architecture.getNqubits(); ++i) { + if (qubitIndices[i] == std::numeric_limits::max()) { + if (hwIndices[hwIndex] != + std::numeric_limits::max()) { + do { + hwIndex += 1; + } while (hwIndices[hwIndex] != + std::numeric_limits::max()); + } + hwToCoordIdx.emplace(i, hwIndex); + hwIndex++; + } else { + hwToCoordIdx.emplace(i, qubitIndices[i]); + } + } + swapDistances = SymmetricMatrix(architecture.getNqubits(), -1); + } + break; + } case Random: std::vector indices(architecture.getNpositions()); std::iota(indices.begin(), indices.end(), 0); @@ -111,6 +139,7 @@ class HardwareQubits { } // Mapping + const qc::Permutation& getHwToCoordIdx() const { return hwToCoordIdx; } /** * @brief Checks if a hardware qubit is mapped to a coordinate. @@ -272,6 +301,11 @@ class HardwareQubits { findClosestFreeCoord(HwQubit qubit, Direction direction, const CoordIndices& excludedCoords = {}); + std::vector + findClosestAncillaCoord(CoordIndex coord, Direction direction, + int circQubitSize, + const CoordIndices& excludedCoords = {}); + // Blocking /** * @brief Computes all hardware qubits that are blocked by a set of hardware diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index 329f0d161..744bd25e3 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -41,7 +41,7 @@ struct MapperParameters { qc::fp shuttlingWeight = 1; uint32_t seed = 0; bool verbose = false; - InitialCoordinateMapping initialMapping = InitialCoordinateMapping::Trivial; + InitialCoordinateMapping initialMapping; }; /** @@ -94,6 +94,8 @@ class NeutralAtomMapper { std::vector decayWeights; // Counter variables uint32_t nSwaps = 0; + uint32_t nBridges = 0; + uint32_t nFAncillas = 0; uint32_t nMoves = 0; // The current placement of the hardware qubits onto the coordinates @@ -101,7 +103,16 @@ class NeutralAtomMapper { // The current mapping between circuit qubits and hardware qubits Mapping mapping; + qc::DAG dag; + // Methods for mapping + + /** + * @brief GraphMatching for initCoordMapping + */ + void graphMatching(std::vector& qubitIndices, + std::vector& hwIndices, const qc::DAG& dag); + /** * @brief Maps the gate to the mapped quantum circuit. * @param op The gate to map @@ -191,6 +202,17 @@ class NeutralAtomMapper { [[nodiscard]] std::set getAllPossibleSwaps(const std::pair& swapsFront) const; + // Methods for bridge operations mapping + std::vector> + findAllBridges(qc::QuantumComputation& qc); + std::vector> + bridgeCostCompareWithSwap( + std::vector> allBridges, + Swap bestSwap, const qc::DAG& dag, NeutralAtomLayer& frontLayer); + void updateMappingBridge( + std::vector> ExecutableBridges, + NeutralAtomLayer& frontLayer, NeutralAtomLayer& lookaheadLayer); + /** * @brief Returns the next best shuttling move operation for the front layer. * @return The next best shuttling move operation for the front layer @@ -205,6 +227,7 @@ class NeutralAtomMapper { * @return The current best move operation */ AtomMove findBestAtomMove(); + std::pair findBestAtomMoveWithOp(); /** * @brief Returns all possible move combinations for the front layer. * @details This includes direct moves, move away and multi-qubit moves. @@ -212,6 +235,8 @@ class NeutralAtomMapper { * @return Vector of possible move combinations for the front layer */ MoveCombs getAllMoveCombinations(); + std::pair>> + getAllMoveCombinationsWithOp(); /** * @brief Returns all possible move away combinations for a move from start to * target. @@ -227,6 +252,24 @@ class NeutralAtomMapper { MoveCombs getMoveAwayCombinations(CoordIndex start, CoordIndex target, const CoordIndices& excludedCoords); + // Methods for flying ancilla operations mapping + std::pair + findBestFlyingAncilla(qc::QuantumComputation& qc, + const qc::Operation* targetOp); + std::set> findQtargetSet(std::set& usedQubits); + CoordIndex returnClosestAncillaCoord(const CoordIndex& c_target, + const CoordIndices& excludeCoords, + qc::QuantumComputation& qc); + bool compareShuttlingAndFlyingancilla(AtomMove bestMove, + qc::QuantumComputation& bestFA, + const qc::DAG& dag, + NeutralAtomLayer& frontLayer); + void updateMappingFlyingAncilla(qc::QuantumComputation& bestFA, + const qc::Operation* targetOp, + uint32_t numPassby, + NeutralAtomLayer& frontLayer, + NeutralAtomLayer& lookaheadLayer); + // Helper methods /** * @brief Distinguishes between two-qubit swaps and multi-qubit swaps. @@ -363,8 +406,10 @@ class NeutralAtomMapper { const MapperParameters& p = MapperParameters()) : arch(architecture), mappedQc(architecture.getNpositions()), mappedQcAOD(architecture.getNpositions()), scheduler(architecture), - parameters(p), hardwareQubits(architecture, parameters.initialMapping, - parameters.seed) { + parameters(p), + hardwareQubits(architecture, parameters.initialMapping, + std::vector(), std::vector(), + parameters.seed) { // need at least on free coordinate to shuttle if (architecture.getNpositions() - architecture.getNqubits() < 1) { this->parameters.gateWeight = 1; @@ -389,8 +434,9 @@ class NeutralAtomMapper { * @brief Resets the mapper and the hardware qubits. */ void reset() { - hardwareQubits = - HardwareQubits(arch, parameters.initialMapping, parameters.seed); + hardwareQubits = HardwareQubits(arch, parameters.initialMapping, + std::vector(), + std::vector(), parameters.seed); } // Methods @@ -420,7 +466,8 @@ class NeutralAtomMapper { * operations */ qc::QuantumComputation map(qc::QuantumComputation& qc, - InitialMapping initialMapping); + InitialMapping initialMapping, + InitialCoordinateMapping initialCoordinateMapping); /** * @brief Maps the given quantum circuit to the given architecture and @@ -429,11 +476,12 @@ class NeutralAtomMapper { * @param initialMapping The initial mapping of the circuit qubits to the * hardware qubits */ - [[maybe_unused]] void mapAndConvert(qc::QuantumComputation& qc, - InitialMapping initialMapping, - bool printInfo) { + [[maybe_unused]] void + mapAndConvert(qc::QuantumComputation& qc, InitialMapping initialMapping, + InitialCoordinateMapping initialCoordinateMapping, + bool printInfo) { this->parameters.verbose = printInfo; - map(qc, initialMapping); + map(qc, initialMapping, initialCoordinateMapping); convertToAod(this->mappedQc); } diff --git a/include/hybridmap/MoveToAodConverter.hpp b/include/hybridmap/MoveToAodConverter.hpp index 5bc0862bd..ccf0998f9 100644 --- a/include/hybridmap/MoveToAodConverter.hpp +++ b/include/hybridmap/MoveToAodConverter.hpp @@ -6,6 +6,7 @@ #pragma once #include "Definitions.hpp" +#include "hybridmap/HardwareQubits.hpp" #include "hybridmap/NeutralAtomArchitecture.hpp" #include "hybridmap/NeutralAtomDefinitions.hpp" #include "hybridmap/NeutralAtomUtils.hpp" @@ -267,12 +268,14 @@ class MoveToAodConverter { const NeutralAtomArchitecture& arch; qc::QuantumComputation qcScheduled; std::vector moveGroups; + const na::HardwareQubits& hardwareQubits; /** * @brief Assigns move operations into groups that can be executed in parallel * @param qc Quantum circuit to schedule */ - void initMoveGroups(qc::QuantumComputation& qc); + void initMoveGroups( + qc::QuantumComputation& qc); //, qc::Permutation& hwToCoordIdx); /** * @brief Converts the move groups into the actual AOD operations * @details For this the following steps are performed: @@ -287,15 +290,18 @@ class MoveToAodConverter { MoveToAodConverter() = delete; MoveToAodConverter(const MoveToAodConverter&) = delete; MoveToAodConverter(MoveToAodConverter&&) = delete; - explicit MoveToAodConverter(const NeutralAtomArchitecture& archArg) - : arch(archArg), qcScheduled(arch.getNpositions()) {} + explicit MoveToAodConverter(const NeutralAtomArchitecture& archArg, + const na::HardwareQubits& hardwareQubitsArg) + : arch(archArg), qcScheduled(arch.getNpositions()), + hardwareQubits(hardwareQubitsArg) {} /** * @brief Schedules the given quantum circuit using AODs * @param qc Quantum circuit to schedule * @return Scheduled quantum circuit, containing AOD operations */ - qc::QuantumComputation schedule(qc::QuantumComputation& qc); + qc::QuantumComputation + schedule(qc::QuantumComputation& qc); //, qc::Permutation hwToCoordIdx); /** * @brief Returns the number of move groups diff --git a/include/hybridmap/NeutralAtomDefinitions.hpp b/include/hybridmap/NeutralAtomDefinitions.hpp index cd8a15d35..f09c6e8fe 100644 --- a/include/hybridmap/NeutralAtomDefinitions.hpp +++ b/include/hybridmap/NeutralAtomDefinitions.hpp @@ -17,6 +17,7 @@ namespace na { // (or not). using CoordIndex = std::uint32_t; using CoordIndices = std::vector; + // A HwQubit corresponds to an atom in the neutral atom architecture. It can be // used as qubit or not and occupies a certain position in the architecture. using HwQubit = uint32_t; @@ -33,9 +34,20 @@ using WeightedSwap = std::pair; using WeightedSwaps = std::vector; // The distance between two hardware qubits using SWAP gates. using SwapDistance = int32_t; +// Bridges +using Bridge = + std::tuple; // q_control, q_target, Q_between +using Bridges = std::vector; // Moves are between coordinates (the first is occupied, the second is not). using AtomMove = std::pair; +// Moves for FlyingAncilla +struct fPoint { + qc::fp x; + qc::fp y; + fPoint(qc::fp x_val, qc::fp y_val) : x(x_val), y(y_val) {} +}; + // Used to represent operations using GateList = std::vector; diff --git a/include/hybridmap/NeutralAtomUtils.hpp b/include/hybridmap/NeutralAtomUtils.hpp index 270c7a361..035b58e9c 100644 --- a/include/hybridmap/NeutralAtomUtils.hpp +++ b/include/hybridmap/NeutralAtomUtils.hpp @@ -21,7 +21,7 @@ namespace na { // Enums for the different initial mappings strategies -enum InitialCoordinateMapping : uint8_t { Trivial, Random }; +enum InitialCoordinateMapping : uint8_t { Trivial, Random, Graph }; enum InitialMapping : uint8_t { Identity }; [[maybe_unused]] static InitialCoordinateMapping @@ -34,6 +34,9 @@ initialCoordinateMappingFromString( if (initialCoordinateMapping == "random" || initialCoordinateMapping == "1") { return InitialCoordinateMapping::Random; } + if (initialCoordinateMapping == "graph" || initialCoordinateMapping == "2") { + return InitialCoordinateMapping::Graph; + } throw std::invalid_argument("Invalid initial coordinate mapping value: " + initialCoordinateMapping); } diff --git a/src/hybridmap/HardwareQubits.cpp b/src/hybridmap/HardwareQubits.cpp index d79b145ad..0e46527c5 100644 --- a/src/hybridmap/HardwareQubits.cpp +++ b/src/hybridmap/HardwareQubits.cpp @@ -236,4 +236,45 @@ HardwareQubits::findClosestFreeCoord(CoordIndex coord, Direction direction, return closestFreeCoords; } +std::vector +HardwareQubits::findClosestAncillaCoord(CoordIndex coord, Direction direction, + int circQubitSize, + const CoordIndices& excludeCoord) { + // return the closest ancilla coord in general + // and the closest free ancilla in the given direction + std::vector closestFreeCoords; + std::queue queue; + queue.push(coord); + std::set visited; + visited.insert(coord); + bool foundClosest = false; + while (!queue.empty()) { + auto currentCoord = queue.front(); + queue.pop(); + auto nearbyCoords = this->arch->getNN(currentCoord); + for (const auto& nearbyCoord : nearbyCoords) { + if (std::find(visited.rbegin(), visited.rend(), nearbyCoord) == + visited.rend()) { + visited.insert(nearbyCoord); + if (this->isMapped(nearbyCoord) && + this->getHwQubit(nearbyCoord) >= circQubitSize && + std::find(excludeCoord.begin(), excludeCoord.end(), nearbyCoord) == + excludeCoord.end()) { + if (!foundClosest) { + closestFreeCoords.push_back(nearbyCoord); + } + foundClosest = true; + if (direction == arch->getVector(coord, nearbyCoord).direction) { + closestFreeCoords.emplace_back(nearbyCoord); + return closestFreeCoords; + } + } else { + queue.push(nearbyCoord); + } + } + } + } + return closestFreeCoords; +} + } // namespace na diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 3acb425a0..877683810 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -32,11 +33,15 @@ #include namespace na { -qc::QuantumComputation NeutralAtomMapper::map(qc::QuantumComputation& qc, - InitialMapping initialMapping) { +qc::QuantumComputation +NeutralAtomMapper::map(qc::QuantumComputation& qc, + InitialMapping initialMapping, + InitialCoordinateMapping initialCoordinateMapping) { mappedQc = qc::QuantumComputation(arch.getNpositions()); nMoves = 0; nSwaps = 0; + nBridges = 0; + nFAncillas = 0; qc::CircuitOptimizer::replaceMCXWithMCZ(qc); qc::CircuitOptimizer::singleQubitGateFusion(qc); qc::CircuitOptimizer::flattenOperations(qc); @@ -45,7 +50,34 @@ qc::QuantumComputation NeutralAtomMapper::map(qc::QuantumComputation& qc, auto dag = qc::CircuitOptimizer::constructDAG(qc); // init mapping - this->mapping = Mapping(qc.getNqubits(), initialMapping); + this->mapping = Mapping(arch.getNqubits(), initialMapping); + + // init coord mapping + auto archNqubits = arch.getNqubits(); + auto archNpositions = arch.getNpositions(); + std::vector qubitIndices( + archNqubits, std::numeric_limits::max()); + std::vector hwIndices(archNpositions, + std::numeric_limits::max()); + + if (initialCoordinateMapping == Graph) { + graphMatching(qubitIndices, hwIndices, dag); + this->hardwareQubits = + HardwareQubits(arch, initialCoordinateMapping, qubitIndices, hwIndices, + parameters.seed); + } + + /* + if(this->parameters.verbose){ + std::cout << "* Init Coord Mapping w/ [row:" << arch.getNrows() << " X col:" + << arch.getNcolumns() << "] hardware" << std::endl; for(uint32_t q=0; + q h " << std::setw(3) << this->mapping.getHwQubit(q); std::cout << " -> c " + << std::setw(3) << this->hardwareQubits.getCoordIndex(q) << std::endl; + } + std::cout << std::endl; + } + */ // init layers NeutralAtomLayer frontLayer(dag); @@ -69,6 +101,9 @@ qc::QuantumComputation NeutralAtomMapper::map(qc::QuantumComputation& qc, while (!frontLayer.getGates().empty()) { // assign gates to layers reassignGatesToLayers(frontLayer.getGates(), lookaheadLayer.getGates()); + if (this->parameters.verbose) { + printLayers(); + } // save last swap to prevent immediate swap back Swap lastSwap = {0, 0}; @@ -76,14 +111,37 @@ qc::QuantumComputation NeutralAtomMapper::map(qc::QuantumComputation& qc, // first do all gate based mapping gates while (!this->frontLayerGate.empty()) { GateList gatesToExecute; - while (gatesToExecute.empty()) { + while (gatesToExecute.empty() && !this->frontLayerGate.empty()) { ++i; if (this->parameters.verbose) { std::cout << "iteration " << i << '\n'; } + + // find swap auto bestSwap = findBestSwap(lastSwap); - lastSwap = bestSwap; - updateMappingSwap(bestSwap); + + // find bridge + auto allBridges = findAllBridges(qc); + auto ExecutableBridges = + bridgeCostCompareWithSwap(allBridges, bestSwap, dag, frontLayer); + + // execute bridge gate + if (!ExecutableBridges.empty()) { + updateMappingBridge(ExecutableBridges, frontLayer, lookaheadLayer); + mapAllPossibleGates(frontLayer); + lookaheadLayer.initLayerOffset(frontLayer.getIteratorOffset()); + reassignGatesToLayers(frontLayer.getGates(), + lookaheadLayer.getGates()); + if (this->parameters.verbose) { + printLayers(); + } + } + + // execute swap gate + else { + lastSwap = bestSwap; + updateMappingSwap(bestSwap); + } gatesToExecute = getExecutableGates(frontLayer.getGates()); } mapAllPossibleGates(frontLayer); @@ -96,13 +154,39 @@ qc::QuantumComputation NeutralAtomMapper::map(qc::QuantumComputation& qc, // then do all shuttling based mapping gates while (!this->frontLayerShuttling.empty()) { GateList gatesToExecute; - while (gatesToExecute.empty()) { + while (gatesToExecute.empty() && !this->frontLayerShuttling.empty()) { ++i; if (this->parameters.verbose) { std::cout << "iteration " << i << '\n'; } - auto bestMove = findBestAtomMove(); - updateMappingMove(bestMove); + + // find best move + auto [bestMove, opForMove] = findBestAtomMoveWithOp(); + + // find flying ancilla + auto [bestFA, numPassby] = findBestFlyingAncilla(qc, opForMove); + + // TODO: compare shuttling vs f.a. + auto useShuttling = + compareShuttlingAndFlyingancilla(bestMove, bestFA, dag, frontLayer); + + // execute shuttling + if (useShuttling) { + updateMappingMove(bestMove); + } + // execute flying ancilla + else { + updateMappingFlyingAncilla(bestFA, opForMove, numPassby, frontLayer, + lookaheadLayer); + mapAllPossibleGates(frontLayer); + lookaheadLayer.initLayerOffset(frontLayer.getIteratorOffset()); + reassignGatesToLayers(frontLayer.getGates(), + lookaheadLayer.getGates()); + if (this->parameters.verbose) { + printLayers(); + } + } + gatesToExecute = getExecutableGates(frontLayer.getGates()); } mapAllPossibleGates(frontLayer); @@ -115,11 +199,177 @@ qc::QuantumComputation NeutralAtomMapper::map(qc::QuantumComputation& qc, } if (this->parameters.verbose) { std::cout << "nSwaps: " << nSwaps << '\n'; + std::cout << "nBridges: " << nBridges << '\n'; + std::cout << "nFAncillas: " << nFAncillas << '\n'; std::cout << "nMoves: " << nMoves << '\n'; } return mappedQc; } +void NeutralAtomMapper::graphMatching(std::vector& qubitIndices, + std::vector& hwIndices, + const qc::DAG& dag) { + auto archNqubits = arch.getNqubits(); + auto archNpositions = arch.getNpositions(); + auto archNrows = arch.getNrows(); + auto archNcolumns = arch.getNcolumns(); + // interaction graph + std::vector> circGraph( + dag.size(), std::vector(dag.size(), 0.0)); + std::vector>> circGraph_degree( + dag.size()); + std::vector>> circGraph_neighbor( + dag.size()); + for (uint32_t qubit = 0; qubit < dag.size(); ++qubit) { + for (auto opPtr : dag[qubit]) { + auto* op = opPtr->get(); + if (op->getUsedQubits().size() > 1) { + for (auto i : op->getUsedQubits()) { + if (i != qubit) { + circGraph[qubit][i] += 1; + } + } + } + } + } + + // generate graph matching queue + for (uint32_t qubit = 0; qubit < dag.size(); qubit++) { + int cnt = 0; + double sum = 0; + for (uint32_t i = 0; i < dag.size(); i++) { + double weight = circGraph[qubit][i]; + if (weight > 0) { + cnt++; + sum += weight; + circGraph_neighbor[qubit].emplace_back(i, weight); + } + } + circGraph_degree[qubit] = std::make_pair(qubit, std::make_pair(cnt, sum)); + } + sort(circGraph_degree.begin(), circGraph_degree.end(), + [](std::pair> a, + std::pair> b) { + if (a.second.first == b.second.first) + return a.second.second > b.second.second; + else + return a.second.first > b.second.first; + }); + std::queue circGraph_queue; + for (const auto i : circGraph_degree) { + circGraph_queue.push(i.first); + } + for (auto& innerVec : circGraph_neighbor) { + sort(innerVec.begin(), innerVec.end(), + [](std::pair& a, std::pair& b) { + return a.second > b.second; + }); + } + + // graph matching + bool firstCenter = true; + uint32_t nMapped = 0; + uint32_t archCenterX = + (archNcolumns % 2 == 0) ? (archNcolumns / 2 - 1) : (archNcolumns - 1) / 2; + uint32_t archCenterY = + (archNrows % 2 == 0) ? (archNrows / 2 - 1) : (archNrows - 1) / 2; + uint32_t archCenter = archCenterY * archNcolumns + archCenterX; + + while (!circGraph_queue.empty() && nMapped != dag.size()) { + uint32_t qc = circGraph_queue.front(); + uint32_t hc = std::numeric_limits::max(); // hardwarCenter + // center mapping + if (firstCenter) { + hc = archCenter; + qubitIndices[qc] = hc; + hwIndices[hc] = qc; + firstCenter = false; + nMapped++; + } else if (qubitIndices[qc] == std::numeric_limits::max()) { + + // ref loc + std::vector refLoc; + for (auto i : circGraph_neighbor[qc]) { + if (qubitIndices[i.first] != std::numeric_limits::max()) { + refLoc.push_back(qubitIndices[i.first]); + } + } + + // candidate loc + std::vector> distCandiLoc; + std::vector initCandiLoc; + for (int i = 0; i < archNpositions; i++) { + if (hwIndices[i] == std::numeric_limits::max()) { + initCandiLoc.push_back(i); + } + } + for (auto v : initCandiLoc) { + int distSum = 0; + for (auto r : refLoc) { + int dist = std::abs(v % archNcolumns - r % archNcolumns) + + std::abs(v / archNcolumns - r / archNcolumns); + distSum += dist; + } + distCandiLoc.emplace_back(v, distSum); + } + sort(distCandiLoc.begin(), distCandiLoc.end(), + [](std::pair a, std::pair b) { + return a.second < b.second; + }); + + // find position + hc = distCandiLoc[0].first; + qubitIndices[qc] = hc; + hwIndices[hc] = qc; + nMapped++; + } else { + hc = qubitIndices[qc]; + } + + // neighbor mapping + if (!circGraph_neighbor[qc].empty()) { + int idx_qc_n = 0; + std::vector qc_n; + for (auto i : circGraph_neighbor[qc]) { + if (qubitIndices[i.first] != std::numeric_limits::max()) + continue; + if (idx_qc_n >= 4) + continue; + else { + qc_n.push_back(i.first); + idx_qc_n++; + } + } + std::vector hw_n; + if (hc != std::numeric_limits::max() && qc_n.size() > 0) { + if ((hc + 1) % archNcolumns != 0 && + hwIndices[hc + 1] == std::numeric_limits::max()) + hw_n.push_back(hc + 1); // right + if (hc / archNcolumns < (archNrows - 1) && + hwIndices[hc + archNcolumns] == + std::numeric_limits::max()) + hw_n.push_back(hc + archNcolumns); // down + if (hc % archNcolumns != 0 && + hwIndices[hc - 1] == std::numeric_limits::max()) + hw_n.push_back(hc - 1); // left + if (hc > archNcolumns && hwIndices[hc - archNcolumns] == + std::numeric_limits::max()) + hw_n.push_back(hc - archNcolumns); // up + } + + int minSize = std::min(qc_n.size(), hw_n.size()); + for (int i = 0; i < minSize; i++) { + int qc_i = qc_n[i]; + int hw_i = hw_n[i]; + qubitIndices[qc_i] = hw_i; + hwIndices[hw_i] = qc_i; + nMapped++; + } + } + circGraph_queue.pop(); + } +} + void NeutralAtomMapper::mapAllPossibleGates(NeutralAtomLayer& layer) { // map single qubit gates for (const auto* opPointer : layer.getMappedSingleQubitGates()) { @@ -148,7 +398,8 @@ NeutralAtomMapper::convertToAod(qc::QuantumComputation& qc) { qc::CircuitOptimizer::singleQubitGateFusion(qc); qc::CircuitOptimizer::flattenOperations(qc); // decompose AOD moves - MoveToAodConverter aodScheduler(arch); + // MoveToAodConverter aodScheduler(arch); + MoveToAodConverter aodScheduler(arch, this->hardwareQubits); mappedQcAOD = aodScheduler.schedule(qc); if (this->parameters.verbose) { std::cout << "nMoveGroups: " << aodScheduler.getNMoveGroups() << '\n'; @@ -798,6 +1049,38 @@ AtomMove NeutralAtomMapper::findBestAtomMove() { return bestMove->first.getFirstMove(); } +std::pair +NeutralAtomMapper::findBestAtomMoveWithOp() { + auto [moveCombs, moveCombsWithOp] = getAllMoveCombinationsWithOp(); + + // compute cost for each move combination + std::vector> moveCosts; + moveCosts.reserve(moveCombs.size()); + for (const auto& moveComb : moveCombs) { + moveCosts.emplace_back(moveComb, moveCostComb(moveComb)); + } + + std::sort(moveCosts.begin(), moveCosts.end(), + [](const auto& move1, const auto& move2) { + return move1.second < move2.second; + }); + + // get move of minimal cost + auto bestMove = std::min_element(moveCosts.begin(), moveCosts.end(), + [](const auto& move1, const auto& move2) { + return move1.second < move2.second; + }); + MoveComb bestAtomMove = bestMove->first; + const qc::Operation* corresOp = nullptr; + for (const auto& pair : moveCombsWithOp) { + if (pair.first == bestAtomMove) { + corresOp = pair.second; + break; + } + } + return make_pair(bestAtomMove.getFirstMove(), corresOp); +} + qc::fp NeutralAtomMapper::moveCostComb(const MoveComb& moveComb) { qc::fp costComb = 0; for (const auto& move : moveComb.moves) { @@ -1032,6 +1315,28 @@ MoveCombs NeutralAtomMapper::getAllMoveCombinations() { return allMoves; } +std::pair>> +NeutralAtomMapper::getAllMoveCombinationsWithOp() { + MoveCombs allMoves; + std::vector> allMovesWithOp; + int i = 1; + for (const auto& op : this->frontLayerShuttling) { + auto usedQubits = op->getUsedQubits(); + auto usedHwQubits = this->mapping.getHwQubits(usedQubits); + auto usedCoordsSet = this->hardwareQubits.getCoordIndices(usedHwQubits); + auto usedCoords = + std::vector(usedCoordsSet.begin(), usedCoordsSet.end()); + auto bestPos = getBestMovePos(usedCoords); + auto moves = getMoveCombinationsToPosition(usedHwQubits, bestPos); + allMoves.addMoveCombs(moves); + for (auto move : moves) { + allMovesWithOp.push_back(std::make_pair(move, op)); + } + } + allMoves.removeLongerMoveCombs(); + return make_pair(allMoves, allMovesWithOp); +} + CoordIndices NeutralAtomMapper::getBestMovePos(const CoordIndices& gateCoords) { size_t const maxMoves = gateCoords.size() * 2; size_t const minMoves = gateCoords.size(); @@ -1301,4 +1606,451 @@ bool NeutralAtomMapper::swapGateBetter(const qc::Operation* opPointer) { fidMoves * parameters.shuttlingWeight; } +std::vector> +NeutralAtomMapper::findAllBridges(qc::QuantumComputation& qc) { + std::vector> allBridges; + for (const auto* op : this->frontLayerGate) { + size_t usedQubitSize = op->getUsedQubits().size(); + Qubits nearbyQubits; + if (usedQubitSize == 2) { + // logical qubits + qc::Qubit q1 = *(op->getUsedQubits().begin()); + qc::Qubit q2 = *(std::next(op->getUsedQubits().begin(), 1)); + // hardware qubits + HwQubit h1 = this->mapping.getHwQubit(q1); + HwQubit h2 = this->mapping.getHwQubit(q2); + qc::fp dist = this->hardwareQubits.getSwapDistance(h1, h2); + if (dist == 1) { + // get nearby + HwQubits h1Near = this->hardwareQubits.getNearbyQubits(h1); + HwQubits h2Near = this->hardwareQubits.getNearbyQubits(h2); + for (const auto& h : h1Near) { + if (h2Near.find(h) != h2Near.end()) { + qc::Qubit qBtw = this->mapping.getCircQubit(h); + if (qBtw < qc.getNqubits() && qBtw != -1) { + nearbyQubits.insert(qBtw); + } + } + } + } + allBridges.emplace_back(op, Bridge(q1, q2, nearbyQubits)); + } + } + return allBridges; +} + +void NeutralAtomMapper::updateMappingBridge( + std::vector> ExecutableBridges, + NeutralAtomLayer& frontLayer, NeutralAtomLayer& lookaheadLayer) { + // CX to Bridge + //[q1] ---c--- = ---------c-----------c--- + //[qb] | = ---c---H-Z-H---c---H-Z-H- + //[q2] -H-Z-H- = -H-Z-H-------H-Z-H------- + + //-> CZ to Bridge w/ QCO + //[q1] -c- = -------c-----------c--- + //[qb] | = -c---H-Z-H---c---H-Z-H- + //[q2] -Z- = -Z-----------Z--------- + + GateList removeGates; + for (auto bridgePair : ExecutableBridges) { + nBridges++; + auto op = bridgePair.first; + auto bridge = bridgePair.second; + qc::Qubit q1 = std::get<0>(bridge); + qc::Qubit q2 = std::get<1>(bridge); + Qubits Qb = std::get<2>(bridge); + auto it = Qb.begin(); + qc::Qubit qb = *it; + if (this->parameters.verbose) { + std::cout << "bridged " << q1 << " " << q2 << " by using " << qb; + std::cout << " physical qubits: "; + std::cout << this->mapping.getHwQubit(q1); + std::cout << " "; + std::cout << this->mapping.getHwQubit(q2); + std::cout << " "; + std::cout << this->mapping.getHwQubit(qb); + std::cout << '\n'; + } + // add BR to mappedQc + mappedQc.cz(qb, q2); + mappedQc.h(qb); + mappedQc.cz(q1, qb); + mappedQc.h(qb); + mappedQc.cz(qb, q2); + mappedQc.h(qb); + mappedQc.cz(q1, qb); + mappedQc.h(qb); + + // remove original gate + removeGates.push_back(op); + } + // remove original gate + frontLayer.removeGatesAndUpdate(removeGates); + lookaheadLayer.removeGatesAndUpdate(removeGates); + for (const auto& gate : removeGates) { + if (std::find(frontLayerGate.begin(), frontLayerGate.end(), gate) != + frontLayerGate.end()) { + frontLayerGate.erase( + std::find(frontLayerGate.begin(), frontLayerGate.end(), gate)); + } + if (std::find(lookaheadLayerGate.begin(), lookaheadLayerGate.end(), gate) != + lookaheadLayerGate.end()) { + lookaheadLayerGate.erase(std::find(lookaheadLayerGate.begin(), + lookaheadLayerGate.end(), gate)); + } + } +} + +std::vector> +NeutralAtomMapper::bridgeCostCompareWithSwap( + std::vector> allBridges, + Swap bestSwap, const qc::DAG& dag, NeutralAtomLayer& frontLayer) { + std::vector> ExecutableBridges; + + for (auto bridgePair : allBridges) { + auto op = bridgePair.first; + auto bridge = bridgePair.second; + qc::Qubit q1 = std::get<0>(bridge); + qc::Qubit q2 = std::get<1>(bridge); + Qubits Qb = std::get<2>(bridge); + auto it = Qb.begin(); + qc::Qubit qb = *it; + + // cost for front layer + qc::fp distbefore = 0; + qc::fp distswap = 0; + HwQubit p1 = this->mapping.getHwQubit(q1); + HwQubit p2 = this->mapping.getHwQubit(q2); + distbefore += this->hardwareQubits.getSwapDistance(p1, p2); + if (p1 == bestSwap.first) { + distswap += this->hardwareQubits.getSwapDistance(bestSwap.second, p2); + } else if (p1 == bestSwap.second) { + distswap += this->hardwareQubits.getSwapDistance(bestSwap.first, p2); + } else if (p2 == bestSwap.first) { + distswap += this->hardwareQubits.getSwapDistance(p1, bestSwap.second); + } else if (p2 == bestSwap.second) { + distswap += this->hardwareQubits.getSwapDistance(p1, bestSwap.first); + } else { + distswap += this->hardwareQubits.getSwapDistance(p1, p2); + } + if (distbefore - distswap > 0) + return ExecutableBridges; + + // cost for look-ahead window + qc::fp costBridge = 0; + qc::fp costBestSwap = 0; + for (auto& q : {q1, q2}) { + HwQubit p = this->mapping.getHwQubit(q); + auto tempIter = dag[q].begin() + frontLayer.getIteratorOffset()[q] + 1; + qc::fp discountFactor = 0.9; + while (tempIter < dag[q].end() && discountFactor > 0.1) { + auto* dagOp = (*tempIter)->get(); + if (dagOp->getUsedQubits().size() != 1) { + Qubits usedQubits = dagOp->getUsedQubits(); + qc::fp distbefore = 0; + qc::fp distswap = 0; + for (auto it1 = usedQubits.begin(); it1 != usedQubits.end(); ++it1) { + for (auto it2 = std::next(it1); it2 != usedQubits.end(); ++it2) { + qc::Qubit qi = *it1; + qc::Qubit qj = *it2; + HwQubit pi = this->mapping.getHwQubit(qi); + HwQubit pj = this->mapping.getHwQubit(qj); + + distbefore += this->hardwareQubits.getSwapDistance(pi, pj); + if (pi == bestSwap.first) { + distswap += + this->hardwareQubits.getSwapDistance(bestSwap.second, pj); + } else if (pi == bestSwap.second) { + distswap += + this->hardwareQubits.getSwapDistance(bestSwap.first, pj); + } else if (pj == bestSwap.first) { + distswap += + this->hardwareQubits.getSwapDistance(pi, bestSwap.second); + } else if (pj == bestSwap.second) { + distswap += + this->hardwareQubits.getSwapDistance(pi, bestSwap.first); + } else { + distswap += this->hardwareQubits.getSwapDistance(pi, pj); + } + } + } + costBridge += distbefore * discountFactor; + costBestSwap += distswap * discountFactor; + discountFactor *= 0.9; + } + tempIter++; + } + } + + if (costBridge <= costBestSwap && costBridge != 0) { + ExecutableBridges.emplace_back(op, Bridge(q1, q2, Qb)); + } + } + return ExecutableBridges; +} + +std::pair +NeutralAtomMapper::findBestFlyingAncilla(qc::QuantumComputation& qc, + const qc::Operation* targetOp) { + // information of operation + qc::QuantumComputation bestAddedQc; + uint32_t bestNumPassby = 0; + auto usedQubits = targetOp->getUsedQubits(); + auto QtargetSet = findQtargetSet(usedQubits); + if (!QtargetSet.empty()) { + int idx = 0; + int bestNumMoves = std::numeric_limits::max(); + + int bestIdx; + std::set bestQtarget; + std::vector bestQsource; + // #F.A. => (Q_source, Q_target) iteration + for (auto Qtarget : QtargetSet) { + int numFA = usedQubits.size() - Qtarget.size(); + uint32_t NumPassby = 0; + std::set Qsource; + std::set_difference(usedQubits.begin(), usedQubits.end(), Qtarget.begin(), + Qtarget.end(), std::inserter(Qsource, Qsource.end())); + + // permutate Qtarget & Qsource + std::vector QsourceVec(Qsource.begin(), Qsource.end()); + do { + qc::QuantumComputation addedQc; + addedQc = qc::QuantumComputation(arch.getNpositions()); + qc::fp r_int = arch.getInteractionRadius(); + + // hardware qubits & coord inices of Qtarget + auto Htarget = this->mapping.getHwQubits(Qtarget); + auto Ctarget = this->hardwareQubits.getCoordIndices(Htarget); + + std::vector Qancilla; + std::vector Hsource, Hancilla; + std::vector Csource, Cancilla; + for (auto qs : QsourceVec) { + // hardware qubits & coord inices of Qsource + auto hs = this->mapping.getHwQubit(qs); + Hsource.push_back(hs); + auto cs = this->hardwareQubits.getCoordIndex(hs); + Csource.push_back(cs); + } + std::vector excludeCoords; + std::copy(Ctarget.begin(), Ctarget.end(), + std::back_inserter(excludeCoords)); + std::copy(Csource.begin(), Csource.end(), + std::back_inserter(excludeCoords)); + + std::vector passbyQtarget; + std::copy(Qtarget.begin(), Qtarget.end(), + std::back_inserter(passbyQtarget)); + + std::vector needPassby(QsourceVec.size(), false); + for (uint32_t i = 0; i < QsourceVec.size(); i++) { + // find ancillaQubit of Qsource + auto qi = QsourceVec[i]; + auto ci = Csource[i]; + auto cA = returnClosestAncillaCoord( + ci, excludeCoords, + qc); // excludeCoord: Ctarget, Csource, Cancilla + auto hA = this->hardwareQubits.getHwQubit(cA); + auto qA = this->mapping.getCircQubit(hA); + Cancilla.push_back(cA); + Hancilla.push_back(hA); + Qancilla.push_back(qA); + excludeCoords.push_back(cA); + + // 1. compare qs, qA + if (arch.getEuclideanDistance(this->arch.getCoordinate(ci), + this->arch.getCoordinate(cA)) > r_int) { + // -> 1-1. passby (qA -> qi) + addedQc.passby(qA, {qi}); + needPassby[i] = true; + NumPassby++; + } + + //-> cx (qi, qA) + addedQc.cx(qi, qA); + + // 2. compare qA, {Qtarget, previous qAs} + // -> 2-1. passby (cA -> Ct) + addedQc.passby(qA, passbyQtarget); + NumPassby++; + passbyQtarget.push_back(qA); + } + + // 2-2. mcz(Qtarget, Qancilla) + qc::Controls mczControl; + mczControl.insert(Qtarget.begin(), Qtarget.end()); + mczControl.insert(Qancilla.begin(), Qancilla.end() - 1); + qc::Qubit mczTarget = Qancilla.back(); + addedQc.mcz(mczControl, mczTarget); + + // 3. passby -> cx + for (uint32_t i = 0; i < QsourceVec.size(); i++) { + if (needPassby[i]) { + auto qA = Qancilla[i]; + auto qi = QsourceVec[i]; + auto cA = Cancilla[i]; + auto ci = Csource[i]; + addedQc.passby(qA, {qi}); + NumPassby++; + } + // else{ //TODO: where q_ancilla is moved? + // addedQc.move( Qancilla[i], Cancilla[i] ); + // } + addedQc.cx(QsourceVec[i], Qancilla[i]); + } + + // find bestAddedQc + qc::CircuitOptimizer::replaceMCXWithMCZ(addedQc); + if (addedQc.size() < bestNumMoves) { + bestNumMoves = addedQc.size(); + bestAddedQc = addedQc; + // for debugging + bestIdx = idx; + bestQtarget = Qtarget; + bestQsource = QsourceVec; + bestNumPassby = NumPassby; + } + // TODO: how to find the best addedQc? + // else if(addedQc.size() == bestNumMoves){ + // } + } while (std::next_permutation(QsourceVec.begin(), QsourceVec.end())); + } + // return the best result + if (this->parameters.verbose) { + std::cout << "best FA: " << bestIdx << "th) Qtarget: {"; + for (auto i : bestQtarget) { + std::cout << i << ", "; + } + std::cout << "} <- bestQsource: {"; + for (auto i : bestQsource) { + std::cout << i << ", "; + } + std::cout << "} w/ numFA: " << bestQsource.size() << "\n"; + } + return std::make_pair(bestAddedQc, bestNumPassby); + } +} + +std::set> +NeutralAtomMapper::findQtargetSet(std::set& usedQubits) { + std::set> QtargetSet; + auto numUsedQubits = usedQubits.size(); + SymmetricMatrix gateQubitDistances(numUsedQubits); + for (uint32_t i = 0; i < numUsedQubits; ++i) { + for (uint32_t j = 0; j <= i; ++j) { + if (i == j) + gateQubitDistances(i, j) = 0; + qc::Qubit qi = *(std::next(usedQubits.begin(), i)); + qc::Qubit qj = *(std::next(usedQubits.begin(), j)); + gateQubitDistances(i, j) = this->hardwareQubits.getSwapDistance( + this->mapping.getHwQubit(qi), this->mapping.getHwQubit(qj)); + } + } + + size_t maxSize = 0; + for (int i = 0; i < numUsedQubits; ++i) { + std::vector currentVec; + qc::Qubit qi = *(std::next(usedQubits.begin(), i)); + currentVec.push_back(qi); + for (int j = 0; j < numUsedQubits; ++j) { + if (i != j) { + qc::Qubit qj = *(std::next(usedQubits.begin(), j)); + bool isInteractable = true; + for (auto& q : currentVec) { + auto it = std::find(usedQubits.begin(), usedQubits.end(), q); + uint32_t idx; + if (it != usedQubits.end()) { + idx = std::distance(usedQubits.begin(), it); + } + if (gateQubitDistances(idx, j) != 0) { + isInteractable = false; + break; + } + } + if (isInteractable) { + currentVec.push_back(qj); + } + } + } + std::set currentSet(currentVec.begin(), currentVec.end()); + if (currentSet.size() > maxSize) { + maxSize = currentSet.size(); + QtargetSet.clear(); + QtargetSet.insert(currentSet); + } else if (currentSet.size() == maxSize) { + QtargetSet.insert(currentSet); + } + } + return QtargetSet; +} + +CoordIndex +NeutralAtomMapper::returnClosestAncillaCoord(const CoordIndex& c_target, + const CoordIndices& excludeCoords, + qc::QuantumComputation& qc) { + auto const originalVector = this->arch.getVector( + c_target + arch.getNcolumns(), c_target); // startCoord, targetCoord + auto const originalDirection = originalVector.direction; + auto AncillaTargets = this->hardwareQubits.findClosestAncillaCoord( + c_target, originalDirection, qc.getNqubits(), excludeCoords); + return AncillaTargets[0]; +} + +bool NeutralAtomMapper::compareShuttlingAndFlyingancilla( + AtomMove bestMove, qc::QuantumComputation& bestFA, const qc::DAG& dag, + NeutralAtomLayer& frontLayer) { + // TODO: implement cost function + return false; +} + +void NeutralAtomMapper::updateMappingFlyingAncilla( + qc::QuantumComputation& bestFA, const qc::Operation* targetOp, + uint32_t numPassby, NeutralAtomLayer& frontLayer, + NeutralAtomLayer& lookaheadLayer) { + // add bestFA to mappedQc + // TODO: solve the error (gate type pass_by could not be converted to + // OpenQASM) + + for (const auto& opPtr : bestFA) { + const auto* op = opPtr.get(); + if (op->getType() == qc::OpType::H) { + mappedQc.h(*op->getUsedQubits().begin()); + } + if (op->getType() == qc::OpType::Z) { + if (op->getUsedQubits().size() > 1) { + mappedQc.mcz(op->getControls(), op->getTargets()[0]); + } else { + mappedQc.z(*op->getUsedQubits().begin()); + } + } + if (op->getType() == qc::OpType::PassBy) { + mappedQc.passby(*op->getControls().begin(), op->getTargets()); + } + if (op->getType() == qc::OpType::Move) { + mappedQc.move(op->getTargets()[0], op->getTargets()[1]); + } + } + + // remove original gate + GateList removeGates; + removeGates.push_back(targetOp); + frontLayer.removeGatesAndUpdate(removeGates); + lookaheadLayer.removeGatesAndUpdate(removeGates); + if (std::find(frontLayerShuttling.begin(), frontLayerShuttling.end(), + targetOp) != frontLayerShuttling.end()) { + frontLayerShuttling.erase(std::find(frontLayerShuttling.begin(), + frontLayerShuttling.end(), targetOp)); + } + if (std::find(lookaheadLayerShuttling.begin(), lookaheadLayerShuttling.end(), + targetOp) != lookaheadLayerShuttling.end()) { + lookaheadLayerShuttling.erase(std::find(lookaheadLayerShuttling.begin(), + lookaheadLayerShuttling.end(), + targetOp)); + } + + nFAncillas += numPassby; +} + } // namespace na diff --git a/src/hybridmap/MoveToAodConverter.cpp b/src/hybridmap/MoveToAodConverter.cpp index ad71c8a1f..cc3fc5c97 100644 --- a/src/hybridmap/MoveToAodConverter.cpp +++ b/src/hybridmap/MoveToAodConverter.cpp @@ -73,6 +73,11 @@ void MoveToAodConverter::initMoveGroups(qc::QuantumComputation& qc) { currentMoveGroup = MoveGroup(); currentMoveGroup.add(move, idx); } + // TODO: make AtomMove for flying ancilla -> add to moveGroup + } else if (op->getType() == qc::OpType::PassBy) { + HwQubit q_control = op->getControls().begin()->qubit; + qc::Targets Q_target = op->getTargets(); + std::vector C_target; } else if (op->getNqubits() > 1 && !currentMoveGroup.moves.empty()) { for (const auto& qubit : op->getUsedQubits()) { if (std::find(currentMoveGroup.qubitsUsedByGates.begin(), diff --git a/test/hybridmap/architectures/rubidium.json b/test/hybridmap/architectures/rubidium_gate.json similarity index 100% rename from test/hybridmap/architectures/rubidium.json rename to test/hybridmap/architectures/rubidium_gate.json diff --git a/test/hybridmap/main.cpp b/test/hybridmap/main.cpp new file mode 100644 index 000000000..a59e082b2 --- /dev/null +++ b/test/hybridmap/main.cpp @@ -0,0 +1,162 @@ +// +// Created by Ludwig Schmid on 06.11.23. +// + +#include "Definitions.hpp" +#include "QuantumComputation.hpp" +#include "filesystem" +#include "hybridmap/HybridNeutralAtomMapper.hpp" +#include "hybridmap/NeutralAtomArchitecture.hpp" +#include "hybridmap/NeutralAtomScheduler.hpp" +#include "hybridmap/NeutralAtomUtils.hpp" + +#include +#include +#include +#include +#include +#include + +int main(int argc, char* argv[]) { + if (argc != 14) { + std::cerr + << "Usage: " << argv[0] + << " " + " " + " " + " " + " \n"; + return 1; + } + + const int runIdx = std::atoi(argv[1]); + const std::string input_directory = argv[2]; + const std::string output_directory = argv[3]; + const double lookaheadGate = std::stod(argv[4]); + const double lookaheadShuttling = std::stod(argv[5]); + const double gateDecay = std::stod(argv[6]); + const double shuttlingTimeWeight = std::stod(argv[7]); + const double gateWeight = std::stod(argv[8]); + const double shuttlingWeight = std::stod(argv[9]); + const bool verbose = std::atoi(argv[10]) != 0; + const std::string json_config_file_path = argv[11]; + const std::string initialCoordinateMapping = argv[12]; + const std::string initialCircuitMapping = argv[13]; + + // Check if the output directory exists and create it if it doesn't. + if (!std::filesystem::exists(output_directory)) { + if (!std::filesystem::create_directory(output_directory)) { + std::cerr << "Failed to create the output directory.\n"; + return 1; + } + } + + // init Mappings + na::InitialCoordinateMapping initialCoordinateMappingEnum = + na::InitialCoordinateMapping::Trivial; + if (initialCoordinateMapping == "trivial") { + initialCoordinateMappingEnum = na::InitialCoordinateMapping::Trivial; + } else if (initialCoordinateMapping == "graph") { + initialCoordinateMappingEnum = na::InitialCoordinateMapping::Graph; + } else if (initialCoordinateMapping == "random") { + initialCoordinateMappingEnum = na::InitialCoordinateMapping::Random; + } else { + std::cerr << "Unknown initial coordinate mapping: " + << initialCoordinateMapping << "\n"; + return 1; + } + na::InitialMapping initialCircuitMappingEnum = na::InitialMapping::Identity; + if (initialCircuitMapping == "identity") { + initialCircuitMappingEnum = na::InitialMapping::Identity; + } else { + std::cerr << "Unknown initial circuit mapping: " << initialCircuitMapping + << "\n"; + return 1; + } + + // read files + std::vector qasmFiles; + for (const auto& entry : + std::filesystem::directory_iterator(input_directory)) { + if (entry.is_regular_file() && entry.path().extension() == ".qasm") { + qasmFiles.push_back(entry.path().string()); + } + } + + // output file + std::ofstream ofsResults(output_directory + "/" + std::to_string(runIdx) + + ".csv"); + + for (const auto& qasmFile : qasmFiles) { + // create arch + na::NeutralAtomArchitecture const arch = + na::NeutralAtomArchitecture(json_config_file_path); + // start mapping + auto startTime = std::chrono::high_resolution_clock::now(); + na::MapperParameters mapperParameters; + mapperParameters.lookaheadWeightSwaps = lookaheadGate; + mapperParameters.lookaheadWeightMoves = lookaheadShuttling; + mapperParameters.decay = gateDecay; + mapperParameters.shuttlingTimeWeight = shuttlingTimeWeight; + mapperParameters.gateWeight = gateWeight; + mapperParameters.shuttlingWeight = shuttlingWeight; + mapperParameters.initialMapping = initialCoordinateMappingEnum; + mapperParameters.verbose = verbose; + na::NeutralAtomMapper mapper = + na::NeutralAtomMapper(arch, mapperParameters); + + std::cout << "Mapping " << qasmFile << "\n"; + qc::QuantumComputation qc = qc::QuantumComputation(qasmFile); + auto qcMapped = + mapper.map(qc, initialCircuitMappingEnum, initialCoordinateMappingEnum); + std::ofstream ofs(output_directory + "/" + + std::filesystem::path(qasmFile).filename().string() + + "_" + std::to_string(runIdx) + ".qasm_ext"); + bool openQASM3 = false; + qcMapped.dumpOpenQASM(ofs, openQASM3); + auto qcAodMapped = mapper.convertToAod(qcMapped); + std::ofstream ofs_aod(output_directory + "/" + + std::filesystem::path(qasmFile).filename().string() + + "_" + std::to_string(runIdx) + ".qasm_aod"); + qcAodMapped.dumpOpenQASM(ofs_aod, openQASM3); + auto endTime = std::chrono::high_resolution_clock::now(); + auto timeTaken = std::chrono::duration_cast( + endTime - startTime) + .count(); + // do the scheduling + na::NeutralAtomScheduler scheduler = na::NeutralAtomScheduler(arch); + bool const createAnimationCsv = true; + qc::fp const shuttlingSpeedFactor = 0.1; + auto schedulerResults = + scheduler.schedule(qcAodMapped, mapper.getInitHwPos(), verbose, + createAnimationCsv, shuttlingSpeedFactor); + scheduler.saveAnimationCsv( + output_directory + "/" + + std::filesystem::path(qasmFile).filename().string() + "_" + + std::to_string(runIdx) + "_animate.csv"); + + // dump the results + ofsResults << std::filesystem::path(qasmFile).filename().string() + ", " + + schedulerResults.toCsv() + "\n"; + + // dump the parameters + std::ofstream ofs_params(output_directory + "/parameters_" + + std::to_string(runIdx) + ".txt"); + ofs_params << "lookaheadGate: " << lookaheadGate << "\n"; + ofs_params << "lookaheadShuttling: " << lookaheadShuttling << "\n"; + ofs_params << "gateDecay: " << gateDecay << "\n"; + ofs_params << "shuttlingTimeWeight: " << shuttlingTimeWeight << "\n"; + ofs_params << "gateWeight: " << gateWeight << "\n"; + ofs_params << "shuttlingWeight: " << shuttlingWeight << "\n"; + ofs_params << "verbose: " << verbose << "\n"; + ofs_params << "json_config_file_path: " << json_config_file_path << "\n"; + ofs_params << "initialCoordinateMapping: " << initialCoordinateMapping + << "\n"; + ofs_params << "initialCircuitMapping: " << initialCircuitMapping << "\n"; + // close the file + ofs_params.close(); + std::cout << "* runtime: " << timeTaken << '\n'; + } + + return 0; +} From 410e1f97929819100263087de0729b00ac1d6e56 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Mon, 20 Jan 2025 15:21:12 +0100 Subject: [PATCH 058/394] =?UTF-8?q?=F0=9F=93=A6=20add=20initial=20coordina?= =?UTF-8?q?te=20mapping=20to=20map=20method.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridNeutralAtomMapper.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index 744bd25e3..4a763eb08 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -465,9 +465,9 @@ class NeutralAtomMapper { * @return The mapped quantum circuit with abstract SWAP gates and MOVE * operations */ - qc::QuantumComputation map(qc::QuantumComputation& qc, - InitialMapping initialMapping, - InitialCoordinateMapping initialCoordinateMapping); + qc::QuantumComputation + map(qc::QuantumComputation& qc, InitialMapping initialMapping, + InitialCoordinateMapping initialCoordinateMapping = Trivial); /** * @brief Maps the given quantum circuit to the given architecture and From 16e4341042c2b894ecc5e46706df76ac4495c1b6 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Tue, 21 Jan 2025 17:20:03 +0100 Subject: [PATCH 059/394] =?UTF-8?q?=F0=9F=90=9B=20fixed=20euclidean=20dist?= =?UTF-8?q?ance=20computation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HardwareQubits.hpp | 14 ++++++++++++++ include/hybridmap/NeutralAtomArchitecture.hpp | 4 ++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/include/hybridmap/HardwareQubits.hpp b/include/hybridmap/HardwareQubits.hpp index 66a98f5f2..fc9377892 100644 --- a/include/hybridmap/HardwareQubits.hpp +++ b/include/hybridmap/HardwareQubits.hpp @@ -160,6 +160,20 @@ class HardwareQubits { */ void move(HwQubit hwQubit, CoordIndex newCoord); + void removeHwQubit(HwQubit hwQubit) { + hwToCoordIdx.erase(hwQubit); + initialHwPos.erase(hwQubit); + // set swap distances to -1 + for (uint32_t i = 0; i < swapDistances.size(); ++i) { + swapDistances(hwQubit, i) = -1; + swapDistances(i, hwQubit) = -1; + } + nearbyQubits.erase(hwQubit); + for (auto& [qubit, nearby] : nearbyQubits) { + nearby.erase(hwQubit); + } + } + /** * @brief Converts gate qubits from hardware qubits to coordinate indices. * @param op The operation. diff --git a/include/hybridmap/NeutralAtomArchitecture.hpp b/include/hybridmap/NeutralAtomArchitecture.hpp index f026f8fa0..dc4d06654 100644 --- a/include/hybridmap/NeutralAtomArchitecture.hpp +++ b/include/hybridmap/NeutralAtomArchitecture.hpp @@ -384,8 +384,8 @@ class NeutralAtomArchitecture { */ [[nodiscard]] qc::fp getEuclideanDistance(const CoordIndex idx1, const CoordIndex idx2) const { - return static_cast(this->coordinates.at(idx1).getEuclideanDistance( - this->coordinates.at(idx2))); + return this->coordinates.at(idx1).getEuclideanDistance( + this->coordinates.at(idx2)); } /** * @brief Get the Euclidean distance between two coordinates From 261b3c626eabc8307021b28e29bd02f3ae1306c1 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Tue, 21 Jan 2025 17:24:11 +0100 Subject: [PATCH 060/394] =?UTF-8?q?=F0=9F=9A=A7=20part=20of=20rework:=20sw?= =?UTF-8?q?ap=20and=20moves?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridNeutralAtomMapper.hpp | 31 +- src/hybridmap/HybridNeutralAtomMapper.cpp | 651 +++++++----------- 2 files changed, 276 insertions(+), 406 deletions(-) diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index 4a763eb08..748bbcf59 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -13,6 +13,7 @@ #include "hybridmap/NeutralAtomDefinitions.hpp" #include "hybridmap/NeutralAtomScheduler.hpp" #include "hybridmap/NeutralAtomUtils.hpp" +#include "ir/Permutation.hpp" #include "ir/QuantumComputation.hpp" #include "ir/operations/Operation.hpp" @@ -44,6 +45,14 @@ struct MapperParameters { InitialCoordinateMapping initialMapping; }; +enum RoutingType { + SwapType, + BridgeType, + MoveType, + PassByType, + FlyingAncillaType +}; + /** * @brief Class to map a quantum circuit to a neutral atom architecture. * @details The mapping has following important parts: @@ -74,14 +83,10 @@ class NeutralAtomMapper { NeutralAtomScheduler scheduler; // The gates that have been executed std::vector executedCommutingGates; - // Gates in the front layer to be executed with swap gates - GateList frontLayerGate; - // Gates in the front layer to be executed with move operations - GateList frontLayerShuttling; - // Gates in the lookahead layer to be executed with swap gates - GateList lookaheadLayerGate; - // Gates in the lookahead layer to be executed with move operations - GateList lookaheadLayerShuttling; + // Gates in the front layer to be executed + NeutralAtomLayer frontLayer; + // Gates in the lookahead layer to be executed + NeutralAtomLayer lookaheadLayer; // The minimal weight for any multi-qubit gate qc::fp twoQubitSwapWeight = 1; // The runtime parameters of the mapper @@ -100,6 +105,7 @@ class NeutralAtomMapper { // The current placement of the hardware qubits onto the coordinates HardwareQubits hardwareQubits; + qc::Permutation flyingAncillas; // The current mapping between circuit qubits and hardware qubits Mapping mapping; @@ -226,8 +232,8 @@ class NeutralAtomMapper { * based on the cost function. * @return The current best move operation */ - AtomMove findBestAtomMove(); - std::pair findBestAtomMoveWithOp(); + MoveComb findBestAtomMove(); + std::pair findBestAtomMoveWithOp(); /** * @brief Returns all possible move combinations for the front layer. * @details This includes direct moves, move away and multi-qubit moves. @@ -365,7 +371,7 @@ class NeutralAtomMapper { * @param layer The layer to compute the distance reduction for * @return The distance reduction cost */ - qc::fp moveCostPerLayer(const AtomMove& move, GateList& layer); + qc::fp moveCostPerLayer(const AtomMove& move, const GateList& layer); /** * @brief Calculates a parallelization cost if the move operation can be @@ -406,7 +412,8 @@ class NeutralAtomMapper { const MapperParameters& p = MapperParameters()) : arch(architecture), mappedQc(architecture.getNpositions()), mappedQcAOD(architecture.getNpositions()), scheduler(architecture), - parameters(p), + frontLayer(NeutralAtomLayer(qc::DAG())), + lookaheadLayer(NeutralAtomLayer(qc::DAG())), parameters(p), hardwareQubits(architecture, parameters.initialMapping, std::vector(), std::vector(), parameters.seed) { diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 877683810..bcc19c6aa 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -37,7 +37,6 @@ qc::QuantumComputation NeutralAtomMapper::map(qc::QuantumComputation& qc, InitialMapping initialMapping, InitialCoordinateMapping initialCoordinateMapping) { - mappedQc = qc::QuantumComputation(arch.getNpositions()); nMoves = 0; nSwaps = 0; nBridges = 0; @@ -47,17 +46,26 @@ NeutralAtomMapper::map(qc::QuantumComputation& qc, qc::CircuitOptimizer::flattenOperations(qc); qc::CircuitOptimizer::removeFinalMeasurements(qc); + // Compute number of flying ancillas + auto nAncillas = arch.getNqubits() - qc.getNqubits(); + auto nQubits = qc.getNqubits(); + + mappedQc = qc::QuantumComputation(qc.getNqubits()); + mappedQc.addQubitRegister(nAncillas, "a"); + // remove ancillas from hardware qubit mapping + for (auto i = nQubits; i < arch.getNqubits(); ++i) { + hardwareQubits.removeHwQubit(i); + } + auto dag = qc::CircuitOptimizer::constructDAG(qc); // init mapping - this->mapping = Mapping(arch.getNqubits(), initialMapping); + this->mapping = Mapping(nQubits, initialMapping); // init coord mapping - auto archNqubits = arch.getNqubits(); - auto archNpositions = arch.getNpositions(); std::vector qubitIndices( - archNqubits, std::numeric_limits::max()); - std::vector hwIndices(archNpositions, + nQubits, std::numeric_limits::max()); + std::vector hwIndices(arch.getNpositions(), std::numeric_limits::max()); if (initialCoordinateMapping == Graph) { @@ -80,10 +88,10 @@ NeutralAtomMapper::map(qc::QuantumComputation& qc, */ // init layers - NeutralAtomLayer frontLayer(dag); + frontLayer = NeutralAtomLayer(dag); frontLayer.initLayerOffset(); mapAllPossibleGates(frontLayer); - NeutralAtomLayer lookaheadLayer(dag); + lookaheadLayer = NeutralAtomLayer(dag); lookaheadLayer.initLayerOffset(frontLayer.getIteratorOffset()); // Checks @@ -96,107 +104,44 @@ NeutralAtomMapper::map(qc::QuantumComputation& qc, for (uint32_t i = this->arch.getNcolumns(); i > 0; --i) { this->decayWeights.emplace_back(std::exp(-this->parameters.decay * i)); } + // save last swap to prevent immediate swap back + Swap lastSwap = {0, 0}; auto i = 0; while (!frontLayer.getGates().empty()) { // assign gates to layers - reassignGatesToLayers(frontLayer.getGates(), lookaheadLayer.getGates()); + ++i; if (this->parameters.verbose) { + std::cout << "iteration " << i << '\n'; printLayers(); } - - // save last swap to prevent immediate swap back - Swap lastSwap = {0, 0}; - - // first do all gate based mapping gates - while (!this->frontLayerGate.empty()) { - GateList gatesToExecute; - while (gatesToExecute.empty() && !this->frontLayerGate.empty()) { - ++i; - if (this->parameters.verbose) { - std::cout << "iteration " << i << '\n'; - } - - // find swap - auto bestSwap = findBestSwap(lastSwap); - - // find bridge - auto allBridges = findAllBridges(qc); - auto ExecutableBridges = - bridgeCostCompareWithSwap(allBridges, bestSwap, dag, frontLayer); - - // execute bridge gate - if (!ExecutableBridges.empty()) { - updateMappingBridge(ExecutableBridges, frontLayer, lookaheadLayer); - mapAllPossibleGates(frontLayer); - lookaheadLayer.initLayerOffset(frontLayer.getIteratorOffset()); - reassignGatesToLayers(frontLayer.getGates(), - lookaheadLayer.getGates()); - if (this->parameters.verbose) { - printLayers(); - } - } - - // execute swap gate - else { - lastSwap = bestSwap; - updateMappingSwap(bestSwap); - } - gatesToExecute = getExecutableGates(frontLayer.getGates()); - } - mapAllPossibleGates(frontLayer); - lookaheadLayer.initLayerOffset(frontLayer.getIteratorOffset()); - reassignGatesToLayers(frontLayer.getGates(), lookaheadLayer.getGates()); - if (this->parameters.verbose) { - printLayers(); - } - } - // then do all shuttling based mapping gates - while (!this->frontLayerShuttling.empty()) { - GateList gatesToExecute; - while (gatesToExecute.empty() && !this->frontLayerShuttling.empty()) { - ++i; - if (this->parameters.verbose) { - std::cout << "iteration " << i << '\n'; - } - - // find best move - auto [bestMove, opForMove] = findBestAtomMoveWithOp(); - - // find flying ancilla - auto [bestFA, numPassby] = findBestFlyingAncilla(qc, opForMove); - - // TODO: compare shuttling vs f.a. - auto useShuttling = - compareShuttlingAndFlyingancilla(bestMove, bestFA, dag, frontLayer); - - // execute shuttling - if (useShuttling) { - updateMappingMove(bestMove); - } - // execute flying ancilla - else { - updateMappingFlyingAncilla(bestFA, opForMove, numPassby, frontLayer, - lookaheadLayer); - mapAllPossibleGates(frontLayer); - lookaheadLayer.initLayerOffset(frontLayer.getIteratorOffset()); - reassignGatesToLayers(frontLayer.getGates(), - lookaheadLayer.getGates()); - if (this->parameters.verbose) { - printLayers(); - } - } - - gatesToExecute = getExecutableGates(frontLayer.getGates()); - } - mapAllPossibleGates(frontLayer); - lookaheadLayer.initLayerOffset(frontLayer.getIteratorOffset()); - reassignGatesToLayers(frontLayer.getGates(), lookaheadLayer.getGates()); - if (this->parameters.verbose) { - printLayers(); - } - } + // find swap + auto bestSwap = findBestSwap(lastSwap); + + // find bridge + auto allBridges = findAllBridges(qc); + + // find best move + auto bestCombAndOp = findBestAtomMoveWithOp(); + auto bestMove = bestCombAndOp.first; + auto opForMove = bestCombAndOp.second; + + // // find flying ancilla + // auto [bestFA, numPassby] = findBestFlyingAncilla(qc, opForMove); + // + // execute swap gate + // if (false) { + // lastSwap = bestSwap; + // updateMappingSwap(bestSwap); + // } else if (false) { + // updateMappingMove(bestMove); + // } else { + // // update mapping for flying ancilla + // } + mapAllPossibleGates(frontLayer); + lookaheadLayer.initLayerOffset(frontLayer.getIteratorOffset()); } + if (this->parameters.verbose) { std::cout << "nSwaps: " << nSwaps << '\n'; std::cout << "nBridges: " << nBridges << '\n'; @@ -407,30 +352,6 @@ NeutralAtomMapper::convertToAod(qc::QuantumComputation& qc) { return mappedQcAOD; } -void NeutralAtomMapper::reassignGatesToLayers(const GateList& frontGates, - const GateList& lookaheadGates) { - // assign gates to gates or shuttling - this->frontLayerGate.clear(); - this->frontLayerShuttling.clear(); - for (const auto& gate : frontGates) { - if (swapGateBetter(gate)) { - this->frontLayerGate.emplace_back(gate); - } else { - this->frontLayerShuttling.emplace_back(gate); - } - } - - this->lookaheadLayerGate.clear(); - this->lookaheadLayerShuttling.clear(); - for (const auto& gate : lookaheadGates) { - if (swapGateBetter(gate)) { - this->lookaheadLayerGate.emplace_back(gate); - } else { - this->lookaheadLayerShuttling.emplace_back(gate); - } - } -} - void NeutralAtomMapper::mapGate(const qc::Operation* op) { if (op->getType() == qc::OpType::I) { return; @@ -471,33 +392,16 @@ bool NeutralAtomMapper::isExecutable(const qc::Operation* opPointer) { } void NeutralAtomMapper::printLayers() { - std::cout << "f,g: "; - for (const auto* op : this->frontLayerGate) { + std::cout << "f: "; + for (const auto* op : this->frontLayer.getGates()) { std::cout << op->getName() << " "; for (auto qubit : op->getUsedQubits()) { std::cout << qubit << " "; } std::cout << '\n'; } - std::cout << "f,s: "; - for (const auto* op : this->frontLayerShuttling) { - std::cout << op->getName() << " "; - for (auto qubit : op->getUsedQubits()) { - std::cout << qubit << " "; - } - std::cout << '\n'; - } - std::cout << "l,g: "; - for (const auto* op : this->lookaheadLayerGate) { - std::cout << op->getName() << " "; - for (auto qubit : op->getUsedQubits()) { - std::cout << qubit << " "; - } - std::cout << '\n'; - } - std::cout << '\n'; - std::cout << "l,g: "; - for (const auto* op : this->lookaheadLayerShuttling) { + std::cout << "l: "; + for (const auto* op : this->lookaheadLayer.getGates()) { std::cout << op->getName() << " "; for (auto qubit : op->getUsedQubits()) { std::cout << qubit << " "; @@ -569,8 +473,8 @@ void NeutralAtomMapper::updateMappingMove(AtomMove move) { Swap NeutralAtomMapper::findBestSwap(const Swap& lastSwap) { // compute necessary movements - auto swapsFront = initSwaps(this->frontLayerGate); - auto swapsLookahead = initSwaps(this->lookaheadLayerGate); + auto swapsFront = initSwaps(this->frontLayer.getGates()); + auto swapsLookahead = initSwaps(this->lookaheadLayer.getGates()); setTwoQubitSwapWeight(swapsFront.second); // evaluate swaps based on cost function @@ -641,12 +545,12 @@ qc::fp NeutralAtomMapper::swapCost( // compute the change in total distance auto distanceChangeFront = swapCostPerLayer(swap, swapCloseByFront, swapExactFront) / - static_cast(this->frontLayerGate.size()); + static_cast(this->frontLayer.getGates().size()); qc::fp distanceChangeLookahead = 0; - if (!this->lookaheadLayerGate.empty()) { + if (!this->lookaheadLayer.getGates().empty()) { distanceChangeLookahead = swapCostPerLayer(swap, swapCloseByLookahead, swapExactLookahead) / - static_cast(this->lookaheadLayerGate.size()); + static_cast(this->lookaheadLayer.getGates().size()); } auto cost = parameters.lookaheadWeightSwaps * distanceChangeLookahead + distanceChangeFront; @@ -826,20 +730,6 @@ HwQubits NeutralAtomMapper::getBestMultiQubitPosition(const qc::Operation* op) { qubitQueue.emplace(totalDist, nearbyQubit); } } - // find gate and move it to the shuttling layer - auto idxFrontGate = - std::find(this->frontLayerGate.begin(), this->frontLayerGate.end(), op); - if (idxFrontGate != this->frontLayerGate.end()) { - this->frontLayerGate.erase(idxFrontGate); - this->frontLayerShuttling.emplace_back(op); - } - // remove from lookahead layer if there - auto idxLookaheadGate = std::find(this->lookaheadLayerGate.begin(), - this->lookaheadLayerGate.end(), op); - if (idxLookaheadGate != this->lookaheadLayerGate.end()) { - this->lookaheadLayerGate.erase(idxLookaheadGate); - this->lookaheadLayerShuttling.emplace_back(op); - } return {}; } @@ -942,20 +832,6 @@ NeutralAtomMapper::getExactSwapsToPosition(const qc::Operation* op, } if (minimalDistance == std::numeric_limits::max()) { // not possible to move to position - // move gate to shuttling layer - auto idxFrontGate = std::find(this->frontLayerGate.begin(), - this->frontLayerGate.end(), op); - if (idxFrontGate != this->frontLayerGate.end()) { - this->frontLayerGate.erase(idxFrontGate); - this->frontLayerShuttling.emplace_back(op); - } - // remove from lookahead layer if there - auto idxLookaheadGate = std::find(this->lookaheadLayerGate.begin(), - this->lookaheadLayerGate.end(), op); - if (idxLookaheadGate != this->lookaheadLayerGate.end()) { - this->lookaheadLayerGate.erase(idxLookaheadGate); - this->lookaheadLayerShuttling.emplace_back(op); - } return {}; } minimalDistances.emplace_back(gateQubit, minimalDistancePosQubit, @@ -1026,7 +902,7 @@ NeutralAtomMapper::getExactSwapsToPosition(const qc::Operation* op, return swapsExact; } -AtomMove NeutralAtomMapper::findBestAtomMove() { +MoveComb NeutralAtomMapper::findBestAtomMove() { auto moveCombs = getAllMoveCombinations(); // compute cost for each move combination @@ -1046,10 +922,10 @@ AtomMove NeutralAtomMapper::findBestAtomMove() { [](const auto& move1, const auto& move2) { return move1.second < move2.second; }); - return bestMove->first.getFirstMove(); + return bestMove->first; } -std::pair +std::pair NeutralAtomMapper::findBestAtomMoveWithOp() { auto [moveCombs, moveCombsWithOp] = getAllMoveCombinationsWithOp(); @@ -1078,7 +954,7 @@ NeutralAtomMapper::findBestAtomMoveWithOp() { break; } } - return make_pair(bestAtomMove.getFirstMove(), corresOp); + return {bestAtomMove, corresOp}; } qc::fp NeutralAtomMapper::moveCostComb(const MoveComb& moveComb) { @@ -1091,20 +967,20 @@ qc::fp NeutralAtomMapper::moveCostComb(const MoveComb& moveComb) { qc::fp NeutralAtomMapper::moveCost(const AtomMove& move) { qc::fp cost = 0; - auto frontCost = moveCostPerLayer(move, this->frontLayerShuttling) / - static_cast(this->frontLayerShuttling.size()); + auto frontCost = moveCostPerLayer(move, this->frontLayer.getGates()) / + static_cast(this->frontLayer.getGates().size()); cost += frontCost; - if (!lookaheadLayerShuttling.empty()) { + if (!lookaheadLayer.getGates().empty()) { auto lookaheadCost = - moveCostPerLayer(move, this->lookaheadLayerShuttling) / - static_cast(this->lookaheadLayerShuttling.size()); + moveCostPerLayer(move, this->lookaheadLayer.getGates()) / + static_cast(this->lookaheadLayer.getGates().size()); cost += parameters.lookaheadWeightMoves * lookaheadCost; } if (!this->lastMoves.empty()) { auto parallelCost = parameters.shuttlingTimeWeight * parallelMoveCost(move) / static_cast(this->lastMoves.size()) / - static_cast(this->frontLayerShuttling.size()); + static_cast(this->frontLayer.getGates().size()); cost += parallelCost; } @@ -1112,7 +988,7 @@ qc::fp NeutralAtomMapper::moveCost(const AtomMove& move) { } qc::fp NeutralAtomMapper::moveCostPerLayer(const AtomMove& move, - GateList& layer) { + const GateList& layer) { // compute cost assuming the move was applied qc::fp distChange = 0; auto toMoveHwQubit = this->hardwareQubits.getHwQubit(move.first); @@ -1301,13 +1177,20 @@ NeutralAtomMapper::getMovePositionRec(MultiQubitMovePos currentPos, MoveCombs NeutralAtomMapper::getAllMoveCombinations() { MoveCombs allMoves; - for (const auto& op : this->frontLayerShuttling) { + for (const auto& op : this->frontLayer.getGates()) { auto usedQubits = op->getUsedQubits(); auto usedHwQubits = this->mapping.getHwQubits(usedQubits); auto usedCoordsSet = this->hardwareQubits.getCoordIndices(usedHwQubits); auto usedCoords = std::vector(usedCoordsSet.begin(), usedCoordsSet.end()); auto bestPos = getBestMovePos(usedCoords); + if (this->parameters.verbose) { + std::cout << "bestPos: "; + for (auto qubit : bestPos) { + std::cout << qubit << " "; + } + std::cout << '\n'; + } auto moves = getMoveCombinationsToPosition(usedHwQubits, bestPos); allMoves.addMoveCombs(moves); } @@ -1320,7 +1203,7 @@ NeutralAtomMapper::getAllMoveCombinationsWithOp() { MoveCombs allMoves; std::vector> allMovesWithOp; int i = 1; - for (const auto& op : this->frontLayerShuttling) { + for (const auto& op : this->frontLayer.getGates()) { auto usedQubits = op->getUsedQubits(); auto usedHwQubits = this->mapping.getHwQubits(usedQubits); auto usedCoordsSet = this->hardwareQubits.getCoordIndices(usedHwQubits); @@ -1609,7 +1492,7 @@ bool NeutralAtomMapper::swapGateBetter(const qc::Operation* opPointer) { std::vector> NeutralAtomMapper::findAllBridges(qc::QuantumComputation& qc) { std::vector> allBridges; - for (const auto* op : this->frontLayerGate) { + for (const auto* op : this->frontLayer.getGates()) { size_t usedQubitSize = op->getUsedQubits().size(); Qubits nearbyQubits; if (usedQubitSize == 2) { @@ -1688,18 +1571,6 @@ void NeutralAtomMapper::updateMappingBridge( // remove original gate frontLayer.removeGatesAndUpdate(removeGates); lookaheadLayer.removeGatesAndUpdate(removeGates); - for (const auto& gate : removeGates) { - if (std::find(frontLayerGate.begin(), frontLayerGate.end(), gate) != - frontLayerGate.end()) { - frontLayerGate.erase( - std::find(frontLayerGate.begin(), frontLayerGate.end(), gate)); - } - if (std::find(lookaheadLayerGate.begin(), lookaheadLayerGate.end(), gate) != - lookaheadLayerGate.end()) { - lookaheadLayerGate.erase(std::find(lookaheadLayerGate.begin(), - lookaheadLayerGate.end(), gate)); - } - } } std::vector> @@ -1790,148 +1661,151 @@ NeutralAtomMapper::bridgeCostCompareWithSwap( return ExecutableBridges; } -std::pair -NeutralAtomMapper::findBestFlyingAncilla(qc::QuantumComputation& qc, - const qc::Operation* targetOp) { - // information of operation - qc::QuantumComputation bestAddedQc; - uint32_t bestNumPassby = 0; - auto usedQubits = targetOp->getUsedQubits(); - auto QtargetSet = findQtargetSet(usedQubits); - if (!QtargetSet.empty()) { - int idx = 0; - int bestNumMoves = std::numeric_limits::max(); - - int bestIdx; - std::set bestQtarget; - std::vector bestQsource; - // #F.A. => (Q_source, Q_target) iteration - for (auto Qtarget : QtargetSet) { - int numFA = usedQubits.size() - Qtarget.size(); - uint32_t NumPassby = 0; - std::set Qsource; - std::set_difference(usedQubits.begin(), usedQubits.end(), Qtarget.begin(), - Qtarget.end(), std::inserter(Qsource, Qsource.end())); - - // permutate Qtarget & Qsource - std::vector QsourceVec(Qsource.begin(), Qsource.end()); - do { - qc::QuantumComputation addedQc; - addedQc = qc::QuantumComputation(arch.getNpositions()); - qc::fp r_int = arch.getInteractionRadius(); - - // hardware qubits & coord inices of Qtarget - auto Htarget = this->mapping.getHwQubits(Qtarget); - auto Ctarget = this->hardwareQubits.getCoordIndices(Htarget); - - std::vector Qancilla; - std::vector Hsource, Hancilla; - std::vector Csource, Cancilla; - for (auto qs : QsourceVec) { - // hardware qubits & coord inices of Qsource - auto hs = this->mapping.getHwQubit(qs); - Hsource.push_back(hs); - auto cs = this->hardwareQubits.getCoordIndex(hs); - Csource.push_back(cs); - } - std::vector excludeCoords; - std::copy(Ctarget.begin(), Ctarget.end(), - std::back_inserter(excludeCoords)); - std::copy(Csource.begin(), Csource.end(), - std::back_inserter(excludeCoords)); - - std::vector passbyQtarget; - std::copy(Qtarget.begin(), Qtarget.end(), - std::back_inserter(passbyQtarget)); - - std::vector needPassby(QsourceVec.size(), false); - for (uint32_t i = 0; i < QsourceVec.size(); i++) { - // find ancillaQubit of Qsource - auto qi = QsourceVec[i]; - auto ci = Csource[i]; - auto cA = returnClosestAncillaCoord( - ci, excludeCoords, - qc); // excludeCoord: Ctarget, Csource, Cancilla - auto hA = this->hardwareQubits.getHwQubit(cA); - auto qA = this->mapping.getCircQubit(hA); - Cancilla.push_back(cA); - Hancilla.push_back(hA); - Qancilla.push_back(qA); - excludeCoords.push_back(cA); - - // 1. compare qs, qA - if (arch.getEuclideanDistance(this->arch.getCoordinate(ci), - this->arch.getCoordinate(cA)) > r_int) { - // -> 1-1. passby (qA -> qi) - addedQc.passby(qA, {qi}); - needPassby[i] = true; - NumPassby++; - } - - //-> cx (qi, qA) - addedQc.cx(qi, qA); - - // 2. compare qA, {Qtarget, previous qAs} - // -> 2-1. passby (cA -> Ct) - addedQc.passby(qA, passbyQtarget); - NumPassby++; - passbyQtarget.push_back(qA); - } - - // 2-2. mcz(Qtarget, Qancilla) - qc::Controls mczControl; - mczControl.insert(Qtarget.begin(), Qtarget.end()); - mczControl.insert(Qancilla.begin(), Qancilla.end() - 1); - qc::Qubit mczTarget = Qancilla.back(); - addedQc.mcz(mczControl, mczTarget); - - // 3. passby -> cx - for (uint32_t i = 0; i < QsourceVec.size(); i++) { - if (needPassby[i]) { - auto qA = Qancilla[i]; - auto qi = QsourceVec[i]; - auto cA = Cancilla[i]; - auto ci = Csource[i]; - addedQc.passby(qA, {qi}); - NumPassby++; - } - // else{ //TODO: where q_ancilla is moved? - // addedQc.move( Qancilla[i], Cancilla[i] ); - // } - addedQc.cx(QsourceVec[i], Qancilla[i]); - } - - // find bestAddedQc - qc::CircuitOptimizer::replaceMCXWithMCZ(addedQc); - if (addedQc.size() < bestNumMoves) { - bestNumMoves = addedQc.size(); - bestAddedQc = addedQc; - // for debugging - bestIdx = idx; - bestQtarget = Qtarget; - bestQsource = QsourceVec; - bestNumPassby = NumPassby; - } - // TODO: how to find the best addedQc? - // else if(addedQc.size() == bestNumMoves){ - // } - } while (std::next_permutation(QsourceVec.begin(), QsourceVec.end())); - } - // return the best result - if (this->parameters.verbose) { - std::cout << "best FA: " << bestIdx << "th) Qtarget: {"; - for (auto i : bestQtarget) { - std::cout << i << ", "; - } - std::cout << "} <- bestQsource: {"; - for (auto i : bestQsource) { - std::cout << i << ", "; - } - std::cout << "} w/ numFA: " << bestQsource.size() << "\n"; - } - return std::make_pair(bestAddedQc, bestNumPassby); - } -} +// std::pair +// NeutralAtomMapper::findBestFlyingAncilla(qc::QuantumComputation& qc, +// const qc::Operation* targetOp) { +// // information of operation +// qc::QuantumComputation bestAddedQc; +// uint32_t bestNumPassby = 0; +// auto usedQubits = targetOp->getUsedQubits(); +// auto QtargetSet = findQtargetSet(usedQubits); +// if (!QtargetSet.empty()) { +// int idx = 0; +// int bestNumMoves = std::numeric_limits::max(); +// +// int bestIdx; +// std::set bestQtarget; +// std::vector bestQsource; +// // #F.A. => (Q_source, Q_target) iteration +// for (auto Qtarget : QtargetSet) { +// int numFA = usedQubits.size() - Qtarget.size(); +// uint32_t NumPassby = 0; +// std::set Qsource; +// std::set_difference(usedQubits.begin(), usedQubits.end(), +// Qtarget.begin(), +// Qtarget.end(), std::inserter(Qsource, +// Qsource.end())); +// +// // permutate Qtarget & Qsource +// std::vector QsourceVec(Qsource.begin(), Qsource.end()); +// do { +// qc::QuantumComputation addedQc; +// addedQc = qc::QuantumComputation(arch.getNpositions()); +// qc::fp r_int = arch.getInteractionRadius(); +// +// // hardware qubits & coord inices of Qtarget +// auto Htarget = this->mapping.getHwQubits(Qtarget); +// auto Ctarget = this->hardwareQubits.getCoordIndices(Htarget); +// +// std::vector Qancilla; +// std::vector Hsource, Hancilla; +// std::vector Csource, Cancilla; +// for (auto qs : QsourceVec) { +// // hardware qubits & coord inices of Qsource +// auto hs = this->mapping.getHwQubit(qs); +// Hsource.push_back(hs); +// auto cs = this->hardwareQubits.getCoordIndex(hs); +// Csource.push_back(cs); +// } +// std::vector excludeCoords; +// std::copy(Ctarget.begin(), Ctarget.end(), +// std::back_inserter(excludeCoords)); +// std::copy(Csource.begin(), Csource.end(), +// std::back_inserter(excludeCoords)); +// +// std::vector passbyQtarget; +// std::copy(Qtarget.begin(), Qtarget.end(), +// std::back_inserter(passbyQtarget)); +// +// std::vector needPassby(QsourceVec.size(), false); +// for (uint32_t i = 0; i < QsourceVec.size(); i++) { +// // find ancillaQubit of Qsource +// auto qi = QsourceVec[i]; +// auto ci = Csource[i]; +// auto cA = returnClosestAncillaCoord( +// ci, excludeCoords, +// qc); // excludeCoord: Ctarget, Csource, Cancilla +// auto hA = this->hardwareQubits.getHwQubit(cA); +// auto qA = this->mapping.getCircQubit(hA); +// Cancilla.push_back(cA); +// Hancilla.push_back(hA); +// Qancilla.push_back(qA); +// excludeCoords.push_back(cA); +// +// // 1. compare qs, qA +// if (arch.getEuclideanDistance(this->arch.getCoordinate(ci), +// this->arch.getCoordinate(cA)) > +// r_int) { +// // -> 1-1. passby (qA -> qi) +// addedQc.passby(qA, {qi}); +// needPassby[i] = true; +// NumPassby++; +// } +// +// //-> cx (qi, qA) +// addedQc.cx(qi, qA); +// +// // 2. compare qA, {Qtarget, previous qAs} +// // -> 2-1. passby (cA -> Ct) +// addedQc.passby(qA, passbyQtarget); +// NumPassby++; +// passbyQtarget.push_back(qA); +// } +// +// // 2-2. mcz(Qtarget, Qancilla) +// qc::Controls mczControl; +// mczControl.insert(Qtarget.begin(), Qtarget.end()); +// mczControl.insert(Qancilla.begin(), Qancilla.end() - 1); +// qc::Qubit mczTarget = Qancilla.back(); +// addedQc.mcz(mczControl, mczTarget); +// +// // 3. passby -> cx +// for (uint32_t i = 0; i < QsourceVec.size(); i++) { +// if (needPassby[i]) { +// auto qA = Qancilla[i]; +// auto qi = QsourceVec[i]; +// auto cA = Cancilla[i]; +// auto ci = Csource[i]; +// addedQc.passby(qA, {qi}); +// NumPassby++; +// } +// // else{ //TODO: where q_ancilla is moved? +// // addedQc.move( Qancilla[i], Cancilla[i] ); +// // } +// addedQc.cx(QsourceVec[i], Qancilla[i]); +// } +// +// // find bestAddedQc +// qc::CircuitOptimizer::replaceMCXWithMCZ(addedQc); +// if (addedQc.size() < bestNumMoves) { +// bestNumMoves = addedQc.size(); +// bestAddedQc = addedQc; +// // for debugging +// bestIdx = idx; +// bestQtarget = Qtarget; +// bestQsource = QsourceVec; +// bestNumPassby = NumPassby; +// } +// // TODO: how to find the best addedQc? +// // else if(addedQc.size() == bestNumMoves){ +// // } +// } while (std::next_permutation(QsourceVec.begin(), QsourceVec.end())); +// } +// // return the best result +// if (this->parameters.verbose) { +// std::cout << "best FA: " << bestIdx << "th) Qtarget: {"; +// for (auto i : bestQtarget) { +// std::cout << i << ", "; +// } +// std::cout << "} <- bestQsource: {"; +// for (auto i : bestQsource) { +// std::cout << i << ", "; +// } +// std::cout << "} w/ numFA: " << bestQsource.size() << "\n"; +// } +// return std::make_pair(bestAddedQc, bestNumPassby); +// } +// } std::set> NeutralAtomMapper::findQtargetSet(std::set& usedQubits) { @@ -2005,52 +1879,41 @@ bool NeutralAtomMapper::compareShuttlingAndFlyingancilla( return false; } -void NeutralAtomMapper::updateMappingFlyingAncilla( - qc::QuantumComputation& bestFA, const qc::Operation* targetOp, - uint32_t numPassby, NeutralAtomLayer& frontLayer, - NeutralAtomLayer& lookaheadLayer) { - // add bestFA to mappedQc - // TODO: solve the error (gate type pass_by could not be converted to - // OpenQASM) - - for (const auto& opPtr : bestFA) { - const auto* op = opPtr.get(); - if (op->getType() == qc::OpType::H) { - mappedQc.h(*op->getUsedQubits().begin()); - } - if (op->getType() == qc::OpType::Z) { - if (op->getUsedQubits().size() > 1) { - mappedQc.mcz(op->getControls(), op->getTargets()[0]); - } else { - mappedQc.z(*op->getUsedQubits().begin()); - } - } - if (op->getType() == qc::OpType::PassBy) { - mappedQc.passby(*op->getControls().begin(), op->getTargets()); - } - if (op->getType() == qc::OpType::Move) { - mappedQc.move(op->getTargets()[0], op->getTargets()[1]); - } - } - - // remove original gate - GateList removeGates; - removeGates.push_back(targetOp); - frontLayer.removeGatesAndUpdate(removeGates); - lookaheadLayer.removeGatesAndUpdate(removeGates); - if (std::find(frontLayerShuttling.begin(), frontLayerShuttling.end(), - targetOp) != frontLayerShuttling.end()) { - frontLayerShuttling.erase(std::find(frontLayerShuttling.begin(), - frontLayerShuttling.end(), targetOp)); - } - if (std::find(lookaheadLayerShuttling.begin(), lookaheadLayerShuttling.end(), - targetOp) != lookaheadLayerShuttling.end()) { - lookaheadLayerShuttling.erase(std::find(lookaheadLayerShuttling.begin(), - lookaheadLayerShuttling.end(), - targetOp)); - } - - nFAncillas += numPassby; -} +// void NeutralAtomMapper::updateMappingFlyingAncilla( +// qc::QuantumComputation& bestFA, const qc::Operation* targetOp, +// uint32_t numPassby, NeutralAtomLayer& frontLayer, +// NeutralAtomLayer& lookaheadLayer) { +// // add bestFA to mappedQc +// // TODO: solve the error (gate type pass_by could not be converted to +// // OpenQASM) +// +// for (const auto& opPtr : bestFA) { +// const auto* op = opPtr.get(); +// if (op->getType() == qc::OpType::H) { +// mappedQc.h(*op->getUsedQubits().begin()); +// } +// if (op->getType() == qc::OpType::Z) { +// if (op->getUsedQubits().size() > 1) { +// mappedQc.mcz(op->getControls(), op->getTargets()[0]); +// } else { +// mappedQc.z(*op->getUsedQubits().begin()); +// } +// } +// if (op->getType() == qc::OpType::PassBy) { +// mappedQc.passby(*op->getControls().begin(), op->getTargets()); +// } +// if (op->getType() == qc::OpType::Move) { +// mappedQc.move(op->getTargets()[0], op->getTargets()[1]); +// } +// } +// +// // remove original gate +// GateList removeGates; +// removeGates.push_back(targetOp); +// frontLayer.removeGatesAndUpdate(removeGates); +// lookaheadLayer.removeGatesAndUpdate(removeGates); +// +// nFAncillas += numPassby; +// } } // namespace na From 20f7d07d01581b2da9cefd755a1a6dcc15c9fb35 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Tue, 21 Jan 2025 17:47:01 +0100 Subject: [PATCH 061/394] =?UTF-8?q?=E2=9C=A8added=20shortest=20path=20comp?= =?UTF-8?q?utation=20for=20HwQubits?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HardwareQubits.hpp | 3 ++ src/hybridmap/HardwareQubits.cpp | 46 ++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/include/hybridmap/HardwareQubits.hpp b/include/hybridmap/HardwareQubits.hpp index fc9377892..3a5ba7d75 100644 --- a/include/hybridmap/HardwareQubits.hpp +++ b/include/hybridmap/HardwareQubits.hpp @@ -73,6 +73,9 @@ class HardwareQubits { */ void computeSwapDistance(HwQubit q1, HwQubit q2); + std::vector computeAllShortestPaths(HwQubit q1, + HwQubit q2) const; + /** * @brief Resets the swap distances between the hardware qubits. * @details Used after each shuttling operation to reset the swap distances. diff --git a/src/hybridmap/HardwareQubits.cpp b/src/hybridmap/HardwareQubits.cpp index 0e46527c5..28471f458 100644 --- a/src/hybridmap/HardwareQubits.cpp +++ b/src/hybridmap/HardwareQubits.cpp @@ -11,12 +11,14 @@ #include "hybridmap/NeutralAtomUtils.hpp" #include +#include #include #include #include #include #include #include +#include #include namespace na { @@ -81,6 +83,50 @@ void HardwareQubits::computeSwapDistance(HwQubit q1, HwQubit q2) { } } +std::vector +HardwareQubits::computeAllShortestPaths(const HwQubit q1, + const HwQubit q2) const { + std::vector allPaths; + std::queue pathsQueue; + size_t shortestPathLength = -1; + + // Initialize the queue with the starting qubit + pathsQueue.push(HwQubitsVector{q1}); + + while (!pathsQueue.empty()) { + auto currentPath = pathsQueue.front(); + pathsQueue.pop(); + + HwQubit const currentQubit = currentPath.back(); + + // Check if the destination is reached + if (currentQubit == q2) { + if (shortestPathLength == -1 || + currentPath.size() == shortestPathLength) { + shortestPathLength = currentPath.size(); + allPaths.push_back(currentPath); + } else if (currentPath.size() > shortestPathLength) { + // Since we use BFS, once a path longer than the shortest length is + // found, stop exploring + break; + } + continue; + } + + // Get nearby qubits and explore paths + for (const auto& neighbor : this->getNearbyQubits(currentQubit)) { + // Avoid cycles by ensuring the neighbor isn't already in the current path + if (std::find(currentPath.begin(), currentPath.end(), neighbor) == + currentPath.end()) { + auto newPath = currentPath; + newPath.push_back(neighbor); + pathsQueue.push(newPath); + } + } + } + + return allPaths; +} void HardwareQubits::resetSwapDistances() { // TODO Improve to only reset the swap distances necessary (use a breadth // first search) From d558223c0e47a5b25d11dc2c27f3de7b7fb19e7e Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Wed, 22 Jan 2025 09:09:21 +0100 Subject: [PATCH 062/394] =?UTF-8?q?=E2=9C=A8added=20computation=20of=20arb?= =?UTF-8?q?itrary=20length=20bridge=20gates?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HardwareQubits.hpp | 6 +- include/hybridmap/HybridNeutralAtomMapper.hpp | 13 + include/hybridmap/NeutralAtomDefinitions.hpp | 10 +- include/hybridmap/NeutralAtomLayer.hpp | 2 +- src/hybridmap/HybridNeutralAtomMapper.cpp | 366 ++++++++++-------- 5 files changed, 218 insertions(+), 179 deletions(-) diff --git a/include/hybridmap/HardwareQubits.hpp b/include/hybridmap/HardwareQubits.hpp index 3a5ba7d75..d2179dd91 100644 --- a/include/hybridmap/HardwareQubits.hpp +++ b/include/hybridmap/HardwareQubits.hpp @@ -73,9 +73,6 @@ class HardwareQubits { */ void computeSwapDistance(HwQubit q1, HwQubit q2); - std::vector computeAllShortestPaths(HwQubit q1, - HwQubit q2) const; - /** * @brief Resets the swap distances between the hardware qubits. * @details Used after each shuttling operation to reset the swap distances. @@ -141,6 +138,9 @@ class HardwareQubits { initialHwPos = hwToCoordIdx; } + std::vector computeAllShortestPaths(HwQubit q1, + HwQubit q2) const; + // Mapping const qc::Permutation& getHwToCoordIdx() const { return hwToCoordIdx; } diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index 748bbcf59..a4264fe9c 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -219,6 +219,19 @@ class NeutralAtomMapper { std::vector> ExecutableBridges, NeutralAtomLayer& frontLayer, NeutralAtomLayer& lookaheadLayer); + Bridge findBestBridge() const; + Bridges getAllBridges() const; + + // std::vector> + // findAllBridges(qc::QuantumComputation& qc); + // std::vector> + // bridgeCostCompareWithSwap( + // std::vector> allBridges, + // Swap bestSwap, const qc::DAG& dag, NeutralAtomLayer& frontLayer); + // void updateMappingBridge( + // std::vector> ExecutableBridges, + // NeutralAtomLayer& frontLayer, NeutralAtomLayer& lookaheadLayer); + /** * @brief Returns the next best shuttling move operation for the front layer. * @return The next best shuttling move operation for the front layer diff --git a/include/hybridmap/NeutralAtomDefinitions.hpp b/include/hybridmap/NeutralAtomDefinitions.hpp index f09c6e8fe..83fa84236 100644 --- a/include/hybridmap/NeutralAtomDefinitions.hpp +++ b/include/hybridmap/NeutralAtomDefinitions.hpp @@ -22,6 +22,9 @@ using CoordIndices = std::vector; // used as qubit or not and occupies a certain position in the architecture. using HwQubit = uint32_t; using HwQubits = std::set; +using HwQubitsVector = std::vector; +using Bridge = std::vector; +using Bridges = std::vector; using HwPositions [[maybe_unused]] = std::vector; // A qc::Qubit corresponds to a qubit in the quantum circuit. It can be mapped // to a hardware qubit. @@ -35,9 +38,10 @@ using WeightedSwaps = std::vector; // The distance between two hardware qubits using SWAP gates. using SwapDistance = int32_t; // Bridges -using Bridge = - std::tuple; // q_control, q_target, Q_between -using Bridges = std::vector; +// using Bridge = +// std::tuple; // q_control, q_target, +// Q_between +// using Bridges = std::vector; // Moves are between coordinates (the first is occupied, the second is not). using AtomMove = std::pair; diff --git a/include/hybridmap/NeutralAtomLayer.hpp b/include/hybridmap/NeutralAtomLayer.hpp index 573d50975..05d44e1b0 100644 --- a/include/hybridmap/NeutralAtomLayer.hpp +++ b/include/hybridmap/NeutralAtomLayer.hpp @@ -72,7 +72,7 @@ class NeutralAtomLayer { * @brief Returns the current layer of gates * @return The current layer of gates */ - GateList getGates() { return gates; } + GateList getGates() const { return gates; } /** * @brief Returns a vector of the iterator indices * @return A copy of the current iterator indices diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index bcc19c6aa..f46c7c477 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -536,6 +536,25 @@ std::set NeutralAtomMapper::getAllPossibleSwaps( } return swaps; } +Bridge NeutralAtomMapper::findBestBridge() const { + auto allBridges = getAllBridges(); +} + +Bridges NeutralAtomMapper::getAllBridges() const { + Bridges allBridges; + for (const auto* const op : this->frontLayer.getGates()) { + if (op->getUsedQubits().size() == 2) { + auto usedQuBits = op->getUsedQubits(); + auto usedHwQubits = this->mapping.getHwQubits(usedQuBits); + const auto bridges = this->hardwareQubits.computeAllShortestPaths( + *usedHwQubits.begin(), *usedHwQubits.rbegin()); + for (const auto& bridge : bridges) { + allBridges.emplace_back(bridge); + } + } + } + return allBridges; +} qc::fp NeutralAtomMapper::swapCost( const Swap& swap, const std::pair& swapsFront, @@ -1488,178 +1507,181 @@ bool NeutralAtomMapper::swapGateBetter(const qc::Operation* opPointer) { return fidSwaps * parameters.gateWeight > fidMoves * parameters.shuttlingWeight; } - -std::vector> -NeutralAtomMapper::findAllBridges(qc::QuantumComputation& qc) { - std::vector> allBridges; - for (const auto* op : this->frontLayer.getGates()) { - size_t usedQubitSize = op->getUsedQubits().size(); - Qubits nearbyQubits; - if (usedQubitSize == 2) { - // logical qubits - qc::Qubit q1 = *(op->getUsedQubits().begin()); - qc::Qubit q2 = *(std::next(op->getUsedQubits().begin(), 1)); - // hardware qubits - HwQubit h1 = this->mapping.getHwQubit(q1); - HwQubit h2 = this->mapping.getHwQubit(q2); - qc::fp dist = this->hardwareQubits.getSwapDistance(h1, h2); - if (dist == 1) { - // get nearby - HwQubits h1Near = this->hardwareQubits.getNearbyQubits(h1); - HwQubits h2Near = this->hardwareQubits.getNearbyQubits(h2); - for (const auto& h : h1Near) { - if (h2Near.find(h) != h2Near.end()) { - qc::Qubit qBtw = this->mapping.getCircQubit(h); - if (qBtw < qc.getNqubits() && qBtw != -1) { - nearbyQubits.insert(qBtw); - } - } - } - } - allBridges.emplace_back(op, Bridge(q1, q2, nearbyQubits)); - } - } - return allBridges; -} - -void NeutralAtomMapper::updateMappingBridge( - std::vector> ExecutableBridges, - NeutralAtomLayer& frontLayer, NeutralAtomLayer& lookaheadLayer) { - // CX to Bridge - //[q1] ---c--- = ---------c-----------c--- - //[qb] | = ---c---H-Z-H---c---H-Z-H- - //[q2] -H-Z-H- = -H-Z-H-------H-Z-H------- - - //-> CZ to Bridge w/ QCO - //[q1] -c- = -------c-----------c--- - //[qb] | = -c---H-Z-H---c---H-Z-H- - //[q2] -Z- = -Z-----------Z--------- - - GateList removeGates; - for (auto bridgePair : ExecutableBridges) { - nBridges++; - auto op = bridgePair.first; - auto bridge = bridgePair.second; - qc::Qubit q1 = std::get<0>(bridge); - qc::Qubit q2 = std::get<1>(bridge); - Qubits Qb = std::get<2>(bridge); - auto it = Qb.begin(); - qc::Qubit qb = *it; - if (this->parameters.verbose) { - std::cout << "bridged " << q1 << " " << q2 << " by using " << qb; - std::cout << " physical qubits: "; - std::cout << this->mapping.getHwQubit(q1); - std::cout << " "; - std::cout << this->mapping.getHwQubit(q2); - std::cout << " "; - std::cout << this->mapping.getHwQubit(qb); - std::cout << '\n'; - } - // add BR to mappedQc - mappedQc.cz(qb, q2); - mappedQc.h(qb); - mappedQc.cz(q1, qb); - mappedQc.h(qb); - mappedQc.cz(qb, q2); - mappedQc.h(qb); - mappedQc.cz(q1, qb); - mappedQc.h(qb); - - // remove original gate - removeGates.push_back(op); - } - // remove original gate - frontLayer.removeGatesAndUpdate(removeGates); - lookaheadLayer.removeGatesAndUpdate(removeGates); -} - -std::vector> -NeutralAtomMapper::bridgeCostCompareWithSwap( - std::vector> allBridges, - Swap bestSwap, const qc::DAG& dag, NeutralAtomLayer& frontLayer) { - std::vector> ExecutableBridges; - - for (auto bridgePair : allBridges) { - auto op = bridgePair.first; - auto bridge = bridgePair.second; - qc::Qubit q1 = std::get<0>(bridge); - qc::Qubit q2 = std::get<1>(bridge); - Qubits Qb = std::get<2>(bridge); - auto it = Qb.begin(); - qc::Qubit qb = *it; - - // cost for front layer - qc::fp distbefore = 0; - qc::fp distswap = 0; - HwQubit p1 = this->mapping.getHwQubit(q1); - HwQubit p2 = this->mapping.getHwQubit(q2); - distbefore += this->hardwareQubits.getSwapDistance(p1, p2); - if (p1 == bestSwap.first) { - distswap += this->hardwareQubits.getSwapDistance(bestSwap.second, p2); - } else if (p1 == bestSwap.second) { - distswap += this->hardwareQubits.getSwapDistance(bestSwap.first, p2); - } else if (p2 == bestSwap.first) { - distswap += this->hardwareQubits.getSwapDistance(p1, bestSwap.second); - } else if (p2 == bestSwap.second) { - distswap += this->hardwareQubits.getSwapDistance(p1, bestSwap.first); - } else { - distswap += this->hardwareQubits.getSwapDistance(p1, p2); - } - if (distbefore - distswap > 0) - return ExecutableBridges; - - // cost for look-ahead window - qc::fp costBridge = 0; - qc::fp costBestSwap = 0; - for (auto& q : {q1, q2}) { - HwQubit p = this->mapping.getHwQubit(q); - auto tempIter = dag[q].begin() + frontLayer.getIteratorOffset()[q] + 1; - qc::fp discountFactor = 0.9; - while (tempIter < dag[q].end() && discountFactor > 0.1) { - auto* dagOp = (*tempIter)->get(); - if (dagOp->getUsedQubits().size() != 1) { - Qubits usedQubits = dagOp->getUsedQubits(); - qc::fp distbefore = 0; - qc::fp distswap = 0; - for (auto it1 = usedQubits.begin(); it1 != usedQubits.end(); ++it1) { - for (auto it2 = std::next(it1); it2 != usedQubits.end(); ++it2) { - qc::Qubit qi = *it1; - qc::Qubit qj = *it2; - HwQubit pi = this->mapping.getHwQubit(qi); - HwQubit pj = this->mapping.getHwQubit(qj); - - distbefore += this->hardwareQubits.getSwapDistance(pi, pj); - if (pi == bestSwap.first) { - distswap += - this->hardwareQubits.getSwapDistance(bestSwap.second, pj); - } else if (pi == bestSwap.second) { - distswap += - this->hardwareQubits.getSwapDistance(bestSwap.first, pj); - } else if (pj == bestSwap.first) { - distswap += - this->hardwareQubits.getSwapDistance(pi, bestSwap.second); - } else if (pj == bestSwap.second) { - distswap += - this->hardwareQubits.getSwapDistance(pi, bestSwap.first); - } else { - distswap += this->hardwareQubits.getSwapDistance(pi, pj); - } - } - } - costBridge += distbefore * discountFactor; - costBestSwap += distswap * discountFactor; - discountFactor *= 0.9; - } - tempIter++; - } - } - - if (costBridge <= costBestSwap && costBridge != 0) { - ExecutableBridges.emplace_back(op, Bridge(q1, q2, Qb)); - } - } - return ExecutableBridges; -} +// +// std::vector> +// NeutralAtomMapper::findAllBridges(qc::QuantumComputation& qc) { +// std::vector> allBridges; +// for (const auto* op : this->frontLayer.getGates()) { +// size_t usedQubitSize = op->getUsedQubits().size(); +// Qubits nearbyQubits; +// if (usedQubitSize == 2) { +// // logical qubits +// qc::Qubit q1 = *(op->getUsedQubits().begin()); +// qc::Qubit q2 = *(std::next(op->getUsedQubits().begin(), 1)); +// // hardware qubits +// HwQubit h1 = this->mapping.getHwQubit(q1); +// HwQubit h2 = this->mapping.getHwQubit(q2); +// qc::fp dist = this->hardwareQubits.getSwapDistance(h1, h2); +// if (dist == 1) { +// // get nearby +// HwQubits h1Near = this->hardwareQubits.getNearbyQubits(h1); +// HwQubits h2Near = this->hardwareQubits.getNearbyQubits(h2); +// for (const auto& h : h1Near) { +// if (h2Near.find(h) != h2Near.end()) { +// qc::Qubit qBtw = this->mapping.getCircQubit(h); +// if (qBtw < qc.getNqubits() && qBtw != -1) { +// nearbyQubits.insert(qBtw); +// } +// } +// } +// } +// allBridges.emplace_back(op, Bridge(q1, q2, nearbyQubits)); +// } +// } +// return allBridges; +// } +// +// void NeutralAtomMapper::updateMappingBridge( +// std::vector> ExecutableBridges, +// NeutralAtomLayer& frontLayer, NeutralAtomLayer& lookaheadLayer) { +// // CX to Bridge +// //[q1] ---c--- = ---------c-----------c--- +// //[qb] | = ---c---H-Z-H---c---H-Z-H- +// //[q2] -H-Z-H- = -H-Z-H-------H-Z-H------- +// +// //-> CZ to Bridge w/ QCO +// //[q1] -c- = -------c-----------c--- +// //[qb] | = -c---H-Z-H---c---H-Z-H- +// //[q2] -Z- = -Z-----------Z--------- +// +// GateList removeGates; +// for (auto bridgePair : ExecutableBridges) { +// nBridges++; +// auto op = bridgePair.first; +// auto bridge = bridgePair.second; +// qc::Qubit q1 = std::get<0>(bridge); +// qc::Qubit q2 = std::get<1>(bridge); +// Qubits Qb = std::get<2>(bridge); +// auto it = Qb.begin(); +// qc::Qubit qb = *it; +// if (this->parameters.verbose) { +// std::cout << "bridged " << q1 << " " << q2 << " by using " << qb; +// std::cout << " physical qubits: "; +// std::cout << this->mapping.getHwQubit(q1); +// std::cout << " "; +// std::cout << this->mapping.getHwQubit(q2); +// std::cout << " "; +// std::cout << this->mapping.getHwQubit(qb); +// std::cout << '\n'; +// } +// // add BR to mappedQc +// mappedQc.cz(qb, q2); +// mappedQc.h(qb); +// mappedQc.cz(q1, qb); +// mappedQc.h(qb); +// mappedQc.cz(qb, q2); +// mappedQc.h(qb); +// mappedQc.cz(q1, qb); +// mappedQc.h(qb); +// +// // remove original gate +// removeGates.push_back(op); +// } +// // remove original gate +// frontLayer.removeGatesAndUpdate(removeGates); +// lookaheadLayer.removeGatesAndUpdate(removeGates); +// } +// +// std::vector> +// NeutralAtomMapper::bridgeCostCompareWithSwap( +// std::vector> allBridges, +// Swap bestSwap, const qc::DAG& dag, NeutralAtomLayer& frontLayer) { +// std::vector> ExecutableBridges; +// +// for (auto bridgePair : allBridges) { +// auto op = bridgePair.first; +// auto bridge = bridgePair.second; +// qc::Qubit q1 = std::get<0>(bridge); +// qc::Qubit q2 = std::get<1>(bridge); +// Qubits Qb = std::get<2>(bridge); +// auto it = Qb.begin(); +// qc::Qubit qb = *it; +// +// // cost for front layer +// qc::fp distbefore = 0; +// qc::fp distswap = 0; +// HwQubit p1 = this->mapping.getHwQubit(q1); +// HwQubit p2 = this->mapping.getHwQubit(q2); +// distbefore += this->hardwareQubits.getSwapDistance(p1, p2); +// if (p1 == bestSwap.first) { +// distswap += this->hardwareQubits.getSwapDistance(bestSwap.second, p2); +// } else if (p1 == bestSwap.second) { +// distswap += this->hardwareQubits.getSwapDistance(bestSwap.first, p2); +// } else if (p2 == bestSwap.first) { +// distswap += this->hardwareQubits.getSwapDistance(p1, bestSwap.second); +// } else if (p2 == bestSwap.second) { +// distswap += this->hardwareQubits.getSwapDistance(p1, bestSwap.first); +// } else { +// distswap += this->hardwareQubits.getSwapDistance(p1, p2); +// } +// if (distbefore - distswap > 0) +// return ExecutableBridges; +// +// // cost for look-ahead window +// qc::fp costBridge = 0; +// qc::fp costBestSwap = 0; +// for (auto& q : {q1, q2}) { +// HwQubit p = this->mapping.getHwQubit(q); +// auto tempIter = dag[q].begin() + frontLayer.getIteratorOffset()[q] + 1; +// qc::fp discountFactor = 0.9; +// while (tempIter < dag[q].end() && discountFactor > 0.1) { +// auto* dagOp = (*tempIter)->get(); +// if (dagOp->getUsedQubits().size() != 1) { +// Qubits usedQubits = dagOp->getUsedQubits(); +// qc::fp distbefore = 0; +// qc::fp distswap = 0; +// for (auto it1 = usedQubits.begin(); it1 != usedQubits.end(); ++it1) +// { +// for (auto it2 = std::next(it1); it2 != usedQubits.end(); ++it2) { +// qc::Qubit qi = *it1; +// qc::Qubit qj = *it2; +// HwQubit pi = this->mapping.getHwQubit(qi); +// HwQubit pj = this->mapping.getHwQubit(qj); +// +// distbefore += this->hardwareQubits.getSwapDistance(pi, pj); +// if (pi == bestSwap.first) { +// distswap += +// this->hardwareQubits.getSwapDistance(bestSwap.second, +// pj); +// } else if (pi == bestSwap.second) { +// distswap += +// this->hardwareQubits.getSwapDistance(bestSwap.first, pj); +// } else if (pj == bestSwap.first) { +// distswap += +// this->hardwareQubits.getSwapDistance(pi, +// bestSwap.second); +// } else if (pj == bestSwap.second) { +// distswap += +// this->hardwareQubits.getSwapDistance(pi, bestSwap.first); +// } else { +// distswap += this->hardwareQubits.getSwapDistance(pi, pj); +// } +// } +// } +// costBridge += distbefore * discountFactor; +// costBestSwap += distswap * discountFactor; +// discountFactor *= 0.9; +// } +// tempIter++; +// } +// } +// +// if (costBridge <= costBestSwap && costBridge != 0) { +// ExecutableBridges.emplace_back(op, Bridge(q1, q2, Qb)); +// } +// } +// return ExecutableBridges; +// } // std::pair // NeutralAtomMapper::findBestFlyingAncilla(qc::QuantumComputation& qc, From 9c79621db9bf448484110c307dc3e3a429d84b93 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Wed, 22 Jan 2025 09:09:48 +0100 Subject: [PATCH 063/394] =?UTF-8?q?=E2=9C=A8added=20computation=20of=20cur?= =?UTF-8?q?rent=20qubit=20usage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridNeutralAtomMapper.hpp | 11 ++------ src/hybridmap/HybridNeutralAtomMapper.cpp | 25 ++++++++++++++++++- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index a4264fe9c..b03fb3db8 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -209,19 +209,12 @@ class NeutralAtomMapper { getAllPossibleSwaps(const std::pair& swapsFront) const; // Methods for bridge operations mapping - std::vector> - findAllBridges(qc::QuantumComputation& qc); - std::vector> - bridgeCostCompareWithSwap( - std::vector> allBridges, - Swap bestSwap, const qc::DAG& dag, NeutralAtomLayer& frontLayer); - void updateMappingBridge( - std::vector> ExecutableBridges, - NeutralAtomLayer& frontLayer, NeutralAtomLayer& lookaheadLayer); Bridge findBestBridge() const; Bridges getAllBridges() const; + CoordIndices computeCurrentCoordUsages() const; + // std::vector> // findAllBridges(qc::QuantumComputation& qc); // std::vector> diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index f46c7c477..cad3713fe 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -119,7 +119,7 @@ NeutralAtomMapper::map(qc::QuantumComputation& qc, auto bestSwap = findBestSwap(lastSwap); // find bridge - auto allBridges = findAllBridges(qc); + auto bestBridge = findBestBridge(); // find best move auto bestCombAndOp = findBestAtomMoveWithOp(); @@ -538,6 +538,7 @@ std::set NeutralAtomMapper::getAllPossibleSwaps( } Bridge NeutralAtomMapper::findBestBridge() const { auto allBridges = getAllBridges(); + auto qubitUsages = computeCurrentCoordUsages(); } Bridges NeutralAtomMapper::getAllBridges() const { @@ -555,6 +556,28 @@ Bridges NeutralAtomMapper::getAllBridges() const { } return allBridges; } +CoordIndices NeutralAtomMapper::computeCurrentCoordUsages() const { + CoordIndices coordUsages(mappedQc.getNqubits(), 0); + // in front layer + for (const auto* const op : this->frontLayer.getGates()) { + for (const auto qubit : op->getUsedQubits()) { + coordUsages[hardwareQubits.getCoordIndex( + hardwareQubits.getHwQubit(qubit))]++; + } + } + // in mapped qc, go backwards same length as front layer + auto nFrontLayerGates = this->frontLayer.getGates().size(); + auto it = this->mappedQc.rbegin(); + while (it != this->mappedQc.rend() && nFrontLayerGates > 0) { + for (const auto qubit : (*it)->getUsedQubits()) { + coordUsages[hardwareQubits.getCoordIndex( + hardwareQubits.getHwQubit(qubit))]++; + } + ++it; + nFrontLayerGates--; + } + return coordUsages; +} qc::fp NeutralAtomMapper::swapCost( const Swap& swap, const std::pair& swapsFront, From 1a1caf6a196868db4deff95c9cca56ef2bc0b9d1 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Wed, 22 Jan 2025 10:39:44 +0100 Subject: [PATCH 064/394] =?UTF-8?q?=E2=9C=A8added=20computation=20of=20gat?= =?UTF-8?q?es=20in=20bridge=20gates?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/NeutralAtomUtils.hpp | 30 +++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/include/hybridmap/NeutralAtomUtils.hpp b/include/hybridmap/NeutralAtomUtils.hpp index 035b58e9c..237483af0 100644 --- a/include/hybridmap/NeutralAtomUtils.hpp +++ b/include/hybridmap/NeutralAtomUtils.hpp @@ -19,7 +19,6 @@ #include namespace na { - // Enums for the different initial mappings strategies enum InitialCoordinateMapping : uint8_t { Trivial, Random, Graph }; enum InitialMapping : uint8_t { Identity }; @@ -216,4 +215,33 @@ struct MultiQubitMovePos { size_t nMoves{0}; }; +inline std::pair computeCzHforBridge(size_t length) { + // ignore the first and last qubit + length = length - 2; + + uint h = 0; + uint cz = 1; + + size_t addMultiplier = 1; + size_t addCounter = 0; + for (size_t i = 0; i < length; ++i) { + h += 4 * addMultiplier; + cz += 3 * addMultiplier; + addCounter++; + if (addCounter == addMultiplier) { + addMultiplier = addMultiplier * 2; + addCounter = 0; + } + } + return {h, cz}; +} + +inline std::vector> getCzH(size_t maxSize) { + std::vector> gates; + for (size_t i = 3; i <= maxSize; ++i) { + gates.emplace_back(computeCzHforBridge(i)); + } + return gates; +} + } // namespace na From f2a9df146b727407ba86afb6bd9b679af20bf302 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Wed, 22 Jan 2025 10:50:23 +0100 Subject: [PATCH 065/394] =?UTF-8?q?=E2=9C=A8added=20computation=20best=20B?= =?UTF-8?q?ridge=20Gate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridNeutralAtomMapper.hpp | 2 +- src/hybridmap/HybridNeutralAtomMapper.cpp | 28 +++++++++++++++++-- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index b03fb3db8..bc87d9db2 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -211,7 +211,7 @@ class NeutralAtomMapper { // Methods for bridge operations mapping Bridge findBestBridge() const; - Bridges getAllBridges() const; + Bridges getShortestBridges() const; CoordIndices computeCurrentCoordUsages() const; diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index cad3713fe..97c04b8d1 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -537,12 +537,32 @@ std::set NeutralAtomMapper::getAllPossibleSwaps( return swaps; } Bridge NeutralAtomMapper::findBestBridge() const { - auto allBridges = getAllBridges(); + auto allBridges = getShortestBridges(); + if (allBridges.empty()) { + return {}; + } else if (allBridges.size() == 1) { + return allBridges.front(); + } + // use bridge along less used qubits auto qubitUsages = computeCurrentCoordUsages(); + size_t bestBridgeIdx = 0; + size_t minUsage = std::numeric_limits::max(); + for (size_t i = 0; i < allBridges.size(); ++i) { + size_t usage = 0; + for (auto qubit : allBridges[i]) { + usage += qubitUsages[qubit]; + } + if (usage < minUsage) { + minUsage = usage; + bestBridgeIdx = i; + } + } + return allBridges[bestBridgeIdx]; } -Bridges NeutralAtomMapper::getAllBridges() const { +Bridges NeutralAtomMapper::getShortestBridges() const { Bridges allBridges; + size_t minBridgeLength = std::numeric_limits::max(); for (const auto* const op : this->frontLayer.getGates()) { if (op->getUsedQubits().size() == 2) { auto usedQuBits = op->getUsedQubits(); @@ -551,6 +571,10 @@ Bridges NeutralAtomMapper::getAllBridges() const { *usedHwQubits.begin(), *usedHwQubits.rbegin()); for (const auto& bridge : bridges) { allBridges.emplace_back(bridge); + if (bridge.size() < minBridgeLength) { + minBridgeLength = bridge.size(); + allBridges.clear(); + } } } } From fbc8467fe5ad48658f6a52daeb8a2b90d3283009 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Wed, 22 Jan 2025 16:29:39 +0100 Subject: [PATCH 066/394] =?UTF-8?q?=E2=9C=A8=20find=20nearest=20hardware?= =?UTF-8?q?=20qubit=20(for=20flying=20ancilla).?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HardwareQubits.hpp | 2 ++ src/hybridmap/HardwareQubits.cpp | 16 ++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/include/hybridmap/HardwareQubits.hpp b/include/hybridmap/HardwareQubits.hpp index d2179dd91..76d00f171 100644 --- a/include/hybridmap/HardwareQubits.hpp +++ b/include/hybridmap/HardwareQubits.hpp @@ -323,6 +323,8 @@ class HardwareQubits { int circQubitSize, const CoordIndices& excludedCoords = {}); + HwQubit getClosestQubit(CoordIndex coord, HwQubits ignored) const; + // Blocking /** * @brief Computes all hardware qubits that are blocked by a set of hardware diff --git a/src/hybridmap/HardwareQubits.cpp b/src/hybridmap/HardwareQubits.cpp index 28471f458..2ade430d7 100644 --- a/src/hybridmap/HardwareQubits.cpp +++ b/src/hybridmap/HardwareQubits.cpp @@ -322,5 +322,21 @@ HardwareQubits::findClosestAncillaCoord(CoordIndex coord, Direction direction, } return closestFreeCoords; } +HwQubit HardwareQubits::getClosestQubit(CoordIndex coord, + HwQubits ignored) const { + HwQubit closestQubit = 0; + auto minDistance = std::numeric_limits::max(); + for (auto const& [qubit, idx] : hwToCoordIdx) { + if (ignored.find(qubit) != ignored.end()) { + continue; + } + auto distance = arch->getEuclideanDistance(coord, idx); + if (distance < minDistance) { + minDistance = distance; + closestQubit = qubit; + } + } + return closestQubit; +} } // namespace na From c5d7e636657ed4f5c3d1ca1ee8fa5f4f0be57cfd Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Wed, 22 Jan 2025 16:30:51 +0100 Subject: [PATCH 067/394] =?UTF-8?q?=E2=9C=A8=20added=20move=20to=20flying?= =?UTF-8?q?=20ancilla=20convertion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridNeutralAtomMapper.hpp | 20 ++- include/hybridmap/NeutralAtomUtils.hpp | 28 +++- src/hybridmap/HybridNeutralAtomMapper.cpp | 154 +++++++++++------- 3 files changed, 135 insertions(+), 67 deletions(-) diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index bc87d9db2..9262e740b 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -105,7 +105,7 @@ class NeutralAtomMapper { // The current placement of the hardware qubits onto the coordinates HardwareQubits hardwareQubits; - qc::Permutation flyingAncillas; + HardwareQubits flyingAncillas; // The current mapping between circuit qubits and hardware qubits Mapping mapping; @@ -215,6 +215,8 @@ class NeutralAtomMapper { CoordIndices computeCurrentCoordUsages() const; + FlyingAncillas convertMoveCombToFlyingAncilla(const MoveComb& move_comb); + // std::vector> // findAllBridges(qc::QuantumComputation& qc); // std::vector> @@ -239,7 +241,7 @@ class NeutralAtomMapper { * @return The current best move operation */ MoveComb findBestAtomMove(); - std::pair findBestAtomMoveWithOp(); + // std::pair findBestAtomMoveWithOp(); /** * @brief Returns all possible move combinations for the front layer. * @details This includes direct moves, move away and multi-qubit moves. @@ -247,8 +249,8 @@ class NeutralAtomMapper { * @return Vector of possible move combinations for the front layer */ MoveCombs getAllMoveCombinations(); - std::pair>> - getAllMoveCombinationsWithOp(); + // std::vector> + // getAllMoveCombinationsWithOp(); /** * @brief Returns all possible move away combinations for a move from start to * target. @@ -265,9 +267,8 @@ class NeutralAtomMapper { const CoordIndices& excludedCoords); // Methods for flying ancilla operations mapping - std::pair - findBestFlyingAncilla(qc::QuantumComputation& qc, - const qc::Operation* targetOp); + std::vector + findBestFlyingAncillaComb(const qc::Operation* targetOp); std::set> findQtargetSet(std::set& usedQubits); CoordIndex returnClosestAncillaCoord(const CoordIndex& c_target, const CoordIndices& excludeCoords, @@ -422,7 +423,10 @@ class NeutralAtomMapper { lookaheadLayer(NeutralAtomLayer(qc::DAG())), parameters(p), hardwareQubits(architecture, parameters.initialMapping, std::vector(), std::vector(), - parameters.seed) { + parameters.seed), + flyingAncillas(architecture, InitialCoordinateMapping::Trivial, + std::vector(), std::vector(), + 0) { // need at least on free coordinate to shuttle if (architecture.getNpositions() - architecture.getNqubits() < 1) { this->parameters.gateWeight = 1; diff --git a/include/hybridmap/NeutralAtomUtils.hpp b/include/hybridmap/NeutralAtomUtils.hpp index 237483af0..b06d2d27f 100644 --- a/include/hybridmap/NeutralAtomUtils.hpp +++ b/include/hybridmap/NeutralAtomUtils.hpp @@ -107,6 +107,16 @@ struct MoveVector { [[nodiscard]] bool include(const MoveVector& other) const; }; +struct FlyingAncilla { + CoordIndex origin; + CoordIndex q1; + CoordIndex q2; + size_t index; + const qc::Operation* op; +}; + +using FlyingAncillas = std::vector; + /** * @brief Helper class to manage multiple atom moves which belong together. * @details E.g. a move-away combined with the actual move. These are combined @@ -115,10 +125,15 @@ struct MoveVector { struct MoveComb { std::vector moves; qc::fp cost = std::numeric_limits::max(); + const qc::Operation* op = nullptr; + CoordIndices bestPos; - MoveComb(std::vector mov, const qc::fp c) - : moves(std::move(mov)), cost(c) {} - MoveComb(AtomMove mov, const qc::fp c) : moves({std::move(mov)}), cost(c) {} + MoveComb(std::vector mov, const qc::fp c, const qc::Operation* o, + CoordIndices pos) + : moves(std::move(mov)), cost(c), op(o), bestPos(std::move(pos)) {} + MoveComb(AtomMove mov, const qc::fp c, const qc::Operation* o, + CoordIndices pos) + : moves({std::move(mov)}), cost(c), op(o), bestPos(std::move(pos)) {} MoveComb() = default; explicit MoveComb(std::vector mov) : moves(std::move(mov)) {} @@ -188,6 +203,13 @@ struct MoveCombs { [[nodiscard]] const_iterator begin() const { return moveCombs.cbegin(); } [[nodiscard]] const_iterator end() const { return moveCombs.cend(); } + void setOperation(const qc::Operation* op, CoordIndices pos) { + for (auto& moveComb : moveCombs) { + moveComb.op = op; + moveComb.bestPos = pos; + } + } + /** * @brief Add a move combination to the list of move combinations. * @param moveComb The move combination to add. diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 97c04b8d1..b2feeb12e 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -51,11 +51,15 @@ NeutralAtomMapper::map(qc::QuantumComputation& qc, auto nQubits = qc.getNqubits(); mappedQc = qc::QuantumComputation(qc.getNqubits()); - mappedQc.addQubitRegister(nAncillas, "a"); + mappedQc.addAncillaryRegister(nAncillas, "a"); // remove ancillas from hardware qubit mapping for (auto i = nQubits; i < arch.getNqubits(); ++i) { hardwareQubits.removeHwQubit(i); } + // remove qubits from flying ancillas + for (auto i = 0; i < nQubits; ++i) { + flyingAncillas.removeHwQubit(i); + } auto dag = qc::CircuitOptimizer::constructDAG(qc); @@ -122,9 +126,9 @@ NeutralAtomMapper::map(qc::QuantumComputation& qc, auto bestBridge = findBestBridge(); // find best move - auto bestCombAndOp = findBestAtomMoveWithOp(); - auto bestMove = bestCombAndOp.first; - auto opForMove = bestCombAndOp.second; + auto bestComb = findBestAtomMove(); + + auto bestFA = convertMoveCombToFlyingAncilla(bestComb); // // find flying ancilla // auto [bestFA, numPassby] = findBestFlyingAncilla(qc, opForMove); @@ -602,6 +606,51 @@ CoordIndices NeutralAtomMapper::computeCurrentCoordUsages() const { } return coordUsages; } +FlyingAncillas +NeutralAtomMapper::convertMoveCombToFlyingAncilla(const MoveComb& moveComb) { + auto usedQubits = moveComb.op->getUsedQubits(); + auto hwQubits = this->mapping.getHwQubits(usedQubits); + const auto usedCoords = this->hardwareQubits.getCoordIndices(hwQubits); + // not enough qubits for a flying ancilla + if (usedCoords.size() - 1 > mappedQc.getNancillae()) { + return {}; + } + + // multi-qubit gate -> only one direction + FlyingAncillas bestFAs; + FlyingAncilla bestFA; + HwQubits usedFA; + for (const auto move : moveComb.moves) { + if (std::find(usedCoords.begin(), usedCoords.end(), move.first) != + usedCoords.end()) { + bestFA.op = moveComb.op; + auto nearFirstIdx = + this->flyingAncillas.getClosestQubit(move.first, usedFA); + auto nearFirst = this->flyingAncillas.getCoordIndex(nearFirstIdx); + auto nearSecondIdx = + this->flyingAncillas.getClosestQubit(move.second, usedFA); + auto nearSecond = this->flyingAncillas.getCoordIndex(nearSecondIdx); + if (usedQubits.size() == 2) { + // both directions possible, check if reversed is better + if (this->arch.getEuclideanDistance(nearFirstIdx, move.first) < + this->arch.getEuclideanDistance(nearSecondIdx, move.second)) { + bestFA.q1 = move.second; + bestFA.q2 = move.first; + bestFA.origin = nearSecond; + bestFA.index = nearSecondIdx; + } + } + bestFA.q1 = move.first; + bestFA.q2 = move.second; + bestFA.origin = nearFirst; + bestFA.index = nearFirstIdx; + + usedFA.emplace(bestFA.index); + bestFAs.emplace_back(bestFA); + } + } + return bestFAs; +} qc::fp NeutralAtomMapper::swapCost( const Swap& swap, const std::pair& swapsFront, @@ -991,37 +1040,25 @@ MoveComb NeutralAtomMapper::findBestAtomMove() { return bestMove->first; } -std::pair -NeutralAtomMapper::findBestAtomMoveWithOp() { - auto [moveCombs, moveCombsWithOp] = getAllMoveCombinationsWithOp(); - - // compute cost for each move combination - std::vector> moveCosts; - moveCosts.reserve(moveCombs.size()); - for (const auto& moveComb : moveCombs) { - moveCosts.emplace_back(moveComb, moveCostComb(moveComb)); - } - - std::sort(moveCosts.begin(), moveCosts.end(), - [](const auto& move1, const auto& move2) { - return move1.second < move2.second; - }); - - // get move of minimal cost - auto bestMove = std::min_element(moveCosts.begin(), moveCosts.end(), - [](const auto& move1, const auto& move2) { - return move1.second < move2.second; - }); - MoveComb bestAtomMove = bestMove->first; - const qc::Operation* corresOp = nullptr; - for (const auto& pair : moveCombsWithOp) { - if (pair.first == bestAtomMove) { - corresOp = pair.second; - break; - } - } - return {bestAtomMove, corresOp}; -} +// std::pair +// NeutralAtomMapper::findBestAtomMoveWithOp() { +// auto moveCombsWithOp = getAllMoveCombinationsWithOp(); +// +// // compute cost for each move combination +// std::vector, qc::fp>> moveCosts; +// moveCosts.reserve(moveCombsWithOp.size()); +// for (const auto& moveCombWithOp : moveCombsWithOp) { +// moveCosts.emplace_back(moveCombWithOp, +// moveCostComb(moveCombWithOp.first)); +// } +// +// std::sort(moveCosts.begin(), moveCosts.end(), +// [](const auto& move1, const auto& move2) { +// return move1.second < move2.second; +// }); +// +// return moveCosts.front().first; +// } qc::fp NeutralAtomMapper::moveCostComb(const MoveComb& moveComb) { qc::fp costComb = 0; @@ -1258,33 +1295,33 @@ MoveCombs NeutralAtomMapper::getAllMoveCombinations() { std::cout << '\n'; } auto moves = getMoveCombinationsToPosition(usedHwQubits, bestPos); + moves.setOperation(op, bestPos); allMoves.addMoveCombs(moves); } allMoves.removeLongerMoveCombs(); return allMoves; } -std::pair>> -NeutralAtomMapper::getAllMoveCombinationsWithOp() { - MoveCombs allMoves; - std::vector> allMovesWithOp; - int i = 1; - for (const auto& op : this->frontLayer.getGates()) { - auto usedQubits = op->getUsedQubits(); - auto usedHwQubits = this->mapping.getHwQubits(usedQubits); - auto usedCoordsSet = this->hardwareQubits.getCoordIndices(usedHwQubits); - auto usedCoords = - std::vector(usedCoordsSet.begin(), usedCoordsSet.end()); - auto bestPos = getBestMovePos(usedCoords); - auto moves = getMoveCombinationsToPosition(usedHwQubits, bestPos); - allMoves.addMoveCombs(moves); - for (auto move : moves) { - allMovesWithOp.push_back(std::make_pair(move, op)); - } - } - allMoves.removeLongerMoveCombs(); - return make_pair(allMoves, allMovesWithOp); -} +// std::vector> +// NeutralAtomMapper::getAllMoveCombinationsWithOp() { +// MoveCombs allMoves; +// int i = 1; +// for (const auto& op : this->frontLayer.getGates()) { +// auto usedQubits = op->getUsedQubits(); +// auto usedHwQubits = this->mapping.getHwQubits(usedQubits); +// auto usedCoordsSet = this->hardwareQubits.getCoordIndices(usedHwQubits); +// auto usedCoords = +// std::vector(usedCoordsSet.begin(), usedCoordsSet.end()); +// auto bestPos = getBestMovePos(usedCoords); +// auto moves = getMoveCombinationsToPosition(usedHwQubits, bestPos); +// allMoves.addMoveCombs(moves); +// for (auto move : moves) { +// allMovesWithOp.push_back(std::make_pair(move, MoveInfo{op, bestPos})); +// } +// } +// allMoves.removeLongerMoveCombs(); +// return make_pair(allMoves, allMovesWithOp); +// } CoordIndices NeutralAtomMapper::getBestMovePos(const CoordIndices& gateCoords) { size_t const maxMoves = gateCoords.size() * 2; @@ -1428,6 +1465,11 @@ NeutralAtomMapper::getMoveAwayCombinations(CoordIndex startCoord, } return moveCombinations; } +std::vector +NeutralAtomMapper::findBestFlyingAncillaComb(const qc::Operation* targetOp) { + std::vector bestFlyingAncillaCombs; + auto usedQubits = targetOp->getUsedQubits(); +} std::pair NeutralAtomMapper::estimateNumSwapGates(const qc::Operation* opPointer) { From 72c3a06eb46ca6d2a1c9faa88e19cedfc821437c Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Wed, 22 Jan 2025 17:34:53 +0100 Subject: [PATCH 068/394] =?UTF-8?q?=F0=9F=9A=A7=20drafted=20new=20routing?= =?UTF-8?q?=20structure=20with=20placeholder=20functions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridNeutralAtomMapper.hpp | 14 +++- include/hybridmap/NeutralAtomDefinitions.hpp | 3 +- include/hybridmap/NeutralAtomUtils.hpp | 7 ++ src/hybridmap/HardwareQubits.cpp | 2 +- src/hybridmap/HybridNeutralAtomMapper.cpp | 83 ++++++++++++++----- 5 files changed, 82 insertions(+), 27 deletions(-) diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index 9262e740b..998b5091b 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -144,16 +144,26 @@ class NeutralAtomMapper { */ bool isExecutable(const qc::Operation* opPointer); + void updateBlockedQubits(Swap swap) { + HwQubits qubits = {swap.first, swap.second}; + updateBlockedQubits(qubits); + } + void updateBlockedQubits(HwQubits qubits); + /** * @brief Update the mapping for the given swap gate. * @param swap The swap gate to update the mapping for */ - void updateMappingSwap(Swap swap); + void applySwap(Swap swap); /** * @brief Update the mapping for the given move operation. * @param move The move operation to update the mapping for */ - void updateMappingMove(AtomMove move); + void applyMove(AtomMove move); + + void applyBridge(const Bridge& bridge); + void applyFlyingAncilla(FlyingAncilla fa); + void applyPassBy(FlyingAncilla fa); // Methods for gate vs. shuttling /** diff --git a/include/hybridmap/NeutralAtomDefinitions.hpp b/include/hybridmap/NeutralAtomDefinitions.hpp index 83fa84236..b53d5e649 100644 --- a/include/hybridmap/NeutralAtomDefinitions.hpp +++ b/include/hybridmap/NeutralAtomDefinitions.hpp @@ -6,6 +6,7 @@ #pragma once #include "Definitions.hpp" +#include "ir/operations/Operation.hpp" #include #include @@ -23,7 +24,7 @@ using CoordIndices = std::vector; using HwQubit = uint32_t; using HwQubits = std::set; using HwQubitsVector = std::vector; -using Bridge = std::vector; +using Bridge = std::pair>; using Bridges = std::vector; using HwPositions [[maybe_unused]] = std::vector; // A qc::Qubit corresponds to a qubit in the quantum circuit. It can be mapped diff --git a/include/hybridmap/NeutralAtomUtils.hpp b/include/hybridmap/NeutralAtomUtils.hpp index b06d2d27f..f7dbeb223 100644 --- a/include/hybridmap/NeutralAtomUtils.hpp +++ b/include/hybridmap/NeutralAtomUtils.hpp @@ -22,6 +22,13 @@ namespace na { // Enums for the different initial mappings strategies enum InitialCoordinateMapping : uint8_t { Trivial, Random, Graph }; enum InitialMapping : uint8_t { Identity }; +enum MappingMethod : uint8_t { + SwapMethod, + BridgeMethod, + MoveMethod, + FlyingAncillaMethod, + PassByMethod +}; [[maybe_unused]] static InitialCoordinateMapping initialCoordinateMappingFromString( diff --git a/src/hybridmap/HardwareQubits.cpp b/src/hybridmap/HardwareQubits.cpp index 2ade430d7..823003d97 100644 --- a/src/hybridmap/HardwareQubits.cpp +++ b/src/hybridmap/HardwareQubits.cpp @@ -210,7 +210,7 @@ std::set HardwareQubits::getBlockedQubits(const std::set& qubits) { std::set blockedQubits; for (const auto& qubit : qubits) { - for (uint32_t i = 0; i < arch->getNqubits(); ++i) { + for (uint32_t i = 0; i < hwToCoordIdx.maxKey(); ++i) { if (i == qubit) { continue; } diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index b2feeb12e..e2738066a 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -130,18 +130,36 @@ NeutralAtomMapper::map(qc::QuantumComputation& qc, auto bestFA = convertMoveCombToFlyingAncilla(bestComb); - // // find flying ancilla - // auto [bestFA, numPassby] = findBestFlyingAncilla(qc, opForMove); - // - // execute swap gate - // if (false) { - // lastSwap = bestSwap; - // updateMappingSwap(bestSwap); - // } else if (false) { - // updateMappingMove(bestMove); - // } else { - // // update mapping for flying ancilla - // } + // compare methods + // auto bestMethod = MappingMethod::SwapMethod; + // auto bestMethod = MappingMethod::MoveMethod; + auto bestMethod = MappingMethod::BridgeMethod; + + switch (bestMethod) { + case MappingMethod::SwapMethod: + lastSwap = bestSwap; + updateBlockedQubits(bestSwap); + applySwap(bestSwap); + break; + case MappingMethod::BridgeMethod: + updateBlockedQubits({bestBridge.second.begin(), bestBridge.second.end()}); + applyBridge(bestBridge); + break; + case MappingMethod::MoveMethod: + // apply whole move combination at once + // for (auto& move : bestComb.moves) { + // applyMove(move); + // } + applyMove(bestComb.moves[0]); + break; + case MappingMethod::FlyingAncillaMethod: + // applyFlyingAncilla(bestFA); + break; + case MappingMethod::PassByMethod: + // applyPassBy(bestFA); + break; + } + mapAllPossibleGates(frontLayer); lookaheadLayer.initLayerOffset(frontLayer.getIteratorOffset()); } @@ -425,14 +443,17 @@ GateList NeutralAtomMapper::getExecutableGates(const GateList& gates) { return executableGates; } -void NeutralAtomMapper::updateMappingSwap(Swap swap) { - nSwaps++; +void NeutralAtomMapper::updateBlockedQubits(HwQubits qubits) { // save to lastSwaps this->lastBlockedQubits.emplace_back( - this->hardwareQubits.getBlockedQubits({swap.first, swap.second})); + this->hardwareQubits.getBlockedQubits(qubits)); if (this->lastBlockedQubits.size() > this->arch.getNcolumns()) { this->lastBlockedQubits.pop_front(); } +} + +void NeutralAtomMapper::applySwap(Swap swap) { + nSwaps++; this->mapping.applySwap(swap); // convert circuit qubits to CoordIndex and append to mappedQc auto idxFirst = this->hardwareQubits.getCoordIndex(swap.first); @@ -455,7 +476,7 @@ void NeutralAtomMapper::updateMappingSwap(Swap swap) { } } -void NeutralAtomMapper::updateMappingMove(AtomMove move) { +void NeutralAtomMapper::applyMove(AtomMove move) { this->lastMoves.emplace_back(move); if (this->lastMoves.size() > 4) { this->lastMoves.pop_front(); @@ -474,6 +495,24 @@ void NeutralAtomMapper::updateMappingMove(AtomMove move) { } nMoves++; } +void NeutralAtomMapper::applyBridge(const Bridge& bridge) { + // add gates to mappedQc + // TODO: implement + + if (this->parameters.verbose) { + std::cout << "bridged " << bridge.first->getName() << " "; + for (auto qubit : bridge.second) { + std::cout << qubit << " "; + } + std::cout << '\n'; + } + + // remove gate from frontLayer + const auto* op = bridge.first; + frontLayer.removeGatesAndUpdate({op}); + + nBridges++; +} Swap NeutralAtomMapper::findBestSwap(const Swap& lastSwap) { // compute necessary movements @@ -553,7 +592,7 @@ Bridge NeutralAtomMapper::findBestBridge() const { size_t minUsage = std::numeric_limits::max(); for (size_t i = 0; i < allBridges.size(); ++i) { size_t usage = 0; - for (auto qubit : allBridges[i]) { + for (auto qubit : allBridges[i].second) { usage += qubitUsages[qubit]; } if (usage < minUsage) { @@ -574,7 +613,7 @@ Bridges NeutralAtomMapper::getShortestBridges() const { const auto bridges = this->hardwareQubits.computeAllShortestPaths( *usedHwQubits.begin(), *usedHwQubits.rbegin()); for (const auto& bridge : bridges) { - allBridges.emplace_back(bridge); + allBridges.emplace_back(op, bridge); if (bridge.size() < minBridgeLength) { minBridgeLength = bridge.size(); allBridges.clear(); @@ -589,17 +628,15 @@ CoordIndices NeutralAtomMapper::computeCurrentCoordUsages() const { // in front layer for (const auto* const op : this->frontLayer.getGates()) { for (const auto qubit : op->getUsedQubits()) { - coordUsages[hardwareQubits.getCoordIndex( - hardwareQubits.getHwQubit(qubit))]++; + coordUsages[hardwareQubits.getCoordIndex(mapping.getHwQubit(qubit))]++; } } // in mapped qc, go backwards same length as front layer auto nFrontLayerGates = this->frontLayer.getGates().size(); auto it = this->mappedQc.rbegin(); while (it != this->mappedQc.rend() && nFrontLayerGates > 0) { - for (const auto qubit : (*it)->getUsedQubits()) { - coordUsages[hardwareQubits.getCoordIndex( - hardwareQubits.getHwQubit(qubit))]++; + for (const auto coordIdx : (*it)->getUsedQubits()) { + coordUsages[coordIdx]++; } ++it; nFrontLayerGates--; From 0ddba7acecddc5b842122819e6d5da49e8388f8c Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Wed, 22 Jan 2025 17:52:26 +0100 Subject: [PATCH 069/394] =?UTF-8?q?=E2=9C=A8=20also=20consider=20blocked?= =?UTF-8?q?=20qubits=20for=20bridge=20gates.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridNeutralAtomMapper.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index e2738066a..313d18eda 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -641,6 +641,14 @@ CoordIndices NeutralAtomMapper::computeCurrentCoordUsages() const { ++it; nFrontLayerGates--; } + // add last blocked qubits + if (this->lastBlockedQubits.empty()) { + return coordUsages; + } + const auto lastBlockedQubits = this->lastBlockedQubits.back(); + for (const auto qubit : lastBlockedQubits) { + coordUsages[hardwareQubits.getCoordIndex(qubit)]++; + } return coordUsages; } FlyingAncillas From 02385527342ee80b169332b1917a6314a03c5253 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 23 Jan 2025 09:46:21 +0100 Subject: [PATCH 070/394] =?UTF-8?q?=E2=9C=A8=20added=20construction=20of?= =?UTF-8?q?=20arbitrary=20sized=20bridge=20gates?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridNeutralAtomMapper.hpp | 8 +++ include/hybridmap/NeutralAtomUtils.hpp | 41 +++++------- src/hybridmap/NeutralAtomUtils.cpp | 63 +++++++++++++++++++ 3 files changed, 88 insertions(+), 24 deletions(-) diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index 998b5091b..42d5a935a 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -109,6 +109,9 @@ class NeutralAtomMapper { // The current mapping between circuit qubits and hardware qubits Mapping mapping; + std::array bridgeCircuits; + std::array, 10> czH; + qc::DAG dag; // Methods for mapping @@ -442,6 +445,11 @@ class NeutralAtomMapper { this->parameters.gateWeight = 1; this->parameters.shuttlingWeight = 0; } + // precompute bridge circuits + for (size_t i = 0; i < 10; i++) { + bridgeCircuits[i] = qc::QuantumComputation(architecture.getNpositions()); + czH[i] = getCzH(bridgeCircuits[i]); + } }; /** diff --git a/include/hybridmap/NeutralAtomUtils.hpp b/include/hybridmap/NeutralAtomUtils.hpp index f7dbeb223..e4883e2a6 100644 --- a/include/hybridmap/NeutralAtomUtils.hpp +++ b/include/hybridmap/NeutralAtomUtils.hpp @@ -6,7 +6,9 @@ #pragma once #include "Definitions.hpp" +#include "circuit_optimizer/CircuitOptimizer.hpp" #include "hybridmap/NeutralAtomDefinitions.hpp" +#include "ir/QuantumComputation.hpp" #include "ir/operations/AodOperation.hpp" #include @@ -244,33 +246,24 @@ struct MultiQubitMovePos { size_t nMoves{0}; }; -inline std::pair computeCzHforBridge(size_t length) { - // ignore the first and last qubit - length = length - 2; - - uint h = 0; - uint cz = 1; - - size_t addMultiplier = 1; - size_t addCounter = 0; - for (size_t i = 0; i < length; ++i) { - h += 4 * addMultiplier; - cz += 3 * addMultiplier; - addCounter++; - if (addCounter == addMultiplier) { - addMultiplier = addMultiplier * 2; - addCounter = 0; +inline std::pair getCzH(const qc::QuantumComputation& qc) { + size_t cz = 0; + size_t h = 0; + for (const auto& op : qc) { + if (op->getType() == qc::OpType::H) { + h++; + } else if (op->getType() == qc::OpType::Z) { + cz++; } } - return {h, cz}; + return {cz, h}; } -inline std::vector> getCzH(size_t maxSize) { - std::vector> gates; - for (size_t i = 3; i <= maxSize; ++i) { - gates.emplace_back(computeCzHforBridge(i)); - } - return gates; -} +qc::QuantumComputation getBridgeCircuit(size_t length); + +qc::QuantumComputation recursiveBridgeIncrease(qc::QuantumComputation qcBridge, + size_t length); +qc::QuantumComputation bridgeExpand(qc::QuantumComputation qcBridge, + size_t qubit); } // namespace na diff --git a/src/hybridmap/NeutralAtomUtils.cpp b/src/hybridmap/NeutralAtomUtils.cpp index 1d63ec5ec..c88176005 100644 --- a/src/hybridmap/NeutralAtomUtils.cpp +++ b/src/hybridmap/NeutralAtomUtils.cpp @@ -6,11 +6,17 @@ #include "hybridmap/NeutralAtomUtils.hpp" #include "Definitions.hpp" +#include "circuit_optimizer/CircuitOptimizer.hpp" +#include "ir/QuantumComputation.hpp" +#include "ir/operations/StandardOperation.hpp" #include #include #include +#include #include +#include +#include namespace na { @@ -92,5 +98,62 @@ void MoveCombs::removeLongerMoveCombs() { } } } +qc::QuantumComputation getBridgeCircuit(size_t length) { + qc::QuantumComputation qcBridge(3); + qcBridge.cx(0, 1); + qcBridge.cx(1, 2); + qcBridge.cx(0, 1); + qcBridge.cx(1, 2); + qcBridge.print(std::cout); + + qcBridge = recursiveBridgeIncrease(qcBridge, length - 3); + // convert to CZ on qubit 0 + qcBridge.h(qcBridge.getNqubits() - 1); + qcBridge.insert(qcBridge.begin(), std::make_unique( + qcBridge.getNqubits() - 1, qc::H)); + + qc::CircuitOptimizer::replaceMCXWithMCZ(qcBridge); + qc::CircuitOptimizer::singleQubitGateFusion(qcBridge); + qcBridge.print(std::cout); + return qcBridge; +} +qc::QuantumComputation recursiveBridgeIncrease(qc::QuantumComputation qcBridge, + size_t length) { + if (length == 0) { + return qcBridge; + } + // determine qubit pair with least amount of gates + std::vector gates(qcBridge.getNqubits() - 1, 0); + for (const auto& gate : qcBridge) { + gates[*gate->getUsedQubits().begin()]++; + } + auto minIndex = std::min_element(gates.begin(), gates.end()) - gates.begin(); + + qcBridge = bridgeExpand(qcBridge, minIndex); + qcBridge.print(std::cout); + + return recursiveBridgeIncrease(qcBridge, length - 1); +} +qc::QuantumComputation bridgeExpand(qc::QuantumComputation qcBridge, + size_t qubit) { + qc::QuantumComputation qcBridgeNew(qcBridge.getNqubits() + 1); + for (auto& gate : qcBridge) { + const auto usedQubits = gate->getUsedQubits(); + const auto q1 = *usedQubits.begin(); + const auto q2 = *usedQubits.rbegin(); + if (q1 == qubit && q2 == qubit + 1) { + qcBridgeNew.cx(q1, q2); + qcBridgeNew.cx(q1 + 1, q2 + 1); + qcBridgeNew.cx(q1, q2); + qcBridgeNew.cx(q1 + 1, q2 + 1); + } else if (*usedQubits.begin() > qubit) { + // shift qubits by one + qcBridgeNew.cx(q1 + 1, q2 + 1); + } else { + qcBridgeNew.cx(q1, q2); + } + } + return qcBridgeNew; +} } // namespace na From 3db2c4141ff749cfb69d8125a8b2465ea900355d Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 23 Jan 2025 10:41:56 +0100 Subject: [PATCH 071/394] =?UTF-8?q?=F0=9F=8E=A8=20removed=20print=20statem?= =?UTF-8?q?ents?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/NeutralAtomUtils.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/hybridmap/NeutralAtomUtils.cpp b/src/hybridmap/NeutralAtomUtils.cpp index c88176005..5a022e46a 100644 --- a/src/hybridmap/NeutralAtomUtils.cpp +++ b/src/hybridmap/NeutralAtomUtils.cpp @@ -104,7 +104,6 @@ qc::QuantumComputation getBridgeCircuit(size_t length) { qcBridge.cx(1, 2); qcBridge.cx(0, 1); qcBridge.cx(1, 2); - qcBridge.print(std::cout); qcBridge = recursiveBridgeIncrease(qcBridge, length - 3); // convert to CZ on qubit 0 @@ -114,7 +113,6 @@ qc::QuantumComputation getBridgeCircuit(size_t length) { qc::CircuitOptimizer::replaceMCXWithMCZ(qcBridge); qc::CircuitOptimizer::singleQubitGateFusion(qcBridge); - qcBridge.print(std::cout); return qcBridge; } qc::QuantumComputation recursiveBridgeIncrease(qc::QuantumComputation qcBridge, @@ -130,7 +128,6 @@ qc::QuantumComputation recursiveBridgeIncrease(qc::QuantumComputation qcBridge, auto minIndex = std::min_element(gates.begin(), gates.end()) - gates.begin(); qcBridge = bridgeExpand(qcBridge, minIndex); - qcBridge.print(std::cout); return recursiveBridgeIncrease(qcBridge, length - 1); } From c20579eebf729294eadaec4ae23933317078a69d Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 23 Jan 2025 10:42:52 +0100 Subject: [PATCH 072/394] =?UTF-8?q?=E2=9C=A8allow=20also=20vectors=20of=20?= =?UTF-8?q?HwQubits=20to=20get=20coordinate=20indices?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HardwareQubits.hpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/include/hybridmap/HardwareQubits.hpp b/include/hybridmap/HardwareQubits.hpp index 76d00f171..0d06aa238 100644 --- a/include/hybridmap/HardwareQubits.hpp +++ b/include/hybridmap/HardwareQubits.hpp @@ -209,6 +209,17 @@ class HardwareQubits { } return coordIndices; } + + [[nodiscard]] std::vector + getCoordIndices(std::vector& hwQubits) const { + std::vector coordIndices; + coordIndices.reserve(hwQubits.size()); + for (auto const& hwQubit : hwQubits) { + coordIndices.emplace_back(this->getCoordIndex(hwQubit)); + } + return coordIndices; + } + /** * @brief Returns the hardware qubit at a coordinate. * @details Returns the hardware qubit at a coordinate. Throws an exception if From 5f4716e2fe21620115bcdaf5bec20e2d9d091cd3 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 23 Jan 2025 10:43:16 +0100 Subject: [PATCH 073/394] =?UTF-8?q?=E2=9C=A8=20added=20bridge=20gate=20map?= =?UTF-8?q?ping=20and=20decomposition?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridNeutralAtomMapper.hpp | 6 ++-- src/hybridmap/HybridNeutralAtomMapper.cpp | 28 +++++++++++++++++-- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index 42d5a935a..88c6c5ffb 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -446,8 +446,8 @@ class NeutralAtomMapper { this->parameters.shuttlingWeight = 0; } // precompute bridge circuits - for (size_t i = 0; i < 10; i++) { - bridgeCircuits[i] = qc::QuantumComputation(architecture.getNpositions()); + for (size_t i = 3; i < 10; i++) { + bridgeCircuits[i] = getBridgeCircuit(i); czH[i] = getCzH(bridgeCircuits[i]); } }; @@ -596,6 +596,8 @@ class NeutralAtomMapper { scheduler.saveAnimationCsv(filename); } + void decomposeBridgeGates(qc::QuantumComputation& qc); + /** * @brief Converts a mapped circuit down to the AOD level and CZ level. * @details SWAP gates are decomposed into CX gates. Then CnX gates are diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 313d18eda..78b752eb9 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -14,6 +14,7 @@ #include "ir/QuantumComputation.hpp" #include "ir/operations/OpType.hpp" #include "ir/operations/Operation.hpp" +#include "ir/operations/StandardOperation.hpp" #include #include @@ -357,10 +358,34 @@ void NeutralAtomMapper::mapAllPossibleGates(NeutralAtomLayer& layer) { } } +void NeutralAtomMapper::decomposeBridgeGates(qc::QuantumComputation& qc) { + auto it = qc.begin(); + while (it != qc.end()) { + if ((*it)->isStandardOperation() && (*it)->getType() == qc::Bridge) { + const auto targets = (*it)->getTargets(); + it = qc.erase(it); + for (const auto& bridgeOp : bridgeCircuits[targets.size()]) { + const auto bridgeQubits = bridgeOp->getUsedQubits(); + if (bridgeOp->getType() == qc::OpType::H) { + it = qc.insert(it, std::make_unique( + targets[*bridgeQubits.begin()], qc::H)); + } else { + it = qc.insert(it, std::make_unique( + qc::Control{targets[*bridgeQubits.begin()]}, + targets[*bridgeQubits.rbegin()], qc::Z)); + } + } + } else { + ++it; + } + } +} + qc::QuantumComputation NeutralAtomMapper::convertToAod(qc::QuantumComputation& qc) { // decompose SWAP gates qc::CircuitOptimizer::decomposeSWAP(qc, false); + decomposeBridgeGates(qc); qc::CircuitOptimizer::replaceMCXWithMCZ(qc); qc::CircuitOptimizer::singleQubitGateFusion(qc); qc::CircuitOptimizer::flattenOperations(qc); @@ -496,8 +521,7 @@ void NeutralAtomMapper::applyMove(AtomMove move) { nMoves++; } void NeutralAtomMapper::applyBridge(const Bridge& bridge) { - // add gates to mappedQc - // TODO: implement + mappedQc.bridge(bridge.second); if (this->parameters.verbose) { std::cout << "bridged " << bridge.first->getName() << " "; From b57ea65a45259a5303292c1e1973a91a85946c87 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 23 Jan 2025 12:59:26 +0100 Subject: [PATCH 074/394] =?UTF-8?q?=F0=9F=8E=A8=20moved=20computation=20of?= =?UTF-8?q?=20exponential=20factors.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridNeutralAtomMapper.hpp | 5 +++++ src/hybridmap/HybridNeutralAtomMapper.cpp | 5 ----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index 88c6c5ffb..7e88e44cf 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -450,6 +450,11 @@ class NeutralAtomMapper { bridgeCircuits[i] = getBridgeCircuit(i); czH[i] = getCzH(bridgeCircuits[i]); } + // precompute exponential decay weights + this->decayWeights.reserve(this->arch.getNcolumns()); + for (uint32_t i = this->arch.getNcolumns(); i > 0; --i) { + this->decayWeights.emplace_back(std::exp(-this->parameters.decay * i)); + } }; /** diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 78b752eb9..0b248b3da 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -104,11 +104,6 @@ NeutralAtomMapper::map(qc::QuantumComputation& qc, throw std::runtime_error("More qubits in circuit than in architecture"); } - // precompute exponential decay weights - this->decayWeights.reserve(this->arch.getNcolumns()); - for (uint32_t i = this->arch.getNcolumns(); i > 0; --i) { - this->decayWeights.emplace_back(std::exp(-this->parameters.decay * i)); - } // save last swap to prevent immediate swap back Swap lastSwap = {0, 0}; From 5e93f6c237e970beb7bac5045e6e93cffd3675d0 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 23 Jan 2025 13:21:02 +0100 Subject: [PATCH 075/394] =?UTF-8?q?=E2=9C=A8=20reintroduced=20separation?= =?UTF-8?q?=20between=20gate=20and=20shuttling=20based=20mapping=20+=20res?= =?UTF-8?q?tructured.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridNeutralAtomMapper.hpp | 16 ++ src/hybridmap/HybridNeutralAtomMapper.cpp | 189 ++++++++++++------ 2 files changed, 140 insertions(+), 65 deletions(-) diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index 7e88e44cf..61a3f4f32 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -87,12 +87,22 @@ class NeutralAtomMapper { NeutralAtomLayer frontLayer; // Gates in the lookahead layer to be executed NeutralAtomLayer lookaheadLayer; + // Gates in the front layer to be executed with swap gates + GateList frontLayerGate; + // Gates in the front layer to be executed with move operations + GateList frontLayerShuttling; + // Gates in the lookahead layer to be executed with swap gates + GateList lookaheadLayerGate; + // Gates in the lookahead layer to be executed with move operations + GateList lookaheadLayerShuttling; // The minimal weight for any multi-qubit gate qc::fp twoQubitSwapWeight = 1; // The runtime parameters of the mapper MapperParameters parameters; // The qubits that are blocked by the last swap std::deque> lastBlockedQubits; + // The last swap that has been executed + Swap lastSwap = {0, 0}; // The last moves that have been executed std::deque lastMoves; // Precomputed decay weights @@ -183,6 +193,12 @@ class NeutralAtomMapper { * @return The minimal number of swap gates and time needed to execute the * given gate */ + + void prepareAncillas(size_t nQcQubits); + + size_t gateBasedMapping(size_t i); + size_t shuttlingBasedMapping(size_t i); + std::pair estimateNumSwapGates(const qc::Operation* opPointer); /** diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 0b248b3da..8399ea1d2 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -47,20 +47,8 @@ NeutralAtomMapper::map(qc::QuantumComputation& qc, qc::CircuitOptimizer::flattenOperations(qc); qc::CircuitOptimizer::removeFinalMeasurements(qc); - // Compute number of flying ancillas - auto nAncillas = arch.getNqubits() - qc.getNqubits(); - auto nQubits = qc.getNqubits(); - - mappedQc = qc::QuantumComputation(qc.getNqubits()); - mappedQc.addAncillaryRegister(nAncillas, "a"); - // remove ancillas from hardware qubit mapping - for (auto i = nQubits; i < arch.getNqubits(); ++i) { - hardwareQubits.removeHwQubit(i); - } - // remove qubits from flying ancillas - for (auto i = 0; i < nQubits; ++i) { - flyingAncillas.removeHwQubit(i); - } + const auto nQubits = qc.getNqubits(); + prepareAncillas(nQubits); auto dag = qc::CircuitOptimizer::constructDAG(qc); @@ -104,60 +92,14 @@ NeutralAtomMapper::map(qc::QuantumComputation& qc, throw std::runtime_error("More qubits in circuit than in architecture"); } - // save last swap to prevent immediate swap back - Swap lastSwap = {0, 0}; - - auto i = 0; + // Mapping Loop + size_t i = 0; while (!frontLayer.getGates().empty()) { // assign gates to layers - ++i; - if (this->parameters.verbose) { - std::cout << "iteration " << i << '\n'; - printLayers(); - } - // find swap - auto bestSwap = findBestSwap(lastSwap); - - // find bridge - auto bestBridge = findBestBridge(); - - // find best move - auto bestComb = findBestAtomMove(); - - auto bestFA = convertMoveCombToFlyingAncilla(bestComb); - - // compare methods - // auto bestMethod = MappingMethod::SwapMethod; - // auto bestMethod = MappingMethod::MoveMethod; - auto bestMethod = MappingMethod::BridgeMethod; - - switch (bestMethod) { - case MappingMethod::SwapMethod: - lastSwap = bestSwap; - updateBlockedQubits(bestSwap); - applySwap(bestSwap); - break; - case MappingMethod::BridgeMethod: - updateBlockedQubits({bestBridge.second.begin(), bestBridge.second.end()}); - applyBridge(bestBridge); - break; - case MappingMethod::MoveMethod: - // apply whole move combination at once - // for (auto& move : bestComb.moves) { - // applyMove(move); - // } - applyMove(bestComb.moves[0]); - break; - case MappingMethod::FlyingAncillaMethod: - // applyFlyingAncilla(bestFA); - break; - case MappingMethod::PassByMethod: - // applyPassBy(bestFA); - break; - } + reassignGatesToLayers(frontLayer.getGates(), lookaheadLayer.getGates()); - mapAllPossibleGates(frontLayer); - lookaheadLayer.initLayerOffset(frontLayer.getIteratorOffset()); + i = gateBasedMapping(i); + i = shuttlingBasedMapping(i); } if (this->parameters.verbose) { @@ -1535,6 +1477,46 @@ NeutralAtomMapper::findBestFlyingAncillaComb(const qc::Operation* targetOp) { auto usedQubits = targetOp->getUsedQubits(); } +size_t NeutralAtomMapper::shuttlingBasedMapping(size_t i) { + while (!this->frontLayerShuttling.empty()) { + GateList gatesToExecute; + while (gatesToExecute.empty()) { + ++i; + if (this->parameters.verbose) { + std::cout << "iteration " << i << '\n'; + } + auto bestComb = findBestAtomMove(); + auto bestFA = convertMoveCombToFlyingAncilla(bestComb); + + MappingMethod compareShuttling = MappingMethod::MoveMethod; + + switch (compareShuttling) { + case MappingMethod::MoveMethod: + // apply whole move combination at once + // for (auto& move : bestComb.moves) { + // applyMove(move); + // } + applyMove(bestComb.moves[0]); + break; + case MappingMethod::FlyingAncillaMethod: + // applyFlyingAncilla(bestFA); + break; + case MappingMethod::PassByMethod: + // applyPassBy(bestFA); + break; + } + gatesToExecute = getExecutableGates(frontLayer.getGates()); + } + mapAllPossibleGates(frontLayer); + lookaheadLayer.initLayerOffset(frontLayer.getIteratorOffset()); + reassignGatesToLayers(frontLayer.getGates(), lookaheadLayer.getGates()); + if (this->parameters.verbose) { + printLayers(); + } + } + return i; +} + std::pair NeutralAtomMapper::estimateNumSwapGates(const qc::Operation* opPointer) { auto usedQubits = opPointer->getUsedQubits(); @@ -1660,6 +1642,83 @@ bool NeutralAtomMapper::swapGateBetter(const qc::Operation* opPointer) { return fidSwaps * parameters.gateWeight > fidMoves * parameters.shuttlingWeight; } + +void NeutralAtomMapper::reassignGatesToLayers(const GateList& frontGates, + const GateList& lookaheadGates) { + // assign gates to gates or shuttling + this->frontLayerGate.clear(); + this->frontLayerShuttling.clear(); + for (const auto& gate : frontGates) { + if (swapGateBetter(gate)) { + this->frontLayerGate.emplace_back(gate); + } else { + this->frontLayerShuttling.emplace_back(gate); + } + } + + this->lookaheadLayerGate.clear(); + this->lookaheadLayerShuttling.clear(); + for (const auto& gate : lookaheadGates) { + if (swapGateBetter(gate)) { + this->lookaheadLayerGate.emplace_back(gate); + } else { + this->lookaheadLayerShuttling.emplace_back(gate); + } + } +} +void NeutralAtomMapper::prepareAncillas(size_t nQubits) { + // Compute number of flying ancillas + auto nAncillas = arch.getNqubits() - nQubits; + + mappedQc = qc::QuantumComputation(nQubits); + mappedQc.addAncillaryRegister(nAncillas, "a"); + // remove ancillas from hardware qubit mapping + for (auto i = nQubits; i < arch.getNqubits(); ++i) { + hardwareQubits.removeHwQubit(i); + } + // remove qubits from flying ancillas + for (auto i = 0; i < nQubits; ++i) { + flyingAncillas.removeHwQubit(i); + } +} + +size_t NeutralAtomMapper::gateBasedMapping(size_t i) { + // first do all gate based mapping gates + while (!this->frontLayerGate.empty()) { + GateList gatesToExecute; + while (gatesToExecute.empty()) { + ++i; + if (this->parameters.verbose) { + std::cout << "iteration " << i << '\n'; + } + + auto bestSwap = findBestSwap(lastSwap); + auto bestBridge = findBestBridge(); + + MappingMethod swapOrBridge = MappingMethod::SwapMethod; + + if (swapOrBridge == MappingMethod::SwapMethod) { + lastSwap = bestSwap; + updateBlockedQubits(bestSwap); + applySwap(bestSwap); + } else { + updateBlockedQubits( + {bestBridge.second.begin(), bestBridge.second.end()}); + applyBridge(bestBridge); + } + + gatesToExecute = getExecutableGates(frontLayer.getGates()); + } + mapAllPossibleGates(frontLayer); + lookaheadLayer.initLayerOffset(frontLayer.getIteratorOffset()); + reassignGatesToLayers(frontLayer.getGates(), lookaheadLayer.getGates()); + if (this->parameters.verbose) { + printLayers(); + } + } + return i; +} + // // std::vector> // NeutralAtomMapper::findAllBridges(qc::QuantumComputation& qc) { From 637bbb26728b25bd6e16bbc6ee5e589aa1b4995b Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 24 Jan 2025 10:51:37 +0100 Subject: [PATCH 076/394] =?UTF-8?q?=E2=9C=A8reworked=20bridge=20gate=20han?= =?UTF-8?q?dling?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/NeutralAtomArchitecture.hpp | 11 +++++ include/hybridmap/NeutralAtomUtils.hpp | 40 +++++++++++------- src/hybridmap/NeutralAtomArchitecture.cpp | 32 ++++++++++----- src/hybridmap/NeutralAtomUtils.cpp | 41 +++++++++++++++---- 4 files changed, 92 insertions(+), 32 deletions(-) diff --git a/include/hybridmap/NeutralAtomArchitecture.hpp b/include/hybridmap/NeutralAtomArchitecture.hpp index dc4d06654..755c233eb 100644 --- a/include/hybridmap/NeutralAtomArchitecture.hpp +++ b/include/hybridmap/NeutralAtomArchitecture.hpp @@ -9,10 +9,13 @@ #include "datastructures/SymmetricMatrix.hpp" #include "hybridmap/NeutralAtomDefinitions.hpp" #include "hybridmap/NeutralAtomUtils.hpp" +#include "ir/QuantumComputation.hpp" #include "ir/operations/OpType.hpp" #include "ir/operations/Operation.hpp" #include "na/NADefinitions.hpp" +#include +#include #include #include #include @@ -20,6 +23,7 @@ #include #include #include +#include #include namespace na { @@ -149,6 +153,8 @@ class NeutralAtomArchitecture { SymmetricMatrix swapDistances; std::vector> nearbyCoordinates; + BridgeCircuits bridgeCircuits = BridgeCircuits(10); + /** * @brief Create the coordinates. */ @@ -375,6 +381,11 @@ class NeutralAtomArchitecture { return static_cast(c.x + c.y * properties.getNcolumns()); } + [[nodiscard]] [[maybe_unused]] qc::QuantumComputation + getBridgeCircuit(size_t length) const { + return bridgeCircuits.bridgeCircuits[length]; + } + // Distance functions /** * @brief Get the Euclidean distance between two coordinate indices diff --git a/include/hybridmap/NeutralAtomUtils.hpp b/include/hybridmap/NeutralAtomUtils.hpp index e4883e2a6..152396dee 100644 --- a/include/hybridmap/NeutralAtomUtils.hpp +++ b/include/hybridmap/NeutralAtomUtils.hpp @@ -246,24 +246,34 @@ struct MultiQubitMovePos { size_t nMoves{0}; }; -inline std::pair getCzH(const qc::QuantumComputation& qc) { - size_t cz = 0; - size_t h = 0; - for (const auto& op : qc) { - if (op->getType() == qc::OpType::H) { - h++; - } else if (op->getType() == qc::OpType::Z) { - cz++; +class BridgeCircuits { +public: + std::vector bridgeCircuits; + std::vector hs; + std::vector czs; + std::vector hDepth; + std::vector czDepth; + + explicit BridgeCircuits(const size_t maxLength) { + bridgeCircuits.resize(maxLength, qc::QuantumComputation()); + hs.resize(maxLength, 0); + czs.resize(maxLength, 0); + hDepth.resize(maxLength, 0); + czDepth.resize(maxLength, 0); + for (size_t i = 3; i < maxLength; ++i) { + computeBridgeCircuit(i); + computeGates(i); } } - return {cz, h}; -} -qc::QuantumComputation getBridgeCircuit(size_t length); +protected: + void computeGates(size_t length); + void computeBridgeCircuit(size_t length); -qc::QuantumComputation recursiveBridgeIncrease(qc::QuantumComputation qcBridge, - size_t length); + static qc::QuantumComputation + recursiveBridgeIncrease(qc::QuantumComputation qcBridge, size_t length); -qc::QuantumComputation bridgeExpand(qc::QuantumComputation qcBridge, - size_t qubit); + static qc::QuantumComputation bridgeExpand(qc::QuantumComputation qcBridge, + size_t qubit); +}; } // namespace na diff --git a/src/hybridmap/NeutralAtomArchitecture.cpp b/src/hybridmap/NeutralAtomArchitecture.cpp index 3d2346d9d..fc18c0bf3 100644 --- a/src/hybridmap/NeutralAtomArchitecture.cpp +++ b/src/hybridmap/NeutralAtomArchitecture.cpp @@ -8,11 +8,13 @@ #include "Definitions.hpp" #include "datastructures/SymmetricMatrix.hpp" #include "hybridmap/NeutralAtomDefinitions.hpp" +#include "hybridmap/NeutralAtomUtils.hpp" #include "ir/operations/AodOperation.hpp" #include "ir/operations/OpType.hpp" #include "ir/operations/Operation.hpp" #include +#include #include #include #include @@ -75,19 +77,29 @@ void NeutralAtomArchitecture::loadJson(const std::string& filename) { shuttlingTimes.emplace(qc::OP_NAME_TO_TYPE.at(key), value); } // compute values for SWAP gate - qc::fp swapGateTime = 0; - qc::fp swapGateFidelity = 1; - for (size_t i = 0; i < 3; ++i) { - swapGateTime += gateTimes.at("cz"); - swapGateFidelity *= gateAverageFidelities.at("cz"); - } - for (size_t i = 0; i < 6; ++i) { - swapGateTime += gateTimes.at("h"); - swapGateFidelity *= gateAverageFidelities.at("h"); - } + qc::fp const swapGateTime = + (gateTimes.at("cz") * 3) + (gateTimes.at("h") * 4); + qc::fp const swapGateFidelity = + std::pow(gateAverageFidelities.at("cz"), 3) * + std::pow(gateAverageFidelities.at("h"), 6); this->parameters.gateTimes.emplace("swap", swapGateTime); this->parameters.gateAverageFidelities.emplace("swap", swapGateFidelity); + // compute values for Bridge gate + // precompute bridge circuits + for (size_t i = 3; i <= 10; i++) { + qc::fp const bridgeGateTime = + (bridgeCircuits.czDepth[i] * gateTimes.at("cz")) + + (bridgeCircuits.hDepth[i] * gateTimes.at("h")); + qc::fp const bridgeFidelity = + std::pow(gateAverageFidelities.at("cz"), bridgeCircuits.czs[i]) * + std::pow(gateAverageFidelities.at("h"), bridgeCircuits.hs[i]); + this->parameters.gateTimes.emplace("bridge" + std::to_string(i), + bridgeGateTime); + this->parameters.gateAverageFidelities.emplace( + "bridge" + std::to_string(i), bridgeFidelity); + } + this->parameters.shuttlingTimes = shuttlingTimes; std::map shuttlingAverageFidelities; for (const auto& [key, value] : diff --git a/src/hybridmap/NeutralAtomUtils.cpp b/src/hybridmap/NeutralAtomUtils.cpp index 5a022e46a..750b3d14e 100644 --- a/src/hybridmap/NeutralAtomUtils.cpp +++ b/src/hybridmap/NeutralAtomUtils.cpp @@ -8,6 +8,7 @@ #include "Definitions.hpp" #include "circuit_optimizer/CircuitOptimizer.hpp" #include "ir/QuantumComputation.hpp" +#include "ir/operations/OpType.hpp" #include "ir/operations/StandardOperation.hpp" #include @@ -98,7 +99,31 @@ void MoveCombs::removeLongerMoveCombs() { } } } -qc::QuantumComputation getBridgeCircuit(size_t length) { + +void BridgeCircuits::computeGates(const size_t length) { + std::vector> hsCzsPerQubit( + bridgeCircuits[length].getNqubits(), {0, 0}); + for (const auto& op : bridgeCircuits[length]) { + if (op->getType() == qc::OpType::H) { + hs[length]++; + hsCzsPerQubit[*op->getTargets().begin()].first++; + } else if (op->getType() == qc::OpType::Z) { + czs[length]++; + hsCzsPerQubit[*op->getUsedQubits().begin()].second++; + hsCzsPerQubit[*op->getUsedQubits().rbegin()].second++; + } + } + // find max depth + auto maxHcZ = + std::max_element(hsCzsPerQubit.begin(), hsCzsPerQubit.end(), + [](const auto& a, const auto& b) { + return a.first + a.second < b.first + b.second; + }); + hDepth[length] = maxHcZ->first; + czDepth[length] = maxHcZ->second; +} + +void BridgeCircuits::computeBridgeCircuit(const size_t length) { qc::QuantumComputation qcBridge(3); qcBridge.cx(0, 1); qcBridge.cx(1, 2); @@ -113,14 +138,16 @@ qc::QuantumComputation getBridgeCircuit(size_t length) { qc::CircuitOptimizer::replaceMCXWithMCZ(qcBridge); qc::CircuitOptimizer::singleQubitGateFusion(qcBridge); - return qcBridge; + bridgeCircuits[length] = qcBridge; } -qc::QuantumComputation recursiveBridgeIncrease(qc::QuantumComputation qcBridge, - size_t length) { + +qc::QuantumComputation +BridgeCircuits::recursiveBridgeIncrease(qc::QuantumComputation qcBridge, + const size_t length) { if (length == 0) { return qcBridge; } - // determine qubit pair with least amount of gates + // determine qubit pair with the least amount of gates std::vector gates(qcBridge.getNqubits() - 1, 0); for (const auto& gate : qcBridge) { gates[*gate->getUsedQubits().begin()]++; @@ -131,8 +158,8 @@ qc::QuantumComputation recursiveBridgeIncrease(qc::QuantumComputation qcBridge, return recursiveBridgeIncrease(qcBridge, length - 1); } -qc::QuantumComputation bridgeExpand(qc::QuantumComputation qcBridge, - size_t qubit) { +qc::QuantumComputation +BridgeCircuits::bridgeExpand(qc::QuantumComputation qcBridge, size_t qubit) { qc::QuantumComputation qcBridgeNew(qcBridge.getNqubits() + 1); for (auto& gate : qcBridge) { const auto usedQubits = gate->getUsedQubits(); From 3b360945ba2de101dee9481b769f505d9f3be94f Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 24 Jan 2025 10:52:15 +0100 Subject: [PATCH 077/394] =?UTF-8?q?=E2=9C=A8added=20comparison=20between?= =?UTF-8?q?=20bridge=20and=20SWAP=20gate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HardwareQubits.hpp | 4 +- include/hybridmap/HybridNeutralAtomMapper.hpp | 21 +-- src/hybridmap/HybridNeutralAtomMapper.cpp | 137 +++++++++++++----- 3 files changed, 108 insertions(+), 54 deletions(-) diff --git a/include/hybridmap/HardwareQubits.hpp b/include/hybridmap/HardwareQubits.hpp index 0d06aa238..f8511384c 100644 --- a/include/hybridmap/HardwareQubits.hpp +++ b/include/hybridmap/HardwareQubits.hpp @@ -202,7 +202,7 @@ class HardwareQubits { * @return The coordinate indices of the hardware qubits. */ [[nodiscard]] std::set - getCoordIndices(std::set& hwQubits) const { + getCoordIndices(const std::set& hwQubits) const { std::set coordIndices; for (auto const& hwQubit : hwQubits) { coordIndices.emplace(this->getCoordIndex(hwQubit)); @@ -211,7 +211,7 @@ class HardwareQubits { } [[nodiscard]] std::vector - getCoordIndices(std::vector& hwQubits) const { + getCoordIndices(const std::vector& hwQubits) const { std::vector coordIndices; coordIndices.reserve(hwQubits.size()); for (auto const& hwQubit : hwQubits) { diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index 61a3f4f32..fdc644efe 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -119,9 +119,6 @@ class NeutralAtomMapper { // The current mapping between circuit qubits and hardware qubits Mapping mapping; - std::array bridgeCircuits; - std::array, 10> czH; - qc::DAG dag; // Methods for mapping @@ -302,15 +299,9 @@ class NeutralAtomMapper { CoordIndex returnClosestAncillaCoord(const CoordIndex& c_target, const CoordIndices& excludeCoords, qc::QuantumComputation& qc); - bool compareShuttlingAndFlyingancilla(AtomMove bestMove, - qc::QuantumComputation& bestFA, - const qc::DAG& dag, - NeutralAtomLayer& frontLayer); - void updateMappingFlyingAncilla(qc::QuantumComputation& bestFA, - const qc::Operation* targetOp, - uint32_t numPassby, - NeutralAtomLayer& frontLayer, - NeutralAtomLayer& lookaheadLayer); + MappingMethod compareSwapAndBridge(const Swap& bestSwap, + const Bridge& bestBridge); + MappingMethod compareShuttlingAndFlyingAncilla(MoveComb bestComb); // Helper methods /** @@ -461,11 +452,7 @@ class NeutralAtomMapper { this->parameters.gateWeight = 1; this->parameters.shuttlingWeight = 0; } - // precompute bridge circuits - for (size_t i = 3; i < 10; i++) { - bridgeCircuits[i] = getBridgeCircuit(i); - czH[i] = getCzH(bridgeCircuits[i]); - } + // precompute exponential decay weights this->decayWeights.reserve(this->arch.getNcolumns()); for (uint32_t i = this->arch.getNcolumns(); i > 0; --i) { diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 8399ea1d2..0a8df9294 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -97,6 +97,10 @@ NeutralAtomMapper::map(qc::QuantumComputation& qc, while (!frontLayer.getGates().empty()) { // assign gates to layers reassignGatesToLayers(frontLayer.getGates(), lookaheadLayer.getGates()); + if (this->parameters.verbose) { + std::cout << "Iteration " << i << std::endl; + printLayers(); + } i = gateBasedMapping(i); i = shuttlingBasedMapping(i); @@ -107,6 +111,8 @@ NeutralAtomMapper::map(qc::QuantumComputation& qc, std::cout << "nBridges: " << nBridges << '\n'; std::cout << "nFAncillas: " << nFAncillas << '\n'; std::cout << "nMoves: " << nMoves << '\n'; + + mappedQc.print(std::cout); } return mappedQc; } @@ -301,7 +307,7 @@ void NeutralAtomMapper::decomposeBridgeGates(qc::QuantumComputation& qc) { if ((*it)->isStandardOperation() && (*it)->getType() == qc::Bridge) { const auto targets = (*it)->getTargets(); it = qc.erase(it); - for (const auto& bridgeOp : bridgeCircuits[targets.size()]) { + for (const auto& bridgeOp : this->arch.getBridgeCircuit(targets.size())) { const auto bridgeQubits = bridgeOp->getUsedQubits(); if (bridgeOp->getType() == qc::OpType::H) { it = qc.insert(it, std::make_unique( @@ -376,16 +382,33 @@ bool NeutralAtomMapper::isExecutable(const qc::Operation* opPointer) { } void NeutralAtomMapper::printLayers() { - std::cout << "f: "; - for (const auto* op : this->frontLayer.getGates()) { + std::cout << "f,g: "; + for (const auto* op : this->frontLayerGate) { + std::cout << op->getName() << " "; + for (auto qubit : op->getUsedQubits()) { + std::cout << qubit << " "; + } + std::cout << '\n'; + } + std::cout << "f,s: "; + for (const auto* op : this->frontLayerShuttling) { std::cout << op->getName() << " "; for (auto qubit : op->getUsedQubits()) { std::cout << qubit << " "; } std::cout << '\n'; } - std::cout << "l: "; - for (const auto* op : this->lookaheadLayer.getGates()) { + std::cout << "l,g: "; + for (const auto* op : this->lookaheadLayerGate) { + std::cout << op->getName() << " "; + for (auto qubit : op->getUsedQubits()) { + std::cout << qubit << " "; + } + std::cout << '\n'; + } + std::cout << '\n'; + std::cout << "l,g: "; + for (const auto* op : this->lookaheadLayerShuttling) { std::cout << op->getName() << " "; for (auto qubit : op->getUsedQubits()) { std::cout << qubit << " "; @@ -458,7 +481,8 @@ void NeutralAtomMapper::applyMove(AtomMove move) { nMoves++; } void NeutralAtomMapper::applyBridge(const Bridge& bridge) { - mappedQc.bridge(bridge.second); + const auto coordIndices = this->hardwareQubits.getCoordIndices(bridge.second); + mappedQc.bridge(coordIndices); if (this->parameters.verbose) { std::cout << "bridged " << bridge.first->getName() << " "; @@ -468,9 +492,10 @@ void NeutralAtomMapper::applyBridge(const Bridge& bridge) { std::cout << '\n'; } - // remove gate from frontLayer + // // remove gate from frontLayer const auto* op = bridge.first; frontLayer.removeGatesAndUpdate({op}); + this->executedCommutingGates.emplace_back(op); nBridges++; } @@ -1488,9 +1513,7 @@ size_t NeutralAtomMapper::shuttlingBasedMapping(size_t i) { auto bestComb = findBestAtomMove(); auto bestFA = convertMoveCombToFlyingAncilla(bestComb); - MappingMethod compareShuttling = MappingMethod::MoveMethod; - - switch (compareShuttling) { + switch (compareShuttlingAndFlyingAncilla(bestComb)) { case MappingMethod::MoveMethod: // apply whole move combination at once // for (auto& move : bestComb.moves) { @@ -1504,6 +1527,8 @@ size_t NeutralAtomMapper::shuttlingBasedMapping(size_t i) { case MappingMethod::PassByMethod: // applyPassBy(bestFA); break; + default: + break; } gatesToExecute = getExecutableGates(frontLayer.getGates()); } @@ -1685,30 +1710,30 @@ void NeutralAtomMapper::prepareAncillas(size_t nQubits) { size_t NeutralAtomMapper::gateBasedMapping(size_t i) { // first do all gate based mapping gates while (!this->frontLayerGate.empty()) { - GateList gatesToExecute; - while (gatesToExecute.empty()) { - ++i; - if (this->parameters.verbose) { - std::cout << "iteration " << i << '\n'; - } + // GateList gatesToExecute; + // while (gatesToExecute.empty()) { + ++i; + if (this->parameters.verbose) { + std::cout << "iteration " << i << '\n'; + } - auto bestSwap = findBestSwap(lastSwap); - auto bestBridge = findBestBridge(); + auto bestSwap = findBestSwap(lastSwap); + auto bestBridge = findBestBridge(); - MappingMethod swapOrBridge = MappingMethod::SwapMethod; + const auto swapOrBridge = compareSwapAndBridge(bestSwap, bestBridge); + // MappingMethod swapOrBridge = MappingMethod::SwapMethod; - if (swapOrBridge == MappingMethod::SwapMethod) { - lastSwap = bestSwap; - updateBlockedQubits(bestSwap); - applySwap(bestSwap); - } else { - updateBlockedQubits( - {bestBridge.second.begin(), bestBridge.second.end()}); - applyBridge(bestBridge); - } - - gatesToExecute = getExecutableGates(frontLayer.getGates()); + if (swapOrBridge == MappingMethod::SwapMethod) { + lastSwap = bestSwap; + updateBlockedQubits(bestSwap); + applySwap(bestSwap); + } else { + updateBlockedQubits({bestBridge.second.begin(), bestBridge.second.end()}); + applyBridge(bestBridge); } + + // gatesToExecute = getExecutableGates(frontLayer.getGates()); + // } mapAllPossibleGates(frontLayer); lookaheadLayer.initLayerOffset(frontLayer.getIteratorOffset()); reassignGatesToLayers(frontLayer.getGates(), lookaheadLayer.getGates()); @@ -2105,12 +2130,54 @@ NeutralAtomMapper::returnClosestAncillaCoord(const CoordIndex& c_target, c_target, originalDirection, qc.getNqubits(), excludeCoords); return AncillaTargets[0]; } +MappingMethod +NeutralAtomMapper::compareSwapAndBridge(const Swap& bestSwap, + const Bridge& bestBridge) { + // swap distance reduction + qc::fp swapDistReduction = 0; + for (const auto& op : this->frontLayerGate) { + auto usedQubits = op->getUsedQubits(); + auto hwQubits = this->mapping.getHwQubits(usedQubits); + const auto& distBefore = + this->hardwareQubits.getAllToAllSwapDistance(hwQubits); + const auto firstPos = hwQubits.find(bestSwap.first); + const auto secondPos = hwQubits.find(bestSwap.second); + if (firstPos != hwQubits.end() && secondPos != hwQubits.end()) { + continue; + } + if (firstPos != hwQubits.end()) { + hwQubits.erase(firstPos); + hwQubits.insert(bestSwap.second); + } + if (secondPos != hwQubits.end()) { + hwQubits.erase(secondPos); + hwQubits.insert(bestSwap.first); + } + const auto& distAfter = + this->hardwareQubits.getAllToAllSwapDistance(hwQubits); + swapDistReduction += distBefore - distAfter; + } + // bridge distance reduction + qc::fp const bridgeDistReduction = bestBridge.second.size() - 2; + + // fidelity comparison + qc::fp const swapFidelity = this->arch.getGateAverageFidelity("swap") * + std::exp(-this->arch.getGateTime("swap") / + this->arch.getDecoherenceTime()); + std::string bridgeName = "bridge" + std::to_string(bestBridge.second.size()); + qc::fp const bridgeFidelity = this->arch.getGateAverageFidelity(bridgeName) * + std::exp(-this->arch.getGateTime(bridgeName) / + this->arch.getDecoherenceTime()); + if (swapDistReduction * swapFidelity > bridgeDistReduction * bridgeFidelity) { + return MappingMethod::SwapMethod; + } else { + return MappingMethod::BridgeMethod; + } +} -bool NeutralAtomMapper::compareShuttlingAndFlyingancilla( - AtomMove bestMove, qc::QuantumComputation& bestFA, const qc::DAG& dag, - NeutralAtomLayer& frontLayer) { - // TODO: implement cost function - return false; +MappingMethod +NeutralAtomMapper::compareShuttlingAndFlyingAncilla(MoveComb bestComb) { + return MappingMethod::MoveMethod; } // void NeutralAtomMapper::updateMappingFlyingAncilla( From bdcf2ed658c24196649e7221b816d84732df3ddb Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 24 Jan 2025 12:35:03 +0100 Subject: [PATCH 078/394] =?UTF-8?q?=E2=9C=A8improved=20comparison=20Swap?= =?UTF-8?q?=20Bridge?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridNeutralAtomMapper.hpp | 2 + src/hybridmap/HybridNeutralAtomMapper.cpp | 59 +++++++++++-------- 2 files changed, 36 insertions(+), 25 deletions(-) diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index fdc644efe..15a1eabca 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -16,6 +16,7 @@ #include "ir/Permutation.hpp" #include "ir/QuantumComputation.hpp" #include "ir/operations/Operation.hpp" +#include "sc/exact/ExactMapper.hpp" #include #include @@ -390,6 +391,7 @@ class NeutralAtomMapper { qc::fp swapCost(const Swap& swap, const std::pair& swapsFront, const std::pair& swapsLookahead); + qc::fp swapDistanceReduction(const Swap& swap, const GateList& layer); /** * @brief Calculates the cost of a move operation. * @details Assumes the move is executed and computes the distance reduction diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 0a8df9294..851259ffb 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -716,6 +716,33 @@ qc::fp NeutralAtomMapper::swapCost( } return cost; } +qc::fp NeutralAtomMapper::swapDistanceReduction(const Swap& swap, + const GateList& layer) { + qc::fp swapDistReduction = 0; + for (const auto& op : layer) { + auto usedQubits = op->getUsedQubits(); + auto hwQubits = this->mapping.getHwQubits(usedQubits); + const auto& distBefore = + this->hardwareQubits.getAllToAllSwapDistance(hwQubits); + const auto firstPos = hwQubits.find(swap.first); + const auto secondPos = hwQubits.find(swap.second); + if (firstPos != hwQubits.end() && secondPos != hwQubits.end()) { + continue; + } + if (firstPos != hwQubits.end()) { + hwQubits.erase(firstPos); + hwQubits.insert(swap.second); + } + if (secondPos != hwQubits.end()) { + hwQubits.erase(secondPos); + hwQubits.insert(swap.first); + } + const auto& distAfter = + this->hardwareQubits.getAllToAllSwapDistance(hwQubits); + swapDistReduction += distBefore - distAfter; + } + return swapDistReduction; +} std::pair NeutralAtomMapper::initSwaps(const GateList& layer) { @@ -1720,8 +1747,8 @@ size_t NeutralAtomMapper::gateBasedMapping(size_t i) { auto bestSwap = findBestSwap(lastSwap); auto bestBridge = findBestBridge(); - const auto swapOrBridge = compareSwapAndBridge(bestSwap, bestBridge); - // MappingMethod swapOrBridge = MappingMethod::SwapMethod; + // const auto swapOrBridge = compareSwapAndBridge(bestSwap, bestBridge); + MappingMethod swapOrBridge = MappingMethod::SwapMethod; if (swapOrBridge == MappingMethod::SwapMethod) { lastSwap = bestSwap; @@ -2134,29 +2161,11 @@ MappingMethod NeutralAtomMapper::compareSwapAndBridge(const Swap& bestSwap, const Bridge& bestBridge) { // swap distance reduction - qc::fp swapDistReduction = 0; - for (const auto& op : this->frontLayerGate) { - auto usedQubits = op->getUsedQubits(); - auto hwQubits = this->mapping.getHwQubits(usedQubits); - const auto& distBefore = - this->hardwareQubits.getAllToAllSwapDistance(hwQubits); - const auto firstPos = hwQubits.find(bestSwap.first); - const auto secondPos = hwQubits.find(bestSwap.second); - if (firstPos != hwQubits.end() && secondPos != hwQubits.end()) { - continue; - } - if (firstPos != hwQubits.end()) { - hwQubits.erase(firstPos); - hwQubits.insert(bestSwap.second); - } - if (secondPos != hwQubits.end()) { - hwQubits.erase(secondPos); - hwQubits.insert(bestSwap.first); - } - const auto& distAfter = - this->hardwareQubits.getAllToAllSwapDistance(hwQubits); - swapDistReduction += distBefore - distAfter; - } + qc::fp const swapDistReduction = + swapDistanceReduction(bestSwap, this->frontLayerGate) + + this->parameters.lookaheadWeightSwaps * + swapDistanceReduction(bestSwap, this->lookaheadLayerGate); + // bridge distance reduction qc::fp const bridgeDistReduction = bestBridge.second.size() - 2; From fdb3760c8e1bffe44a2d8258992c34a34d60a5b5 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 24 Jan 2025 13:20:05 +0100 Subject: [PATCH 079/394] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20set=20mqt-core=20t?= =?UTF-8?q?o=20main?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmake/ExternalDependencies.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/ExternalDependencies.cmake b/cmake/ExternalDependencies.cmake index 90f29e58e..d2be1ef1b 100644 --- a/cmake/ExternalDependencies.cmake +++ b/cmake/ExternalDependencies.cmake @@ -29,7 +29,7 @@ endif() # cmake-format: off set(MQT_CORE_VERSION 2.6.1 CACHE STRING "MQT Core version") -set(MQT_CORE_REV "41eea72cdbefbd86ba06f76dc41a911950dd3081" +set(MQT_CORE_REV "na-hybrid-extension" CACHE STRING "MQT Core identifier (tag, branch or commit hash)") set(MQT_CORE_REPO_OWNER "cda-tum" CACHE STRING "MQT Core repository owner (change when using a fork)") From 30421c3de8ba6dd1bbe9008d277859dae2b5b351 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 24 Jan 2025 19:08:16 +0100 Subject: [PATCH 080/394] =?UTF-8?q?=F0=9F=8E=A8=20clang=20tidy=20+=20clion?= =?UTF-8?q?=20improvements?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HardwareQubits.hpp | 45 ++- include/hybridmap/HybridNeutralAtomMapper.hpp | 85 ++--- include/hybridmap/Mapping.hpp | 10 +- include/hybridmap/NeutralAtomArchitecture.hpp | 42 ++- include/hybridmap/NeutralAtomScheduler.hpp | 14 +- include/hybridmap/NeutralAtomUtils.hpp | 37 +- src/hybridmap/HardwareQubits.cpp | 65 ++-- src/hybridmap/HybridAnimation.cpp | 27 +- src/hybridmap/HybridNeutralAtomMapper.cpp | 341 +++++++++--------- src/hybridmap/NeutralAtomArchitecture.cpp | 19 +- src/hybridmap/NeutralAtomScheduler.cpp | 31 +- src/hybridmap/NeutralAtomUtils.cpp | 38 +- test/hybridmap/test_hybridmap.cpp | 21 +- 13 files changed, 401 insertions(+), 374 deletions(-) diff --git a/include/hybridmap/HardwareQubits.hpp b/include/hybridmap/HardwareQubits.hpp index 899b3b481..34a401b49 100644 --- a/include/hybridmap/HardwareQubits.hpp +++ b/include/hybridmap/HardwareQubits.hpp @@ -83,7 +83,7 @@ class HardwareQubits { // Constructors HardwareQubits() = default; HardwareQubits(const NeutralAtomArchitecture& architecture, - InitialCoordinateMapping initialCoordinateMapping, + const InitialCoordinateMapping initialCoordinateMapping, const std::vector& qubitIndices, const std::vector& hwIndices, uint32_t seed) : arch(&architecture), swapDistances(architecture.getNqubits()) { @@ -139,11 +139,13 @@ class HardwareQubits { initialHwPos = hwToCoordIdx; } - std::vector computeAllShortestPaths(HwQubit q1, - HwQubit q2) const; + [[nodiscard]] std::vector + computeAllShortestPaths(HwQubit q1, HwQubit q2) const; // Mapping - const qc::Permutation& getHwToCoordIdx() const { return hwToCoordIdx; } + [[nodiscard]] const qc::Permutation& getHwToCoordIdx() const { + return hwToCoordIdx; + } /** * @brief Checks if a hardware qubit is mapped to a coordinate. @@ -164,7 +166,7 @@ class HardwareQubits { */ void move(HwQubit hwQubit, CoordIndex newCoord); - void removeHwQubit(HwQubit hwQubit) { + void removeHwQubit(const HwQubit hwQubit) { hwToCoordIdx.erase(hwQubit); initialHwPos.erase(hwQubit); // set swap distances to -1 @@ -194,7 +196,7 @@ class HardwareQubits { * @param qubit The hardware qubit. * @return The coordinate index of the hardware qubit. */ - [[nodiscard]] CoordIndex getCoordIndex(HwQubit qubit) const { + [[nodiscard]] CoordIndex getCoordIndex(const HwQubit qubit) const { return hwToCoordIdx.at(qubit); } /** @@ -228,7 +230,7 @@ class HardwareQubits { * @param coordIndex The coordinate index. * @return The hardware qubit at the coordinate. */ - [[nodiscard]] HwQubit getHwQubit(CoordIndex coordIndex) const { + [[nodiscard]] HwQubit getHwQubit(const CoordIndex coordIndex) const { for (auto const& [hwQubit, index] : hwToCoordIdx) { if (index == coordIndex) { return hwQubit; @@ -246,7 +248,7 @@ class HardwareQubits { * @return The nearby coordinates of the hardware qubit. */ [[nodiscard]] [[maybe_unused]] std::set - getNearbyCoordinates(HwQubit q) const { + getNearbyCoordinates(const HwQubit q) const { return this->arch->getNearbyCoordinates(this->getCoordIndex(q)); } @@ -263,8 +265,8 @@ class HardwareQubits { * or just to its vicinity. * @return The swap distance between the two hardware qubits. */ - [[nodiscard]] SwapDistance getSwapDistance(HwQubit q1, HwQubit q2, - bool closeBy = true) { + [[nodiscard]] SwapDistance getSwapDistance(const HwQubit q1, const HwQubit q2, + const bool closeBy = true) { if (q1 == q2) { return 0; } @@ -282,7 +284,7 @@ class HardwareQubits { * @param q The hardware qubit. * @return The nearby hardware qubits of the hardware qubit. */ - [[nodiscard]] HwQubits getNearbyQubits(HwQubit q) const { + [[nodiscard]] HwQubits getNearbyQubits(const HwQubit q) const { return nearbyQubits.at(q); } @@ -298,7 +300,8 @@ class HardwareQubits { * @param idx The coordinate index. * @return The unoccupied coordinates in the vicinity of the coordinate. */ - std::set getNearbyFreeCoordinatesByCoord(CoordIndex idx); + [[nodiscard]] std::set + getNearbyFreeCoordinatesByCoord(CoordIndex idx) const; /** * @brief Returns the occupied coordinates in the vicinity of a coordinate. @@ -320,22 +323,23 @@ class HardwareQubits { * @brief Computes the closest free coordinate in a given direction. * @details Uses a breadth-first search to find the closest free coordinate in * a given direction. - * @param qubit The hardware qubit to start the search from. + * @param coord The hardware qubit to start the search from. * @param direction The direction in which the search is performed * (Left/Right, Down/Up) * @param excludedCoords Coordinates to be ignored in the search. * @return The closest free coordinate in the given direction. */ - std::vector - findClosestFreeCoord(HwQubit qubit, Direction direction, - const CoordIndices& excludedCoords = {}); + [[nodiscard]] std::vector + findClosestFreeCoord(HwQubit coord, Direction direction, + const CoordIndices& excludedCoords = {}) const; - std::vector + [[nodiscard]] std::vector findClosestAncillaCoord(CoordIndex coord, Direction direction, int circQubitSize, - const CoordIndices& excludedCoords = {}); + const CoordIndices& excludedCoords = {}) const; - HwQubit getClosestQubit(CoordIndex coord, HwQubits ignored) const; + [[nodiscard]] HwQubit getClosestQubit(CoordIndex coord, + HwQubits ignored) const; // Blocking /** @@ -344,7 +348,8 @@ class HardwareQubits { * @param qubits The input hardware qubits. * @return The blocked hardware qubits. */ - std::set getBlockedQubits(const std::set& qubits); + [[nodiscard]] std::set + getBlockedQubits(const std::set& qubits) const; [[nodiscard]] std::map getInitHwPos() const { std::map initialHwPosMap; diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index fee3ed6c7..c632e4252 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -13,10 +13,7 @@ #include "hybridmap/NeutralAtomDefinitions.hpp" #include "hybridmap/NeutralAtomScheduler.hpp" #include "hybridmap/NeutralAtomUtils.hpp" -#include "ir/Permutation.hpp" #include "ir/QuantumComputation.hpp" -#include "ir/operations/Operation.hpp" -#include "sc/exact/ExactMapper.hpp" #include #include @@ -27,7 +24,6 @@ #include #include #include -#include #include namespace na { @@ -47,7 +43,7 @@ struct MapperParameters { InitialCoordinateMapping initialMapping; }; -enum RoutingType { +enum RoutingType : uint8_t { SwapType, BridgeType, MoveType, @@ -133,7 +129,8 @@ class NeutralAtomMapper { /** * @brief Maps all currently possible gates and updated until no more gates * can be mapped. - * @param layer The layer to map all possible gates for + * @param frontLayer The front layer to map all possible gates for + * @param lookaheadLayer The lookahead layer to map all possible gates for */ void mapAllPossibleGates(NeutralAtomLayer& frontLayer, NeutralAtomLayer& lookaheadLayer); @@ -151,17 +148,17 @@ class NeutralAtomMapper { */ bool isExecutable(const qc::Operation* opPointer); - void updateBlockedQubits(Swap swap) { - HwQubits qubits = {swap.first, swap.second}; + void updateBlockedQubits(const Swap& swap) { + const HwQubits qubits = {swap.first, swap.second}; updateBlockedQubits(qubits); } - void updateBlockedQubits(HwQubits qubits); + void updateBlockedQubits(const HwQubits& qubits); /** * @brief Update the mapping for the given swap gate. * @param swap The swap gate to update the mapping for */ - void applySwap(Swap swap); + void applySwap(const Swap& swap); /** * @brief Update the mapping for the given move operation. * @param move The move operation to update the mapping for @@ -205,7 +202,8 @@ class NeutralAtomMapper { * @return The minimal number of move operations and time needed to execute * the given gate */ - std::pair estimateNumMove(const qc::Operation* opPointer); + std::pair + estimateNumMove(const qc::Operation* opPointer) const; /** * @brief Uses estimateNumSwapGates and estimateNumMove to decide if a swap * gate or move operation is better. @@ -235,12 +233,13 @@ class NeutralAtomMapper { // Methods for bridge operations mapping - Bridge findBestBridge() const; - Bridges getShortestBridges() const; + [[nodiscard]] Bridge findBestBridge() const; + [[nodiscard]] Bridges getShortestBridges() const; - CoordIndices computeCurrentCoordUsages() const; + [[nodiscard]] CoordIndices computeCurrentCoordUsages() const; - FlyingAncillas convertMoveCombToFlyingAncilla(const MoveComb& move_comb); + [[nodiscard]] FlyingAncillas + convertMoveCombToFlyingAncilla(const MoveComb& moveComb) const; // std::vector> // findAllBridges(qc::QuantumComputation& qc); @@ -283,24 +282,27 @@ class NeutralAtomMapper { * operations that move qubits away and then the performs the actual move * operation. The move away is chosen such that it is in the same direction as * the second move operation. - * @param start The start position of the actual move operation - * @param target The target position of the actual move operation + * @param startCoord The start position of the actual move operation + * @param targetCoord The target position of the actual move operation * @param excludedCoords Coordinates the qubits should not be moved to * @return All possible move away combinations for a move from start to target */ - MoveCombs getMoveAwayCombinations(CoordIndex start, CoordIndex target, - const CoordIndices& excludedCoords); + [[nodiscard]] MoveCombs + getMoveAwayCombinations(CoordIndex startCoord, CoordIndex targetCoord, + const CoordIndices& excludedCoords) const; // Methods for flying ancilla operations mapping std::vector findBestFlyingAncillaComb(const qc::Operation* targetOp); std::set> findQtargetSet(std::set& usedQubits); - CoordIndex returnClosestAncillaCoord(const CoordIndex& c_target, - const CoordIndices& excludeCoords, - qc::QuantumComputation& qc); - MappingMethod compareSwapAndBridge(const Swap& bestSwap, - const Bridge& bestBridge); - MappingMethod compareShuttlingAndFlyingAncilla(MoveComb bestComb); + [[nodiscard]] CoordIndex + returnClosestAncillaCoord(const CoordIndex& cTarget, + const CoordIndices& excludeCoords, + const qc::QuantumComputation& qc) const; + [[nodiscard]] MappingMethod compareSwapAndBridge(const Swap& bestSwap, + const Bridge& bestBridge); + [[nodiscard]] MappingMethod + compareShuttlingAndFlyingAncilla(MoveComb bestComb); // Helper methods /** @@ -338,8 +340,9 @@ class NeutralAtomMapper { * @return Possible move combinations to move the gate qubits to the given * position */ - MoveCombs getMoveCombinationsToPosition(HwQubits& gateQubits, - CoordIndices& position); + [[nodiscard]] MoveCombs + getMoveCombinationsToPosition(const HwQubits& gateQubits, + const CoordIndices& position) const; // Multi-qubit gate based methods /** @@ -398,7 +401,8 @@ class NeutralAtomMapper { * @param layer The layer to compute the distance reduction for * @return The distance reduction cost */ - qc::fp moveCostPerLayer(const AtomMove& move, const GateList& layer); + [[nodiscard]] qc::fp moveCostPerLayer(const AtomMove& move, + const GateList& layer) const; /** * @brief Calculates a parallelization cost if the move operation can be @@ -406,7 +410,7 @@ class NeutralAtomMapper { * @param move The move operation to compute the cost for * @return The parallelization cost */ - qc::fp parallelMoveCost(const AtomMove& move); + [[nodiscard]] qc::fp parallelMoveCost(const AtomMove& move) const; /** * @brief Calculates the cost of a move operation. * @details The cost of a move operation is computed with the following terms: @@ -416,19 +420,19 @@ class NeutralAtomMapper { * @param move The move operation to compute the cost for * @return The cost of the move operation */ - qc::fp moveCost(const AtomMove& move); + [[nodiscard]] qc::fp moveCost(const AtomMove& move) const; /** * @brief Calculates the cost of a series of move operations by summing up the * cost of each move. * @param moveComb The series of move operations to compute the cost for * @return The total cost of the series of move operations */ - qc::fp moveCostComb(const MoveComb& moveComb); + [[nodiscard]] qc::fp moveCostComb(const MoveComb& moveComb) const; /** * @brief Print the current layers for debugging. */ - void printLayers(); + void printLayers() const; public: // Constructors @@ -529,7 +533,7 @@ class NeutralAtomMapper { } qc::QuantumComputation map(qc::QuantumComputation& qc, - InitialMapping initialMapping) { + const InitialMapping initialMapping) { return map(qc, Mapping(qc.getNqubits(), initialMapping)); } @@ -549,8 +553,7 @@ class NeutralAtomMapper { * hardware qubits */ [[maybe_unused]] void mapAndConvert(qc::QuantumComputation& qc, - InitialMapping initialMapping, - bool printInfo) { + const InitialMapping initialMapping) { map(qc, initialMapping); convertToAod(); } @@ -565,7 +568,7 @@ class NeutralAtomMapper { * @brief Prints the mapped circuits as an extended OpenQASM string. * @return The mapped quantum circuit with abstract SWAP gates and MOVE */ - [[maybe_unused]] std::string getMappedQcQasm() { + [[nodiscard]] [[maybe_unused]] std::string getMappedQcQasm() const { std::stringstream ss; this->mappedQc.dumpOpenQASM(ss, false); return ss.str(); @@ -575,7 +578,7 @@ class NeutralAtomMapper { * @brief Saves the mapped quantum circuit to a file. * @param filename The name of the file to save the mapped quantum circuit to */ - [[maybe_unused]] void saveMappedQcQasm(const std::string& filename) { + [[maybe_unused]] void saveMappedQcQasm(const std::string& filename) const { std::ofstream ofs(filename); this->mappedQc.dumpOpenQASM(ofs, false); } @@ -627,8 +630,8 @@ class NeutralAtomMapper { * @return The results of the scheduler */ [[maybe_unused]] SchedulerResults - schedule(bool verboseArg = false, bool createAnimationCsv = false, - qc::fp shuttlingSpeedFactor = 1.0) { + schedule(const bool verboseArg = false, const bool createAnimationCsv = false, + const qc::fp shuttlingSpeedFactor = 1.0) { if (mappedQcAOD.empty()) { convertToAod(); } @@ -649,11 +652,11 @@ class NeutralAtomMapper { * @brief Saves the animation csv file of the scheduled quantum circuit. * @param filename The name of the file to save the animation csv file to */ - [[maybe_unused]] void saveAnimationCsv(const std::string& filename) { + [[maybe_unused]] void saveAnimationCsv(const std::string& filename) const { scheduler.saveAnimationCsv(filename); } - void decomposeBridgeGates(qc::QuantumComputation& qc); + void decomposeBridgeGates(qc::QuantumComputation& qc) const; /** * @brief Converts a mapped circuit down to the AOD level and CZ level. diff --git a/include/hybridmap/Mapping.hpp b/include/hybridmap/Mapping.hpp index 5d2ca76e3..c309f7e4f 100644 --- a/include/hybridmap/Mapping.hpp +++ b/include/hybridmap/Mapping.hpp @@ -30,7 +30,7 @@ class Mapping { public: Mapping() = default; - Mapping(size_t nQubits, InitialMapping initialMapping) { + Mapping(const size_t nQubits, const InitialMapping initialMapping) { switch (initialMapping) { case Identity: for (size_t i = 0; i < nQubits; ++i) { @@ -46,7 +46,7 @@ class Mapping { * @param qubit The circuit qubit to be assigned * @param hwQubit The hardware qubit to be assigned */ - void setCircuitQubit(qc::Qubit qubit, HwQubit hwQubit) { + void setCircuitQubit(const qc::Qubit qubit, const HwQubit hwQubit) { circToHw[qubit] = hwQubit; } @@ -55,7 +55,7 @@ class Mapping { * @param qubit The circuit qubit to be queried * @return The hardware qubit assigned to the given circuit qubit */ - [[nodiscard]] HwQubit getHwQubit(qc::Qubit qubit) const { + [[nodiscard]] HwQubit getHwQubit(const qc::Qubit qubit) const { return circToHw.at(qubit); } @@ -65,7 +65,7 @@ class Mapping { * @return The hardware qubits assigned to the given circuit qubits */ [[nodiscard]] std::set - getHwQubits(std::set& qubits) const { + getHwQubits(const std::set& qubits) const { std::set hwQubits; for (const auto& qubit : qubits) { hwQubits.emplace(this->getHwQubit(qubit)); @@ -80,7 +80,7 @@ class Mapping { * @param qubit The hardware qubit to be queried * @return The circuit qubit assigned to the given hardware qubit */ - [[nodiscard]] qc::Qubit getCircQubit(HwQubit qubit) const { + [[nodiscard]] qc::Qubit getCircQubit(const HwQubit qubit) const { for (const auto& [circQubit, hwQubit] : circToHw) { if (hwQubit == qubit) { return circQubit; diff --git a/include/hybridmap/NeutralAtomArchitecture.hpp b/include/hybridmap/NeutralAtomArchitecture.hpp index b601ea0a4..3b337c196 100644 --- a/include/hybridmap/NeutralAtomArchitecture.hpp +++ b/include/hybridmap/NeutralAtomArchitecture.hpp @@ -73,9 +73,10 @@ class NeutralAtomArchitecture { public: Properties() = default; - Properties(std::uint16_t rows, std::uint16_t columns, std::uint16_t aods, - std::uint16_t aodCoordinates, qc::fp qubitDistance, - qc::fp radius, qc::fp blockingFac, qc::fp aodDist) + Properties(const std::uint16_t rows, const std::uint16_t columns, + const std::uint16_t aods, const std::uint16_t aodCoordinates, + const qc::fp qubitDistance, const qc::fp radius, + const qc::fp blockingFac, const qc::fp aodDist) : nRows(rows), nColumns(columns), nAods(aods), nAodIntermediateLevels( static_cast(qubitDistance / aodDist)), @@ -261,8 +262,8 @@ class NeutralAtomArchitecture { * @param idx2 The index of the second coordinate * @return The swap distance between the two coordinates */ - [[nodiscard]] SwapDistance getSwapDistance(CoordIndex idx1, - CoordIndex idx2) const { + [[nodiscard]] SwapDistance getSwapDistance(const CoordIndex idx1, + const CoordIndex idx2) const { return swapDistances(idx1, idx2); } /** @@ -343,7 +344,7 @@ class NeutralAtomArchitecture { * @param shuttlingType The type of the shuttling operation * @return The shuttling time of the shuttling operation */ - [[nodiscard]] qc::fp getShuttlingTime(qc::OpType shuttlingType) const { + [[nodiscard]] qc::fp getShuttlingTime(const qc::OpType shuttlingType) const { return parameters.shuttlingTimes.at(shuttlingType); } /** @@ -352,7 +353,7 @@ class NeutralAtomArchitecture { * @return The average fidelity of the shuttling operation */ [[nodiscard]] qc::fp - getShuttlingAverageFidelity(qc::OpType shuttlingType) const { + getShuttlingAverageFidelity(const qc::OpType shuttlingType) const { return parameters.shuttlingAverageFidelities.at(shuttlingType); } /** @@ -369,7 +370,7 @@ class NeutralAtomArchitecture { * @param idx The index * @return The coordinate corresponding to the index */ - [[nodiscard]] Point getCoordinate(CoordIndex idx) const { + [[nodiscard]] Point getCoordinate(const CoordIndex idx) const { return coordinates[idx]; } /** @@ -377,12 +378,12 @@ class NeutralAtomArchitecture { * @param c The coordinate * @return The index corresponding to the coordinate */ - [[nodiscard]] [[maybe_unused]] CoordIndex getIndex(const Point& c) { - return static_cast(c.x + c.y * properties.getNcolumns()); + [[nodiscard]] [[maybe_unused]] CoordIndex getIndex(const Point& c) const { + return static_cast(c.x + (c.y * properties.getNcolumns())); } [[nodiscard]] [[maybe_unused]] qc::QuantumComputation - getBridgeCircuit(size_t length) const { + getBridgeCircuit(const size_t length) const { return bridgeCircuits.bridgeCircuits[length]; } @@ -395,8 +396,8 @@ class NeutralAtomArchitecture { */ [[nodiscard]] qc::fp getEuclideanDistance(const CoordIndex idx1, const CoordIndex idx2) const { - return this->coordinates.at(idx1).getEuclideanDistance( - this->coordinates.at(idx2)); + return static_cast(this->coordinates.at(idx1).getEuclideanDistance( + this->coordinates.at(idx2))); } /** * @brief Get the Euclidean distance between two coordinates @@ -414,8 +415,8 @@ class NeutralAtomArchitecture { * @param idx2 The index of the second coordinate * @return The Manhattan distance between the two coordinate indices */ - [[nodiscard]] CoordIndex getManhattanDistanceX(CoordIndex idx1, - CoordIndex idx2) const { + [[nodiscard]] CoordIndex getManhattanDistanceX(const CoordIndex idx1, + const CoordIndex idx2) const { return static_cast( this->coordinates.at(idx1).getManhattanDistanceX( this->coordinates.at(idx2))); @@ -426,8 +427,8 @@ class NeutralAtomArchitecture { * @param idx2 The index of the second coordinate * @return The Manhattan distance between the two coordinate indices */ - [[nodiscard]] CoordIndex getManhattanDistanceY(CoordIndex idx1, - CoordIndex idx2) const { + [[nodiscard]] CoordIndex getManhattanDistanceY(const CoordIndex idx1, + const CoordIndex idx2) const { return static_cast( this->coordinates.at(idx1).getManhattanDistanceY( this->coordinates.at(idx2))); @@ -440,7 +441,7 @@ class NeutralAtomArchitecture { * @return The precomputed nearby coordinates for the coordinate index */ [[nodiscard]] std::set - getNearbyCoordinates(CoordIndex idx) const { + getNearbyCoordinates(const CoordIndex idx) const { return nearbyCoordinates[idx]; } /** @@ -464,7 +465,7 @@ class NeutralAtomArchitecture { auto y = static_cast(intRad); size_t x = 0; while (x <= xMax) { - if (static_cast(x * x + y * y) > intRad * intRad) { + if (static_cast((x * x) + (y * y)) > intRad * intRad) { y--; } else { maxGateSize += y + 1; @@ -481,7 +482,8 @@ class NeutralAtomArchitecture { * @param idx2 The index of the second coordinate * @return The MoveVector between the two coordinate indices */ - [[nodiscard]] MoveVector getVector(CoordIndex idx1, CoordIndex idx2) const { + [[nodiscard]] MoveVector getVector(const CoordIndex idx1, + const CoordIndex idx2) const { return {this->coordinates[idx1].x, this->coordinates[idx1].y, this->coordinates[idx2].x, this->coordinates[idx2].y}; } diff --git a/include/hybridmap/NeutralAtomScheduler.hpp b/include/hybridmap/NeutralAtomScheduler.hpp index 7778fcd01..b4d7342ce 100644 --- a/include/hybridmap/NeutralAtomScheduler.hpp +++ b/include/hybridmap/NeutralAtomScheduler.hpp @@ -31,8 +31,9 @@ struct SchedulerResults { qc::fp totalFidelities; uint32_t nCZs = 0; - SchedulerResults(qc::fp executionTime, qc::fp idleTime, qc::fp gateFidelities, - qc::fp fidelities, uint32_t cZs) + SchedulerResults(const qc::fp executionTime, const qc::fp idleTime, + const qc::fp gateFidelities, const qc::fp fidelities, + const uint32_t cZs) : totalExecutionTime(executionTime), totalIdleTime(idleTime), totalGateFidelities(gateFidelities), totalFidelities(fidelities), nCZs(cZs) {} @@ -97,13 +98,13 @@ class NeutralAtomScheduler { qc::fp shuttlingSpeedFactor = 1.0); std::string getAnimationCsv() { return animationCsv; } - void saveAnimationCsv(const std::string& filename) { + void saveAnimationCsv(const std::string& filename) const { // save animation std::ofstream file(filename); file << animationCsv; file.close(); // save architecture - auto filenameWithoutExtension = + const auto filenameWithoutExtension = filename.substr(0, filename.find_last_of('.')); file.open(filenameWithoutExtension + "_architecture.csv"); file << animationArchitectureCsv; @@ -116,8 +117,9 @@ class NeutralAtomScheduler { qc::fp totalGateFidelities, qc::fp totalFidelities, uint32_t nCZs); static void printTotalExecutionTimes( - std::vector& totalExecutionTimes, - std::vector>>& blockedQubitsTimes); + const std::vector& totalExecutionTimes, + const std::vector>>& + blockedQubitsTimes); }; } // namespace na diff --git a/include/hybridmap/NeutralAtomUtils.hpp b/include/hybridmap/NeutralAtomUtils.hpp index 7bd22602c..4ab81e6ce 100644 --- a/include/hybridmap/NeutralAtomUtils.hpp +++ b/include/hybridmap/NeutralAtomUtils.hpp @@ -22,7 +22,7 @@ namespace na { -class NeutralAtomException : public std::runtime_error { +class NeutralAtomException final : public std::runtime_error { std::string msg; public: @@ -80,8 +80,10 @@ struct Direction { bool x; bool y; - [[maybe_unused]] Direction(bool xDir, bool yDir) : x(xDir), y(yDir) {} - Direction(qc::fp deltaX, qc::fp deltaY) : x(deltaX >= 0), y(deltaY >= 0) {} + [[maybe_unused]] Direction(const bool xDir, const bool yDir) + : x(xDir), y(yDir) {} + Direction(const qc::fp deltaX, const qc::fp deltaY) + : x(deltaX >= 0), y(deltaY >= 0) {} [[nodiscard]] bool operator==(const Direction& other) const { return x == other.x && y == other.y; @@ -91,7 +93,7 @@ struct Direction { } [[nodiscard]] int32_t getSignX() const { return x ? 1 : -1; } [[nodiscard]] int32_t getSignY() const { return y ? 1 : -1; } - [[nodiscard]] int32_t getSign(Dimension dim) const { + [[nodiscard]] int32_t getSign(const Dimension dim) const { return dim == Dimension::X ? getSignX() : getSignY(); } }; @@ -108,16 +110,17 @@ struct MoveVector { qc::fp yEnd; Direction direction; - MoveVector(qc::fp xstart, qc::fp ystart, qc::fp xend, qc::fp yend) - : xStart(xstart), yStart(ystart), xEnd(xend), yEnd(yend), - direction(xend - xstart, yend - ystart) {} - MoveVector(std::int64_t xstart, std::int64_t ystart, std::int64_t xend, - std::int64_t yend) - : xStart(static_cast(xstart)), - yStart(static_cast(ystart)), xEnd(static_cast(xend)), - yEnd(static_cast(yend)), - direction(static_cast(xend - xstart), - static_cast(yend - ystart)) {} + MoveVector(const qc::fp xStart, const qc::fp yStart, const qc::fp xEnd, + const qc::fp yEnd) + : xStart(xStart), yStart(yStart), xEnd(xEnd), yEnd(yEnd), + direction(xEnd - xStart, yEnd - yStart) {} + MoveVector(const std::int64_t xStart, const std::int64_t yStart, + const std::int64_t xEnd, const std::int64_t yEnd) + : xStart(static_cast(xStart)), + yStart(static_cast(yStart)), xEnd(static_cast(xEnd)), + yEnd(static_cast(yEnd)), + direction(static_cast(xEnd - xStart), + static_cast(yEnd - yStart)) {} [[nodiscard]] [[maybe_unused]] bool sameDirection(const MoveVector& other) const { @@ -226,7 +229,7 @@ struct MoveCombs { [[nodiscard]] const_iterator begin() const { return moveCombs.cbegin(); } [[nodiscard]] const_iterator end() const { return moveCombs.cend(); } - void setOperation(const qc::Operation* op, CoordIndices pos) { + void setOperation(const qc::Operation* op, const CoordIndices& pos) { for (auto& moveComb : moveCombs) { moveComb.op = op; moveComb.bestPos = pos; @@ -287,7 +290,7 @@ class BridgeCircuits { static qc::QuantumComputation recursiveBridgeIncrease(qc::QuantumComputation qcBridge, size_t length); - static qc::QuantumComputation bridgeExpand(qc::QuantumComputation qcBridge, - size_t qubit); + static qc::QuantumComputation + bridgeExpand(const qc::QuantumComputation& qcBridge, size_t qubit); }; } // namespace na diff --git a/src/hybridmap/HardwareQubits.cpp b/src/hybridmap/HardwareQubits.cpp index 823003d97..14aa8dd19 100644 --- a/src/hybridmap/HardwareQubits.cpp +++ b/src/hybridmap/HardwareQubits.cpp @@ -18,7 +18,6 @@ #include #include #include -#include #include namespace na { @@ -38,10 +37,10 @@ void HardwareQubits::initNearbyQubits() { } } -void HardwareQubits::computeSwapDistance(HwQubit q1, HwQubit q2) { +void HardwareQubits::computeSwapDistance(HwQubit q1, const HwQubit q2) { std::queue q; - std::vector visited(swapDistances.size(), false); - std::vector parent(swapDistances.size(), q2); + std::vector visited(swapDistances.size(), false); + std::vector parent(swapDistances.size(), q2); q.push(q1); visited[q1] = true; @@ -133,7 +132,7 @@ void HardwareQubits::resetSwapDistances() { swapDistances = SymmetricMatrix(arch->getNqubits(), -1); } -void HardwareQubits::move(HwQubit hwQubit, CoordIndex newCoord) { +void HardwareQubits::move(HwQubit hwQubit, const CoordIndex newCoord) { if (newCoord >= arch->getNpositions()) { throw std::runtime_error("Invalid coordinate"); } @@ -145,7 +144,7 @@ void HardwareQubits::move(HwQubit hwQubit, CoordIndex newCoord) { } // remove qubit from old nearby qubits - auto prevNearbyQubits = nearbyQubits.at(hwQubit); + const auto prevNearbyQubits = nearbyQubits.at(hwQubit); for (const auto& qubit : prevNearbyQubits) { nearbyQubits.at(qubit).erase(std::find( nearbyQubits.at(qubit).begin(), nearbyQubits.at(qubit).end(), hwQubit)); @@ -155,7 +154,7 @@ void HardwareQubits::move(HwQubit hwQubit, CoordIndex newCoord) { computeNearbyQubits(hwQubit); // add qubit to new nearby qubits - auto newNearbyQubits = nearbyQubits.at(hwQubit); + const auto newNearbyQubits = nearbyQubits.at(hwQubit); for (const auto& qubit : newNearbyQubits) { nearbyQubits.at(qubit).emplace(hwQubit); } @@ -173,11 +172,11 @@ std::vector HardwareQubits::getNearbySwaps(HwQubit q) const { return swaps; } -void HardwareQubits::computeNearbyQubits(HwQubit q) { +void HardwareQubits::computeNearbyQubits(const HwQubit qubit) { std::set newNearbyQubits; - auto coordQ = hwToCoordIdx.at(q); + const auto coordQ = hwToCoordIdx.at(qubit); for (const auto& coord : hwToCoordIdx) { - if (coord.first == q) { + if (coord.first == qubit) { continue; } if (arch->getEuclideanDistance(coordQ, coord.second) <= @@ -185,15 +184,15 @@ void HardwareQubits::computeNearbyQubits(HwQubit q) { newNearbyQubits.emplace(coord.first); } } - nearbyQubits.insert_or_assign(q, newNearbyQubits); + nearbyQubits.insert_or_assign(qubit, newNearbyQubits); } qc::fp HardwareQubits::getAllToAllSwapDistance(std::set& qubits) { // two qubit gates if (qubits.size() == 2) { auto it = qubits.begin(); - auto q1 = *it; - auto q2 = *(++it); + const auto q1 = *it; + const auto q2 = *++it; return getSwapDistance(q1, q2); } // for n > 2 all qubits need to be within the interaction radius of each other @@ -207,7 +206,7 @@ qc::fp HardwareQubits::getAllToAllSwapDistance(std::set& qubits) { } std::set -HardwareQubits::getBlockedQubits(const std::set& qubits) { +HardwareQubits::getBlockedQubits(const std::set& qubits) const { std::set blockedQubits; for (const auto& qubit : qubits) { for (uint32_t i = 0; i < hwToCoordIdx.maxKey(); ++i) { @@ -227,7 +226,7 @@ HardwareQubits::getBlockedQubits(const std::set& qubits) { } std::set -HardwareQubits::getNearbyFreeCoordinatesByCoord(CoordIndex idx) { +HardwareQubits::getNearbyFreeCoordinatesByCoord(const CoordIndex idx) const { std::set nearbyFreeCoordinates; for (auto const& coordIndex : this->arch->getNearbyCoordinates(idx)) { if (!this->isMapped(coordIndex)) { @@ -237,15 +236,16 @@ HardwareQubits::getNearbyFreeCoordinatesByCoord(CoordIndex idx) { return nearbyFreeCoordinates; } -std::set -HardwareQubits::getNearbyOccupiedCoordinatesByCoord(CoordIndex idx) const { - auto nearbyHwQubits = this->getNearbyQubits(this->getHwQubit(idx)); +std::set HardwareQubits::getNearbyOccupiedCoordinatesByCoord( + const CoordIndex idx) const { + const auto nearbyHwQubits = this->getNearbyQubits(this->getHwQubit(idx)); return this->getCoordIndices(nearbyHwQubits); } std::vector -HardwareQubits::findClosestFreeCoord(CoordIndex coord, Direction direction, - const CoordIndices& excludeCoord) { +HardwareQubits::findClosestFreeCoord(CoordIndex coord, + const Direction direction, + const CoordIndices& excludedCoords) const { // return the closest free coord in general // and the closest free coord in the given direction std::vector closestFreeCoords; @@ -255,7 +255,7 @@ HardwareQubits::findClosestFreeCoord(CoordIndex coord, Direction direction, visited.emplace(coord); bool foundClosest = false; while (!queue.empty()) { - auto currentCoord = queue.front(); + const auto currentCoord = queue.front(); queue.pop(); auto nearbyCoords = this->arch->getNN(currentCoord); for (const auto& nearbyCoord : nearbyCoords) { @@ -263,8 +263,8 @@ HardwareQubits::findClosestFreeCoord(CoordIndex coord, Direction direction, visited.rend()) { visited.emplace(nearbyCoord); if (!this->isMapped(nearbyCoord) && - std::find(excludeCoord.begin(), excludeCoord.end(), nearbyCoord) == - excludeCoord.end()) { + std::find(excludedCoords.begin(), excludedCoords.end(), + nearbyCoord) == excludedCoords.end()) { if (!foundClosest) { closestFreeCoords.emplace_back(nearbyCoord); } @@ -282,10 +282,9 @@ HardwareQubits::findClosestFreeCoord(CoordIndex coord, Direction direction, return closestFreeCoords; } -std::vector -HardwareQubits::findClosestAncillaCoord(CoordIndex coord, Direction direction, - int circQubitSize, - const CoordIndices& excludeCoord) { +std::vector HardwareQubits::findClosestAncillaCoord( + const CoordIndex coord, const Direction direction, const int circQubitSize, + const CoordIndices& excludedCoords) const { // return the closest ancilla coord in general // and the closest free ancilla in the given direction std::vector closestFreeCoords; @@ -295,7 +294,7 @@ HardwareQubits::findClosestAncillaCoord(CoordIndex coord, Direction direction, visited.insert(coord); bool foundClosest = false; while (!queue.empty()) { - auto currentCoord = queue.front(); + const auto currentCoord = queue.front(); queue.pop(); auto nearbyCoords = this->arch->getNN(currentCoord); for (const auto& nearbyCoord : nearbyCoords) { @@ -304,8 +303,8 @@ HardwareQubits::findClosestAncillaCoord(CoordIndex coord, Direction direction, visited.insert(nearbyCoord); if (this->isMapped(nearbyCoord) && this->getHwQubit(nearbyCoord) >= circQubitSize && - std::find(excludeCoord.begin(), excludeCoord.end(), nearbyCoord) == - excludeCoord.end()) { + std::find(excludedCoords.begin(), excludedCoords.end(), + nearbyCoord) == excludedCoords.end()) { if (!foundClosest) { closestFreeCoords.push_back(nearbyCoord); } @@ -322,7 +321,7 @@ HardwareQubits::findClosestAncillaCoord(CoordIndex coord, Direction direction, } return closestFreeCoords; } -HwQubit HardwareQubits::getClosestQubit(CoordIndex coord, +HwQubit HardwareQubits::getClosestQubit(const CoordIndex coord, HwQubits ignored) const { HwQubit closestQubit = 0; auto minDistance = std::numeric_limits::max(); @@ -330,8 +329,8 @@ HwQubit HardwareQubits::getClosestQubit(CoordIndex coord, if (ignored.find(qubit) != ignored.end()) { continue; } - auto distance = arch->getEuclideanDistance(coord, idx); - if (distance < minDistance) { + if (const auto distance = arch->getEuclideanDistance(coord, idx); + distance < minDistance) { minDistance = distance; closestQubit = qubit; } diff --git a/src/hybridmap/HybridAnimation.cpp b/src/hybridmap/HybridAnimation.cpp index 1de1bc863..42f0984c8 100644 --- a/src/hybridmap/HybridAnimation.cpp +++ b/src/hybridmap/HybridAnimation.cpp @@ -22,12 +22,12 @@ namespace na { AnimationAtoms::AnimationAtoms(const std::map& initHwPos, const NeutralAtomArchitecture& arch) { - auto nCols = arch.getNcolumns(); + const auto nCols = arch.getNcolumns(); for (const auto& [id, coord] : initHwPos) { coordIdxToId[coord] = id; - auto column = coord % nCols; - auto row = coord / nCols; + const auto column = coord % nCols; + const auto row = coord / nCols; idToCoord[id] = {column * arch.getInterQubitDistance(), row * arch.getInterQubitDistance()}; } @@ -45,7 +45,7 @@ std::string AnimationAtoms::getInitString() { return initString; } -std::string AnimationAtoms::getEndString(qc::fp endTime) { +std::string AnimationAtoms::getEndString(const qc::fp endTime) { std::string initString; for (const auto& [id, coord] : idToCoord) { initString += std::to_string(endTime) + ";" + std::to_string(id) + ";" + @@ -55,7 +55,7 @@ std::string AnimationAtoms::getEndString(qc::fp endTime) { return initString; } -AnimationAtoms::axesId AnimationAtoms::addAxis(HwQubit id) { +AnimationAtoms::axesId AnimationAtoms::addAxis(const HwQubit id) { if (axesIds.find(id) == axesIds.end()) { axesIdCounter++; axesIds[id] = axesIdCounter; @@ -66,7 +66,7 @@ AnimationAtoms::axesId AnimationAtoms::addAxis(HwQubit id) { } return axesIds[id]; } -AnimationAtoms::marginId AnimationAtoms::addMargin(HwQubit id) { +AnimationAtoms::marginId AnimationAtoms::addMargin(const HwQubit id) { if (marginIds.find(id) == marginIds.end()) { marginIdCounter++; marginIds[id] = marginIdCounter; @@ -99,9 +99,9 @@ AnimationAtoms::createCsvOp(const std::unique_ptr& op, auto coord = idAndCoord.second; auto col = coordIdx % arch.getNcolumns(); auto row = coordIdx / arch.getNcolumns(); - if (std::abs(coord.first - col * arch.getInterQubitDistance()) < + if (std::abs(coord.first - (col * arch.getInterQubitDistance())) < 0.0001 && - std::abs(coord.second - row * arch.getInterQubitDistance()) < + std::abs(coord.second - (row * arch.getInterQubitDistance())) < 0.0001) { // remove old coordIdx with same id for (const auto& [oldCoordIdx, oldId] : coordIdxToId) { @@ -204,10 +204,13 @@ AnimationAtoms::createCsvOp(const std::unique_ptr& op, } return csvLine; } -std::string AnimationAtoms::createCsvLine( - qc::fp startTime, HwQubit id, qc::fp x, qc::fp y, uint32_t size, - uint32_t color, bool axes, AnimationAtoms::axesId axId, bool margin, - AnimationAtoms::marginId marginId, qc::fp marginSize) { +std::string AnimationAtoms::createCsvLine(const qc::fp startTime, + const HwQubit id, const qc::fp x, + const qc::fp y, const uint32_t size, + const uint32_t color, const bool axes, + const axesId axId, const bool margin, + const marginId marginId, + const qc::fp marginSize) { std::string csvLine; csvLine += std::to_string(startTime) + ";" + std::to_string(id) + ";" + diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index b828dc67a..eebc8c96a 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -22,16 +22,15 @@ #include #include #include -#include #include #include #include +#include #include #include #include #include #include -#include #include namespace na { @@ -47,7 +46,7 @@ void NeutralAtomMapper::mapAppend(qc::QuantumComputation& qc, const auto nQubits = qc.getNqubits(); prepareAncillas(nQubits); - auto dag = qc::CircuitOptimizer::constructDAG(qc); + const auto dag = qc::CircuitOptimizer::constructDAG(qc); /* // init mapping @@ -298,7 +297,7 @@ void NeutralAtomMapper::mapAllPossibleGates(NeutralAtomLayer& frontLayer, } } -void NeutralAtomMapper::decomposeBridgeGates(qc::QuantumComputation& qc) { +void NeutralAtomMapper::decomposeBridgeGates(qc::QuantumComputation& qc) const { auto it = qc.begin(); while (it != qc.end()) { if ((*it)->isStandardOperation() && (*it)->getType() == qc::Bridge) { @@ -371,13 +370,13 @@ void NeutralAtomMapper::reassignGatesToLayers(const GateList& frontGates, void NeutralAtomMapper::mapGate(const qc::Operation* op) { if (this->parameters->verbose) { std::cout << "mapped " << op->getName() << " "; - for (auto qubit : op->getUsedQubits()) { + for (const auto qubit : op->getUsedQubits()) { std::cout << qubit << " "; } std::cout << "\n"; } // convert circuit qubits to CoordIndex and append to mappedQc - auto opCopyUnique = op->clone(); + const auto opCopyUnique = op->clone(); auto* opCopy = opCopyUnique.get(); this->mapping.mapToHwQubits(opCopy); this->hardwareQubits.mapToCoordIdx(opCopy); @@ -385,23 +384,22 @@ void NeutralAtomMapper::mapGate(const qc::Operation* op) { } bool NeutralAtomMapper::isExecutable(const qc::Operation* opPointer) { - auto usedQubits = opPointer->getUsedQubits(); - auto nUsedQubits = usedQubits.size(); - if (nUsedQubits == 1) { + const auto usedQubits = opPointer->getUsedQubits(); + if (usedQubits.size() == 1) { return true; } std::set usedHwQubits; - for (auto qubit : usedQubits) { + for (const auto qubit : usedQubits) { usedHwQubits.emplace(this->mapping.getHwQubit(qubit)); } return this->hardwareQubits.getAllToAllSwapDistance(usedHwQubits) == 0; } -void NeutralAtomMapper::printLayers() { +void NeutralAtomMapper::printLayers() const { std::cout << "f,g: "; for (const auto* op : this->frontLayerGate) { std::cout << op->getName() << " "; - for (auto qubit : op->getUsedQubits()) { + for (const auto qubit : op->getUsedQubits()) { std::cout << qubit << " "; } std::cout << '\n'; @@ -409,7 +407,7 @@ void NeutralAtomMapper::printLayers() { std::cout << "f,s: "; for (const auto* op : this->frontLayerShuttling) { std::cout << op->getName() << " "; - for (auto qubit : op->getUsedQubits()) { + for (const auto qubit : op->getUsedQubits()) { std::cout << qubit << " "; } std::cout << '\n'; @@ -417,7 +415,7 @@ void NeutralAtomMapper::printLayers() { std::cout << "l,g: "; for (const auto* op : this->lookaheadLayerGate) { std::cout << op->getName() << " "; - for (auto qubit : op->getUsedQubits()) { + for (const auto qubit : op->getUsedQubits()) { std::cout << qubit << " "; } std::cout << '\n'; @@ -426,7 +424,7 @@ void NeutralAtomMapper::printLayers() { std::cout << "l,g: "; for (const auto* op : this->lookaheadLayerShuttling) { std::cout << op->getName() << " "; - for (auto qubit : op->getUsedQubits()) { + for (const auto qubit : op->getUsedQubits()) { std::cout << qubit << " "; } std::cout << '\n'; @@ -444,7 +442,7 @@ GateList NeutralAtomMapper::getExecutableGates(const GateList& gates) { return executableGates; } -void NeutralAtomMapper::updateBlockedQubits(HwQubits qubits) { +void NeutralAtomMapper::updateBlockedQubits(const HwQubits& qubits) { // save to lastSwaps this->lastBlockedQubits.emplace_back( this->hardwareQubits.getBlockedQubits(qubits)); @@ -453,13 +451,13 @@ void NeutralAtomMapper::updateBlockedQubits(HwQubits qubits) { } } -void NeutralAtomMapper::applySwap(Swap swap) { +void NeutralAtomMapper::applySwap(const Swap& swap) { nSwaps++; this->mapping.applySwap(swap); // convert circuit qubits to CoordIndex and append to mappedQc - auto idxFirst = this->hardwareQubits.getCoordIndex(swap.first); - auto idxSecond = this->hardwareQubits.getCoordIndex(swap.second); + const auto idxFirst = this->hardwareQubits.getCoordIndex(swap.first); + const auto idxSecond = this->hardwareQubits.getCoordIndex(swap.second); this->mappedQc.swap(idxFirst, idxSecond); if (this->parameters->verbose) { std::cout << "swapped " << swap.first << " " << swap.second; @@ -484,7 +482,7 @@ void NeutralAtomMapper::applyMove(AtomMove move) { this->lastMoves.pop_front(); } mappedQc.move(move.first, move.second); - auto toMoveHwQubit = this->hardwareQubits.getHwQubit(move.first); + const auto toMoveHwQubit = this->hardwareQubits.getHwQubit(move.first); this->hardwareQubits.move(toMoveHwQubit, move.second); if (this->parameters->verbose) { std::cout << "moved " << move.first << " to " << move.second; @@ -504,7 +502,7 @@ void NeutralAtomMapper::applyBridge(NeutralAtomLayer& frontLayer, if (this->parameters->verbose) { std::cout << "bridged " << bridge.first->getName() << " "; - for (auto qubit : bridge.second) { + for (const auto qubit : bridge.second) { std::cout << qubit << " "; } std::cout << '\n'; @@ -519,8 +517,8 @@ void NeutralAtomMapper::applyBridge(NeutralAtomLayer& frontLayer, Swap NeutralAtomMapper::findBestSwap(const Swap& lastSwap) { // compute necessary movements - auto swapsFront = initSwaps(this->frontLayerGate); - auto swapsLookahead = initSwaps(this->lookaheadLayerGate); + const auto swapsFront = initSwaps(this->frontLayerGate); + const auto swapsLookahead = initSwaps(this->lookaheadLayerGate); setTwoQubitSwapWeight(swapsFront.second); // evaluate swaps based on cost function @@ -544,10 +542,11 @@ Swap NeutralAtomMapper::findBestSwap(const Swap& lastSwap) { return swap1.second < swap2.second; }); // get swap of minimal cost - auto bestSwap = std::min_element(swapCosts.begin(), swapCosts.end(), - [](const auto& swap1, const auto& swap2) { - return swap1.second < swap2.second; - }); + const auto bestSwap = + std::min_element(swapCosts.begin(), swapCosts.end(), + [](const auto& swap1, const auto& swap2) { + return swap1.second < swap2.second; + }); return bestSwap->first; } @@ -586,16 +585,17 @@ Bridge NeutralAtomMapper::findBestBridge() const { auto allBridges = getShortestBridges(); if (allBridges.empty()) { return {}; - } else if (allBridges.size() == 1) { + } + if (allBridges.size() == 1) { return allBridges.front(); } // use bridge along less used qubits - auto qubitUsages = computeCurrentCoordUsages(); + const auto qubitUsages = computeCurrentCoordUsages(); size_t bestBridgeIdx = 0; size_t minUsage = std::numeric_limits::max(); for (size_t i = 0; i < allBridges.size(); ++i) { size_t usage = 0; - for (auto qubit : allBridges[i].second) { + for (const auto qubit : allBridges[i].second) { usage += qubitUsages[qubit]; } if (usage < minUsage) { @@ -654,10 +654,10 @@ CoordIndices NeutralAtomMapper::computeCurrentCoordUsages() const { } return coordUsages; } -FlyingAncillas -NeutralAtomMapper::convertMoveCombToFlyingAncilla(const MoveComb& moveComb) { - auto usedQubits = moveComb.op->getUsedQubits(); - auto hwQubits = this->mapping.getHwQubits(usedQubits); +FlyingAncillas NeutralAtomMapper::convertMoveCombToFlyingAncilla( + const MoveComb& moveComb) const { + const auto usedQubits = moveComb.op->getUsedQubits(); + const auto hwQubits = this->mapping.getHwQubits(usedQubits); const auto usedCoords = this->hardwareQubits.getCoordIndices(hwQubits); // not enough qubits for a flying ancilla if (usedCoords.size() - 1 > mappedQc.getNancillae()) { @@ -666,18 +666,17 @@ NeutralAtomMapper::convertMoveCombToFlyingAncilla(const MoveComb& moveComb) { // multi-qubit gate -> only one direction FlyingAncillas bestFAs; - FlyingAncilla bestFA; + FlyingAncilla bestFA{}; HwQubits usedFA; for (const auto move : moveComb.moves) { - if (std::find(usedCoords.begin(), usedCoords.end(), move.first) != - usedCoords.end()) { + if (usedCoords.find(move.first) != usedCoords.end()) { bestFA.op = moveComb.op; - auto nearFirstIdx = + const auto nearFirstIdx = this->flyingAncillas.getClosestQubit(move.first, usedFA); - auto nearFirst = this->flyingAncillas.getCoordIndex(nearFirstIdx); - auto nearSecondIdx = + const auto nearFirst = this->flyingAncillas.getCoordIndex(nearFirstIdx); + const auto nearSecondIdx = this->flyingAncillas.getClosestQubit(move.second, usedFA); - auto nearSecond = this->flyingAncillas.getCoordIndex(nearSecondIdx); + const auto nearSecond = this->flyingAncillas.getCoordIndex(nearSecondIdx); if (usedQubits.size() == 2) { // both directions possible, check if reversed is better if (this->arch->getEuclideanDistance(nearFirstIdx, move.first) < @@ -706,7 +705,7 @@ qc::fp NeutralAtomMapper::swapCost( auto [swapCloseByFront, swapExactFront] = swapsFront; auto [swapCloseByLookahead, swapExactLookahead] = swapsLookahead; // compute the change in total distance - auto distanceChangeFront = + const auto distanceChangeFront = swapCostPerLayer(swap, swapCloseByFront, swapExactFront) / static_cast(this->frontLayerGate.size()); qc::fp distanceChangeLookahead = 0; @@ -715,7 +714,7 @@ qc::fp NeutralAtomMapper::swapCost( swapCostPerLayer(swap, swapCloseByLookahead, swapExactLookahead) / static_cast(this->lookaheadLayerGate.size()); } - auto cost = parameters->lookaheadWeightSwaps * distanceChangeLookahead + + auto cost = (parameters->lookaheadWeightSwaps * distanceChangeLookahead) + distanceChangeFront; // compute the last time one of the swap qubits was used if (this->parameters->decay != 0) { @@ -777,7 +776,7 @@ NeutralAtomMapper::initSwaps(const GateList& layer) { auto bestPos = getBestMultiQubitPosition(gate); if (this->parameters->verbose) { std::cout << "bestPos: "; - for (auto qubit : bestPos) { + for (const auto qubit : bestPos) { std::cout << qubit << " "; } std::cout << '\n'; @@ -829,8 +828,8 @@ qc::fp NeutralAtomMapper::swapCostPerLayer(const Swap& swap, // move qubits to the exact position for multi-qubit gates for (const auto& [exactSwap, weight] : swapExact) { - auto origin = exactSwap.first; - auto destination = exactSwap.second; + const auto origin = exactSwap.first; + const auto destination = exactSwap.second; distBefore = this->hardwareQubits.getSwapDistance(origin, destination, false); if (distBefore == std::numeric_limits::max()) { @@ -861,7 +860,8 @@ qc::fp NeutralAtomMapper::swapCostPerLayer(const Swap& swap, return distChange; } -HwQubits NeutralAtomMapper::getBestMultiQubitPosition(const qc::Operation* op) { +HwQubits +NeutralAtomMapper::getBestMultiQubitPosition(const qc::Operation* opPointer) { // try to find position around gate Qubits recursively // if not, search through coupling graph until found according to a // priority queue based on the distance to the other qubits @@ -870,8 +870,8 @@ HwQubits NeutralAtomMapper::getBestMultiQubitPosition(const qc::Operation* op) { std::vector>, std::greater<>> qubitQueue; // add the gate qubits to the priority queue - auto gateQubits = op->getUsedQubits(); - auto gateHwQubits = this->mapping.getHwQubits(gateQubits); + const auto gateQubits = opPointer->getUsedQubits(); + const auto gateHwQubits = this->mapping.getHwQubits(gateQubits); // add the gate qubits to the priority queue for (const auto& gateQubit : gateHwQubits) { qc::fp totalDist = 0; @@ -921,18 +921,19 @@ HwQubits NeutralAtomMapper::getBestMultiQubitPosition(const qc::Operation* op) { } } // find gate and move it to the shuttling layer - auto idxFrontGate = - std::find(this->frontLayerGate.begin(), this->frontLayerGate.end(), op); + const auto idxFrontGate = std::find(this->frontLayerGate.begin(), + this->frontLayerGate.end(), opPointer); if (idxFrontGate != this->frontLayerGate.end()) { this->frontLayerGate.erase(idxFrontGate); - this->frontLayerShuttling.emplace_back(op); + this->frontLayerShuttling.emplace_back(opPointer); } // remove from lookahead layer if there - auto idxLookaheadGate = std::find(this->lookaheadLayerGate.begin(), - this->lookaheadLayerGate.end(), op); + const auto idxLookaheadGate = + std::find(this->lookaheadLayerGate.begin(), + this->lookaheadLayerGate.end(), opPointer); if (idxLookaheadGate != this->lookaheadLayerGate.end()) { this->lookaheadLayerGate.erase(idxLookaheadGate); - this->lookaheadLayerShuttling.emplace_back(op); + this->lookaheadLayerShuttling.emplace_back(opPointer); } return {}; } @@ -946,7 +947,7 @@ HwQubits NeutralAtomMapper::getBestMultiQubitPositionRec( return bestPos; } // update remainingNearbyQubits - auto newQubit = *selectedQubits.rbegin(); + const auto newQubit = *selectedQubits.rbegin(); auto nearbyNextQubit = this->hardwareQubits.getNearbyQubits(newQubit); // compute remaining qubits as the intersection with current Qubits newRemainingQubits; @@ -983,7 +984,7 @@ HwQubits NeutralAtomMapper::getBestMultiQubitPositionRec( summedDistances.emplace_back(hwQubit, distance); } // select next qubit as the one with minimal distance - auto nextQubitDist = + const auto nextQubitDist = std::min_element(summedDistances.begin(), summedDistances.end(), [](const auto& qubit1, const auto& qubit2) { return qubit1.second < qubit2.second; @@ -995,7 +996,7 @@ HwQubits NeutralAtomMapper::getBestMultiQubitPositionRec( auto closesDistance = this->hardwareQubits.getSwapDistance(closesGateQubits, nextQubit, true); for (const auto& gateQubit : remainingGateQubits) { - auto distance = + const auto distance = this->hardwareQubits.getSwapDistance(gateQubit, nextQubit, true); if (distance < closesDistance) { closesGateQubits = gateQubit; @@ -1014,7 +1015,7 @@ NeutralAtomMapper::getExactSwapsToPosition(const qc::Operation* op, if (position.empty()) { return {}; } - auto gateQubits = op->getUsedQubits(); + const auto gateQubits = op->getUsedQubits(); auto gateHwQubits = this->mapping.getHwQubits(gateQubits); WeightedSwaps swapsExact; while (!position.empty() && !gateHwQubits.empty()) { @@ -1024,7 +1025,7 @@ NeutralAtomMapper::getExactSwapsToPosition(const qc::Operation* op, for (const auto& gateQubit : gateHwQubits) { SwapDistance minimalDistance = std::numeric_limits::max(); for (const auto& posQubit : position) { - auto distance = + const auto distance = this->hardwareQubits.getSwapDistance(gateQubit, posQubit, false); if (distance < minimalDistance) { minimalDistance = distance; @@ -1037,15 +1038,16 @@ NeutralAtomMapper::getExactSwapsToPosition(const qc::Operation* op, if (minimalDistance == std::numeric_limits::max()) { // not possible to move to position // move gate to shuttling layer - auto idxFrontGate = std::find(this->frontLayerGate.begin(), - this->frontLayerGate.end(), op); + const auto idxFrontGate = std::find(this->frontLayerGate.begin(), + this->frontLayerGate.end(), op); if (idxFrontGate != this->frontLayerGate.end()) { this->frontLayerGate.erase(idxFrontGate); this->frontLayerShuttling.emplace_back(op); } // remove from lookahead layer if there - auto idxLookaheadGate = std::find(this->lookaheadLayerGate.begin(), - this->lookaheadLayerGate.end(), op); + const auto idxLookaheadGate = + std::find(this->lookaheadLayerGate.begin(), + this->lookaheadLayerGate.end(), op); if (idxLookaheadGate != this->lookaheadLayerGate.end()) { this->lookaheadLayerGate.erase(idxLookaheadGate); this->lookaheadLayerShuttling.emplace_back(op); @@ -1110,8 +1112,8 @@ NeutralAtomMapper::getExactSwapsToPosition(const qc::Operation* op, } // add cost to the moves -> move first qubit corresponding to almost finished // positions - auto nQubits = op->getUsedQubits().size(); - auto multiQubitFactor = + const auto nQubits = op->getUsedQubits().size(); + const auto multiQubitFactor = (static_cast(nQubits) * static_cast(nQubits - 1)) / 2; for (auto& move : swapsExact) { move.second = multiQubitFactor / static_cast(totalDistance); @@ -1136,10 +1138,11 @@ MoveComb NeutralAtomMapper::findBestAtomMove() { }); // get move of minimal cost - auto bestMove = std::min_element(moveCosts.begin(), moveCosts.end(), - [](const auto& move1, const auto& move2) { - return move1.second < move2.second; - }); + const auto bestMove = + std::min_element(moveCosts.begin(), moveCosts.end(), + [](const auto& move1, const auto& move2) { + return move1.second < move2.second; + }); return bestMove->first; } @@ -1163,7 +1166,7 @@ MoveComb NeutralAtomMapper::findBestAtomMove() { // return moveCosts.front().first; // } -qc::fp NeutralAtomMapper::moveCostComb(const MoveComb& moveComb) { +qc::fp NeutralAtomMapper::moveCostComb(const MoveComb& moveComb) const { qc::fp costComb = 0; for (const auto& move : moveComb.moves) { costComb += moveCost(move); @@ -1171,22 +1174,22 @@ qc::fp NeutralAtomMapper::moveCostComb(const MoveComb& moveComb) { return costComb; } -qc::fp NeutralAtomMapper::moveCost(const AtomMove& move) { +qc::fp NeutralAtomMapper::moveCost(const AtomMove& move) const { qc::fp cost = 0; - auto frontCost = moveCostPerLayer(move, this->frontLayerShuttling) / - static_cast(this->frontLayerShuttling.size()); + const auto frontCost = moveCostPerLayer(move, this->frontLayerShuttling) / + static_cast(this->frontLayerShuttling.size()); cost += frontCost; if (!lookaheadLayerShuttling.empty()) { - auto lookaheadCost = + const auto lookaheadCost = moveCostPerLayer(move, this->lookaheadLayerShuttling) / static_cast(this->lookaheadLayerShuttling.size()); cost += parameters->lookaheadWeightMoves * lookaheadCost; } if (!this->lastMoves.empty()) { - auto parallelCost = parameters->shuttlingTimeWeight * - parallelMoveCost(move) / - static_cast(this->lastMoves.size()) / - static_cast(this->frontLayerShuttling.size()); + const auto parallelCost = + parameters->shuttlingTimeWeight * parallelMoveCost(move) / + static_cast(this->lastMoves.size()) / + static_cast(this->frontLayerShuttling.size()); cost += parallelCost; } @@ -1194,23 +1197,23 @@ qc::fp NeutralAtomMapper::moveCost(const AtomMove& move) { } qc::fp NeutralAtomMapper::moveCostPerLayer(const AtomMove& move, - const GateList& layer) { + const GateList& layer) const { // compute cost assuming the move was applied qc::fp distChange = 0; - auto toMoveHwQubit = this->hardwareQubits.getHwQubit(move.first); - if (this->mapping.isMapped(toMoveHwQubit)) { - auto toMoveCircuitQubit = this->mapping.getCircQubit(toMoveHwQubit); + if (const auto toMoveHwQubit = this->hardwareQubits.getHwQubit(move.first); + this->mapping.isMapped(toMoveHwQubit)) { + const auto toMoveCircuitQubit = this->mapping.getCircQubit(toMoveHwQubit); for (const auto& gate : layer) { - auto usedQubits = gate->getUsedQubits(); - if (usedQubits.find(toMoveCircuitQubit) != usedQubits.end()) { + if (auto const usedQubits = gate->getUsedQubits(); + usedQubits.find(toMoveCircuitQubit) != usedQubits.end()) { // check distance reduction qc::fp distanceBefore = 0; for (const auto& qubit : usedQubits) { if (qubit == toMoveCircuitQubit) { continue; } - auto hwQubit = this->mapping.getHwQubit(qubit); - auto dist = this->arch->getEuclideanDistance( + const auto hwQubit = this->mapping.getHwQubit(qubit); + const auto dist = this->arch->getEuclideanDistance( this->hardwareQubits.getCoordIndex(hwQubit), this->hardwareQubits.getCoordIndex(toMoveHwQubit)); distanceBefore += dist; @@ -1220,8 +1223,8 @@ qc::fp NeutralAtomMapper::moveCostPerLayer(const AtomMove& move, if (qubit == toMoveCircuitQubit) { continue; } - auto hwQubit = this->mapping.getHwQubit(qubit); - auto dist = this->arch->getEuclideanDistance( + const auto hwQubit = this->mapping.getHwQubit(qubit); + const auto dist = this->arch->getEuclideanDistance( this->hardwareQubits.getCoordIndex(hwQubit), move.second); distanceAfter += dist; } @@ -1232,9 +1235,9 @@ qc::fp NeutralAtomMapper::moveCostPerLayer(const AtomMove& move, return distChange; } -qc::fp NeutralAtomMapper::parallelMoveCost(const AtomMove& move) { +qc::fp NeutralAtomMapper::parallelMoveCost(const AtomMove& move) const { qc::fp parallelCost = 0; - auto moveVector = this->arch->getVector(move.first, move.second); + const auto moveVector = this->arch->getVector(move.first, move.second); std::vector lastEndingCoords; if (this->lastMoves.empty()) { parallelCost += arch->getVectorShuttlingTime(moveVector); @@ -1257,13 +1260,13 @@ qc::fp NeutralAtomMapper::parallelMoveCost(const AtomMove& move) { } // check if in same row/column like last moves // then can may be loaded in parallel - auto moveCoordInit = this->arch->getCoordinate(move.first); - auto moveCoordEnd = this->arch->getCoordinate(move.second); + const auto moveCoordInit = this->arch->getCoordinate(move.first); + const auto moveCoordEnd = this->arch->getCoordinate(move.second); parallelCost += arch->getShuttlingTime(qc::OpType::AodActivate) + arch->getShuttlingTime(qc::OpType::AodDeactivate); for (const auto& lastMove : this->lastMoves) { - auto lastMoveCoordInit = this->arch->getCoordinate(lastMove.first); - auto lastMoveCoordEnd = this->arch->getCoordinate(lastMove.second); + const auto lastMoveCoordInit = this->arch->getCoordinate(lastMove.first); + const auto lastMoveCoordEnd = this->arch->getCoordinate(lastMove.second); if (moveCoordInit.x == lastMoveCoordInit.x || moveCoordInit.y == lastMoveCoordInit.y) { parallelCost -= arch->getShuttlingTime(qc::OpType::AodActivate); @@ -1294,7 +1297,7 @@ NeutralAtomMapper::getMovePositionRec(MultiQubitMovePos currentPos, return {}; } - auto nearbyCoords = + const auto nearbyCoords = this->arch->getNearbyCoordinates(currentPos.coords.back()); // filter out coords that have a SWAP distance unequal to 0 to any of the // current qubits. Also sort out coords that are already in the vector @@ -1351,30 +1354,30 @@ NeutralAtomMapper::getMovePositionRec(MultiQubitMovePos currentPos, } for (const auto& gateCoord : occupiedGateCoords) { - MultiQubitMovePos nextPos = MultiQubitMovePos(currentPos); + auto nextPos = MultiQubitMovePos(currentPos); nextPos.coords.emplace_back(gateCoord); - auto bestPos = getMovePositionRec(nextPos, gateCoords, maxNMoves); - if (bestPos.coords.size() == gateCoords.size()) { + if (auto bestPos = getMovePositionRec(nextPos, gateCoords, maxNMoves); + bestPos.coords.size() == gateCoords.size()) { return bestPos; } } for (const auto& freeCoord : freeNearbyCoords) { - MultiQubitMovePos nextPos = MultiQubitMovePos(currentPos); + auto nextPos = MultiQubitMovePos(currentPos); nextPos.coords.emplace_back(freeCoord); nextPos.nMoves += 1; - auto bestPos = getMovePositionRec(nextPos, gateCoords, maxNMoves); - if (bestPos.coords.size() == gateCoords.size()) { + if (auto bestPos = getMovePositionRec(nextPos, gateCoords, maxNMoves); + bestPos.coords.size() == gateCoords.size()) { return bestPos; } } for (const auto& occCoord : occupiedNearbyCoords) { - MultiQubitMovePos nextPos = MultiQubitMovePos(currentPos); + auto nextPos = MultiQubitMovePos(currentPos); nextPos.coords.emplace_back(occCoord); nextPos.nMoves += 2; - auto bestPos = getMovePositionRec(nextPos, gateCoords, maxNMoves); - if (bestPos.coords.size() == gateCoords.size()) { + if (auto bestPos = getMovePositionRec(nextPos, gateCoords, maxNMoves); + bestPos.coords.size() == gateCoords.size()) { return bestPos; } } @@ -1389,12 +1392,11 @@ MoveCombs NeutralAtomMapper::getAllMoveCombinations() { auto usedQubits = op->getUsedQubits(); auto usedHwQubits = this->mapping.getHwQubits(usedQubits); auto usedCoordsSet = this->hardwareQubits.getCoordIndices(usedHwQubits); - auto usedCoords = - std::vector(usedCoordsSet.begin(), usedCoordsSet.end()); + auto usedCoords = std::vector(usedCoordsSet.begin(), usedCoordsSet.end()); auto bestPos = getBestMovePos(usedCoords); if (this->parameters->verbose) { std::cout << "bestPos: "; - for (auto qubit : bestPos) { + for (const auto qubit : bestPos) { std::cout << qubit << " "; } std::cout << '\n'; @@ -1487,9 +1489,8 @@ CoordIndices NeutralAtomMapper::getBestMovePos(const CoordIndices& gateCoords) { return finalBestPos.coords; } -MoveCombs -NeutralAtomMapper::getMoveCombinationsToPosition(HwQubits& gateQubits, - CoordIndices& position) { +MoveCombs NeutralAtomMapper::getMoveCombinationsToPosition( + const HwQubits& gateQubits, const CoordIndices& position) const { if (position.empty()) { throw qc::QFRException("No position given"); } @@ -1536,10 +1537,10 @@ NeutralAtomMapper::getMoveCombinationsToPosition(HwQubits& gateQubits, } } // find minimal cost - auto bestCost = std::min_element(costs.begin(), costs.end(), - [](const auto& cost1, const auto& cost2) { - return cost1.second < cost2.second; - }); + const auto bestCost = std::min_element( + costs.begin(), costs.end(), [](const auto& cost1, const auto& cost2) { + return cost1.second < cost2.second; + }); auto bestCoord = bestCost->first; if (this->hardwareQubits.isMapped(bestCoord)) { auto moveAwayComb = @@ -1557,15 +1558,14 @@ NeutralAtomMapper::getMoveCombinationsToPosition(HwQubits& gateQubits, return MoveCombs({moveComb}); } -MoveCombs -NeutralAtomMapper::getMoveAwayCombinations(CoordIndex startCoord, - CoordIndex targetCoord, - const CoordIndices& excludedCoords) { +MoveCombs NeutralAtomMapper::getMoveAwayCombinations( + CoordIndex startCoord, CoordIndex targetCoord, + const CoordIndices& excludedCoords) const { MoveCombs moveCombinations; auto const originalVector = this->arch->getVector(startCoord, targetCoord); auto const originalDirection = originalVector.direction; // Find move away target in the same direction as the original move - auto moveAwayTargets = this->hardwareQubits.findClosestFreeCoord( + const auto moveAwayTargets = this->hardwareQubits.findClosestFreeCoord( targetCoord, originalDirection, excludedCoords); for (const auto& moveAwayTarget : moveAwayTargets) { const AtomMove move = {startCoord, targetCoord}; @@ -1579,8 +1579,9 @@ NeutralAtomMapper::getMoveAwayCombinations(CoordIndex startCoord, } std::vector NeutralAtomMapper::findBestFlyingAncillaComb(const qc::Operation* targetOp) { - std::vector bestFlyingAncillaCombs; + std::vector const bestFlyingAncillaCombs; auto usedQubits = targetOp->getUsedQubits(); + return bestFlyingAncillaCombs; } size_t NeutralAtomMapper::shuttlingBasedMapping( @@ -1625,8 +1626,8 @@ size_t NeutralAtomMapper::shuttlingBasedMapping( std::pair NeutralAtomMapper::estimateNumSwapGates(const qc::Operation* opPointer) { - auto usedQubits = opPointer->getUsedQubits(); - auto usedHwQubits = this->mapping.getHwQubits(usedQubits); + const auto usedQubits = opPointer->getUsedQubits(); + const auto usedHwQubits = this->mapping.getHwQubits(usedQubits); qc::fp minNumSwaps = 0; if (usedHwQubits.size() == 2) { SwapDistance minDistance = std::numeric_limits::max(); @@ -1642,7 +1643,7 @@ NeutralAtomMapper::estimateNumSwapGates(const qc::Operation* opPointer) { } minNumSwaps = minDistance; } else { // multi-qubit gates - auto bestPos = getBestMultiQubitPosition(opPointer); + const auto bestPos = getBestMultiQubitPosition(opPointer); if (bestPos.empty()) { return {std::numeric_limits::max(), std::numeric_limits::max()}; @@ -1662,9 +1663,9 @@ NeutralAtomMapper::estimateNumSwapGates(const qc::Operation* opPointer) { } std::pair -NeutralAtomMapper::estimateNumMove(const qc::Operation* opPointer) { - auto usedQubits = opPointer->getUsedQubits(); - auto usedHwQubits = this->mapping.getHwQubits(usedQubits); +NeutralAtomMapper::estimateNumMove(const qc::Operation* opPointer) const { + const auto usedQubits = opPointer->getUsedQubits(); + const auto usedHwQubits = this->mapping.getHwQubits(usedQubits); auto usedCoords = this->hardwareQubits.getCoordIndices(usedHwQubits); // estimate the number of moves as: // compute distance between qubits @@ -1686,9 +1687,9 @@ NeutralAtomMapper::estimateNumMove(const qc::Operation* opPointer) { auto nearbyFreeIt = nearbyFreeCoords.begin(); auto nearbyOccIt = nearbyOccupiedCoords.begin(); while (otherQubitsIt != usedCoords.end()) { - auto otherCoord = *otherQubitsIt; + const auto otherCoord = *otherQubitsIt; if (otherCoord == coord) { - otherQubitsIt++; + ++otherQubitsIt; continue; } if (nearbyFreeIt != nearbyFreeCoords.end()) { @@ -1696,7 +1697,7 @@ NeutralAtomMapper::estimateNumMove(const qc::Operation* opPointer) { this->arch->getVector(otherCoord, *nearbyFreeIt)); totalTime += this->arch->getShuttlingTime(qc::OpType::AodActivate) + this->arch->getShuttlingTime(qc::OpType::AodDeactivate); - nearbyFreeIt++; + ++nearbyFreeIt; totalMoves++; } else if (nearbyOccIt != nearbyOccupiedCoords.end()) { totalTime += 2 * this->arch->getVectorShuttlingTime( @@ -1704,7 +1705,7 @@ NeutralAtomMapper::estimateNumMove(const qc::Operation* opPointer) { totalTime += 2 * (this->arch->getShuttlingTime(qc::OpType::AodActivate) + this->arch->getShuttlingTime(qc::OpType::AodDeactivate)); - nearbyOccIt++; + ++nearbyOccIt; totalMoves += 2; } else { throw std::runtime_error("No space to " @@ -1714,7 +1715,7 @@ NeutralAtomMapper::estimateNumMove(const qc::Operation* opPointer) { std::to_string(usedQubits.size())); } - otherQubitsIt++; + ++otherQubitsIt; } if (totalTime < minTime) { @@ -1732,11 +1733,11 @@ bool NeutralAtomMapper::swapGateBetter(const qc::Operation* opPointer) { return true; } auto [minMoves, minTimeMoves] = estimateNumMove(opPointer); - auto fidSwaps = + const auto fidSwaps = std::exp(-minTimeSwaps * this->arch->getNqubits() / this->arch->getDecoherenceTime()) * std::pow(this->arch->getGateAverageFidelity("swap"), minNumSwaps); - auto fidMoves = + const auto fidMoves = std::exp(-minTimeMoves * this->arch->getNqubits() / this->arch->getDecoherenceTime()) * std::pow( @@ -1775,18 +1776,18 @@ bool NeutralAtomMapper::swapGateBetter(const qc::Operation* opPointer) { // } // } -void NeutralAtomMapper::prepareAncillas(size_t nQubits) { +void NeutralAtomMapper::prepareAncillas(const size_t nQcQubits) { // Compute number of flying ancillas - auto nAncillas = arch->getNqubits() - nQubits; + const auto nAncillas = arch->getNqubits() - nQcQubits; - mappedQc = qc::QuantumComputation(nQubits); + mappedQc = qc::QuantumComputation(nQcQubits); mappedQc.addAncillaryRegister(nAncillas, "a"); // remove ancillas from hardware qubit mapping - for (auto i = nQubits; i < arch->getNqubits(); ++i) { + for (auto i = nQcQubits; i < arch->getNqubits(); ++i) { hardwareQubits.removeHwQubit(i); } // remove qubits from flying ancillas - for (auto i = 0; i < nQubits; ++i) { + for (auto i = 0; i < nQcQubits; ++i) { flyingAncillas.removeHwQubit(i); } } @@ -1806,10 +1807,8 @@ size_t NeutralAtomMapper::gateBasedMapping(NeutralAtomLayer& frontLayer, auto bestSwap = findBestSwap(lastSwap); auto bestBridge = findBestBridge(); - const auto swapOrBridge = compareSwapAndBridge(bestSwap, bestBridge); - // MappingMethod swapOrBridge = MappingMethod::SwapMethod; - - if (swapOrBridge == MappingMethod::SwapMethod) { + if (compareSwapAndBridge(bestSwap, bestBridge) == + MappingMethod::SwapMethod) { lastSwap = bestSwap; updateBlockedQubits(bestSwap); applySwap(bestSwap); @@ -2154,15 +2153,16 @@ size_t NeutralAtomMapper::gateBasedMapping(NeutralAtomLayer& frontLayer, std::set> NeutralAtomMapper::findQtargetSet(std::set& usedQubits) { - std::set> QtargetSet; - auto numUsedQubits = usedQubits.size(); + std::set> qTargetSet; + const auto numUsedQubits = usedQubits.size(); SymmetricMatrix gateQubitDistances(numUsedQubits); for (uint32_t i = 0; i < numUsedQubits; ++i) { for (uint32_t j = 0; j <= i; ++j) { - if (i == j) + if (i == j) { gateQubitDistances(i, j) = 0; - qc::Qubit qi = *(std::next(usedQubits.begin(), i)); - qc::Qubit qj = *(std::next(usedQubits.begin(), j)); + } + const qc::Qubit qi = *(std::next(usedQubits.begin(), i)); + const qc::Qubit qj = *(std::next(usedQubits.begin(), j)); gateQubitDistances(i, j) = this->hardwareQubits.getSwapDistance( this->mapping.getHwQubit(qi), this->mapping.getHwQubit(qj)); } @@ -2171,15 +2171,15 @@ NeutralAtomMapper::findQtargetSet(std::set& usedQubits) { size_t maxSize = 0; for (int i = 0; i < numUsedQubits; ++i) { std::vector currentVec; - qc::Qubit qi = *(std::next(usedQubits.begin(), i)); + const qc::Qubit qi = *(std::next(usedQubits.begin(), i)); currentVec.push_back(qi); for (int j = 0; j < numUsedQubits; ++j) { if (i != j) { - qc::Qubit qj = *(std::next(usedQubits.begin(), j)); + const qc::Qubit qj = *(std::next(usedQubits.begin(), j)); bool isInteractable = true; for (auto& q : currentVec) { - auto it = std::find(usedQubits.begin(), usedQubits.end(), q); - uint32_t idx; + auto it = usedQubits.find(q); + uint32_t idx = 0; if (it != usedQubits.end()) { idx = std::distance(usedQubits.begin(), it); } @@ -2193,28 +2193,27 @@ NeutralAtomMapper::findQtargetSet(std::set& usedQubits) { } } } - std::set currentSet(currentVec.begin(), currentVec.end()); - if (currentSet.size() > maxSize) { + if (const std::set currentSet(currentVec.begin(), currentVec.end()); + currentSet.size() > maxSize) { maxSize = currentSet.size(); - QtargetSet.clear(); - QtargetSet.insert(currentSet); + qTargetSet.clear(); + qTargetSet.insert(currentSet); } else if (currentSet.size() == maxSize) { - QtargetSet.insert(currentSet); + qTargetSet.insert(currentSet); } } - return QtargetSet; + return qTargetSet; } -CoordIndex -NeutralAtomMapper::returnClosestAncillaCoord(const CoordIndex& c_target, - const CoordIndices& excludeCoords, - qc::QuantumComputation& qc) { +CoordIndex NeutralAtomMapper::returnClosestAncillaCoord( + const CoordIndex& cTarget, const CoordIndices& excludeCoords, + const qc::QuantumComputation& qc) const { auto const originalVector = this->arch->getVector( - c_target + arch->getNcolumns(), c_target); // startCoord, targetCoord + cTarget + arch->getNcolumns(), cTarget); // startCoord, targetCoord auto const originalDirection = originalVector.direction; - auto AncillaTargets = this->hardwareQubits.findClosestAncillaCoord( - c_target, originalDirection, qc.getNqubits(), excludeCoords); - return AncillaTargets[0]; + const auto ancillaTargets = this->hardwareQubits.findClosestAncillaCoord( + cTarget, originalDirection, qc.getNqubits(), excludeCoords); + return ancillaTargets[0]; } MappingMethod NeutralAtomMapper::compareSwapAndBridge(const Swap& bestSwap, @@ -2222,8 +2221,8 @@ NeutralAtomMapper::compareSwapAndBridge(const Swap& bestSwap, // swap distance reduction qc::fp const swapDistReduction = swapDistanceReduction(bestSwap, this->frontLayerGate) + - this->parameters->lookaheadWeightSwaps * - swapDistanceReduction(bestSwap, this->lookaheadLayerGate); + (this->parameters->lookaheadWeightSwaps * + swapDistanceReduction(bestSwap, this->lookaheadLayerGate)); // bridge distance reduction qc::fp const bridgeDistReduction = bestBridge.second.size() - 2; @@ -2232,15 +2231,15 @@ NeutralAtomMapper::compareSwapAndBridge(const Swap& bestSwap, qc::fp const swapFidelity = this->arch->getGateAverageFidelity("swap") * std::exp(-this->arch->getGateTime("swap") / this->arch->getDecoherenceTime()); - std::string bridgeName = "bridge" + std::to_string(bestBridge.second.size()); + const std::string bridgeName = + "bridge" + std::to_string(bestBridge.second.size()); qc::fp const bridgeFidelity = this->arch->getGateAverageFidelity(bridgeName) * std::exp(-this->arch->getGateTime(bridgeName) / this->arch->getDecoherenceTime()); if (swapDistReduction * swapFidelity > bridgeDistReduction * bridgeFidelity) { return MappingMethod::SwapMethod; - } else { - return MappingMethod::BridgeMethod; } + return MappingMethod::BridgeMethod; } MappingMethod diff --git a/src/hybridmap/NeutralAtomArchitecture.cpp b/src/hybridmap/NeutralAtomArchitecture.cpp index cbd0f09ef..af932fbee 100644 --- a/src/hybridmap/NeutralAtomArchitecture.cpp +++ b/src/hybridmap/NeutralAtomArchitecture.cpp @@ -90,8 +90,9 @@ void NeutralAtomArchitecture::loadJson(const std::string& filename) { // precompute bridge circuits for (size_t i = 3; i <= 10; i++) { qc::fp const bridgeGateTime = - (bridgeCircuits.czDepth[i] * gateTimes.at("cz")) + - (bridgeCircuits.hDepth[i] * gateTimes.at("h")); + (static_cast(bridgeCircuits.czDepth[i]) * + gateTimes.at("cz")) + + (static_cast(bridgeCircuits.hDepth[i]) * gateTimes.at("h")); qc::fp const bridgeFidelity = std::pow(gateAverageFidelities.at("cz"), bridgeCircuits.czs[i]) * std::pow(gateAverageFidelities.at("h"), bridgeCircuits.hs[i]); @@ -137,7 +138,8 @@ NeutralAtomArchitecture::NeutralAtomArchitecture(const std::string& filename) { this->loadJson(filename); } -void NeutralAtomArchitecture::computeSwapDistances(qc::fp interactionRadius) { +void NeutralAtomArchitecture::computeSwapDistances( + const qc::fp interactionRadius) { // compute diagonal distances struct DiagonalDistance { std::uint32_t x; @@ -197,8 +199,8 @@ void NeutralAtomArchitecture::computeSwapDistances(qc::fp interactionRadius) { } void NeutralAtomArchitecture::computeNearbyCoordinates() { - this->nearbyCoordinates = std::vector>( - this->getNpositions(), std::set()); + this->nearbyCoordinates = + std::vector(this->getNpositions(), std::set()); for (CoordIndex coordIndex = 0; coordIndex < this->getNpositions(); coordIndex++) { for (CoordIndex otherCoordIndex = 0; otherCoordIndex < coordIndex; @@ -211,7 +213,8 @@ void NeutralAtomArchitecture::computeNearbyCoordinates() { } } -std::vector NeutralAtomArchitecture::getNN(CoordIndex idx) const { +std::vector +NeutralAtomArchitecture::getNN(const CoordIndex idx) const { std::vector nn; if (idx % this->getNcolumns() != 0) { nn.emplace_back(idx - 1); @@ -249,9 +252,9 @@ qc::fp NeutralAtomArchitecture::getOpTime(const qc::Operation* op) const { // use time of theta = pi and linearly scale opName += "z"; auto param = abs(op->getParameter().back()); - const auto pi = 3.14159265358979323846; + constexpr auto pi = 3.14159265358979323846; while (param > pi) { - param = abs(param - 2 * pi); + param = abs(param - (2 * pi)); } return getGateTime(opName) * param / 3.14159265358979323846; } diff --git a/src/hybridmap/NeutralAtomScheduler.cpp b/src/hybridmap/NeutralAtomScheduler.cpp index 9eb62d14a..74f233984 100644 --- a/src/hybridmap/NeutralAtomScheduler.cpp +++ b/src/hybridmap/NeutralAtomScheduler.cpp @@ -25,18 +25,17 @@ #include #include -na::SchedulerResults -na::NeutralAtomScheduler::schedule(const qc::QuantumComputation& qc, - const std::map& initHwPos, - bool verbose, bool createAnimationCsv, - qc::fp shuttlingSpeedFactor) { +na::SchedulerResults na::NeutralAtomScheduler::schedule( + const qc::QuantumComputation& qc, + const std::map& initHwPos, const bool verbose, + const bool createAnimationCsv, const qc::fp shuttlingSpeedFactor) { if (verbose) { std::cout << "\n* schedule start!\n"; } std::vector totalExecutionTimes(arch->getNpositions(), 0); // saves for each coord the time slots that are blocked by a multi qubit gate - std::vector>> rydbergBlockedQubitsTimes( + std::vector rydbergBlockedQubitsTimes( arch->getNpositions(), std::deque>()); qc::fp aodLastBlockedTime = 0; qc::fp totalGateTime = 0; @@ -68,7 +67,7 @@ na::NeutralAtomScheduler::schedule(const qc::QuantumComputation& qc, op->getType() == qc::AodDeactivate) { opTime *= shuttlingSpeedFactor; } - auto opFidelity = arch->getOpFidelity(op.get()); + const auto opFidelity = arch->getOpFidelity(op.get()); // DEBUG info if (verbose) { @@ -104,8 +103,8 @@ na::NeutralAtomScheduler::schedule(const qc::QuantumComputation& qc, for (const auto& qubit : rydbergBlockedQubits) { // check if qubit is blocked at maxTime for (const auto& startEnd : rydbergBlockedQubitsTimes[qubit]) { - auto start = startEnd.first; - auto end = startEnd.second; + const auto start = startEnd.first; + const auto end = startEnd.second; if ((start <= maxTime && end > maxTime) || (start <= maxTime + opTime && end > maxTime + opTime)) { rydbergBlocked = true; @@ -165,7 +164,7 @@ na::NeutralAtomScheduler::schedule(const qc::QuantumComputation& qc, const auto maxExecutionTime = *std::max_element(totalExecutionTimes.begin(), totalExecutionTimes.end()); const auto totalIdleTime = - maxExecutionTime * arch->getNqubits() - totalGateTime; + (maxExecutionTime * arch->getNqubits()) - totalGateTime; const auto totalFidelities = totalGateFidelities * std::exp(-totalIdleTime / arch->getDecoherenceTime()); @@ -182,9 +181,10 @@ na::NeutralAtomScheduler::schedule(const qc::QuantumComputation& qc, } void na::NeutralAtomScheduler::printSchedulerResults( - std::vector& totalExecutionTimes, qc::fp totalIdleTime, - qc::fp totalGateFidelities, qc::fp totalFidelities, uint32_t nCZs) { - auto totalExecutionTime = + std::vector& totalExecutionTimes, const qc::fp totalIdleTime, + const qc::fp totalGateFidelities, const qc::fp totalFidelities, + const uint32_t nCZs) { + const auto totalExecutionTime = *std::max_element(totalExecutionTimes.begin(), totalExecutionTimes.end()); std::cout << "\ntotalExecutionTimes: " << totalExecutionTime << "\n"; std::cout << "totalIdleTime: " << totalIdleTime << "\n"; @@ -194,8 +194,9 @@ void na::NeutralAtomScheduler::printSchedulerResults( } void na::NeutralAtomScheduler::printTotalExecutionTimes( - std::vector& totalExecutionTimes, - std::vector>>& blockedQubitsTimes) { + const std::vector& totalExecutionTimes, + const std::vector>>& + blockedQubitsTimes) { std::cout << "ExecutionTime: " << "\n"; for (size_t qubit = 0; qubit < totalExecutionTimes.size(); qubit++) { diff --git a/src/hybridmap/NeutralAtomUtils.cpp b/src/hybridmap/NeutralAtomUtils.cpp index 750b3d14e..43842c438 100644 --- a/src/hybridmap/NeutralAtomUtils.cpp +++ b/src/hybridmap/NeutralAtomUtils.cpp @@ -14,9 +14,9 @@ #include #include #include -#include #include #include +#include #include namespace na { @@ -34,18 +34,22 @@ bool MoveVector::overlap(const MoveVector& other) const { // need to compute all combinations, as sometimes the start and end x/y points // are the same - auto overlapXFirstStart = + const auto overlapXFirstStart = firstStartX >= secondStartX && firstStartX <= secondEndX; - auto overlapXFirstEnd = firstEndX >= secondStartX && firstEndX <= secondEndX; - auto overlapXSecondStart = + const auto overlapXFirstEnd = + firstEndX >= secondStartX && firstEndX <= secondEndX; + const auto overlapXSecondStart = secondStartX >= firstStartX && secondStartX <= firstEndX; - auto overlapXSecondEnd = secondEndX >= firstStartX && secondEndX <= firstEndX; - auto overlapYFirstStart = + const auto overlapXSecondEnd = + secondEndX >= firstStartX && secondEndX <= firstEndX; + const auto overlapYFirstStart = firstStartY >= secondStartY && firstStartY <= secondEndY; - auto overlapYFirstEnd = firstEndY >= secondStartY && firstEndY <= secondEndY; - auto overlapYSecondStart = + const auto overlapYFirstEnd = + firstEndY >= secondStartY && firstEndY <= secondEndY; + const auto overlapYSecondStart = secondStartY >= firstStartY && secondStartY <= firstEndY; - auto overlapYSecondEnd = secondEndY >= firstStartY && secondEndY <= firstEndY; + const auto overlapYSecondEnd = + secondEndY >= firstStartY && secondEndY <= firstEndY; return (overlapXFirstStart || overlapXFirstEnd || overlapXSecondStart || overlapXSecondEnd || overlapYFirstStart || overlapYFirstEnd || @@ -70,14 +74,14 @@ bool MoveVector::include(const MoveVector& other) const { return includeX || includeY; } -void MoveCombs::addMoveComb(const MoveComb& otherMove) { +void MoveCombs::addMoveComb(const MoveComb& moveComb) { for (auto& comb : moveCombs) { - if (comb == otherMove) { + if (comb == moveComb) { comb.cost = std::numeric_limits::max(); return; } } - moveCombs.emplace_back(otherMove); + moveCombs.emplace_back(moveComb); } void MoveCombs::addMoveCombs(const MoveCombs& otherMoveCombs) { @@ -114,7 +118,7 @@ void BridgeCircuits::computeGates(const size_t length) { } } // find max depth - auto maxHcZ = + const auto maxHcZ = std::max_element(hsCzsPerQubit.begin(), hsCzsPerQubit.end(), [](const auto& a, const auto& b) { return a.first + a.second < b.first + b.second; @@ -152,16 +156,18 @@ BridgeCircuits::recursiveBridgeIncrease(qc::QuantumComputation qcBridge, for (const auto& gate : qcBridge) { gates[*gate->getUsedQubits().begin()]++; } - auto minIndex = std::min_element(gates.begin(), gates.end()) - gates.begin(); + const auto minIndex = + std::min_element(gates.begin(), gates.end()) - gates.begin(); qcBridge = bridgeExpand(qcBridge, minIndex); return recursiveBridgeIncrease(qcBridge, length - 1); } qc::QuantumComputation -BridgeCircuits::bridgeExpand(qc::QuantumComputation qcBridge, size_t qubit) { +BridgeCircuits::bridgeExpand(const qc::QuantumComputation& qcBridge, + const size_t qubit) { qc::QuantumComputation qcBridgeNew(qcBridge.getNqubits() + 1); - for (auto& gate : qcBridge) { + for (const auto& gate : qcBridge) { const auto usedQubits = gate->getUsedQubits(); const auto q1 = *usedQubits.begin(); const auto q2 = *usedQubits.rbegin(); diff --git a/test/hybridmap/test_hybridmap.cpp b/test/hybridmap/test_hybridmap.cpp index b20c94e4d..818b91ae9 100644 --- a/test/hybridmap/test_hybridmap.cpp +++ b/test/hybridmap/test_hybridmap.cpp @@ -26,14 +26,14 @@ class NeutralAtomArchitectureTest TEST_P(NeutralAtomArchitectureTest, LoadArchitectures) { std::cout << "wd: " << std::filesystem::current_path() << '\n'; - auto arch = na::NeutralAtomArchitecture(testArchitecturePath); + const auto arch = na::NeutralAtomArchitecture(testArchitecturePath); // Test get properties EXPECT_LE(arch.getNqubits(), arch.getNpositions()); EXPECT_EQ(arch.getNpositions(), arch.getNrows() * arch.getNcolumns()); // Test precomputed values - auto c1 = arch.getCoordinate(0); - auto c2 = arch.getCoordinate(1); + const auto c1 = arch.getCoordinate(0); + const auto c2 = arch.getCoordinate(1); EXPECT_GE(arch.getSwapDistance(c1, c2), 0); EXPECT_GE(arch.getNAodIntermediateLevels(), 1); // Test get parameters @@ -42,7 +42,7 @@ TEST_P(NeutralAtomArchitectureTest, LoadArchitectures) { // Test distance functions EXPECT_GE(arch.getEuclideanDistance(c1, c2), 0); // Test MoveVector functions - auto mv = arch.getVector(0, 1); + const auto mv = arch.getVector(0, 1); EXPECT_GE(arch.getVectorShuttlingTime(mv), 0); } @@ -70,7 +70,7 @@ class NeutralAtomMapperTestParams uint32_t seed = 42; void SetUp() override { - auto params = GetParam(); + const auto params = GetParam(); testArchitecturePath += std::get<0>(params) + ".json"; testQcPath += std::get<1>(params) + ".qasm"; gateWeight = std::get<2>(params); @@ -81,8 +81,8 @@ class NeutralAtomMapperTestParams }; TEST_P(NeutralAtomMapperTestParams, MapCircuitsIdentity) { - auto arch = na::NeutralAtomArchitecture(testArchitecturePath); - na::InitialMapping const initialMapping = na::InitialMapping::Identity; + const auto arch = na::NeutralAtomArchitecture(testArchitecturePath); + constexpr na::InitialMapping initialMapping = na::InitialMapping::Identity; na::NeutralAtomMapper mapper(arch); na::MapperParameters mapperParameters; mapperParameters.initialMapping = initialCoordinateMapping; @@ -97,11 +97,11 @@ TEST_P(NeutralAtomMapperTestParams, MapCircuitsIdentity) { mapper.setParameters(mapperParameters); qc::QuantumComputation qc(testQcPath); - auto qcMapped = mapper.map(qc, initialMapping); + const auto qcMapped = mapper.map(qc, initialMapping); ASSERT_GT(qcMapped.size(), qc.size()); mapper.convertToAod(); - auto scheduleResults = mapper.schedule(true, true); + const auto scheduleResults = mapper.schedule(true, true); ASSERT_GT(scheduleResults.totalFidelities, 0); ASSERT_GT(scheduleResults.totalIdleTime, 0); @@ -157,7 +157,8 @@ TEST_F(NeutralAtomMapperTest, Output) { auto qcAodMapped = mapper.convertToAod(); // qcAodMapped.dumpOpenQASM(std::cout, false); - auto scheduleResults = mapper.schedule(false, true); + // const auto scheduleResults = mapper.schedule(false, true); + const auto scheduleResults = mapper.schedule(true, true); std::cout << scheduleResults.toCsv(); ASSERT_GT(scheduleResults.totalFidelities, 0); From 03e160f99754dc7d5f198e66a1135d6cb23cfd76 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 30 Jan 2025 10:30:43 +0100 Subject: [PATCH 081/394] =?UTF-8?q?=F0=9F=8E=A8=20fixed=20layer=20output?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridNeutralAtomMapper.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index eebc8c96a..d177f05df 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -404,6 +404,7 @@ void NeutralAtomMapper::printLayers() const { } std::cout << '\n'; } + std::cout << '\n'; std::cout << "f,s: "; for (const auto* op : this->frontLayerShuttling) { std::cout << op->getName() << " "; @@ -412,6 +413,7 @@ void NeutralAtomMapper::printLayers() const { } std::cout << '\n'; } + std::cout << '\n'; std::cout << "l,g: "; for (const auto* op : this->lookaheadLayerGate) { std::cout << op->getName() << " "; @@ -421,7 +423,7 @@ void NeutralAtomMapper::printLayers() const { std::cout << '\n'; } std::cout << '\n'; - std::cout << "l,g: "; + std::cout << "l,s: "; for (const auto* op : this->lookaheadLayerShuttling) { std::cout << op->getName() << " "; for (const auto qubit : op->getUsedQubits()) { From 82c111d8b1e480462570b378b4a5f7c000bb2e6b Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 31 Jan 2025 10:27:14 +0100 Subject: [PATCH 082/394] =?UTF-8?q?=E2=9C=A8=20Changed=20HardwareQubits=20?= =?UTF-8?q?to=20support=20Flying=20Ancillas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HardwareQubits.hpp | 55 +++++++------------ include/hybridmap/HybridNeutralAtomMapper.hpp | 18 ++++-- src/hybridmap/HardwareQubits.cpp | 8 +-- src/hybridmap/HybridNeutralAtomMapper.cpp | 29 ++++------ test/hybridmap/test_hybridmap.cpp | 10 ++-- 5 files changed, 52 insertions(+), 68 deletions(-) diff --git a/include/hybridmap/HardwareQubits.hpp b/include/hybridmap/HardwareQubits.hpp index 34a401b49..2c3a99217 100644 --- a/include/hybridmap/HardwareQubits.hpp +++ b/include/hybridmap/HardwareQubits.hpp @@ -36,6 +36,7 @@ namespace na { class HardwareQubits { protected: const NeutralAtomArchitecture* arch = nullptr; + CoordIndex nQubits = 0; qc::Permutation hwToCoordIdx; SymmetricMatrix swapDistances; std::map nearbyQubits; @@ -82,45 +83,27 @@ class HardwareQubits { public: // Constructors HardwareQubits() = default; - HardwareQubits(const NeutralAtomArchitecture& architecture, - const InitialCoordinateMapping initialCoordinateMapping, - const std::vector& qubitIndices, - const std::vector& hwIndices, uint32_t seed) - : arch(&architecture), swapDistances(architecture.getNqubits()) { + explicit HardwareQubits( + const NeutralAtomArchitecture& architecture, const CoordIndex nQubits = 0, + const InitialCoordinateMapping initialCoordinateMapping = + InitialCoordinateMapping::Trivial, + uint32_t seed = 0) + : arch(&architecture) { + if (nQubits == 0) { + this->nQubits = architecture.getNqubits(); + } else { + this->nQubits = nQubits; + } + swapDistances = SymmetricMatrix(this->nQubits); + switch (initialCoordinateMapping) { case Trivial: - for (uint32_t i = 0; i < architecture.getNqubits(); ++i) { + case Graph: + for (uint32_t i = 0; i < this->nQubits; ++i) { hwToCoordIdx.emplace(i, i); } initTrivialSwapDistances(); break; - case Graph: { - if (qubitIndices.empty()) { - for (uint32_t i = 0; i < architecture.getNqubits(); ++i) { - hwToCoordIdx.emplace(i, i); - } - initTrivialSwapDistances(); - } else { - int hwIndex = 0; - for (uint32_t i = 0; i < architecture.getNqubits(); ++i) { - if (qubitIndices[i] == std::numeric_limits::max()) { - if (hwIndices[hwIndex] != - std::numeric_limits::max()) { - do { - hwIndex += 1; - } while (hwIndices[hwIndex] != - std::numeric_limits::max()); - } - hwToCoordIdx.emplace(i, hwIndex); - hwIndex++; - } else { - hwToCoordIdx.emplace(i, qubitIndices[i]); - } - } - swapDistances = SymmetricMatrix(architecture.getNqubits(), -1); - } - break; - } case Random: std::vector indices(architecture.getNpositions()); std::iota(indices.begin(), indices.end(), 0); @@ -129,11 +112,11 @@ class HardwareQubits { } std::mt19937 g(seed); std::shuffle(indices.begin(), indices.end(), g); - for (uint32_t i = 0; i < architecture.getNqubits(); ++i) { + for (uint32_t i = 0; i < this->nQubits; ++i) { hwToCoordIdx.emplace(i, indices[i]); } - swapDistances = SymmetricMatrix(architecture.getNqubits(), -1); + swapDistances = SymmetricMatrix(this->nQubits, -1); } initNearbyQubits(); initialHwPos = hwToCoordIdx; @@ -147,6 +130,8 @@ class HardwareQubits { return hwToCoordIdx; } + [[nodiscard]] CoordIndex getNumQubits() const { return nQubits; } + /** * @brief Checks if a hardware qubit is mapped to a coordinate. * @param idx The coordinate index. diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index c632e4252..e543eeac5 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -39,8 +39,9 @@ struct MapperParameters { qc::fp gateWeight = 1; qc::fp shuttlingWeight = 1; uint32_t seed = 0; + uint32_t numFlyingAncillas = 0; bool verbose = false; - InitialCoordinateMapping initialMapping; + InitialCoordinateMapping initialCoordMapping; }; enum RoutingType : uint8_t { @@ -185,8 +186,6 @@ class NeutralAtomMapper { * given gate */ - void prepareAncillas(size_t nQcQubits); - size_t gateBasedMapping(NeutralAtomLayer& frontLayer, NeutralAtomLayer& lookaheadLayer, size_t i); size_t shuttlingBasedMapping(NeutralAtomLayer& frontLayer, @@ -440,7 +439,10 @@ class NeutralAtomMapper { explicit NeutralAtomMapper(const NeutralAtomArchitecture* architecture, const MapperParameters* p = nullptr) : arch(architecture), scheduler(*architecture), parameters(p), - hardwareQubits(*arch, p->initialMapping, {}, {}, p->seed) { + hardwareQubits(*arch, arch->getNqubits() - p->numFlyingAncillas, + p->initialCoordMapping, p->seed), + flyingAncillas(*arch, p->numFlyingAncillas, + InitialCoordinateMapping::Trivial, p->seed) { if (arch->getNpositions() - arch->getNqubits() < 1 && p->shuttlingWeight > 0) { throw std::runtime_error( @@ -490,8 +492,12 @@ class NeutralAtomMapper { * @brief Resets the mapper and the hardware qubits. */ void reset() { - hardwareQubits = HardwareQubits(*arch, parameters->initialMapping, {}, {}, - parameters->seed); + hardwareQubits = HardwareQubits( + *arch, arch->getNqubits() - parameters->numFlyingAncillas, + parameters->initialCoordMapping, parameters->seed); + flyingAncillas = + HardwareQubits(*arch, parameters->numFlyingAncillas, + InitialCoordinateMapping::Trivial, parameters->seed); } // Methods diff --git a/src/hybridmap/HardwareQubits.cpp b/src/hybridmap/HardwareQubits.cpp index 14aa8dd19..cb7a32d75 100644 --- a/src/hybridmap/HardwareQubits.cpp +++ b/src/hybridmap/HardwareQubits.cpp @@ -22,8 +22,8 @@ namespace na { void HardwareQubits::initTrivialSwapDistances() { - swapDistances = SymmetricMatrix(arch->getNqubits()); - for (uint32_t i = 0; i < arch->getNqubits(); ++i) { + swapDistances = SymmetricMatrix(nQubits); + for (uint32_t i = 0; i < nQubits; ++i) { for (uint32_t j = 0; j < i; ++j) { swapDistances(i, j) = arch->getSwapDistance(hwToCoordIdx.at(i), hwToCoordIdx.at(j)); @@ -32,7 +32,7 @@ void HardwareQubits::initTrivialSwapDistances() { } void HardwareQubits::initNearbyQubits() { - for (uint32_t i = 0; i < arch->getNqubits(); ++i) { + for (uint32_t i = 0; i < nQubits; ++i) { computeNearbyQubits(i); } } @@ -129,7 +129,7 @@ HardwareQubits::computeAllShortestPaths(const HwQubit q1, void HardwareQubits::resetSwapDistances() { // TODO Improve to only reset the swap distances necessary (use a breadth // first search) - swapDistances = SymmetricMatrix(arch->getNqubits(), -1); + swapDistances = SymmetricMatrix(nQubits, -1); } void HardwareQubits::move(HwQubit hwQubit, const CoordIndex newCoord) { diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index d177f05df..d729adfb8 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -36,6 +36,13 @@ namespace na { void NeutralAtomMapper::mapAppend(qc::QuantumComputation& qc, Mapping initialMapping) { + if (qc.getNqubits() + this->parameters->numFlyingAncillas > + arch->getNqubits()) { + throw std::runtime_error( + "Not enough qubits in architecture for circuit and flying ancillas"); + } + mappedQc.addAncillaryRegister(this->parameters->numFlyingAncillas); + mapping = std::move(initialMapping); qc::CircuitOptimizer::replaceMCXWithMCZ(qc); @@ -43,9 +50,6 @@ void NeutralAtomMapper::mapAppend(qc::QuantumComputation& qc, qc::CircuitOptimizer::flattenOperations(qc); qc::CircuitOptimizer::removeFinalMeasurements(qc); - const auto nQubits = qc.getNqubits(); - prepareAncillas(nQubits); - const auto dag = qc::CircuitOptimizer::constructDAG(qc); /* @@ -658,6 +662,9 @@ CoordIndices NeutralAtomMapper::computeCurrentCoordUsages() const { } FlyingAncillas NeutralAtomMapper::convertMoveCombToFlyingAncilla( const MoveComb& moveComb) const { + if (this->flyingAncillas.getNumQubits() == 0) { + return {}; + } const auto usedQubits = moveComb.op->getUsedQubits(); const auto hwQubits = this->mapping.getHwQubits(usedQubits); const auto usedCoords = this->hardwareQubits.getCoordIndices(hwQubits); @@ -1778,22 +1785,6 @@ bool NeutralAtomMapper::swapGateBetter(const qc::Operation* opPointer) { // } // } -void NeutralAtomMapper::prepareAncillas(const size_t nQcQubits) { - // Compute number of flying ancillas - const auto nAncillas = arch->getNqubits() - nQcQubits; - - mappedQc = qc::QuantumComputation(nQcQubits); - mappedQc.addAncillaryRegister(nAncillas, "a"); - // remove ancillas from hardware qubit mapping - for (auto i = nQcQubits; i < arch->getNqubits(); ++i) { - hardwareQubits.removeHwQubit(i); - } - // remove qubits from flying ancillas - for (auto i = 0; i < nQcQubits; ++i) { - flyingAncillas.removeHwQubit(i); - } -} - size_t NeutralAtomMapper::gateBasedMapping(NeutralAtomLayer& frontLayer, NeutralAtomLayer& lookaheadLayer, size_t i) { diff --git a/test/hybridmap/test_hybridmap.cpp b/test/hybridmap/test_hybridmap.cpp index 818b91ae9..12973cabc 100644 --- a/test/hybridmap/test_hybridmap.cpp +++ b/test/hybridmap/test_hybridmap.cpp @@ -85,7 +85,7 @@ TEST_P(NeutralAtomMapperTestParams, MapCircuitsIdentity) { constexpr na::InitialMapping initialMapping = na::InitialMapping::Identity; na::NeutralAtomMapper mapper(arch); na::MapperParameters mapperParameters; - mapperParameters.initialMapping = initialCoordinateMapping; + mapperParameters.initialCoordMapping = initialCoordinateMapping; mapperParameters.lookaheadWeightSwaps = lookAheadWeight; mapperParameters.lookaheadWeightMoves = lookAheadWeight; mapperParameters.decay = decay; @@ -133,15 +133,17 @@ class NeutralAtomMapperTest : public ::testing::Test { void SetUp() override { mapper = na::NeutralAtomMapper(arch); - mapperParameters.initialMapping = na::InitialCoordinateMapping::Trivial; + mapperParameters.initialCoordMapping = + na::InitialCoordinateMapping::Trivial; mapperParameters.lookaheadWeightSwaps = 0.1; mapperParameters.lookaheadWeightMoves = 0.1; mapperParameters.decay = 0; mapperParameters.shuttlingTimeWeight = 0.1; - mapperParameters.gateWeight = 1; - mapperParameters.shuttlingWeight = 0; + mapperParameters.gateWeight = 0; + mapperParameters.shuttlingWeight = 1; mapperParameters.seed = 43; mapperParameters.verbose = true; + mapperParameters.numFlyingAncillas = 1; mapper.setParameters(mapperParameters); qc = qc::QuantumComputation( // "circuits/dj_nativegates_rigetti_qiskit_opt3_10.qasm"); From e73f2a040c039515931019e732cb61f533390840 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Mon, 3 Feb 2025 13:10:08 +0100 Subject: [PATCH 083/394] =?UTF-8?q?=F0=9F=8E=A8=20Added=20comparison=20bet?= =?UTF-8?q?ween=20Move=20and=20Flying=20Ancillas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridNeutralAtomMapper.hpp | 9 +- include/hybridmap/NeutralAtomArchitecture.hpp | 32 +++++++ include/hybridmap/NeutralAtomUtils.hpp | 6 +- src/hybridmap/HybridNeutralAtomMapper.cpp | 83 +++++++++++++++++-- 4 files changed, 116 insertions(+), 14 deletions(-) diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index e543eeac5..7306c108d 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -237,8 +237,8 @@ class NeutralAtomMapper { [[nodiscard]] CoordIndices computeCurrentCoordUsages() const; - [[nodiscard]] FlyingAncillas - convertMoveCombToFlyingAncilla(const MoveComb& moveComb) const; + [[nodiscard]] FlyingAncillaComb + convertMoveCombToFlyingAncillaComb(const MoveComb& moveComb) const; // std::vector> // findAllBridges(qc::QuantumComputation& qc); @@ -301,7 +301,8 @@ class NeutralAtomMapper { [[nodiscard]] MappingMethod compareSwapAndBridge(const Swap& bestSwap, const Bridge& bestBridge); [[nodiscard]] MappingMethod - compareShuttlingAndFlyingAncilla(MoveComb bestComb); + compareShuttlingAndFlyingAncilla(const MoveComb& bestMoveComb, + const FlyingAncillaComb& bestFaComb) const; // Helper methods /** @@ -391,6 +392,8 @@ class NeutralAtomMapper { qc::fp swapCost(const Swap& swap, const std::pair& swapsFront, const std::pair& swapsLookahead); + qc::fp moveCombDistanceReduction(const MoveComb& moveComb, + const GateList& layer) const; qc::fp swapDistanceReduction(const Swap& swap, const GateList& layer); /** * @brief Calculates the cost of a move operation. diff --git a/include/hybridmap/NeutralAtomArchitecture.hpp b/include/hybridmap/NeutralAtomArchitecture.hpp index 3b337c196..e47b35c58 100644 --- a/include/hybridmap/NeutralAtomArchitecture.hpp +++ b/include/hybridmap/NeutralAtomArchitecture.hpp @@ -399,6 +399,38 @@ class NeutralAtomArchitecture { return static_cast(this->coordinates.at(idx1).getEuclideanDistance( this->coordinates.at(idx2))); } + [[nodiscard]] qc::fp + getAllToAllEuclideanDistance(const std::set& coords) const { + qc::fp dist = 0; + for (auto const c1 : coords) { + for (auto const c2 : coords) { + if (c1 == c2) { + continue; + } + dist += getSwapDistance(c1, c2); + } + } + return dist; + } + [[nodiscard]] qc::fp + getMoveCombEuclideanDistance(const MoveComb& moveComb) const { + qc::fp dist = 0; + for (const auto& move : moveComb.moves) { + dist += getEuclideanDistance(move.first, move.second); + } + return dist; + } + + [[nodiscard]] qc::fp + getFaEuclideanDistance(const FlyingAncillaComb& faComb) const { + qc::fp dist = 0; + for (const auto& fa : faComb.moves) { + dist += getEuclideanDistance(fa.origin, fa.q1); + dist += getEuclideanDistance(fa.q1, fa.q2) * 2; + } + return dist; + } + /** * @brief Get the Euclidean distance between two coordinates * @param c1 The first coordinate diff --git a/include/hybridmap/NeutralAtomUtils.hpp b/include/hybridmap/NeutralAtomUtils.hpp index 4ab81e6ce..bf5dd0865 100644 --- a/include/hybridmap/NeutralAtomUtils.hpp +++ b/include/hybridmap/NeutralAtomUtils.hpp @@ -138,10 +138,12 @@ struct FlyingAncilla { CoordIndex q1; CoordIndex q2; size_t index; - const qc::Operation* op; }; -using FlyingAncillas = std::vector; +struct FlyingAncillaComb { + std::vector moves; + const qc::Operation* op; +}; /** * @brief Helper class to manage multiple atom moves which belong together. diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index d729adfb8..16b8391af 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -660,7 +660,7 @@ CoordIndices NeutralAtomMapper::computeCurrentCoordUsages() const { } return coordUsages; } -FlyingAncillas NeutralAtomMapper::convertMoveCombToFlyingAncilla( +FlyingAncillaComb NeutralAtomMapper::convertMoveCombToFlyingAncillaComb( const MoveComb& moveComb) const { if (this->flyingAncillas.getNumQubits() == 0) { return {}; @@ -674,12 +674,11 @@ FlyingAncillas NeutralAtomMapper::convertMoveCombToFlyingAncilla( } // multi-qubit gate -> only one direction - FlyingAncillas bestFAs; + std::vector bestFAs; FlyingAncilla bestFA{}; HwQubits usedFA; for (const auto move : moveComb.moves) { if (usedCoords.find(move.first) != usedCoords.end()) { - bestFA.op = moveComb.op; const auto nearFirstIdx = this->flyingAncillas.getClosestQubit(move.first, usedFA); const auto nearFirst = this->flyingAncillas.getCoordIndex(nearFirstIdx); @@ -705,7 +704,7 @@ FlyingAncillas NeutralAtomMapper::convertMoveCombToFlyingAncilla( bestFAs.emplace_back(bestFA); } } - return bestFAs; + return {bestFAs, moveComb.op}; } qc::fp NeutralAtomMapper::swapCost( @@ -769,6 +768,29 @@ qc::fp NeutralAtomMapper::swapDistanceReduction(const Swap& swap, return swapDistReduction; } +qc::fp +NeutralAtomMapper::moveCombDistanceReduction(const MoveComb& moveComb, + const GateList& layer) const { + qc::fp moveDistReduction = 0; + for (const auto& op : layer) { + auto usedQubits = op->getUsedQubits(); + auto hwQubits = this->mapping.getHwQubits(usedQubits); + auto coordIndices = this->hardwareQubits.getCoordIndices(hwQubits); + const auto& distBefore = + this->arch->getAllToAllEuclideanDistance(coordIndices); + for (const auto& move : moveComb.moves) { + if (coordIndices.find(move.first) != coordIndices.end()) { + coordIndices.erase(move.first); + coordIndices.insert(move.second); + } + } + const auto& distAfter = + this->arch->getAllToAllEuclideanDistance(coordIndices); + moveDistReduction += distBefore - distAfter; + } + return moveDistReduction; +} + std::pair NeutralAtomMapper::initSwaps(const GateList& layer) { Swaps swapCloseBy = {}; @@ -1603,9 +1625,9 @@ size_t NeutralAtomMapper::shuttlingBasedMapping( std::cout << "iteration " << i << '\n'; } auto bestComb = findBestAtomMove(); - auto bestFA = convertMoveCombToFlyingAncilla(bestComb); + auto bestFaComb = convertMoveCombToFlyingAncillaComb(bestComb); - switch (compareShuttlingAndFlyingAncilla(bestComb)) { + switch (compareShuttlingAndFlyingAncilla(bestComb, bestFaComb)) { case MappingMethod::MoveMethod: // apply whole move combination at once // for (auto& move : bestComb.moves) { @@ -2235,9 +2257,52 @@ NeutralAtomMapper::compareSwapAndBridge(const Swap& bestSwap, return MappingMethod::BridgeMethod; } -MappingMethod -NeutralAtomMapper::compareShuttlingAndFlyingAncilla(MoveComb bestComb) { - return MappingMethod::MoveMethod; +MappingMethod NeutralAtomMapper::compareShuttlingAndFlyingAncilla( + const MoveComb& bestMoveComb, const FlyingAncillaComb& bestFaComb) const { + // move distance reduction + auto const moveDistReduction = + moveCombDistanceReduction(bestMoveComb, this->frontLayerShuttling) + + (this->parameters->lookaheadWeightMoves * + moveCombDistanceReduction(bestMoveComb, this->lookaheadLayerShuttling)); + + // flying ancilla distance reduction + auto const faCoords = this->hardwareQubits.getCoordIndices( + this->mapping.getHwQubits(bestFaComb.op->getUsedQubits())); + auto const faDistReduction = + this->arch->getAllToAllEuclideanDistance(faCoords); + + // fidelity comparison + auto const moveDist = this->arch->getMoveCombEuclideanDistance(bestMoveComb); + auto const moveCombSize = bestMoveComb.size(); + auto const moveOpFidelity = std::pow( + this->arch->getShuttlingAverageFidelity(qc::OpType::AodMove) * + this->arch->getShuttlingAverageFidelity(qc::OpType::AodActivate) * + this->arch->getShuttlingAverageFidelity(qc::OpType::AodDeactivate), + moveCombSize); + auto const moveTime = + moveDist / this->arch->getShuttlingTime(qc::OpType::AodMove) + + this->arch->getShuttlingTime(qc::OpType::AodActivate) * moveCombSize + + this->arch->getShuttlingTime(qc::OpType::AodDeactivate) * moveCombSize; + auto const moveDecoherence = + std::exp(-moveTime / this->arch->getDecoherenceTime()); + auto const moveFidelity = moveOpFidelity * moveDecoherence; + + auto const faDist = this->arch->getFaEuclideanDistance(bestFaComb); + auto const faCombSize = bestFaComb.moves.size(); + auto const faOpFidelity = + std::pow(this->arch->getShuttlingAverageFidelity(qc::OpType::AodMove) * + std::pow(this->arch->getGateAverageFidelity("CZ"), 2) * + std::pow(this->arch->getGateAverageFidelity("H"), 4), + faCombSize); + auto const faDecoherence = + std::exp(-faDist / this->arch->getShuttlingTime(qc::OpType::AodMove) / + this->arch->getDecoherenceTime()); + auto const faFidelity = faOpFidelity * faDecoherence; + + if (moveDistReduction * moveFidelity > faDistReduction * faFidelity) { + return MappingMethod::MoveMethod; + } + return MappingMethod::FlyingAncillaMethod; } // void NeutralAtomMapper::updateMappingFlyingAncilla( From 1e4f2103e6deb9ca5c6b70cdf65a454dbe6cce35 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Mon, 3 Feb 2025 14:41:19 +0100 Subject: [PATCH 084/394] =?UTF-8?q?=F0=9F=8E=A8=20Added=20passby=20and=20t?= =?UTF-8?q?he=20application=20to=20qcMapped?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridNeutralAtomMapper.hpp | 7 +- include/hybridmap/NeutralAtomArchitecture.hpp | 9 ++ src/hybridmap/HybridNeutralAtomMapper.cpp | 103 +++++++++++++++--- 3 files changed, 104 insertions(+), 15 deletions(-) diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index 7306c108d..b474d7aa9 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -105,6 +105,7 @@ class NeutralAtomMapper { uint32_t nBridges = 0; uint32_t nFAncillas = 0; uint32_t nMoves = 0; + uint32_t nPassBy = 0; // The current placement of the hardware qubits onto the coordinates HardwareQubits hardwareQubits; @@ -167,8 +168,10 @@ class NeutralAtomMapper { void applyMove(AtomMove move); void applyBridge(NeutralAtomLayer& frontLayer, const Bridge& bridge); - void applyFlyingAncilla(FlyingAncilla fa); - void applyPassBy(FlyingAncilla fa); + void applyFlyingAncilla(NeutralAtomLayer& frontLayer, + const FlyingAncillaComb& faComb); + void applyPassBy(NeutralAtomLayer& frontLayer, + const FlyingAncillaComb& faComb); // Methods for gate vs. shuttling /** diff --git a/include/hybridmap/NeutralAtomArchitecture.hpp b/include/hybridmap/NeutralAtomArchitecture.hpp index e47b35c58..21b580e3b 100644 --- a/include/hybridmap/NeutralAtomArchitecture.hpp +++ b/include/hybridmap/NeutralAtomArchitecture.hpp @@ -431,6 +431,15 @@ class NeutralAtomArchitecture { return dist; } + [[nodiscard]] qc::fp + getPassByEuclideanDistance(const FlyingAncillaComb& faComb) const { + qc::fp dist = 0; + for (const auto& fa : faComb.moves) { + dist += getEuclideanDistance(fa.q1, fa.q2) * 2; + } + return dist; + } + /** * @brief Get the Euclidean distance between two coordinates * @param c1 The first coordinate diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 16b8391af..e679a1c54 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -341,6 +341,26 @@ qc::QuantumComputation NeutralAtomMapper::convertToAod() { return mappedQcAOD; } +void NeutralAtomMapper::applyPassBy(NeutralAtomLayer& frontLayer, + const FlyingAncillaComb& faComb) { + for (const auto& passBy : faComb.moves) { + mappedQc.passby(passBy.q1, passBy.q2); + if (this->parameters->verbose) { + std::cout << "passby " << passBy.q1 << " " << passBy.q2 << '\n'; + } + } + mapGate(faComb.op); + for (const auto& passBy : faComb.moves) { + mappedQc.passby(passBy.q2, passBy.q1); + if (this->parameters->verbose) { + std::cout << "passby " << passBy.q2 << " " << passBy.q1 << '\n'; + } + } + + frontLayer.removeGatesAndUpdate({faComb.op}); + nPassBy += faComb.moves.size(); +} + void NeutralAtomMapper::reassignGatesToLayers(const GateList& frontGates, const GateList& lookaheadGates) { // assign gates to gates or shuttling @@ -520,6 +540,35 @@ void NeutralAtomMapper::applyBridge(NeutralAtomLayer& frontLayer, nBridges++; } +void NeutralAtomMapper::applyFlyingAncilla(NeutralAtomLayer& frontLayer, + const FlyingAncillaComb& faComb) { + for (const auto& passBy : faComb.moves) { + mappedQc.passby(passBy.origin, passBy.q1); + mappedQc.h(passBy.origin); + mappedQc.cz(passBy.origin, passBy.q1); + mappedQc.h(passBy.origin); + mappedQc.passby(passBy.origin, passBy.q2); + if (this->parameters->verbose) { + std::cout << "passby (flying ancilla) " << passBy.origin << " " + << passBy.q1 << " " << passBy.q2 << '\n'; + } + } + mapGate(faComb.op); + for (const auto& passBy : faComb.moves) { + mappedQc.passby(passBy.origin, passBy.q1); + mappedQc.h(passBy.origin); + mappedQc.cz(passBy.origin, passBy.q1); + mappedQc.h(passBy.origin); + + if (this->parameters->verbose) { + std::cout << "passby (flying ancilla)" << passBy.origin << " " + << passBy.q2 << " " << passBy.q1 << '\n'; + } + } + + frontLayer.removeGatesAndUpdate({faComb.op}); + nFAncillas += faComb.moves.size(); +} Swap NeutralAtomMapper::findBestSwap(const Swap& lastSwap) { // compute necessary movements @@ -1630,16 +1679,16 @@ size_t NeutralAtomMapper::shuttlingBasedMapping( switch (compareShuttlingAndFlyingAncilla(bestComb, bestFaComb)) { case MappingMethod::MoveMethod: // apply whole move combination at once - // for (auto& move : bestComb.moves) { - // applyMove(move); - // } - applyMove(bestComb.moves[0]); + for (const auto& move : bestComb.moves) { + applyMove(move); + } + // applyMove(bestComb.moves[0]); break; case MappingMethod::FlyingAncillaMethod: - // applyFlyingAncilla(bestFA); + applyFlyingAncilla(frontLayer, bestFaComb); break; case MappingMethod::PassByMethod: - // applyPassBy(bestFA); + applyPassBy(frontLayer, bestFaComb); break; default: break; @@ -2272,6 +2321,7 @@ MappingMethod NeutralAtomMapper::compareShuttlingAndFlyingAncilla( this->arch->getAllToAllEuclideanDistance(faCoords); // fidelity comparison + // move auto const moveDist = this->arch->getMoveCombEuclideanDistance(bestMoveComb); auto const moveCombSize = bestMoveComb.size(); auto const moveOpFidelity = std::pow( @@ -2280,29 +2330,56 @@ MappingMethod NeutralAtomMapper::compareShuttlingAndFlyingAncilla( this->arch->getShuttlingAverageFidelity(qc::OpType::AodDeactivate), moveCombSize); auto const moveTime = - moveDist / this->arch->getShuttlingTime(qc::OpType::AodMove) + - this->arch->getShuttlingTime(qc::OpType::AodActivate) * moveCombSize + - this->arch->getShuttlingTime(qc::OpType::AodDeactivate) * moveCombSize; + (moveDist / this->arch->getShuttlingTime(qc::OpType::AodMove)) + + (this->arch->getShuttlingTime(qc::OpType::AodActivate) * + static_cast(moveCombSize)) + + (this->arch->getShuttlingTime(qc::OpType::AodDeactivate) * + static_cast(moveCombSize)); auto const moveDecoherence = std::exp(-moveTime / this->arch->getDecoherenceTime()); auto const moveFidelity = moveOpFidelity * moveDecoherence; + // flying ancilla auto const faDist = this->arch->getFaEuclideanDistance(bestFaComb); auto const faCombSize = bestFaComb.moves.size(); auto const faOpFidelity = std::pow(this->arch->getShuttlingAverageFidelity(qc::OpType::AodMove) * - std::pow(this->arch->getGateAverageFidelity("CZ"), 2) * - std::pow(this->arch->getGateAverageFidelity("H"), 4), + std::pow(this->arch->getGateAverageFidelity("cz"), 2) * + std::pow(this->arch->getGateAverageFidelity("h"), 4), faCombSize); auto const faDecoherence = std::exp(-faDist / this->arch->getShuttlingTime(qc::OpType::AodMove) / this->arch->getDecoherenceTime()); auto const faFidelity = faOpFidelity * faDecoherence; - if (moveDistReduction * moveFidelity > faDistReduction * faFidelity) { + // passby + auto const passByDist = this->arch->getPassByEuclideanDistance(bestFaComb); + auto const passByTime = + (passByDist / this->arch->getShuttlingTime(qc::OpType::AodMove)) + + (this->arch->getShuttlingTime(qc::OpType::AodActivate) * + static_cast(faCombSize)) + + (this->arch->getShuttlingTime(qc::OpType::AodDeactivate) * + static_cast(faCombSize)); + auto const passByFidelity = + std::pow( + this->arch->getShuttlingAverageFidelity(qc::OpType::AodMove) * + this->arch->getShuttlingAverageFidelity(qc::OpType::AodActivate) * + this->arch->getShuttlingAverageFidelity( + qc::OpType::AodDeactivate), + faCombSize) * + std::exp(-passByTime / this->arch->getDecoherenceTime()); + + const auto move = moveDistReduction * moveFidelity; + const auto fa = faDistReduction * faFidelity; + const auto passBy = faDistReduction * passByFidelity; + + if (move > fa && move > passBy) { return MappingMethod::MoveMethod; } - return MappingMethod::FlyingAncillaMethod; + if (fa > move && fa > passBy) { + return MappingMethod::FlyingAncillaMethod; + } + return MappingMethod::PassByMethod; } // void NeutralAtomMapper::updateMappingFlyingAncilla( From 3c69852113c7b1ff8f0ad61198c5ed8aa878d38a Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Mon, 3 Feb 2025 14:56:21 +0100 Subject: [PATCH 085/394] =?UTF-8?q?=F0=9F=90=9B=20Fixed=20return=20of=20mu?= =?UTF-8?q?ltiple=20move-away=20options?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridNeutralAtomMapper.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index e679a1c54..13b2be9f0 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -1625,9 +1625,10 @@ MoveCombs NeutralAtomMapper::getMoveCombinationsToPosition( if (this->hardwareQubits.isMapped(bestCoord)) { auto moveAwayComb = getMoveAwayCombinations(currentGateQubit, bestCoord, remainingCoords); - for (const auto& moveAway : moveAwayComb) { - moveComb.append(moveAway); - } + // for (const auto& moveAway : moveAwayComb) { + // moveComb.append(moveAway); + // } + moveComb.append(moveAwayComb.moveCombs[0]); } else { moveComb.append(AtomMove{currentGateQubit, bestCoord}); } From 99438eb65c2299d22a404f3a6e383805ed0d7aca Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Mon, 3 Feb 2025 14:57:41 +0100 Subject: [PATCH 086/394] =?UTF-8?q?=E2=9C=A8=20added=20passBy=20to=20verbo?= =?UTF-8?q?se=20output?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridNeutralAtomMapper.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 13b2be9f0..37582f560 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -112,6 +112,7 @@ void NeutralAtomMapper::mapAppend(qc::QuantumComputation& qc, std::cout << "nBridges: " << nBridges << '\n'; std::cout << "nFAncillas: " << nFAncillas << '\n'; std::cout << "nMoves: " << nMoves << '\n'; + std::cout << "nPassBy: " << nPassBy << '\n'; mappedQc.print(std::cout); } From a0b7e48bcfd0caab487891247293f48b547dbaed Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Mon, 3 Feb 2025 17:11:32 +0100 Subject: [PATCH 087/394] =?UTF-8?q?=E2=9C=A8=20fixed=20flying=20ancilla=20?= =?UTF-8?q?mapping?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridNeutralAtomMapper.cpp | 49 ++++++++++++++++------- 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 37582f560..3d57ec622 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -328,12 +328,17 @@ void NeutralAtomMapper::decomposeBridgeGates(qc::QuantumComputation& qc) const { qc::QuantumComputation NeutralAtomMapper::convertToAod() { // decompose SWAP gates + mappedQc.dumpOpenQASM(std::cout, false); qc::CircuitOptimizer::decomposeSWAP(mappedQc, false); + mappedQc.dumpOpenQASM(std::cout, false); + // decompose bridge gates decomposeBridgeGates(mappedQc); + mappedQc.dumpOpenQASM(std::cout, false); qc::CircuitOptimizer::replaceMCXWithMCZ(mappedQc); qc::CircuitOptimizer::singleQubitGateFusion(mappedQc); qc::CircuitOptimizer::flattenOperations(mappedQc); // decompose AOD moves + mappedQc.dumpOpenQASM(std::cout, false); MoveToAodConverter aodScheduler(*arch, hardwareQubits); mappedQcAOD = aodScheduler.schedule(mappedQc); if (this->parameters->verbose) { @@ -543,27 +548,42 @@ void NeutralAtomMapper::applyBridge(NeutralAtomLayer& frontLayer, } void NeutralAtomMapper::applyFlyingAncilla(NeutralAtomLayer& frontLayer, const FlyingAncillaComb& faComb) { + auto usedQubits = faComb.op->getUsedQubits(); for (const auto& passBy : faComb.moves) { - mappedQc.passby(passBy.origin, passBy.q1); - mappedQc.h(passBy.origin); - mappedQc.cz(passBy.origin, passBy.q1); - mappedQc.h(passBy.origin); - mappedQc.passby(passBy.origin, passBy.q2); + const auto faCoordIdx = passBy.index + this->arch->getNpositions(); + mappedQc.passby(faCoordIdx, passBy.q1); + mappedQc.h(faCoordIdx); + mappedQc.cz(faCoordIdx, passBy.q1); + mappedQc.h(faCoordIdx); + mappedQc.passby(faCoordIdx, passBy.q2); + + if (usedQubits.find(passBy.q1) != usedQubits.end()) { + usedQubits.erase(passBy.q1); + usedQubits.insert(faCoordIdx); + } + if (this->parameters->verbose) { - std::cout << "passby (flying ancilla) " << passBy.origin << " " - << passBy.q1 << " " << passBy.q2 << '\n'; + std::cout << "passby (flying ancilla) " << faCoordIdx << " " << passBy.q1 + << " " << passBy.q2 << '\n'; } } - mapGate(faComb.op); + const auto opCopy = faComb.op->clone(); + const std::vector usedQubitsVec = {usedQubits.begin(), + usedQubits.end()}; + opCopy->setTargets(usedQubitsVec); + opCopy->setControls({}); + mappedQc.emplace_back(opCopy->clone()); + for (const auto& passBy : faComb.moves) { - mappedQc.passby(passBy.origin, passBy.q1); - mappedQc.h(passBy.origin); - mappedQc.cz(passBy.origin, passBy.q1); - mappedQc.h(passBy.origin); + const auto faCoordIdx = passBy.index + this->arch->getNpositions(); + mappedQc.passby(faCoordIdx, passBy.q1); + mappedQc.h(faCoordIdx); + mappedQc.cz(faCoordIdx, passBy.q1); + mappedQc.h(faCoordIdx); if (this->parameters->verbose) { - std::cout << "passby (flying ancilla)" << passBy.origin << " " - << passBy.q2 << " " << passBy.q1 << '\n'; + std::cout << "passby (flying ancilla)" << faCoordIdx << " " << passBy.q2 + << " " << passBy.q1 << '\n'; } } @@ -2374,6 +2394,7 @@ MappingMethod NeutralAtomMapper::compareShuttlingAndFlyingAncilla( const auto move = moveDistReduction * moveFidelity; const auto fa = faDistReduction * faFidelity; const auto passBy = faDistReduction * passByFidelity; + return MappingMethod::FlyingAncillaMethod; if (move > fa && move > passBy) { return MappingMethod::MoveMethod; From 9a317ac71fd227588b2dce39fc3031d50d1a96eb Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Mon, 3 Feb 2025 17:28:51 +0100 Subject: [PATCH 088/394] =?UTF-8?q?=F0=9F=8E=A8=20renamed=20move=20add=20m?= =?UTF-8?q?ethods=20to=20seperate=20them=20from=20passby?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/MoveToAodConverter.hpp | 5 +++-- src/hybridmap/MoveToAodConverter.cpp | 14 +++++++------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/include/hybridmap/MoveToAodConverter.hpp b/include/hybridmap/MoveToAodConverter.hpp index b2479ba90..83791f164 100644 --- a/include/hybridmap/MoveToAodConverter.hpp +++ b/include/hybridmap/MoveToAodConverter.hpp @@ -231,13 +231,14 @@ class MoveToAodConverter { * @param move Move to check * @return True if the move can be added, false otherwise */ - bool canAdd(const AtomMove& move, const NeutralAtomArchitecture& archArg); + bool canAddMove(const AtomMove& move, + const NeutralAtomArchitecture& archArg); /** * @brief Adds the given move to the move group * @param move Move to add * @param idx Index of the move in the original quantum circuit */ - void add(const AtomMove& move, uint32_t idx); + void addMove(const AtomMove& move, uint32_t idx); /** * @brief Returns the circuit index of the first move in the move group * @return Circuit index of the first move in the move group diff --git a/src/hybridmap/MoveToAodConverter.cpp b/src/hybridmap/MoveToAodConverter.cpp index 77dac9763..156067d76 100644 --- a/src/hybridmap/MoveToAodConverter.cpp +++ b/src/hybridmap/MoveToAodConverter.cpp @@ -66,12 +66,12 @@ void MoveToAodConverter::initMoveGroups(qc::QuantumComputation& qc) { for (auto& op : qc) { if (op->getType() == qc::OpType::Move) { AtomMove const move{op->getTargets()[0], op->getTargets()[1]}; - if (currentMoveGroup.canAdd(move, arch)) { - currentMoveGroup.add(move, idx); + if (currentMoveGroup.canAddMove(move, arch)) { + currentMoveGroup.addMove(move, idx); } else { moveGroups.emplace_back(currentMoveGroup); currentMoveGroup = MoveGroup(); - currentMoveGroup.add(move, idx); + currentMoveGroup.addMove(move, idx); } // TODO: make AtomMove for flying ancilla -> add to moveGroup } else if (op->getType() == qc::OpType::PassBy) { @@ -94,7 +94,7 @@ void MoveToAodConverter::initMoveGroups(qc::QuantumComputation& qc) { } } -bool MoveToAodConverter::MoveGroup::canAdd( +bool MoveToAodConverter::MoveGroup::canAddMove( const AtomMove& move, const NeutralAtomArchitecture& archArg) { // if move would move a qubit that is used by a gate in this move group // return false @@ -129,8 +129,8 @@ bool MoveToAodConverter::MoveGroup::parallelCheck(const MoveVector& v1, return true; } -void MoveToAodConverter::MoveGroup::add(const AtomMove& move, - const uint32_t idx) { +void MoveToAodConverter::MoveGroup::addMove(const AtomMove& move, + const uint32_t idx) { moves.emplace_back(move, idx); qubitsUsedByGates.emplace_back(move.second); } @@ -333,7 +333,7 @@ void MoveToAodConverter::processMoveGroups() { deactivationCanAddXY.second == ActivationMergeType::Impossible) { // move could not be added as not sufficient intermediate levels // add new move group and add move to it - possibleNewMoveGroup.add(move, idx); + possibleNewMoveGroup.addMove(move, idx); movesToRemove.emplace_back(move); } else { aodActivationHelper.addActivation(activationCanAddXY, origin, move, v); From f485443192faa4f0eec60916224bc0b2697d3291 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Tue, 4 Feb 2025 09:24:08 +0100 Subject: [PATCH 089/394] =?UTF-8?q?=E2=9C=A8=20added=20correct=20update=20?= =?UTF-8?q?of=20flying=20ancillas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridNeutralAtomMapper.cpp | 38 +++++++++++++---------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 3d57ec622..e28b16ae1 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -41,7 +41,7 @@ void NeutralAtomMapper::mapAppend(qc::QuantumComputation& qc, throw std::runtime_error( "Not enough qubits in architecture for circuit and flying ancillas"); } - mappedQc.addAncillaryRegister(this->parameters->numFlyingAncillas); + mappedQc.addAncillaryRegister(this->arch->getNpositions()); mapping = std::move(initialMapping); @@ -549,22 +549,23 @@ void NeutralAtomMapper::applyBridge(NeutralAtomLayer& frontLayer, void NeutralAtomMapper::applyFlyingAncilla(NeutralAtomLayer& frontLayer, const FlyingAncillaComb& faComb) { auto usedQubits = faComb.op->getUsedQubits(); + const auto nPos = this->arch->getNpositions(); for (const auto& passBy : faComb.moves) { - const auto faCoordIdx = passBy.index + this->arch->getNpositions(); - mappedQc.passby(faCoordIdx, passBy.q1); - mappedQc.h(faCoordIdx); - mappedQc.cz(faCoordIdx, passBy.q1); - mappedQc.h(faCoordIdx); - mappedQc.passby(faCoordIdx, passBy.q2); + const auto ancQ1 = passBy.q1 + nPos; + const auto ancQ2 = passBy.q2 + nPos; + mappedQc.passby(passBy.origin + nPos, ancQ1); + mappedQc.h(ancQ1); + mappedQc.cz(passBy.q1, ancQ1); + mappedQc.h(ancQ1); + mappedQc.passby(ancQ1, ancQ2); if (usedQubits.find(passBy.q1) != usedQubits.end()) { usedQubits.erase(passBy.q1); - usedQubits.insert(faCoordIdx); + usedQubits.insert(ancQ1); } if (this->parameters->verbose) { - std::cout << "passby (flying ancilla) " << faCoordIdx << " " << passBy.q1 - << " " << passBy.q2 << '\n'; + std::cout << "passby (flying ancilla) " << ancQ1 << " " << ancQ2 << '\n'; } } const auto opCopy = faComb.op->clone(); @@ -575,15 +576,18 @@ void NeutralAtomMapper::applyFlyingAncilla(NeutralAtomLayer& frontLayer, mappedQc.emplace_back(opCopy->clone()); for (const auto& passBy : faComb.moves) { - const auto faCoordIdx = passBy.index + this->arch->getNpositions(); - mappedQc.passby(faCoordIdx, passBy.q1); - mappedQc.h(faCoordIdx); - mappedQc.cz(faCoordIdx, passBy.q1); - mappedQc.h(faCoordIdx); + const auto ancQ1 = passBy.q1 + nPos; + const auto ancQ2 = passBy.q2 + nPos; + mappedQc.passby(ancQ2, ancQ1); + mappedQc.h(ancQ1); + mappedQc.cz(passBy.q2, ancQ1); + mappedQc.h(ancQ1); + + // update position of flying ancillas + this->flyingAncillas.move(passBy.index, passBy.q1); if (this->parameters->verbose) { - std::cout << "passby (flying ancilla)" << faCoordIdx << " " << passBy.q2 - << " " << passBy.q1 << '\n'; + std::cout << "passby (flying ancilla)" << ancQ2 << " " << ancQ1 << '\n'; } } From aae2962d0f19793a17dc76a2ce7f4faa49f263bb Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Tue, 4 Feb 2025 09:56:13 +0100 Subject: [PATCH 090/394] =?UTF-8?q?=E2=9C=A8=20Move=20Group=20init=20inclu?= =?UTF-8?q?ding=20passby?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/MoveToAodConverter.hpp | 4 ++++ include/hybridmap/NeutralAtomDefinitions.hpp | 1 + src/hybridmap/MoveToAodConverter.cpp | 25 +++++++++++++++----- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/include/hybridmap/MoveToAodConverter.hpp b/include/hybridmap/MoveToAodConverter.hpp index 83791f164..960370cbe 100644 --- a/include/hybridmap/MoveToAodConverter.hpp +++ b/include/hybridmap/MoveToAodConverter.hpp @@ -217,6 +217,7 @@ class MoveToAodConverter { // the moves and the index they appear in the original quantum circuit (to // insert them back later) std::vector> moves; + std::vector> passBys; std::vector processedOpsInit; std::vector processedOpsFinal; AodOperation processedOpShuttle; @@ -243,6 +244,9 @@ class MoveToAodConverter { * @brief Returns the circuit index of the first move in the move group * @return Circuit index of the first move in the move group */ + + void addPassBy(const AtomPassBy& passBy, uint32_t idx); + [[nodiscard]] uint32_t getFirstIdx() const { return moves.front().second; } /** * @brief Checks if the two moves can be executed in parallel diff --git a/include/hybridmap/NeutralAtomDefinitions.hpp b/include/hybridmap/NeutralAtomDefinitions.hpp index 0b2e60911..df4c2bf34 100644 --- a/include/hybridmap/NeutralAtomDefinitions.hpp +++ b/include/hybridmap/NeutralAtomDefinitions.hpp @@ -47,6 +47,7 @@ using SwapDistance = int32_t; // using Bridges = std::vector; // Moves are between coordinates (the first is occupied, the second is not). using AtomMove = std::pair; +using AtomPassBy = std::pair; // Moves for FlyingAncilla struct fPoint { diff --git a/src/hybridmap/MoveToAodConverter.cpp b/src/hybridmap/MoveToAodConverter.cpp index 156067d76..46bcfca7b 100644 --- a/src/hybridmap/MoveToAodConverter.cpp +++ b/src/hybridmap/MoveToAodConverter.cpp @@ -59,6 +59,12 @@ MoveToAodConverter::schedule(qc::QuantumComputation& qc) { return qcScheduled; } +void MoveToAodConverter::MoveGroup::addPassBy(const AtomPassBy& passBy, + uint32_t idx) { + passBys.emplace_back(passBy, idx); + qubitsUsedByGates.emplace_back(passBy.second); +} + void MoveToAodConverter::initMoveGroups(qc::QuantumComputation& qc) { MoveGroup currentMoveGroup; MoveGroup const lastMoveGroup; @@ -73,12 +79,19 @@ void MoveToAodConverter::initMoveGroups(qc::QuantumComputation& qc) { currentMoveGroup = MoveGroup(); currentMoveGroup.addMove(move, idx); } - // TODO: make AtomMove for flying ancilla -> add to moveGroup } else if (op->getType() == qc::OpType::PassBy) { - HwQubit q_control = op->getControls().begin()->qubit; - qc::Targets Q_target = op->getTargets(); - std::vector C_target; - } else if (op->getNqubits() > 1 && !currentMoveGroup.moves.empty()) { + AtomPassBy const passBy{op->getTargets()[0], op->getTargets()[1]}; + const AtomMove passByConverted = {passBy.first - arch.getNpositions(), + passBy.second - arch.getNpositions()}; + if (currentMoveGroup.canAddMove(passByConverted, arch)) { + currentMoveGroup.addPassBy(passBy, idx); + } else { + moveGroups.emplace_back(currentMoveGroup); + currentMoveGroup = MoveGroup(); + currentMoveGroup.addPassBy(passBy, idx); + } + } else if (op->getNqubits() > 1 && (!currentMoveGroup.moves.empty() || + !currentMoveGroup.passBys.empty())) { for (const auto& qubit : op->getUsedQubits()) { if (std::find(currentMoveGroup.qubitsUsedByGates.begin(), currentMoveGroup.qubitsUsedByGates.end(), @@ -89,7 +102,7 @@ void MoveToAodConverter::initMoveGroups(qc::QuantumComputation& qc) { } idx++; } - if (!currentMoveGroup.moves.empty()) { + if (!currentMoveGroup.moves.empty() || !currentMoveGroup.passBys.empty()) { moveGroups.emplace_back(std::move(currentMoveGroup)); } } From 5dccc6fb53a6c59fd339adb17b611839541e1b0c Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Tue, 4 Feb 2025 14:05:21 +0100 Subject: [PATCH 091/394] =?UTF-8?q?=F0=9F=8E=A8=20refactored=20move=20acti?= =?UTF-8?q?vation=20handling?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/MoveToAodConverter.hpp | 5 ++ src/hybridmap/MoveToAodConverter.cpp | 79 ++++++++++++++---------- 2 files changed, 51 insertions(+), 33 deletions(-) diff --git a/include/hybridmap/MoveToAodConverter.hpp b/include/hybridmap/MoveToAodConverter.hpp index 960370cbe..385312563 100644 --- a/include/hybridmap/MoveToAodConverter.hpp +++ b/include/hybridmap/MoveToAodConverter.hpp @@ -291,6 +291,11 @@ class MoveToAodConverter { */ void processMoveGroups(); + std::pair, MoveGroup> + processMoves(const std::vector>& moves, + AodActivationHelper& aodActivationHelper, + AodActivationHelper& aodDeactivationHelper); + public: MoveToAodConverter() = delete; MoveToAodConverter(const MoveToAodConverter&) = delete; diff --git a/src/hybridmap/MoveToAodConverter.cpp b/src/hybridmap/MoveToAodConverter.cpp index 46bcfca7b..2575cb9c4 100644 --- a/src/hybridmap/MoveToAodConverter.cpp +++ b/src/hybridmap/MoveToAodConverter.cpp @@ -322,38 +322,12 @@ void MoveToAodConverter::processMoveGroups() { ++groupIt) { AodActivationHelper aodActivationHelper{arch, qc::OpType::AodActivate}; AodActivationHelper aodDeactivationHelper{arch, qc::OpType::AodDeactivate}; - MoveGroup possibleNewMoveGroup; - std::vector movesToRemove; - for (auto& movePair : groupIt->moves) { - auto& move = movePair.first; - auto idx = movePair.second; - auto origin = arch.getCoordinate(move.first); - auto target = arch.getCoordinate(move.second); - auto v = arch.getVector(move.first, move.second); - auto vReverse = arch.getVector(move.second, move.first); - auto canAddX = - canAddActivation(aodActivationHelper, aodDeactivationHelper, origin, - v, target, vReverse, Dimension::X); - auto canAddY = - canAddActivation(aodActivationHelper, aodDeactivationHelper, origin, - v, target, vReverse, Dimension::Y); - auto activationCanAddXY = std::make_pair(canAddX.first, canAddY.first); - auto deactivationCanAddXY = - std::make_pair(canAddX.second, canAddY.second); - if (activationCanAddXY.first == ActivationMergeType::Impossible || - activationCanAddXY.second == ActivationMergeType::Impossible || - deactivationCanAddXY.first == ActivationMergeType::Impossible || - deactivationCanAddXY.second == ActivationMergeType::Impossible) { - // move could not be added as not sufficient intermediate levels - // add new move group and add move to it - possibleNewMoveGroup.addMove(move, idx); - movesToRemove.emplace_back(move); - } else { - aodActivationHelper.addActivation(activationCanAddXY, origin, move, v); - aodDeactivationHelper.addActivation(deactivationCanAddXY, target, move, - vReverse); - } - } + + const auto resultMoves = processMoves(groupIt->moves, aodActivationHelper, + aodDeactivationHelper); + auto movesToRemove = resultMoves.first; + auto possibleNewMoveGroup = resultMoves.second; + // remove from current move group for (const auto& moveToRemove : movesToRemove) { groupIt->moves.erase( @@ -363,7 +337,8 @@ void MoveToAodConverter::processMoveGroups() { }), groupIt->moves.end()); } - if (!possibleNewMoveGroup.moves.empty()) { + if (!possibleNewMoveGroup.moves.empty() || + !possibleNewMoveGroup.passBys.empty()) { groupIt = moveGroups.emplace(groupIt + 1, std::move(possibleNewMoveGroup)); possibleNewMoveGroup = MoveGroup(); @@ -375,6 +350,44 @@ void MoveToAodConverter::processMoveGroups() { groupIt->processedOpsInit, groupIt->processedOpsFinal); } } +std::pair, MoveToAodConverter::MoveGroup> +MoveToAodConverter::processMoves( + const std::vector>& moves, + AodActivationHelper& aodActivationHelper, + AodActivationHelper& aodDeactivationHelper) { + + MoveGroup possibleNewMoveGroup; + std::vector movesToRemove; + for (auto& movePair : moves) { + auto& move = movePair.first; + auto idx = movePair.second; + auto origin = arch.getCoordinate(move.first); + auto target = arch.getCoordinate(move.second); + auto v = arch.getVector(move.first, move.second); + auto vReverse = arch.getVector(move.second, move.first); + auto canAddX = canAddActivation(aodActivationHelper, aodDeactivationHelper, + origin, v, target, vReverse, Dimension::X); + auto canAddY = canAddActivation(aodActivationHelper, aodDeactivationHelper, + origin, v, target, vReverse, Dimension::Y); + auto activationCanAddXY = std::make_pair(canAddX.first, canAddY.first); + auto deactivationCanAddXY = std::make_pair(canAddX.second, canAddY.second); + if (activationCanAddXY.first == ActivationMergeType::Impossible || + activationCanAddXY.second == ActivationMergeType::Impossible || + deactivationCanAddXY.first == ActivationMergeType::Impossible || + deactivationCanAddXY.second == ActivationMergeType::Impossible) { + // move could not be added as not sufficient intermediate levels + // add new move group and add move to it + possibleNewMoveGroup.addMove(move, idx); + movesToRemove.emplace_back(move); + } else { + aodActivationHelper.addActivation(activationCanAddXY, origin, move, v); + aodDeactivationHelper.addActivation(deactivationCanAddXY, target, move, + vReverse); + } + } + + return {movesToRemove, possibleNewMoveGroup}; +} AodOperation MoveToAodConverter::MoveGroup::connectAodOperations( const std::vector& opsInit, From 72ec6a601fb49183785c7bfdf547ab23754978e3 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Tue, 4 Feb 2025 16:23:19 +0100 Subject: [PATCH 092/394] =?UTF-8?q?=E2=9A=A1=EF=B8=8Fadded=20group=20creat?= =?UTF-8?q?ion=20including=20passbys=20and=20replaced=20passby=20with=20mo?= =?UTF-8?q?ves?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/MoveToAodConverter.hpp | 16 ++- include/hybridmap/NeutralAtomArchitecture.hpp | 2 +- include/hybridmap/NeutralAtomDefinitions.hpp | 15 ++- src/hybridmap/HybridNeutralAtomMapper.cpp | 65 +++++----- src/hybridmap/MoveToAodConverter.cpp | 120 +++++++++--------- 5 files changed, 116 insertions(+), 102 deletions(-) diff --git a/include/hybridmap/MoveToAodConverter.hpp b/include/hybridmap/MoveToAodConverter.hpp index 385312563..6bbd36bfa 100644 --- a/include/hybridmap/MoveToAodConverter.hpp +++ b/include/hybridmap/MoveToAodConverter.hpp @@ -53,6 +53,8 @@ class MoveToAodConverter { struct AodMove { // start of the move uint32_t init; + // need load/unload or not + bool load; // need offset move to avoid crossing int32_t offset; // delta of the actual move @@ -60,8 +62,10 @@ class MoveToAodConverter { AodMove() = default; - AodMove(uint32_t initMove, qc::fp deltaMove, int32_t offsetMove) - : init(initMove), offset(offsetMove), delta(deltaMove) {} + AodMove(uint32_t initMove, qc::fp deltaMove, int32_t offsetMove, + bool loadMove) + : init(initMove), offset(offsetMove), delta(deltaMove), + load(loadMove) {} }; /** * @brief Manages the activation of an atom using an AOD. @@ -135,10 +139,12 @@ class MoveToAodConverter { * @param origin The origin of the move * @param move The move to add * @param v The move vector of the move + * @param needLoad */ void addActivation(std::pair merge, - const Point& origin, const AtomMove& move, MoveVector v); + const Point& origin, const AtomMove& move, MoveVector v, + bool needLoad); /** * @brief Merges the given activation into the current activations * @param dim The dimension/direction of the activation @@ -217,7 +223,6 @@ class MoveToAodConverter { // the moves and the index they appear in the original quantum circuit (to // insert them back later) std::vector> moves; - std::vector> passBys; std::vector processedOpsInit; std::vector processedOpsFinal; AodOperation processedOpShuttle; @@ -245,8 +250,6 @@ class MoveToAodConverter { * @return Circuit index of the first move in the move group */ - void addPassBy(const AtomPassBy& passBy, uint32_t idx); - [[nodiscard]] uint32_t getFirstIdx() const { return moves.front().second; } /** * @brief Checks if the two moves can be executed in parallel @@ -275,6 +278,7 @@ class MoveToAodConverter { std::vector moveGroups; const na::HardwareQubits& hardwareQubits; + AtomMove convertOpToMove(qc::Operation* get); /** * @brief Assigns move operations into groups that can be executed in parallel * @param qc Quantum circuit to schedule diff --git a/include/hybridmap/NeutralAtomArchitecture.hpp b/include/hybridmap/NeutralAtomArchitecture.hpp index 21b580e3b..b0ae045ea 100644 --- a/include/hybridmap/NeutralAtomArchitecture.hpp +++ b/include/hybridmap/NeutralAtomArchitecture.hpp @@ -416,7 +416,7 @@ class NeutralAtomArchitecture { getMoveCombEuclideanDistance(const MoveComb& moveComb) const { qc::fp dist = 0; for (const auto& move : moveComb.moves) { - dist += getEuclideanDistance(move.first, move.second); + dist += getEuclideanDistance(move.c1, move.c2); } return dist; } diff --git a/include/hybridmap/NeutralAtomDefinitions.hpp b/include/hybridmap/NeutralAtomDefinitions.hpp index df4c2bf34..c2fec2c48 100644 --- a/include/hybridmap/NeutralAtomDefinitions.hpp +++ b/include/hybridmap/NeutralAtomDefinitions.hpp @@ -46,8 +46,19 @@ using SwapDistance = int32_t; // Q_between // using Bridges = std::vector; // Moves are between coordinates (the first is occupied, the second is not). -using AtomMove = std::pair; -using AtomPassBy = std::pair; +struct AtomMove { + CoordIndex c1; + CoordIndex c2; + bool load1; + bool load2; + + // implement operator== + bool operator==(const AtomMove& other) const { + return c1 == other.c1 && c2 == other.c2 && load1 == other.load1 && + load2 == other.load2; + } + bool operator!=(const AtomMove& other) const { return !(*this == other); } +}; // Moves for FlyingAncilla struct fPoint { diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index e28b16ae1..0a0c76406 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -350,14 +350,14 @@ qc::QuantumComputation NeutralAtomMapper::convertToAod() { void NeutralAtomMapper::applyPassBy(NeutralAtomLayer& frontLayer, const FlyingAncillaComb& faComb) { for (const auto& passBy : faComb.moves) { - mappedQc.passby(passBy.q1, passBy.q2); + mappedQc.move(passBy.q1, passBy.q2 + arch->getNpositions()); if (this->parameters->verbose) { std::cout << "passby " << passBy.q1 << " " << passBy.q2 << '\n'; } } mapGate(faComb.op); for (const auto& passBy : faComb.moves) { - mappedQc.passby(passBy.q2, passBy.q1); + mappedQc.move(passBy.q2 + arch->getNpositions(), passBy.q1); if (this->parameters->verbose) { std::cout << "passby " << passBy.q2 << " " << passBy.q1 << '\n'; } @@ -513,11 +513,11 @@ void NeutralAtomMapper::applyMove(AtomMove move) { if (this->lastMoves.size() > 4) { this->lastMoves.pop_front(); } - mappedQc.move(move.first, move.second); - const auto toMoveHwQubit = this->hardwareQubits.getHwQubit(move.first); - this->hardwareQubits.move(toMoveHwQubit, move.second); + mappedQc.move(move.c1, move.c2); + const auto toMoveHwQubit = this->hardwareQubits.getHwQubit(move.c1); + this->hardwareQubits.move(toMoveHwQubit, move.c2); if (this->parameters->verbose) { - std::cout << "moved " << move.first << " to " << move.second; + std::cout << "moved " << move.c1 << " to " << move.c2; if (this->mapping.isMapped(toMoveHwQubit)) { std::cout << " logical qubit: " << this->mapping.getCircQubit(toMoveHwQubit) << '\n'; @@ -553,11 +553,11 @@ void NeutralAtomMapper::applyFlyingAncilla(NeutralAtomLayer& frontLayer, for (const auto& passBy : faComb.moves) { const auto ancQ1 = passBy.q1 + nPos; const auto ancQ2 = passBy.q2 + nPos; - mappedQc.passby(passBy.origin + nPos, ancQ1); + mappedQc.move(passBy.origin + nPos, ancQ1); mappedQc.h(ancQ1); mappedQc.cz(passBy.q1, ancQ1); mappedQc.h(ancQ1); - mappedQc.passby(ancQ1, ancQ2); + mappedQc.move(ancQ1, ancQ2); if (usedQubits.find(passBy.q1) != usedQubits.end()) { usedQubits.erase(passBy.q1); @@ -578,7 +578,7 @@ void NeutralAtomMapper::applyFlyingAncilla(NeutralAtomLayer& frontLayer, for (const auto& passBy : faComb.moves) { const auto ancQ1 = passBy.q1 + nPos; const auto ancQ2 = passBy.q2 + nPos; - mappedQc.passby(ancQ2, ancQ1); + mappedQc.move(ancQ2, ancQ1); mappedQc.h(ancQ1); mappedQc.cz(passBy.q2, ancQ1); mappedQc.h(ancQ1); @@ -752,25 +752,25 @@ FlyingAncillaComb NeutralAtomMapper::convertMoveCombToFlyingAncillaComb( FlyingAncilla bestFA{}; HwQubits usedFA; for (const auto move : moveComb.moves) { - if (usedCoords.find(move.first) != usedCoords.end()) { + if (usedCoords.find(move.c1) != usedCoords.end()) { const auto nearFirstIdx = - this->flyingAncillas.getClosestQubit(move.first, usedFA); + this->flyingAncillas.getClosestQubit(move.c1, usedFA); const auto nearFirst = this->flyingAncillas.getCoordIndex(nearFirstIdx); const auto nearSecondIdx = - this->flyingAncillas.getClosestQubit(move.second, usedFA); + this->flyingAncillas.getClosestQubit(move.c2, usedFA); const auto nearSecond = this->flyingAncillas.getCoordIndex(nearSecondIdx); if (usedQubits.size() == 2) { // both directions possible, check if reversed is better - if (this->arch->getEuclideanDistance(nearFirstIdx, move.first) < - this->arch->getEuclideanDistance(nearSecondIdx, move.second)) { - bestFA.q1 = move.second; - bestFA.q2 = move.first; + if (this->arch->getEuclideanDistance(nearFirstIdx, move.c1) < + this->arch->getEuclideanDistance(nearSecondIdx, move.c2)) { + bestFA.q1 = move.c2; + bestFA.q2 = move.c1; bestFA.origin = nearSecond; bestFA.index = nearSecondIdx; } } - bestFA.q1 = move.first; - bestFA.q2 = move.second; + bestFA.q1 = move.c1; + bestFA.q2 = move.c2; bestFA.origin = nearFirst; bestFA.index = nearFirstIdx; @@ -853,9 +853,9 @@ NeutralAtomMapper::moveCombDistanceReduction(const MoveComb& moveComb, const auto& distBefore = this->arch->getAllToAllEuclideanDistance(coordIndices); for (const auto& move : moveComb.moves) { - if (coordIndices.find(move.first) != coordIndices.end()) { - coordIndices.erase(move.first); - coordIndices.insert(move.second); + if (coordIndices.find(move.c1) != coordIndices.end()) { + coordIndices.erase(move.c1); + coordIndices.insert(move.c2); } } const auto& distAfter = @@ -1305,7 +1305,7 @@ qc::fp NeutralAtomMapper::moveCostPerLayer(const AtomMove& move, const GateList& layer) const { // compute cost assuming the move was applied qc::fp distChange = 0; - if (const auto toMoveHwQubit = this->hardwareQubits.getHwQubit(move.first); + if (const auto toMoveHwQubit = this->hardwareQubits.getHwQubit(move.c1); this->mapping.isMapped(toMoveHwQubit)) { const auto toMoveCircuitQubit = this->mapping.getCircQubit(toMoveHwQubit); for (const auto& gate : layer) { @@ -1330,7 +1330,7 @@ qc::fp NeutralAtomMapper::moveCostPerLayer(const AtomMove& move, } const auto hwQubit = this->mapping.getHwQubit(qubit); const auto dist = this->arch->getEuclideanDistance( - this->hardwareQubits.getCoordIndex(hwQubit), move.second); + this->hardwareQubits.getCoordIndex(hwQubit), move.c2); distanceAfter += dist; } distChange += distanceAfter - distanceBefore; @@ -1342,16 +1342,15 @@ qc::fp NeutralAtomMapper::moveCostPerLayer(const AtomMove& move, qc::fp NeutralAtomMapper::parallelMoveCost(const AtomMove& move) const { qc::fp parallelCost = 0; - const auto moveVector = this->arch->getVector(move.first, move.second); + const auto moveVector = this->arch->getVector(move.c1, move.c2); std::vector lastEndingCoords; if (this->lastMoves.empty()) { parallelCost += arch->getVectorShuttlingTime(moveVector); } for (const auto& lastMove : this->lastMoves) { - lastEndingCoords.emplace_back(lastMove.second); + lastEndingCoords.emplace_back(lastMove.c2); // decide of shuttling can be done in parallel - auto lastMoveVector = - this->arch->getVector(lastMove.first, lastMove.second); + auto lastMoveVector = this->arch->getVector(lastMove.c1, lastMove.c2); if (moveVector.overlap(lastMoveVector)) { if (moveVector.direction != lastMoveVector.direction) { parallelCost += arch->getVectorShuttlingTime(moveVector); @@ -1365,13 +1364,13 @@ qc::fp NeutralAtomMapper::parallelMoveCost(const AtomMove& move) const { } // check if in same row/column like last moves // then can may be loaded in parallel - const auto moveCoordInit = this->arch->getCoordinate(move.first); - const auto moveCoordEnd = this->arch->getCoordinate(move.second); + const auto moveCoordInit = this->arch->getCoordinate(move.c1); + const auto moveCoordEnd = this->arch->getCoordinate(move.c2); parallelCost += arch->getShuttlingTime(qc::OpType::AodActivate) + arch->getShuttlingTime(qc::OpType::AodDeactivate); for (const auto& lastMove : this->lastMoves) { - const auto lastMoveCoordInit = this->arch->getCoordinate(lastMove.first); - const auto lastMoveCoordEnd = this->arch->getCoordinate(lastMove.second); + const auto lastMoveCoordInit = this->arch->getCoordinate(lastMove.c1); + const auto lastMoveCoordEnd = this->arch->getCoordinate(lastMove.c2); if (moveCoordInit.x == lastMoveCoordInit.x || moveCoordInit.y == lastMoveCoordInit.y) { parallelCost -= arch->getShuttlingTime(qc::OpType::AodActivate); @@ -1383,7 +1382,7 @@ qc::fp NeutralAtomMapper::parallelMoveCost(const AtomMove& move) const { } // check if move can use AOD atom from last moves // if (std::find(lastEndingCoords.begin(), lastEndingCoords.end(), - // move.first) == + // move.c1) == // lastEndingCoords.end()) { // parallelCost += arch->getShuttlingTime(qc::OpType::AodActivate) + // arch->getShuttlingTime(qc::OpType::AodDeactivate); @@ -2398,7 +2397,7 @@ MappingMethod NeutralAtomMapper::compareShuttlingAndFlyingAncilla( const auto move = moveDistReduction * moveFidelity; const auto fa = faDistReduction * faFidelity; const auto passBy = faDistReduction * passByFidelity; - return MappingMethod::FlyingAncillaMethod; + return MappingMethod::PassByMethod; if (move > fa && move > passBy) { return MappingMethod::MoveMethod; diff --git a/src/hybridmap/MoveToAodConverter.cpp b/src/hybridmap/MoveToAodConverter.cpp index 2575cb9c4..ffc20758e 100644 --- a/src/hybridmap/MoveToAodConverter.cpp +++ b/src/hybridmap/MoveToAodConverter.cpp @@ -59,10 +59,18 @@ MoveToAodConverter::schedule(qc::QuantumComputation& qc) { return qcScheduled; } -void MoveToAodConverter::MoveGroup::addPassBy(const AtomPassBy& passBy, - uint32_t idx) { - passBys.emplace_back(passBy, idx); - qubitsUsedByGates.emplace_back(passBy.second); +AtomMove MoveToAodConverter::convertOpToMove(qc::Operation* get) { + auto q1 = get->getTargets().front(); + auto q2 = get->getTargets().back(); + const auto load1 = q1 < arch.getNpositions(); + const auto load2 = q2 < arch.getNpositions(); + if (!load1) { + q1 -= arch.getNpositions(); + } + if (!load2) { + q2 -= arch.getNpositions(); + } + return {q1, q2, load1, load2}; } void MoveToAodConverter::initMoveGroups(qc::QuantumComputation& qc) { @@ -71,7 +79,7 @@ void MoveToAodConverter::initMoveGroups(qc::QuantumComputation& qc) { uint32_t idx = 0; for (auto& op : qc) { if (op->getType() == qc::OpType::Move) { - AtomMove const move{op->getTargets()[0], op->getTargets()[1]}; + const auto move = convertOpToMove(op.get()); if (currentMoveGroup.canAddMove(move, arch)) { currentMoveGroup.addMove(move, idx); } else { @@ -79,19 +87,7 @@ void MoveToAodConverter::initMoveGroups(qc::QuantumComputation& qc) { currentMoveGroup = MoveGroup(); currentMoveGroup.addMove(move, idx); } - } else if (op->getType() == qc::OpType::PassBy) { - AtomPassBy const passBy{op->getTargets()[0], op->getTargets()[1]}; - const AtomMove passByConverted = {passBy.first - arch.getNpositions(), - passBy.second - arch.getNpositions()}; - if (currentMoveGroup.canAddMove(passByConverted, arch)) { - currentMoveGroup.addPassBy(passBy, idx); - } else { - moveGroups.emplace_back(currentMoveGroup); - currentMoveGroup = MoveGroup(); - currentMoveGroup.addPassBy(passBy, idx); - } - } else if (op->getNqubits() > 1 && (!currentMoveGroup.moves.empty() || - !currentMoveGroup.passBys.empty())) { + } else if (op->getNqubits() > 1 && (!currentMoveGroup.moves.empty())) { for (const auto& qubit : op->getUsedQubits()) { if (std::find(currentMoveGroup.qubitsUsedByGates.begin(), currentMoveGroup.qubitsUsedByGates.end(), @@ -102,7 +98,7 @@ void MoveToAodConverter::initMoveGroups(qc::QuantumComputation& qc) { } idx++; } - if (!currentMoveGroup.moves.empty() || !currentMoveGroup.passBys.empty()) { + if (!currentMoveGroup.moves.empty()) { moveGroups.emplace_back(std::move(currentMoveGroup)); } } @@ -111,17 +107,17 @@ bool MoveToAodConverter::MoveGroup::canAddMove( const AtomMove& move, const NeutralAtomArchitecture& archArg) { // if move would move a qubit that is used by a gate in this move group // return false - if (std::find(qubitsUsedByGates.begin(), qubitsUsedByGates.end(), - move.first) != qubitsUsedByGates.end()) { + if (std::find(qubitsUsedByGates.begin(), qubitsUsedByGates.end(), move.c1) != + qubitsUsedByGates.end()) { return false; } // checks if the op can be executed in parallel - auto moveVector = archArg.getVector(move.first, move.second); + auto moveVector = archArg.getVector(move.c1, move.c2); return std::all_of( moves.begin(), moves.end(), [&moveVector, &archArg](const std::pair opPair) { auto moveGroup = opPair.first; - auto opVector = archArg.getVector(moveGroup.first, moveGroup.second); + auto opVector = archArg.getVector(moveGroup.c1, moveGroup.c2); return parallelCheck(moveVector, opVector); }); } @@ -145,12 +141,12 @@ bool MoveToAodConverter::MoveGroup::parallelCheck(const MoveVector& v1, void MoveToAodConverter::MoveGroup::addMove(const AtomMove& move, const uint32_t idx) { moves.emplace_back(move, idx); - qubitsUsedByGates.emplace_back(move.second); + qubitsUsedByGates.emplace_back(move.c2); } void MoveToAodConverter::AodActivationHelper::addActivation( std::pair merge, - const Point& origin, const AtomMove& move, MoveVector v) { + const Point& origin, const AtomMove& move, MoveVector v, bool needLoad) { const auto x = static_cast(origin.x); const auto y = static_cast(origin.y); const auto signX = v.direction.getSignX(); @@ -164,19 +160,20 @@ void MoveToAodConverter::AodActivationHelper::addActivation( case ActivationMergeType::Trivial: switch (merge.second) { case ActivationMergeType::Trivial: - allActivations.emplace_back( - AodActivation{{x, deltaX, signX}, {y, deltaY, signY}, move}); + allActivations.emplace_back(AodActivation{ + {x, deltaX, signX, needLoad}, {y, deltaY, signY, needLoad}, move}); break; case ActivationMergeType::Merge: - mergeActivationDim(Dimension::Y, - AodActivation{Dimension::Y, {y, deltaY, signY}, move}, - AodActivation{Dimension::X, {x, deltaX, signX}, move}); + mergeActivationDim( + Dimension::Y, + AodActivation{Dimension::Y, {y, deltaY, signY, needLoad}, move}, + AodActivation{Dimension::X, {x, deltaX, signX, needLoad}, move}); aodMovesY = getAodMovesFromInit(Dimension::Y, y); reAssignOffsets(aodMovesY, signY); break; case ActivationMergeType::Append: - allActivations.emplace_back( - AodActivation{{x, deltaX, signX}, {y, deltaY, signY}, move}); + allActivations.emplace_back(AodActivation{ + {x, deltaX, signX, needLoad}, {y, deltaY, signY, needLoad}, move}); aodMovesY = getAodMovesFromInit(Dimension::Y, y); reAssignOffsets(aodMovesY, signY); break; @@ -187,18 +184,20 @@ void MoveToAodConverter::AodActivationHelper::addActivation( case ActivationMergeType::Merge: switch (merge.second) { case ActivationMergeType::Trivial: - mergeActivationDim(Dimension::X, - AodActivation{Dimension::X, {x, deltaX, signX}, move}, - AodActivation{Dimension::Y, {y, deltaY, signY}, move}); + mergeActivationDim( + Dimension::X, + AodActivation{Dimension::X, {x, deltaX, signX, needLoad}, move}, + AodActivation{Dimension::Y, {y, deltaY, signY, needLoad}, move}); aodMovesX = getAodMovesFromInit(Dimension::X, x); reAssignOffsets(aodMovesX, signX); break; case ActivationMergeType::Merge: throw std::runtime_error("Merge in both dimensions should never happen."); case ActivationMergeType::Append: - mergeActivationDim(Dimension::X, - AodActivation{Dimension::X, {x, deltaX, signX}, move}, - AodActivation{Dimension::Y, {y, deltaY, signY}, move}); + mergeActivationDim( + Dimension::X, + AodActivation{Dimension::X, {x, deltaX, signX, needLoad}, move}, + AodActivation{Dimension::Y, {y, deltaY, signY, needLoad}, move}); aodMovesY = getAodMovesFromInit(Dimension::Y, y); reAssignOffsets(aodMovesY, signY); break; @@ -209,21 +208,22 @@ void MoveToAodConverter::AodActivationHelper::addActivation( case ActivationMergeType::Append: switch (merge.second) { case ActivationMergeType::Trivial: - allActivations.emplace_back( - AodActivation{{x, deltaX, signX}, {y, deltaY, signY}, move}); + allActivations.emplace_back(AodActivation{ + {x, deltaX, signX, needLoad}, {y, deltaY, signY, needLoad}, move}); aodMovesX = getAodMovesFromInit(Dimension::X, x); reAssignOffsets(aodMovesX, signX); break; case ActivationMergeType::Merge: - mergeActivationDim(Dimension::Y, - AodActivation{Dimension::Y, {y, deltaY, signY}, move}, - AodActivation{Dimension::X, {x, deltaX, signX}, move}); + mergeActivationDim( + Dimension::Y, + AodActivation{Dimension::Y, {y, deltaY, signY, needLoad}, move}, + AodActivation{Dimension::X, {x, deltaX, signX, needLoad}, move}); aodMovesX = getAodMovesFromInit(Dimension::X, x); reAssignOffsets(aodMovesX, signX); break; case ActivationMergeType::Append: - allActivations.emplace_back( - AodActivation{{x, deltaX, signX}, {y, deltaY, signY}, move}); + allActivations.emplace_back(AodActivation{ + {x, deltaX, signX, needLoad}, {y, deltaY, signY, needLoad}, move}); aodMovesX = getAodMovesFromInit(Dimension::X, x); reAssignOffsets(aodMovesX, signX); aodMovesY = getAodMovesFromInit(Dimension::Y, y); @@ -337,8 +337,7 @@ void MoveToAodConverter::processMoveGroups() { }), groupIt->moves.end()); } - if (!possibleNewMoveGroup.moves.empty() || - !possibleNewMoveGroup.passBys.empty()) { + if (!possibleNewMoveGroup.moves.empty()) { groupIt = moveGroups.emplace(groupIt + 1, std::move(possibleNewMoveGroup)); possibleNewMoveGroup = MoveGroup(); @@ -360,11 +359,11 @@ MoveToAodConverter::processMoves( std::vector movesToRemove; for (auto& movePair : moves) { auto& move = movePair.first; - auto idx = movePair.second; - auto origin = arch.getCoordinate(move.first); - auto target = arch.getCoordinate(move.second); - auto v = arch.getVector(move.first, move.second); - auto vReverse = arch.getVector(move.second, move.first); + const auto idx = movePair.second; + auto origin = arch.getCoordinate(move.c1); + auto target = arch.getCoordinate(move.c2); + auto v = arch.getVector(move.c1, move.c2); + auto vReverse = arch.getVector(move.c2, move.c1); auto canAddX = canAddActivation(aodActivationHelper, aodDeactivationHelper, origin, v, target, vReverse, Dimension::X); auto canAddY = canAddActivation(aodActivationHelper, aodDeactivationHelper, @@ -380,9 +379,10 @@ MoveToAodConverter::processMoves( possibleNewMoveGroup.addMove(move, idx); movesToRemove.emplace_back(move); } else { - aodActivationHelper.addActivation(activationCanAddXY, origin, move, v); + aodActivationHelper.addActivation(activationCanAddXY, origin, move, v, + move.load1); aodDeactivationHelper.addActivation(deactivationCanAddXY, target, move, - vReverse); + vReverse, move.load2); } } @@ -536,21 +536,21 @@ MoveToAodConverter::AodActivationHelper::getAodOperation( qubitsActivation.reserve(activation.moves.size()); for (const auto& move : activation.moves) { if (type == qc::OpType::AodActivate) { - qubitsActivation.emplace_back(move.first); + qubitsActivation.emplace_back(move.c1); } else { - qubitsActivation.emplace_back(move.second); + qubitsActivation.emplace_back(move.c2); } } std::vector qubitsMove; qubitsMove.reserve(activation.moves.size() * 2); for (const auto& move : activation.moves) { - if (std::find(qubitsMove.begin(), qubitsMove.end(), move.first) == + if (std::find(qubitsMove.begin(), qubitsMove.end(), move.c1) == qubitsMove.end()) { - qubitsMove.emplace_back(move.first); + qubitsMove.emplace_back(move.c1); } - if (std::find(qubitsMove.begin(), qubitsMove.end(), move.second) == + if (std::find(qubitsMove.begin(), qubitsMove.end(), move.c2) == qubitsMove.end()) { - qubitsMove.emplace_back(move.second); + qubitsMove.emplace_back(move.c2); } } From 40f005a6c22ea2f12cb9ebfed38a351e885def9d Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Tue, 4 Feb 2025 16:56:49 +0100 Subject: [PATCH 093/394] =?UTF-8?q?=F0=9F=8E=A8=20restructured=20init=20an?= =?UTF-8?q?d=20offset=20operation=20convertion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/MoveToAodConverter.hpp | 6 +- src/hybridmap/MoveToAodConverter.cpp | 72 ++++++++++++------------ 2 files changed, 40 insertions(+), 38 deletions(-) diff --git a/include/hybridmap/MoveToAodConverter.hpp b/include/hybridmap/MoveToAodConverter.hpp index 6bbd36bfa..ad15b0fc3 100644 --- a/include/hybridmap/MoveToAodConverter.hpp +++ b/include/hybridmap/MoveToAodConverter.hpp @@ -187,6 +187,10 @@ class MoveToAodConverter { uint32_t init, int32_t sign) const; + void computeInitAndOffsetOperations( + Dimension dimension, const std::shared_ptr& move, + std::vector& initOperations, + std::vector& offsetOperations) const; // Convert activation to AOD operations /** * @brief Converts activation into AOD operation (activate, move, @@ -197,7 +201,7 @@ class MoveToAodConverter { * @param type The type of the activation (loading or unloading) * @return The activation as AOD operation */ - [[nodiscard]] std::pair + [[nodiscard]] std::vector getAodOperation(const AodActivation& activation) const; /** * @brief Converts all activations into AOD operations diff --git a/src/hybridmap/MoveToAodConverter.cpp b/src/hybridmap/MoveToAodConverter.cpp index ffc20758e..779fcc245 100644 --- a/src/hybridmap/MoveToAodConverter.cpp +++ b/src/hybridmap/MoveToAodConverter.cpp @@ -503,6 +503,30 @@ bool MoveToAodConverter::AodActivationHelper::checkIntermediateSpaceAtInit( getMaxOffsetAtInit(dim, neighborX, sign) < arch->getNAodIntermediateLevels(); } +void MoveToAodConverter::AodActivationHelper::computeInitAndOffsetOperations( + Dimension dimension, const std::shared_ptr& aodMove, + std::vector& initOperations, + std::vector& offsetOperations) const { + + auto d = this->arch->getInterQubitDistance(); + auto interD = this->arch->getInterQubitDistance() / + this->arch->getNAodIntermediateLevels(); + + initOperations.emplace_back(dimension, static_cast(aodMove->init) * d, + static_cast(aodMove->init) * d); + if (type == qc::OpType::AodActivate) { + offsetOperations.emplace_back( + dimension, static_cast(aodMove->init) * d, + static_cast(aodMove->init) * d + + static_cast(aodMove->offset) * interD); + } else { + offsetOperations.emplace_back(dimension, + static_cast(aodMove->init) * d + + static_cast(aodMove->offset) * + interD, + static_cast(aodMove->init) * d); + } +} void MoveToAodConverter::AodActivationHelper::mergeActivationDim( Dimension dim, const AodActivation& activationDim, @@ -529,7 +553,7 @@ void MoveToAodConverter::AodActivationHelper::mergeActivationDim( } } -std::pair +std::vector MoveToAodConverter::AodActivationHelper::getAodOperation( const AodActivationHelper::AodActivation& activation) const { std::vector qubitsActivation; @@ -557,42 +581,16 @@ MoveToAodConverter::AodActivationHelper::getAodOperation( std::vector initOperations; std::vector offsetOperations; - auto d = this->arch->getInterQubitDistance(); - auto interD = this->arch->getInterQubitDistance() / - this->arch->getNAodIntermediateLevels(); - for (const auto& aodMove : activation.activateXs) { - initOperations.emplace_back(Dimension::X, - static_cast(aodMove->init) * d, - static_cast(aodMove->init) * d); - if (type == qc::OpType::AodActivate) { - offsetOperations.emplace_back( - Dimension::X, static_cast(aodMove->init) * d, - static_cast(aodMove->init) * d + - static_cast(aodMove->offset) * interD); - } else { - offsetOperations.emplace_back(Dimension::X, - static_cast(aodMove->init) * d + - static_cast(aodMove->offset) * - interD, - static_cast(aodMove->init) * d); + if (aodMove->load) { + computeInitAndOffsetOperations(Dimension::X, aodMove, initOperations, + offsetOperations); } } for (const auto& aodMove : activation.activateYs) { - initOperations.emplace_back(Dimension::Y, - static_cast(aodMove->init) * d, - static_cast(aodMove->init) * d); - if (type == qc::OpType::AodActivate) { - offsetOperations.emplace_back( - Dimension::Y, static_cast(aodMove->init) * d, - static_cast(aodMove->init) * d + - static_cast(aodMove->offset) * interD); - } else { - offsetOperations.emplace_back(Dimension::Y, - static_cast(aodMove->init) * d + - static_cast(aodMove->offset) * - interD, - static_cast(aodMove->init) * d); + if (aodMove->load) { + computeInitAndOffsetOperations(Dimension::Y, aodMove, initOperations, + offsetOperations); } } @@ -600,9 +598,9 @@ MoveToAodConverter::AodActivationHelper::getAodOperation( auto offsetOp = AodOperation(qc::OpType::AodMove, qubitsMove, offsetOperations); if (this->type == qc::OpType::AodActivate) { - return std::make_pair(initOp, offsetOp); + return {initOp, offsetOp}; } - return std::make_pair(offsetOp, initOp); + return {offsetOp, initOp}; } std::vector @@ -610,8 +608,8 @@ MoveToAodConverter::AodActivationHelper::getAodOperations() const { std::vector aodOperations; for (const auto& activation : allActivations) { auto operations = getAodOperation(activation); - aodOperations.emplace_back(std::move(operations.first)); - aodOperations.emplace_back(std::move(operations.second)); + aodOperations.insert(aodOperations.end(), operations.begin(), + operations.end()); } return aodOperations; } From 58dbdacdec8de8c63cbb625c52f3ddf04c8928d9 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Tue, 4 Feb 2025 17:19:54 +0100 Subject: [PATCH 094/394] =?UTF-8?q?=F0=9F=8E=A8=20minor=20restructuring?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/MoveToAodConverter.cpp | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/hybridmap/MoveToAodConverter.cpp b/src/hybridmap/MoveToAodConverter.cpp index 779fcc245..5c6c0e96a 100644 --- a/src/hybridmap/MoveToAodConverter.cpp +++ b/src/hybridmap/MoveToAodConverter.cpp @@ -560,23 +560,22 @@ MoveToAodConverter::AodActivationHelper::getAodOperation( qubitsActivation.reserve(activation.moves.size()); for (const auto& move : activation.moves) { if (type == qc::OpType::AodActivate) { - qubitsActivation.emplace_back(move.c1); + if (move.load1) { + qubitsActivation.emplace_back(move.c1); + } } else { - qubitsActivation.emplace_back(move.c2); + if (move.load2) { + qubitsActivation.emplace_back(move.c2); + } } } - std::vector qubitsMove; - qubitsMove.reserve(activation.moves.size() * 2); + std::set qubitsMoveSet; for (const auto& move : activation.moves) { - if (std::find(qubitsMove.begin(), qubitsMove.end(), move.c1) == - qubitsMove.end()) { - qubitsMove.emplace_back(move.c1); - } - if (std::find(qubitsMove.begin(), qubitsMove.end(), move.c2) == - qubitsMove.end()) { - qubitsMove.emplace_back(move.c2); - } + qubitsMoveSet.insert(move.c1); + qubitsMoveSet.insert(move.c2); } + std::vector const qubitsMove(qubitsMoveSet.begin(), + qubitsMoveSet.end()); std::vector initOperations; std::vector offsetOperations; @@ -593,6 +592,9 @@ MoveToAodConverter::AodActivationHelper::getAodOperation( offsetOperations); } } + if (initOperations.empty() && offsetOperations.empty()) { + return {}; + } auto initOp = AodOperation(type, qubitsActivation, initOperations); auto offsetOp = From 3b74a9b40a7aaa83d70b8499de9cf0968c1ef56b Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Tue, 4 Feb 2025 18:37:30 +0100 Subject: [PATCH 095/394] =?UTF-8?q?=E2=9C=A8=20removed=20suboptimal=20choi?= =?UTF-8?q?ce=20of=20shuttling=20position.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridNeutralAtomMapper.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 0a0c76406..73ba5b27a 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -1567,9 +1567,6 @@ CoordIndices NeutralAtomMapper::getBestMovePos(const CoordIndices& gateCoords) { currentPos.nMoves = 1; } auto bestPos = getMovePositionRec(currentPos, gateCoords, nMovesGate); - if (!bestPos.coords.empty() && bestPos.nMoves <= minMoves) { - return bestPos.coords; - } if (!bestPos.coords.empty() && bestPos.nMoves < nMovesGate) { nMovesGate = bestPos.nMoves; finalBestPos = bestPos; @@ -2397,7 +2394,7 @@ MappingMethod NeutralAtomMapper::compareShuttlingAndFlyingAncilla( const auto move = moveDistReduction * moveFidelity; const auto fa = faDistReduction * faFidelity; const auto passBy = faDistReduction * passByFidelity; - return MappingMethod::PassByMethod; + return MappingMethod::MoveMethod; if (move > fa && move > passBy) { return MappingMethod::MoveMethod; From 52d7e688c3af308fec0067456213ce21db48d4e1 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Tue, 4 Feb 2025 19:44:28 +0100 Subject: [PATCH 096/394] =?UTF-8?q?=E2=9C=A8=20Rewrite=20of=20connect=20AO?= =?UTF-8?q?D=20Operations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/MoveToAodConverter.hpp | 15 +++- src/hybridmap/MoveToAodConverter.cpp | 90 ++++++++++-------------- 2 files changed, 50 insertions(+), 55 deletions(-) diff --git a/include/hybridmap/MoveToAodConverter.hpp b/include/hybridmap/MoveToAodConverter.hpp index ad15b0fc3..e7e2659c7 100644 --- a/include/hybridmap/MoveToAodConverter.hpp +++ b/include/hybridmap/MoveToAodConverter.hpp @@ -273,14 +273,22 @@ class MoveToAodConverter { * operations */ static AodOperation - connectAodOperations(const std::vector& opsInit, - const std::vector& opsFinal); + connectAodOperations(const AodActivationHelper& aodActivationHelper, + const AodActivationHelper& aodDeactivationHelper); }; + struct AncillaAtom { + bool occupied = false; + uint32_t xOffset = 1; + uint32_t yOffset = 1; + }; + + using AncillaAtoms = std::vector; const NeutralAtomArchitecture& arch; qc::QuantumComputation qcScheduled; std::vector moveGroups; const na::HardwareQubits& hardwareQubits; + AncillaAtoms ancillaAtoms; AtomMove convertOpToMove(qc::Operation* get); /** @@ -311,7 +319,8 @@ class MoveToAodConverter { explicit MoveToAodConverter(const NeutralAtomArchitecture& archArg, const na::HardwareQubits& hardwareQubitsArg) : arch(archArg), qcScheduled(arch.getNpositions()), - hardwareQubits(hardwareQubitsArg) {} + hardwareQubits(hardwareQubitsArg), + ancillaAtoms(AncillaAtoms(arch.getNpositions(), AncillaAtom())) {} /** * @brief Schedules the given quantum circuit using AODs diff --git a/src/hybridmap/MoveToAodConverter.cpp b/src/hybridmap/MoveToAodConverter.cpp index 5c6c0e96a..8209303b7 100644 --- a/src/hybridmap/MoveToAodConverter.cpp +++ b/src/hybridmap/MoveToAodConverter.cpp @@ -57,6 +57,7 @@ MoveToAodConverter::schedule(qc::QuantumComputation& qc) { } return qcScheduled; + qcScheduled.print(std::cout); } AtomMove MoveToAodConverter::convertOpToMove(qc::Operation* get) { @@ -346,7 +347,7 @@ void MoveToAodConverter::processMoveGroups() { groupIt->processedOpsInit = aodActivationHelper.getAodOperations(); groupIt->processedOpsFinal = aodDeactivationHelper.getAodOperations(); groupIt->processedOpShuttle = MoveGroup::connectAodOperations( - groupIt->processedOpsInit, groupIt->processedOpsFinal); + aodActivationHelper, aodDeactivationHelper); } } std::pair, MoveToAodConverter::MoveGroup> @@ -390,61 +391,46 @@ MoveToAodConverter::processMoves( } AodOperation MoveToAodConverter::MoveGroup::connectAodOperations( - const std::vector& opsInit, - const std::vector& opsFinal) { + const AodActivationHelper& aodActivationHelper, + const AodActivationHelper& aodDeactivationHelper) { // for each init operation find the corresponding final operation // and connect with an aod move operations // all can be done in parallel in a single move std::vector aodOperations; std::set targetQubits; - for (const auto& opInit : opsInit) { - if (opInit.getType() == qc::OpType::AodMove) { - for (const auto& opFinal : opsFinal) { - if (opFinal.getType() == qc::OpType::AodMove) { - if (opInit.getTargets().size() <= 1 || - opFinal.getTargets().size() <= 1) { - throw qc::QFRException( - "AodScheduler::MoveGroup::connectAodOperations: " - "AodMove operation with less than 2 targets"); - } - if (opInit.getTargets() == opFinal.getTargets()) { - targetQubits.insert(opInit.getTargets().begin(), - opInit.getTargets().end()); - // found corresponding final operation - // connect with aod move - const auto startXs = opInit.getEnds(Dimension::X); - const auto endXs = opFinal.getStarts(Dimension::X); - const auto startYs = opInit.getEnds(Dimension::Y); - const auto endYs = opFinal.getStarts(Dimension::Y); - if (!startXs.empty() && !endXs.empty()) { - for (size_t i = 0; i < startXs.size(); i++) { - const auto startX = startXs[i]; - const auto endX = endXs[i]; - if (std::abs(startX - endX) > 0.0001) { - aodOperations.emplace_back(Dimension::X, startX, endX); - } - } - } - if (!startYs.empty() && !endYs.empty()) { - for (size_t i = 0; i < startYs.size(); i++) { - const auto startY = startYs[i]; - const auto endY = endYs[i]; - if (std::abs(startY - endY) > 0.0001) { - aodOperations.emplace_back(Dimension::Y, startY, endY); - } - } + auto d = aodActivationHelper.arch->getInterQubitDistance(); + auto interD = aodActivationHelper.arch->getInterQubitDistance() / + aodActivationHelper.arch->getNAodIntermediateLevels(); + + std::vector dimensions = {na::Dimension::X, na::Dimension::Y}; + + // connect move operations + for (const auto& activation : aodActivationHelper.allActivations) { + for (const auto& deactivation : aodDeactivationHelper.allActivations) { + if (activation.moves == deactivation.moves) { + targetQubits.insert(activation.moves[0].c1); + targetQubits.insert(activation.moves[0].c2); + + for (const auto& dim : dimensions) { + const auto& activationDim = activation.getActivates(dim); + const auto& deactivationDim = deactivation.getActivates(dim); + for (size_t i = 0; i < activationDim.size(); i++) { + const auto& start = + activationDim[i]->init * d + activationDim[i]->offset * interD; + const auto& end = deactivationDim[i]->init * d + + deactivationDim[i]->offset * interD; + if (std::abs(start - end) > 0.0001) { + aodOperations.emplace_back(dim, start, end); } } } } } } - std::vector targetQubitsVec; - targetQubitsVec.reserve(targetQubits.size()); - for (const auto& qubit : targetQubits) { - targetQubitsVec.emplace_back(qubit); - } + + std::vector targetQubitsVec = {targetQubits.begin(), + targetQubits.end()}; return {qc::OpType::AodMove, targetQubitsVec, aodOperations}; } @@ -581,16 +567,16 @@ MoveToAodConverter::AodActivationHelper::getAodOperation( std::vector offsetOperations; for (const auto& aodMove : activation.activateXs) { - if (aodMove->load) { - computeInitAndOffsetOperations(Dimension::X, aodMove, initOperations, - offsetOperations); - } + // if (aodMove->load) { + computeInitAndOffsetOperations(Dimension::X, aodMove, initOperations, + offsetOperations); + // } } for (const auto& aodMove : activation.activateYs) { - if (aodMove->load) { - computeInitAndOffsetOperations(Dimension::Y, aodMove, initOperations, - offsetOperations); - } + // if (aodMove->load) { + computeInitAndOffsetOperations(Dimension::Y, aodMove, initOperations, + offsetOperations); + // } } if (initOperations.empty() && offsetOperations.empty()) { return {}; From 1e85abaa50ef8ac01bb011075ff18c63f68f62d4 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Tue, 4 Feb 2025 19:52:46 +0100 Subject: [PATCH 097/394] =?UTF-8?q?=F0=9F=90=9B=20Fixed=20missing=20reassi?= =?UTF-8?q?gnment=20to=20layers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridNeutralAtomMapper.cpp | 49 +++++++++++------------ 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 73ba5b27a..9aa25b967 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -1690,32 +1690,29 @@ size_t NeutralAtomMapper::shuttlingBasedMapping( NeutralAtomLayer& frontLayer, NeutralAtomLayer& lookaheadLayer, size_t i) { while (!this->frontLayerShuttling.empty()) { GateList gatesToExecute; - while (gatesToExecute.empty()) { - ++i; - if (this->parameters->verbose) { - std::cout << "iteration " << i << '\n'; - } - auto bestComb = findBestAtomMove(); - auto bestFaComb = convertMoveCombToFlyingAncillaComb(bestComb); - - switch (compareShuttlingAndFlyingAncilla(bestComb, bestFaComb)) { - case MappingMethod::MoveMethod: - // apply whole move combination at once - for (const auto& move : bestComb.moves) { - applyMove(move); - } - // applyMove(bestComb.moves[0]); - break; - case MappingMethod::FlyingAncillaMethod: - applyFlyingAncilla(frontLayer, bestFaComb); - break; - case MappingMethod::PassByMethod: - applyPassBy(frontLayer, bestFaComb); - break; - default: - break; + ++i; + if (this->parameters->verbose) { + std::cout << "iteration " << i << '\n'; + } + auto bestComb = findBestAtomMove(); + auto bestFaComb = convertMoveCombToFlyingAncillaComb(bestComb); + + switch (compareShuttlingAndFlyingAncilla(bestComb, bestFaComb)) { + case MappingMethod::MoveMethod: + // apply whole move combination at once + for (const auto& move : bestComb.moves) { + applyMove(move); } - gatesToExecute = getExecutableGates(frontLayer.getGates()); + // applyMove(bestComb.moves[0]); + break; + case MappingMethod::FlyingAncillaMethod: + applyFlyingAncilla(frontLayer, bestFaComb); + break; + case MappingMethod::PassByMethod: + applyPassBy(frontLayer, bestFaComb); + break; + default: + break; } mapAllPossibleGates(frontLayer, lookaheadLayer); reassignGatesToLayers(frontLayer.getGates(), lookaheadLayer.getGates()); @@ -2394,7 +2391,7 @@ MappingMethod NeutralAtomMapper::compareShuttlingAndFlyingAncilla( const auto move = moveDistReduction * moveFidelity; const auto fa = faDistReduction * faFidelity; const auto passBy = faDistReduction * passByFidelity; - return MappingMethod::MoveMethod; + return MappingMethod::PassByMethod; if (move > fa && move > passBy) { return MappingMethod::MoveMethod; From 1e0d4ffcf3767a5a3184e42f60c1308410e79d02 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Tue, 4 Feb 2025 20:05:57 +0100 Subject: [PATCH 098/394] =?UTF-8?q?=F0=9F=90=9B=20Ignore=20init=20and=20of?= =?UTF-8?q?fset=20if=20passby?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/MoveToAodConverter.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/hybridmap/MoveToAodConverter.cpp b/src/hybridmap/MoveToAodConverter.cpp index 8209303b7..6b953f443 100644 --- a/src/hybridmap/MoveToAodConverter.cpp +++ b/src/hybridmap/MoveToAodConverter.cpp @@ -567,16 +567,16 @@ MoveToAodConverter::AodActivationHelper::getAodOperation( std::vector offsetOperations; for (const auto& aodMove : activation.activateXs) { - // if (aodMove->load) { - computeInitAndOffsetOperations(Dimension::X, aodMove, initOperations, - offsetOperations); - // } + if (aodMove->load) { + computeInitAndOffsetOperations(Dimension::X, aodMove, initOperations, + offsetOperations); + } } for (const auto& aodMove : activation.activateYs) { - // if (aodMove->load) { - computeInitAndOffsetOperations(Dimension::Y, aodMove, initOperations, - offsetOperations); - // } + if (aodMove->load) { + computeInitAndOffsetOperations(Dimension::Y, aodMove, initOperations, + offsetOperations); + } } if (initOperations.empty() && offsetOperations.empty()) { return {}; From d446f1f6c5b7bcacf3e551ceeb53f46f94fce077 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Tue, 4 Feb 2025 20:26:32 +0100 Subject: [PATCH 099/394] =?UTF-8?q?=F0=9F=90=9B=20Fixed=20problem=20with?= =?UTF-8?q?=20multiple=20flying=20ancilla=20per=20coord.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridNeutralAtomMapper.cpp | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 9aa25b967..68499a1e6 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -553,7 +553,9 @@ void NeutralAtomMapper::applyFlyingAncilla(NeutralAtomLayer& frontLayer, for (const auto& passBy : faComb.moves) { const auto ancQ1 = passBy.q1 + nPos; const auto ancQ2 = passBy.q2 + nPos; - mappedQc.move(passBy.origin + nPos, ancQ1); + if (passBy.origin + nPos != ancQ1) { + mappedQc.move(passBy.origin + nPos, ancQ1); + } mappedQc.h(ancQ1); mappedQc.cz(passBy.q1, ancQ1); mappedQc.h(ancQ1); @@ -565,7 +567,8 @@ void NeutralAtomMapper::applyFlyingAncilla(NeutralAtomLayer& frontLayer, } if (this->parameters->verbose) { - std::cout << "passby (flying ancilla) " << ancQ1 << " " << ancQ2 << '\n'; + std::cout << "passby (flying ancilla) " << passBy.origin << " " + << passBy.q1 << " " << passBy.q2 << '\n'; } } const auto opCopy = faComb.op->clone(); @@ -584,7 +587,16 @@ void NeutralAtomMapper::applyFlyingAncilla(NeutralAtomLayer& frontLayer, mappedQc.h(ancQ1); // update position of flying ancillas - this->flyingAncillas.move(passBy.index, passBy.q1); + if (this->flyingAncillas.isMapped(passBy.q1)) { + // move away + const auto& freeCoords = + this->flyingAncillas.getNearbyFreeCoordinatesByCoord(passBy.q1); + const auto& freeCoord = *freeCoords.begin(); + mappedQc.move(passBy.q1 + nPos, freeCoord + nPos); + this->flyingAncillas.move(passBy.q1, freeCoord); + } else { + this->flyingAncillas.move(passBy.index, passBy.q1); + } if (this->parameters->verbose) { std::cout << "passby (flying ancilla)" << ancQ2 << " " << ancQ1 << '\n'; @@ -2391,7 +2403,7 @@ MappingMethod NeutralAtomMapper::compareShuttlingAndFlyingAncilla( const auto move = moveDistReduction * moveFidelity; const auto fa = faDistReduction * faFidelity; const auto passBy = faDistReduction * passByFidelity; - return MappingMethod::PassByMethod; + return MappingMethod::FlyingAncillaMethod; if (move > fa && move > passBy) { return MappingMethod::MoveMethod; From abf1319e97ae34961a446503ac657c41039f746e Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Tue, 4 Feb 2025 20:31:34 +0100 Subject: [PATCH 100/394] =?UTF-8?q?=E2=9C=A8=20added=20ancilla=20register?= =?UTF-8?q?=20to=20aod=20circuit=20to=20fix=20qasm=20output?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/MoveToAodConverter.hpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/include/hybridmap/MoveToAodConverter.hpp b/include/hybridmap/MoveToAodConverter.hpp index e7e2659c7..99c074657 100644 --- a/include/hybridmap/MoveToAodConverter.hpp +++ b/include/hybridmap/MoveToAodConverter.hpp @@ -320,7 +320,9 @@ class MoveToAodConverter { const na::HardwareQubits& hardwareQubitsArg) : arch(archArg), qcScheduled(arch.getNpositions()), hardwareQubits(hardwareQubitsArg), - ancillaAtoms(AncillaAtoms(arch.getNpositions(), AncillaAtom())) {} + ancillaAtoms(AncillaAtoms(arch.getNpositions(), AncillaAtom())) { + qcScheduled.addAncillaryRegister(arch.getNpositions()); + } /** * @brief Schedules the given quantum circuit using AODs From 5e64d10e4663e8bb618e3940b04407e5a2d02b6b Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Tue, 4 Feb 2025 20:36:43 +0100 Subject: [PATCH 101/394] =?UTF-8?q?=F0=9F=90=9B=20Fixed=20qubit=20register?= =?UTF-8?q?=20for=20Ancilla=20qubits?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/MoveToAodConverter.cpp | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/hybridmap/MoveToAodConverter.cpp b/src/hybridmap/MoveToAodConverter.cpp index 6b953f443..afce3f00a 100644 --- a/src/hybridmap/MoveToAodConverter.cpp +++ b/src/hybridmap/MoveToAodConverter.cpp @@ -409,8 +409,21 @@ AodOperation MoveToAodConverter::MoveGroup::connectAodOperations( for (const auto& activation : aodActivationHelper.allActivations) { for (const auto& deactivation : aodDeactivationHelper.allActivations) { if (activation.moves == deactivation.moves) { - targetQubits.insert(activation.moves[0].c1); - targetQubits.insert(activation.moves[0].c2); + // get target qubits + for (const auto& move : activation.moves) { + if (move.load1) { + targetQubits.insert(move.c1); + } else { + targetQubits.insert(move.c1 + + aodActivationHelper.arch->getNpositions()); + } + if (move.load2) { + targetQubits.insert(move.c2); + } else { + targetQubits.insert(move.c2 + + aodActivationHelper.arch->getNpositions()); + } + } for (const auto& dim : dimensions) { const auto& activationDim = activation.getActivates(dim); From e34a41da1d89082477a8ea857c8aaa4cbbae2f94 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Tue, 4 Feb 2025 20:42:20 +0100 Subject: [PATCH 102/394] =?UTF-8?q?=F0=9F=90=9B=20Fixed=20flying=20ancilla?= =?UTF-8?q?=20verbose=20print.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridNeutralAtomMapper.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 68499a1e6..44e805554 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -599,7 +599,8 @@ void NeutralAtomMapper::applyFlyingAncilla(NeutralAtomLayer& frontLayer, } if (this->parameters->verbose) { - std::cout << "passby (flying ancilla)" << ancQ2 << " " << ancQ1 << '\n'; + std::cout << "passby (flying ancilla) " << passBy.q2 << " " << passBy.q1 + << '\n'; } } From a2aa366d8e05988591b62cba06ab6584adb48826 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Tue, 4 Feb 2025 20:43:05 +0100 Subject: [PATCH 103/394] =?UTF-8?q?=F0=9F=90=9B=20Removed=20debug=20statem?= =?UTF-8?q?ent?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridNeutralAtomMapper.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 44e805554..ffb795cc0 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -2404,7 +2404,7 @@ MappingMethod NeutralAtomMapper::compareShuttlingAndFlyingAncilla( const auto move = moveDistReduction * moveFidelity; const auto fa = faDistReduction * faFidelity; const auto passBy = faDistReduction * passByFidelity; - return MappingMethod::FlyingAncillaMethod; + // return MappingMethod::FlyingAncillaMethod; if (move > fa && move > passBy) { return MappingMethod::MoveMethod; From 77ec1e596665f1e8fbdc051e2b9edbb552e236ed Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Tue, 4 Feb 2025 21:50:31 +0100 Subject: [PATCH 104/394] =?UTF-8?q?=F0=9F=90=9B=20Fixed=20bridge=20gate=20?= =?UTF-8?q?computation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/NeutralAtomArchitecture.hpp | 6 +++--- src/hybridmap/HybridNeutralAtomMapper.cpp | 13 +++++++++---- src/hybridmap/NeutralAtomArchitecture.cpp | 2 +- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/include/hybridmap/NeutralAtomArchitecture.hpp b/include/hybridmap/NeutralAtomArchitecture.hpp index b0ae045ea..db1c62ab1 100644 --- a/include/hybridmap/NeutralAtomArchitecture.hpp +++ b/include/hybridmap/NeutralAtomArchitecture.hpp @@ -396,8 +396,8 @@ class NeutralAtomArchitecture { */ [[nodiscard]] qc::fp getEuclideanDistance(const CoordIndex idx1, const CoordIndex idx2) const { - return static_cast(this->coordinates.at(idx1).getEuclideanDistance( - this->coordinates.at(idx2))); + return this->coordinates.at(idx1).getEuclideanDistanceFp( + this->coordinates.at(idx2)); } [[nodiscard]] qc::fp getAllToAllEuclideanDistance(const std::set& coords) const { @@ -448,7 +448,7 @@ class NeutralAtomArchitecture { */ [[nodiscard]] static qc::fp getEuclideanDistance(const Point& c1, const Point& c2) { - return static_cast(c1.getEuclideanDistance(c2)); + return c1.getEuclideanDistanceFp(c2); } /** * @brief Get the Manhattan distance between two coordinate indices diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index ffb795cc0..34f358c84 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -708,11 +708,16 @@ Bridges NeutralAtomMapper::getShortestBridges() const { auto usedHwQubits = this->mapping.getHwQubits(usedQuBits); const auto bridges = this->hardwareQubits.computeAllShortestPaths( *usedHwQubits.begin(), *usedHwQubits.rbegin()); + if (bridges.empty()) { + continue; + } + if (bridges.front().size() < minBridgeLength) { + minBridgeLength = bridges.front().size(); + allBridges.clear(); + } for (const auto& bridge : bridges) { - allBridges.emplace_back(op, bridge); - if (bridge.size() < minBridgeLength) { - minBridgeLength = bridge.size(); - allBridges.clear(); + if (bridge.size() == minBridgeLength) { + allBridges.emplace_back(op, bridge); } } } diff --git a/src/hybridmap/NeutralAtomArchitecture.cpp b/src/hybridmap/NeutralAtomArchitecture.cpp index af932fbee..ed4b675cf 100644 --- a/src/hybridmap/NeutralAtomArchitecture.cpp +++ b/src/hybridmap/NeutralAtomArchitecture.cpp @@ -88,7 +88,7 @@ void NeutralAtomArchitecture::loadJson(const std::string& filename) { // compute values for Bridge gate // precompute bridge circuits - for (size_t i = 3; i <= 10; i++) { + for (size_t i = 3; i < 10; i++) { qc::fp const bridgeGateTime = (static_cast(bridgeCircuits.czDepth[i]) * gateTimes.at("cz")) + From 63af5fb259b8bdb216d137ce548dd61e2d291864 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Wed, 5 Feb 2025 09:12:18 +0100 Subject: [PATCH 105/394] =?UTF-8?q?=E2=9C=A8=20Added=20error=20message=20f?= =?UTF-8?q?or=20too=20small=20interaction=20radius?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/hybridmap/test_hybridmap.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/test/hybridmap/test_hybridmap.cpp b/test/hybridmap/test_hybridmap.cpp index 12973cabc..6fccc02bb 100644 --- a/test/hybridmap/test_hybridmap.cpp +++ b/test/hybridmap/test_hybridmap.cpp @@ -139,29 +139,29 @@ class NeutralAtomMapperTest : public ::testing::Test { mapperParameters.lookaheadWeightMoves = 0.1; mapperParameters.decay = 0; mapperParameters.shuttlingTimeWeight = 0.1; - mapperParameters.gateWeight = 0; - mapperParameters.shuttlingWeight = 1; + mapperParameters.gateWeight = 1; + mapperParameters.shuttlingWeight = 0; mapperParameters.seed = 43; mapperParameters.verbose = true; - mapperParameters.numFlyingAncillas = 1; + mapperParameters.numFlyingAncillas = 2; mapper.setParameters(mapperParameters); qc = qc::QuantumComputation( // "circuits/dj_nativegates_rigetti_qiskit_opt3_10.qasm"); - "circuits/dj_nativegates_rigetti_qiskit_opt3_10.qasm"); + "circuits/modulo_2.qasm"); } }; TEST_F(NeutralAtomMapperTest, Output) { auto qcMapped = mapper.map(qc, initialMapping); - - // qcMapped.dumpOpenQASM(std::cout, false); + setvbuf(stdout, NULL, _IONBF, 0); + qcMapped.dumpOpenQASM(std::cout, false); auto qcAodMapped = mapper.convertToAod(); - // qcAodMapped.dumpOpenQASM(std::cout, false); + qcAodMapped.dumpOpenQASM(std::cout, false); // const auto scheduleResults = mapper.schedule(false, true); - const auto scheduleResults = mapper.schedule(true, true); - std::cout << scheduleResults.toCsv(); + // const auto scheduleResults = mapper.schedule(true, true); + // std::cout << scheduleResults.toCsv(); - ASSERT_GT(scheduleResults.totalFidelities, 0); + // ASSERT_GT(scheduleResults.totalFidelities, 0); } From c96dd9a1fd9fb118c51907769ade4cd4f7121faa Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Wed, 5 Feb 2025 09:15:01 +0100 Subject: [PATCH 106/394] =?UTF-8?q?=F0=9F=90=9B=20Use=20swap=20if=20no=20b?= =?UTF-8?q?ridges=20are=20found?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridNeutralAtomMapper.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 34f358c84..415d0805b 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -1602,8 +1602,15 @@ CoordIndices NeutralAtomMapper::getBestMovePos(const CoordIndices& gateCoords) { } } if (finalBestPos.coords.empty()) { - throw qc::QFRException( - "No move position found (check if enough free coords are available)"); + // check if interaction radius too small + if (std::sqrt(gateCoords.size()) > this->arch->getInteractionRadius()) { + throw qc::QFRException( + "Interaction radius too small for the given gate size of " + + std::to_string(gateCoords.size())); + } else { + throw qc::QFRException( + "No move position found (check if enough free coords are available)"); + } } return finalBestPos.coords; } @@ -2319,6 +2326,9 @@ CoordIndex NeutralAtomMapper::returnClosestAncillaCoord( MappingMethod NeutralAtomMapper::compareSwapAndBridge(const Swap& bestSwap, const Bridge& bestBridge) { + if (bestBridge == Bridge()) { + return MappingMethod::SwapMethod; + } // swap distance reduction qc::fp const swapDistReduction = swapDistanceReduction(bestSwap, this->frontLayerGate) + From bec59b8e2c1312032da63ad7af972de3e177e13a Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Wed, 5 Feb 2025 09:38:28 +0100 Subject: [PATCH 107/394] =?UTF-8?q?=E2=9C=85=20Increased=20qubit=20number?= =?UTF-8?q?=20to=20allow=20flying=20ancillas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/hybridmap/architectures/rubidium_shuttling.json | 2 +- test/hybridmap/test_hybridmap.cpp | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/test/hybridmap/architectures/rubidium_shuttling.json b/test/hybridmap/architectures/rubidium_shuttling.json index 451603912..421575c0c 100644 --- a/test/hybridmap/architectures/rubidium_shuttling.json +++ b/test/hybridmap/architectures/rubidium_shuttling.json @@ -11,7 +11,7 @@ "blockingFactor": 1 }, "parameters": { - "nQubits": 11, + "nQubits": 12, "gateTimes": { "none": 0.5, "rx": 0.5, diff --git a/test/hybridmap/test_hybridmap.cpp b/test/hybridmap/test_hybridmap.cpp index 6fccc02bb..fbeb26097 100644 --- a/test/hybridmap/test_hybridmap.cpp +++ b/test/hybridmap/test_hybridmap.cpp @@ -140,7 +140,7 @@ class NeutralAtomMapperTest : public ::testing::Test { mapperParameters.decay = 0; mapperParameters.shuttlingTimeWeight = 0.1; mapperParameters.gateWeight = 1; - mapperParameters.shuttlingWeight = 0; + mapperParameters.shuttlingWeight = 1; mapperParameters.seed = 43; mapperParameters.verbose = true; mapperParameters.numFlyingAncillas = 2; @@ -159,9 +159,9 @@ TEST_F(NeutralAtomMapperTest, Output) { auto qcAodMapped = mapper.convertToAod(); qcAodMapped.dumpOpenQASM(std::cout, false); - // const auto scheduleResults = mapper.schedule(false, true); + const auto scheduleResults = mapper.schedule(false, false); // const auto scheduleResults = mapper.schedule(true, true); - // std::cout << scheduleResults.toCsv(); + std::cout << scheduleResults.toCsv(); - // ASSERT_GT(scheduleResults.totalFidelities, 0); + ASSERT_GT(scheduleResults.totalFidelities, 0); } From a40bc96a57288c68c20a6f253f1b3270aebd6bbd Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 6 Feb 2025 08:51:51 +0100 Subject: [PATCH 108/394] =?UTF-8?q?=F0=9F=90=9B=20Fixed=20bug=20when=20fly?= =?UTF-8?q?ing=20ancilla=20origin=20is=20also=20first=20qubit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridNeutralAtomMapper.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 415d0805b..956627863 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -587,14 +587,15 @@ void NeutralAtomMapper::applyFlyingAncilla(NeutralAtomLayer& frontLayer, mappedQc.h(ancQ1); // update position of flying ancillas - if (this->flyingAncillas.isMapped(passBy.q1)) { + if (this->flyingAncillas.isMapped(passBy.q1) && + passBy.q1 != passBy.origin) { // move away const auto& freeCoords = this->flyingAncillas.getNearbyFreeCoordinatesByCoord(passBy.q1); const auto& freeCoord = *freeCoords.begin(); mappedQc.move(passBy.q1 + nPos, freeCoord + nPos); this->flyingAncillas.move(passBy.q1, freeCoord); - } else { + } else if (passBy.q1 != passBy.origin) { this->flyingAncillas.move(passBy.index, passBy.q1); } @@ -2419,7 +2420,7 @@ MappingMethod NeutralAtomMapper::compareShuttlingAndFlyingAncilla( const auto move = moveDistReduction * moveFidelity; const auto fa = faDistReduction * faFidelity; const auto passBy = faDistReduction * passByFidelity; - // return MappingMethod::FlyingAncillaMethod; + return MappingMethod::FlyingAncillaMethod; if (move > fa && move > passBy) { return MappingMethod::MoveMethod; From 6beac46db2174eb440315e0427fd95b8c348f697 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 6 Feb 2025 09:05:09 +0100 Subject: [PATCH 109/394] =?UTF-8?q?=E2=9C=A8=20Scheduling=20supports=20fly?= =?UTF-8?q?ing=20ancilla=20blocking?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/NeutralAtomArchitecture.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/hybridmap/NeutralAtomArchitecture.cpp b/src/hybridmap/NeutralAtomArchitecture.cpp index ed4b675cf..32e3ca1c7 100644 --- a/src/hybridmap/NeutralAtomArchitecture.cpp +++ b/src/hybridmap/NeutralAtomArchitecture.cpp @@ -284,7 +284,11 @@ NeutralAtomArchitecture::getBlockedCoordIndices(const qc::Operation* op) const { return op->getUsedQubits(); } std::set blockedCoordIndices; - for (const auto& coord : op->getUsedQubits()) { + for (auto coord : op->getUsedQubits()) { + // qubits in ancilla register + if (coord >= getNpositions()) { + coord -= getNpositions(); + } for (uint32_t i = 0; i < getNqubits(); ++i) { if (i == coord) { continue; From 7e35d56ef4becb8dbbc0e2cc3db2ad78e279ce44 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 6 Feb 2025 09:10:07 +0100 Subject: [PATCH 110/394] =?UTF-8?q?=F0=9F=90=9B=20Fixed=20missing=20qubit?= =?UTF-8?q?=20blocking.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/NeutralAtomArchitecture.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hybridmap/NeutralAtomArchitecture.cpp b/src/hybridmap/NeutralAtomArchitecture.cpp index 32e3ca1c7..542bfd640 100644 --- a/src/hybridmap/NeutralAtomArchitecture.cpp +++ b/src/hybridmap/NeutralAtomArchitecture.cpp @@ -289,7 +289,7 @@ NeutralAtomArchitecture::getBlockedCoordIndices(const qc::Operation* op) const { if (coord >= getNpositions()) { coord -= getNpositions(); } - for (uint32_t i = 0; i < getNqubits(); ++i) { + for (uint32_t i = 0; i < getNpositions(); ++i) { if (i == coord) { continue; } From 451ad233fe5be02e2026593eaea7524a3df3bae4 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 6 Feb 2025 13:33:28 +0100 Subject: [PATCH 111/394] =?UTF-8?q?=E2=9C=A8=20New=20Animation:=20Restruct?= =?UTF-8?q?ure=20and=20default=20style.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridNeutralAtomMapper.hpp | 8 +-- include/hybridmap/NeutralAtomArchitecture.hpp | 28 +++++++++- include/hybridmap/NeutralAtomScheduler.hpp | 34 +++++++---- include/hybridmap/default_style.hpp | 56 +++++++++++++++++++ src/hybridmap/NeutralAtomScheduler.cpp | 9 +-- test/hybridmap/test_hybridmap.cpp | 18 +++++- 6 files changed, 129 insertions(+), 24 deletions(-) create mode 100644 include/hybridmap/default_style.hpp diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index b474d7aa9..7f94df268 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -656,16 +656,16 @@ class NeutralAtomMapper { * @brief Saves the animation csv file of the scheduled quantum circuit. * @return The animation csv string */ - [[maybe_unused]] std::string getAnimationCsv() { - return scheduler.getAnimationCsv(); + [[maybe_unused]] std::string getAnimationViz() const { + return scheduler.getAnimationViz(); } /** * @brief Saves the animation csv file of the scheduled quantum circuit. * @param filename The name of the file to save the animation csv file to */ - [[maybe_unused]] void saveAnimationCsv(const std::string& filename) const { - scheduler.saveAnimationCsv(filename); + [[maybe_unused]] void saveAnimationFiles(const std::string& filename) const { + scheduler.saveAnimationFiles(filename); } void decomposeBridgeGates(qc::QuantumComputation& qc) const; diff --git a/include/hybridmap/NeutralAtomArchitecture.hpp b/include/hybridmap/NeutralAtomArchitecture.hpp index db1c62ab1..a5c4a64e3 100644 --- a/include/hybridmap/NeutralAtomArchitecture.hpp +++ b/include/hybridmap/NeutralAtomArchitecture.hpp @@ -9,6 +9,7 @@ #include "datastructures/SymmetricMatrix.hpp" #include "hybridmap/NeutralAtomDefinitions.hpp" #include "hybridmap/NeutralAtomUtils.hpp" +#include "hybridmap/default_style.hpp" #include "ir/QuantumComputation.hpp" #include "ir/operations/OpType.hpp" #include "ir/operations/Operation.hpp" @@ -20,8 +21,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -542,7 +545,7 @@ class NeutralAtomArchitecture { * @brief Returns a csv string for the animation of the architecture * @return The csv string for the animation of the architecture */ - [[nodiscard]] std::string getAnimationCsv() const { + [[nodiscard]] std::string getAnimationMachine() const { std::string csv = "x;y;size;color\n"; for (auto i = 0; i < getNcolumns(); i++) { for (auto j = 0; j < getNrows(); j++) { @@ -553,13 +556,32 @@ class NeutralAtomArchitecture { return csv; } + [[nodiscard]] std::string getAnimationStyle() const { + std::string style(defaultStyle); + const std::string toReplace = "XXX"; + const std::string replaceWith = std::to_string(getInteractionRadius()); + + size_t pos = 0; + while ((pos = style.find(toReplace, pos)) != std::string::npos) { + style.replace(pos, toReplace.length(), replaceWith); + pos += replaceWith.length(); + } + return style; + } + /** * @brief Save the animation of the architecture to a csv file * @param filename The name of the csv file */ - [[maybe_unused]] void saveAnimationCsv(const std::string& filename) const { + [[maybe_unused]] void + saveAnimationMachine(const std::string& filename) const { + std::ofstream file(filename); + file << getAnimationMachine(); + } + + [[maybe_unused]] void saveAnimationStyle(const std::string& filename) const { std::ofstream file(filename); - file << getAnimationCsv(); + file << getAnimationStyle(); } }; diff --git a/include/hybridmap/NeutralAtomScheduler.hpp b/include/hybridmap/NeutralAtomScheduler.hpp index b4d7342ce..99fc4f20c 100644 --- a/include/hybridmap/NeutralAtomScheduler.hpp +++ b/include/hybridmap/NeutralAtomScheduler.hpp @@ -73,8 +73,9 @@ struct SchedulerResults { class NeutralAtomScheduler { protected: const NeutralAtomArchitecture* arch = nullptr; - std::string animationCsv; - std::string animationArchitectureCsv; + std::string animation; + std::string animationMachine; + std::string animationStyle; public: // Constructor @@ -97,17 +98,28 @@ class NeutralAtomScheduler { bool verbose, bool createAnimationCsv = false, qc::fp shuttlingSpeedFactor = 1.0); - std::string getAnimationCsv() { return animationCsv; } - void saveAnimationCsv(const std::string& filename) const { - // save animation - std::ofstream file(filename); - file << animationCsv; - file.close(); - // save architecture + std::string getAnimationMachine() const { return animationMachine; } + std::string getAnimationViz() const { return animation; } + std::string getAnimationStyle() const { return animationStyle; } + + void saveAnimationFiles(const std::string& filename) const { const auto filenameWithoutExtension = filename.substr(0, filename.find_last_of('.')); - file.open(filenameWithoutExtension + "_architecture.csv"); - file << animationArchitectureCsv; + const auto filenameViz = filenameWithoutExtension + ".naviz"; + const auto filenameMachine = filenameWithoutExtension + ".namachine"; + const auto filenameStyle = filenameWithoutExtension + ".nastyle"; + + // save animation + std::ofstream file = std::ofstream(filenameViz); + file << getAnimationViz(); + file.close(); + // save machine + file.open(filenameMachine); + file << getAnimationMachine(); + file.close(); + // save style + file.open(filenameStyle); + file << getAnimationStyle(); file.close(); } diff --git a/include/hybridmap/default_style.hpp b/include/hybridmap/default_style.hpp new file mode 100644 index 000000000..f9f977c6a --- /dev/null +++ b/include/hybridmap/default_style.hpp @@ -0,0 +1,56 @@ +#pragma once +inline const char* defaultStyle = + "name: \"hybrid\"\n\natom {\n trapped {\n color: " + "#a2ad00\n }\n shuttling {\n color: #005293\n " + "}\n legend {\n name {\n " + "^atom(.*)$: \"$1\"\n ^.*$: \"$0\"\n " + "}\n font {\n family: \"DejaVu " + "Sans\"\n size: 3\n color: " + "#000000\n }\n }\n radius: 2\n}\n\nzone {\n config " + "^zone_cz.*$ {\n color: #e37222\n line " + "{\n thickness: 1\n dash " + "{\n length: 0\n " + " duty: 100%\n }\n }\n name: " + "\"CZ\"\n }\n config ^zone.*$ {\n color: " + "#64a0c8\n line {\n thickness: 1\n " + " dash {\n length: 0\n " + " duty: 100%\n }\n " + "}\n name: \"\"\n }\n legend {\n " + "display: true\n title: \"Zones\"\n }\n}\n\noperation " + "{\n config {\n ry {\n color: " + "#98c6ea\n name: \"RY\"\n radius: " + "150%\n }\n rz {\n color: " + "#64a0c8\n name: \"RZ\"\n radius: " + "150%\n }\n cz {\n color: " + "#e37222\n name: \"CZ\"\n radius: " + "XXX\n }\n }\n legend {\n display: " + "true\n title: \"Gates\"\n }\n}\n\nmachine {\n trap " + "{\n color: #999999\n radius: 2\n " + " line_width: 0.5\n name: \"SLM\"\n }\n shuttle " + "{\n color: #999999\n line {\n " + " thickness: 0.5\n dash {\n " + " length: 5\n duty: " + "50%\n }\n }\n name: " + "\"AOD\"\n }\n legend {\n display: true\n title: " + "\"Atom States\"\n }\n}\n\ncoordinate {\n tick {\n x: " + "20\n y: 20\n color: #999999\n line " + "{\n thickness: 0.5\n dash " + "{\n length: 0\n " + " duty: 100%\n }\n }\n }\n number " + "{\n x {\n distance: 40\n " + " position: bottom\n }\n y {\n " + " distance: 40\n position: left\n " + "}\n display: true\n font {\n " + " family: \"DajaVu Sans\"\n size: 8\n " + " color: #000000\n }\n }\n axis " + "{\n x: \"x\"\n y: \"y\"\n " + "display: true\n font {\n family: " + "\"DajaVu Sans\"\n size: 8\n color: " + "#000000\n }\n }\n margin: 20\n}\n\nsidebar {\n font " + "{\n family: \"DejaVu Sans\"\n size: " + "32\n color: #000000\n }\n margin: 8\n " + "padding {\n color: 16\n heading: 52\n " + " entry: 45\n }\n color_radius: 16\n}\n\ntime {\n display: " + "true\n prefix: \"Time: \"\n precision: 1\n font {\n " + " family: \"DejaVu Sans\"\n size: 12\n color: " + "#000000\n }\n}\n\nviewport {\n margin: 4\n color: #ffffff\n}\n"; diff --git a/src/hybridmap/NeutralAtomScheduler.cpp b/src/hybridmap/NeutralAtomScheduler.cpp index 74f233984..22ca32990 100644 --- a/src/hybridmap/NeutralAtomScheduler.cpp +++ b/src/hybridmap/NeutralAtomScheduler.cpp @@ -43,8 +43,9 @@ na::SchedulerResults na::NeutralAtomScheduler::schedule( AnimationAtoms animationAtoms(initHwPos, *arch); if (createAnimationCsv) { - animationCsv += animationAtoms.getInitString(); - animationArchitectureCsv = arch->getAnimationCsv(); + animation += animationAtoms.getInitString(); + animationMachine = arch->getAnimationMachine(); + animationStyle = arch->getAnimationStyle(); } int index = 0; @@ -152,7 +153,7 @@ na::SchedulerResults na::NeutralAtomScheduler::schedule( // update animation if (createAnimationCsv) { - animationCsv += + animation += animationAtoms.createCsvOp(op, maxTime, maxTime + opTime, *arch); } } @@ -170,7 +171,7 @@ na::SchedulerResults na::NeutralAtomScheduler::schedule( std::exp(-totalIdleTime / arch->getDecoherenceTime()); if (createAnimationCsv) { - animationCsv += animationAtoms.getEndString(maxExecutionTime); + animation += animationAtoms.getEndString(maxExecutionTime); } if (verbose) { printSchedulerResults(totalExecutionTimes, totalIdleTime, diff --git a/test/hybridmap/test_hybridmap.cpp b/test/hybridmap/test_hybridmap.cpp index fbeb26097..f714a0e59 100644 --- a/test/hybridmap/test_hybridmap.cpp +++ b/test/hybridmap/test_hybridmap.cpp @@ -140,7 +140,7 @@ class NeutralAtomMapperTest : public ::testing::Test { mapperParameters.decay = 0; mapperParameters.shuttlingTimeWeight = 0.1; mapperParameters.gateWeight = 1; - mapperParameters.shuttlingWeight = 1; + mapperParameters.shuttlingWeight = 0; mapperParameters.seed = 43; mapperParameters.verbose = true; mapperParameters.numFlyingAncillas = 2; @@ -159,7 +159,21 @@ TEST_F(NeutralAtomMapperTest, Output) { auto qcAodMapped = mapper.convertToAod(); qcAodMapped.dumpOpenQASM(std::cout, false); - const auto scheduleResults = mapper.schedule(false, false); + const auto scheduleResults = mapper.schedule(false, true); + const auto animation = mapper.getAnimationViz(); + // save to file + std::ofstream outFile("circuit.csv"); + if (outFile.is_open()) { + outFile << animation; + outFile.close(); + } + const auto archCsv = arch.getAnimationMachine(); + std::ofstream outFile2("arch.csv"); + if (outFile2.is_open()) { + outFile2 << animation; + outFile2.close(); + } + // const auto scheduleResults = mapper.schedule(true, true); std::cout << scheduleResults.toCsv(); From e0fd1109e58b7c6b0c6e54b2cd7307104c1bb656 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 6 Feb 2025 14:02:48 +0100 Subject: [PATCH 112/394] =?UTF-8?q?=E2=9C=A8=20New=20Animation:=20Added=20?= =?UTF-8?q?Arch=20->=20namachine=20convertion.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/NeutralAtomArchitecture.hpp | 17 ++----- src/hybridmap/NeutralAtomArchitecture.cpp | 46 +++++++++++++++++++ src/hybridmap/NeutralAtomScheduler.cpp | 2 +- 3 files changed, 52 insertions(+), 13 deletions(-) diff --git a/include/hybridmap/NeutralAtomArchitecture.hpp b/include/hybridmap/NeutralAtomArchitecture.hpp index a5c4a64e3..bed5444d0 100644 --- a/include/hybridmap/NeutralAtomArchitecture.hpp +++ b/include/hybridmap/NeutralAtomArchitecture.hpp @@ -545,16 +545,8 @@ class NeutralAtomArchitecture { * @brief Returns a csv string for the animation of the architecture * @return The csv string for the animation of the architecture */ - [[nodiscard]] std::string getAnimationMachine() const { - std::string csv = "x;y;size;color\n"; - for (auto i = 0; i < getNcolumns(); i++) { - for (auto j = 0; j < getNrows(); j++) { - csv += std::to_string(i * getInterQubitDistance()) + ";" + - std::to_string(j * getInterQubitDistance()) + ";1;2\n"; - } - } - return csv; - } + [[nodiscard]] std::string + getAnimationMachine(qc::fp shuttlingSpeedFactor) const; [[nodiscard]] std::string getAnimationStyle() const { std::string style(defaultStyle); @@ -574,9 +566,10 @@ class NeutralAtomArchitecture { * @param filename The name of the csv file */ [[maybe_unused]] void - saveAnimationMachine(const std::string& filename) const { + saveAnimationMachine(const std::string& filename, + const qc::fp shuttlingSpeedFactor) const { std::ofstream file(filename); - file << getAnimationMachine(); + file << getAnimationMachine(shuttlingSpeedFactor); } [[maybe_unused]] void saveAnimationStyle(const std::string& filename) const { diff --git a/src/hybridmap/NeutralAtomArchitecture.cpp b/src/hybridmap/NeutralAtomArchitecture.cpp index 542bfd640..94955d198 100644 --- a/src/hybridmap/NeutralAtomArchitecture.cpp +++ b/src/hybridmap/NeutralAtomArchitecture.cpp @@ -231,6 +231,52 @@ NeutralAtomArchitecture::getNN(const CoordIndex idx) const { } return nn; } +std::string NeutralAtomArchitecture::getAnimationMachine( + const qc::fp shuttlingSpeedFactor) const { + std::string animationMachine = "name: \"Hyrbid_" + this->name + "\"\n"; + + animationMachine += + "movement {\n\tmax_speed: " + + std::to_string(this->getShuttlingTime(qc::OpType::AodMove) * + shuttlingSpeedFactor) + + "\n}\n"; + + animationMachine += + "time {\n\tload: " + + std::to_string(this->getShuttlingTime(qc::OpType::AodActivate) * + shuttlingSpeedFactor) + + "\n\tstore: " + + std::to_string(this->getShuttlingTime(qc::OpType::AodDeactivate) * + shuttlingSpeedFactor) + + "\n\trz: " + std::to_string(this->getGateTime("x")) + + "\n\tcz: " + std::to_string(this->getGateTime("cz")) + + "\n\tunit: \"us\"\n}\n"; + + animationMachine += "distance {\n\tinteraction: " + + std::to_string(this->getInteractionRadius()) + + "\n\tunit: \"um\"\n}\n"; + const auto zoneStart = -this->getInterQubitDistance(); + const auto zoneEndX = this->getNcolumns() * this->getInterQubitDistance() + + this->getInterQubitDistance(); + const auto zoneEndY = this->getNrows() * this->getInterQubitDistance() + + this->getInterQubitDistance(); + animationMachine += "zone hybrid {\n\tfrom: (" + std::to_string(zoneStart) + + ", " + std::to_string(zoneStart) + ")\n\tto: (" + + std::to_string(zoneEndX) + ", " + + std::to_string(zoneEndY) + ") \n}\n"; + + for (size_t colIdx = 0; colIdx < this->getNcolumns(); colIdx++) { + for (size_t rowIdx = 0; rowIdx < this->getNrows(); rowIdx++) { + const auto coordIdx = colIdx + (rowIdx * this->getNcolumns()); + animationMachine += + "trap trap" + std::to_string(coordIdx) + " {\n\tposition: (" + + std::to_string(colIdx * this->getInterQubitDistance()) + ", " + + std::to_string(rowIdx * this->getInterQubitDistance()) + ")\n}\n"; + } + } + + return animationMachine; +} qc::fp NeutralAtomArchitecture::getOpTime(const qc::Operation* op) const { if (op->getType() == qc::OpType::AodActivate || diff --git a/src/hybridmap/NeutralAtomScheduler.cpp b/src/hybridmap/NeutralAtomScheduler.cpp index 22ca32990..f30937620 100644 --- a/src/hybridmap/NeutralAtomScheduler.cpp +++ b/src/hybridmap/NeutralAtomScheduler.cpp @@ -44,7 +44,7 @@ na::SchedulerResults na::NeutralAtomScheduler::schedule( AnimationAtoms animationAtoms(initHwPos, *arch); if (createAnimationCsv) { animation += animationAtoms.getInitString(); - animationMachine = arch->getAnimationMachine(); + animationMachine = arch->getAnimationMachine(shuttlingSpeedFactor); animationStyle = arch->getAnimationStyle(); } From 2253134ad05310f3a961fd5d6031aabf1bc8c9a2 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 6 Feb 2025 14:21:38 +0100 Subject: [PATCH 113/394] =?UTF-8?q?=E2=9C=A8=20New=20Animation:=20Allow=20?= =?UTF-8?q?passing=20of=20custom=20Style.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/NeutralAtomArchitecture.hpp | 22 ++++++++++++++++--- src/hybridmap/NeutralAtomScheduler.cpp | 2 +- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/include/hybridmap/NeutralAtomArchitecture.hpp b/include/hybridmap/NeutralAtomArchitecture.hpp index bed5444d0..76ab93b61 100644 --- a/include/hybridmap/NeutralAtomArchitecture.hpp +++ b/include/hybridmap/NeutralAtomArchitecture.hpp @@ -548,8 +548,22 @@ class NeutralAtomArchitecture { [[nodiscard]] std::string getAnimationMachine(qc::fp shuttlingSpeedFactor) const; - [[nodiscard]] std::string getAnimationStyle() const { + [[nodiscard]] std::string + getAnimationStyle(const std::string& stylePath) const { std::string style(defaultStyle); + if (!stylePath.empty()) { + std::ifstream file(stylePath); + if (file.is_open()) { + std::string line; + while (std::getline(file, line)) { + style += line + "\n"; + } + file.close(); + } else { + std::cerr << "Could not open file " << stylePath + << "! Using default style.\n"; + } + } const std::string toReplace = "XXX"; const std::string replaceWith = std::to_string(getInteractionRadius()); @@ -572,9 +586,11 @@ class NeutralAtomArchitecture { file << getAnimationMachine(shuttlingSpeedFactor); } - [[maybe_unused]] void saveAnimationStyle(const std::string& filename) const { + [[maybe_unused]] void + saveAnimationStyle(const std::string& filename, + const std::string& stylePath = "") const { std::ofstream file(filename); - file << getAnimationStyle(); + file << getAnimationStyle(stylePath); } }; diff --git a/src/hybridmap/NeutralAtomScheduler.cpp b/src/hybridmap/NeutralAtomScheduler.cpp index f30937620..ec6c3bd4f 100644 --- a/src/hybridmap/NeutralAtomScheduler.cpp +++ b/src/hybridmap/NeutralAtomScheduler.cpp @@ -45,7 +45,7 @@ na::SchedulerResults na::NeutralAtomScheduler::schedule( if (createAnimationCsv) { animation += animationAtoms.getInitString(); animationMachine = arch->getAnimationMachine(shuttlingSpeedFactor); - animationStyle = arch->getAnimationStyle(); + animationStyle = arch->getAnimationStyle(""); } int index = 0; From a490fb68af9201a754f371bfca87bff492a047bc Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 6 Feb 2025 20:19:59 +0100 Subject: [PATCH 114/394] =?UTF-8?q?=E2=9C=A8=20New=20Animation:=20Initial?= =?UTF-8?q?=20placement=20of=20the=20atoms?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridAnimation.hpp | 35 +- include/hybridmap/HybridNeutralAtomMapper.hpp | 4 +- include/hybridmap/NeutralAtomScheduler.hpp | 3 +- src/hybridmap/HybridAnimation.cpp | 381 +++++++++--------- src/hybridmap/NeutralAtomScheduler.cpp | 13 +- 5 files changed, 214 insertions(+), 222 deletions(-) diff --git a/include/hybridmap/HybridAnimation.hpp b/include/hybridmap/HybridAnimation.hpp index a6c869907..e170c47bf 100644 --- a/include/hybridmap/HybridAnimation.hpp +++ b/include/hybridmap/HybridAnimation.hpp @@ -18,43 +18,18 @@ namespace na { class AnimationAtoms { - using axesId = std::uint32_t; - using marginId = std::uint32_t; - protected: - uint32_t colorSlm = 0; - uint32_t colorAod = 1; - uint32_t colorLocal = 2; - [[maybe_unused]] uint32_t colorGlobal = 3; - uint32_t colorCz = 4; - std::map coordIdxToId; std::map> idToCoord; - std::map axesIds; - std::map marginIds; - uint32_t axesIdCounter = 0; - uint32_t marginIdCounter = 0; - - axesId addAxis(HwQubit id); - void removeAxis(HwQubit id) { axesIds.erase(id); } - marginId addMargin(HwQubit id); - void removeMargin(HwQubit id) { marginIds.erase(id); } public: - AnimationAtoms(const std::map& initHwPos, + AnimationAtoms(const std::map& initHwPos, + const std::map& initFaPos, const NeutralAtomArchitecture& arch); - std::string getInitString(); - std::string getEndString(qc::fp endTime); - static std::string createCsvLine(qc::fp startTime, HwQubit id, qc::fp x, - qc::fp y, uint32_t size = 1, - uint32_t color = 0, bool axes = false, - axesId axId = 0, bool margin = false, - marginId marginId = 0, - qc::fp marginSize = 0); - std::string createCsvOp(const std::unique_ptr& op, - qc::fp startTime, qc::fp endTime, - const NeutralAtomArchitecture& arch); + std::string placeInitAtoms(); + std::string opToNaViz(const std::unique_ptr& op, + qc::fp startTime); }; } // namespace na diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index 7f94df268..fe84d41e7 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -648,8 +648,8 @@ class NeutralAtomMapper { convertToAod(); } return scheduler.schedule(mappedQcAOD, hardwareQubits.getInitHwPos(), - verboseArg, createAnimationCsv, - shuttlingSpeedFactor); + flyingAncillas.getInitHwPos(), verboseArg, + createAnimationCsv, shuttlingSpeedFactor); } /** diff --git a/include/hybridmap/NeutralAtomScheduler.hpp b/include/hybridmap/NeutralAtomScheduler.hpp index 99fc4f20c..94a48dd3e 100644 --- a/include/hybridmap/NeutralAtomScheduler.hpp +++ b/include/hybridmap/NeutralAtomScheduler.hpp @@ -94,7 +94,8 @@ class NeutralAtomScheduler { * @return SchedulerResults */ SchedulerResults schedule(const qc::QuantumComputation& qc, - const std::map& initHwPos, + const std::map& initHwPos, + const std::map& initFaPos, bool verbose, bool createAnimationCsv = false, qc::fp shuttlingSpeedFactor = 1.0); diff --git a/src/hybridmap/HybridAnimation.cpp b/src/hybridmap/HybridAnimation.cpp index 42f0984c8..825380cb1 100644 --- a/src/hybridmap/HybridAnimation.cpp +++ b/src/hybridmap/HybridAnimation.cpp @@ -21,6 +21,7 @@ namespace na { AnimationAtoms::AnimationAtoms(const std::map& initHwPos, + const std::map& initFaPos, const NeutralAtomArchitecture& arch) { const auto nCols = arch.getNcolumns(); @@ -31,195 +32,213 @@ AnimationAtoms::AnimationAtoms(const std::map& initHwPos, idToCoord[id] = {column * arch.getInterQubitDistance(), row * arch.getInterQubitDistance()}; } -} -std::string AnimationAtoms::getInitString() { - std::string initString; - initString += - "time;id;x;y;size;fill;color;axes;axesId;margin;marginId;marginSize\n"; - for (const auto& [id, coord] : idToCoord) { - initString += "0.000;" + std::to_string(id) + ";" + - std::to_string(coord.first) + ";" + - std::to_string(coord.second) + ";1;0;0;0;0;0;0;0\n"; + for (const auto& [id, coord] : initFaPos) { + coordIdxToId[coord + arch.getNpositions()] = id + initHwPos.size(); + const auto column = coord % nCols; + const auto row = coord / nCols; + const auto offset = + arch.getInterQubitDistance() / arch.getNAodIntermediateLevels(); + idToCoord[id + initHwPos.size()] = { + (column * arch.getInterQubitDistance()) + offset, + (row * arch.getInterQubitDistance()) + offset}; } - return initString; } -std::string AnimationAtoms::getEndString(const qc::fp endTime) { +std::string AnimationAtoms::placeInitAtoms() { std::string initString; - for (const auto& [id, coord] : idToCoord) { - initString += std::to_string(endTime) + ";" + std::to_string(id) + ";" + - std::to_string(coord.first) + ";" + - std::to_string(coord.second) + ";1;0;0;0;0;0;0;0\n"; + for (const auto& [id, coords] : idToCoord) { + initString += "atom (" + std::to_string(coords.first) + ", " + + std::to_string(coords.second) + ") atom" + + std::to_string(id) + "\n"; } return initString; } -AnimationAtoms::axesId AnimationAtoms::addAxis(const HwQubit id) { - if (axesIds.find(id) == axesIds.end()) { - axesIdCounter++; - axesIds[id] = axesIdCounter; - } else { - throw std::invalid_argument( - "Tried to add axis but axis already exists for qubit " + - std::to_string(id)); - } - return axesIds[id]; -} -AnimationAtoms::marginId AnimationAtoms::addMargin(const HwQubit id) { - if (marginIds.find(id) == marginIds.end()) { - marginIdCounter++; - marginIds[id] = marginIdCounter; - } else { - throw std::invalid_argument( - "Tried to add margin but margin already exists for qubit " + - std::to_string(id)); - } - return marginIds[id]; -} - -std::string -AnimationAtoms::createCsvOp(const std::unique_ptr& op, - qc::fp startTime, qc::fp endTime, - const NeutralAtomArchitecture& arch) { - std::string csvLine; - - for (const auto& coordIdx : op->getUsedQubits()) { - // if coordIdx unmapped -> continue except it is an AodDeactivate - if (qc::OpType::AodDeactivate != op->getType() && - coordIdxToId.find(coordIdx) == coordIdxToId.end()) { - continue; - } - if (op->getType() == qc::OpType::AodDeactivate) { - // check if there is a qubit at coordIdx - // if yes -> update coordIdxToId with new coordIdx - // if not -> throw exception - for (const auto& idAndCoord : idToCoord) { - auto id = idAndCoord.first; - auto coord = idAndCoord.second; - auto col = coordIdx % arch.getNcolumns(); - auto row = coordIdx / arch.getNcolumns(); - if (std::abs(coord.first - (col * arch.getInterQubitDistance())) < - 0.0001 && - std::abs(coord.second - (row * arch.getInterQubitDistance())) < - 0.0001) { - // remove old coordIdx with same id - for (const auto& [oldCoordIdx, oldId] : coordIdxToId) { - if (oldId == id) { - coordIdxToId.erase(oldCoordIdx); - break; - } - } - // add new coordIdx with id - coordIdxToId[coordIdx] = id; - break; - } - } - } - if (coordIdxToId.find(coordIdx) == coordIdxToId.end() || - idToCoord.find(coordIdxToId.at(coordIdx)) == idToCoord.end()) { - throw std::invalid_argument( - "Tried to create csv line for qubit at coordIdx " + - std::to_string(coordIdx) + " but there is no qubit at this coordIdx"); - } - auto id = coordIdxToId.at(coordIdx); - auto coord = idToCoord.at(id); - if (op->getType() == qc::OpType::AodActivate) { - addAxis(id); - if (axesIds.find(id) == axesIds.end()) { - throw std::invalid_argument( - "Tried to activate qubit at coordIdx " + std::to_string(coordIdx) + - " but there is no axis for qubit " + std::to_string(id)); - } - csvLine += createCsvLine(startTime, id, coord.first, coord.second, 1, - colorSlm, true, axesIds.at(id)); - csvLine += createCsvLine(endTime, id, coord.first, coord.second, 1, - colorAod, true, axesIds.at(id)); - } else if (op->getType() == qc::OpType::AodDeactivate) { - if (axesIds.find(id) == axesIds.end()) { - throw std::invalid_argument("Tried to deactivate qubit at coordIdx " + - std::to_string(coordIdx) + - " but there is no axis for qubit " + - std::to_string(id)); - } - csvLine += createCsvLine(startTime, id, coord.first, coord.second, 1, - colorAod, true, axesIds.at(id)); - csvLine += createCsvLine(endTime, id, coord.first, coord.second, 1, - colorSlm, true, axesIds.at(id)); - removeAxis(id); - - } else if (op->getType() == qc::OpType::AodMove) { - if (axesIds.find(id) == axesIds.end()) { - throw std::invalid_argument( - "Tried to move qubit at coordIdx " + std::to_string(coordIdx) + - " but there is no axis for qubit " + std::to_string(id)); - } - csvLine += createCsvLine(startTime, id, coord.first, coord.second, 1, - colorAod, true, axesIds.at(id)); - - // update atom coordinates - auto startsX = - dynamic_cast(op.get())->getStarts(Dimension::X); - auto endsX = dynamic_cast(op.get())->getEnds(Dimension::X); - auto startsY = - dynamic_cast(op.get())->getStarts(Dimension::Y); - auto endsY = dynamic_cast(op.get())->getEnds(Dimension::Y); - for (size_t i = 0; i < startsX.size(); i++) { - if (std::abs(startsX[i] - coord.first) < 0.0001) { - coord.first = endsX[i]; - } - } - for (size_t i = 0; i < startsY.size(); i++) { - if (std::abs(startsY[i] - coord.second) < 0.0001) { - coord.second = endsY[i]; - } - } - // save new coordinates - idToCoord[id] = coord; - csvLine += createCsvLine(endTime, id, coord.first, coord.second, 1, - colorAod, true, axesIds.at(id)); - } else if (op->getUsedQubits().size() > 1) { // multi qubit gates - addMargin(id); - csvLine += createCsvLine(startTime, id, coord.first, coord.second, 1, - colorSlm, false, 0, false, marginIds.at(id)); - auto midTime = (startTime + endTime) / 2; - csvLine += - createCsvLine(midTime, id, coord.first, coord.second, 1, colorCz, - false, 0, true, marginIds.at(id), - arch.getBlockingFactor() * arch.getInteractionRadius() * - arch.getInterQubitDistance()); - csvLine += createCsvLine(endTime, id, coord.first, coord.second, 1, - colorSlm, false, 0, false, marginIds.at(id)); - removeMargin(id); - - } else { // single qubit gates - csvLine += - createCsvLine(startTime, id, coord.first, coord.second, 1, colorSlm); - auto midTime = (startTime + endTime) / 2; - csvLine += - createCsvLine(midTime, id, coord.first, coord.second, 1, colorLocal); - csvLine += - createCsvLine(endTime, id, coord.first, coord.second, 1, colorSlm); - } - } - return csvLine; -} -std::string AnimationAtoms::createCsvLine(const qc::fp startTime, - const HwQubit id, const qc::fp x, - const qc::fp y, const uint32_t size, - const uint32_t color, const bool axes, - const axesId axId, const bool margin, - const marginId marginId, - const qc::fp marginSize) { - std::string csvLine; - csvLine += - std::to_string(startTime) + ";" + std::to_string(id) + ";" + - std::to_string(x) + ";" + std::to_string(y) + ";" + std::to_string(size) + - ";" + std::to_string(color) + ";" + std::to_string(color) + ";" + - std::to_string(static_cast(axes)) + ";" + std::to_string(axId) + - ";" + std::to_string(static_cast(margin)) + ";" + - std::to_string(marginId) + ";" + std::to_string(marginSize) + "\n"; - return csvLine; -} +// std::string AnimationAtoms::getEndString(const qc::fp endTime) { +// std::string initString; +// for (const auto& [id, coord] : idToCoord) { +// initString += std::to_string(endTime) + ";" + std::to_string(id) + ";" + +// std::to_string(coord.first) + ";" + +// std::to_string(coord.second) + ";1;0;0;0;0;0;0;0\n"; +// } +// return initString; +// } +// +// AnimationAtoms::axesId AnimationAtoms::addAxis(const HwQubit id) { +// if (axesIds.find(id) == axesIds.end()) { +// axesIdCounter++; +// axesIds[id] = axesIdCounter; +// } else { +// throw std::invalid_argument( +// "Tried to add axis but axis already exists for qubit " + +// std::to_string(id)); +// } +// return axesIds[id]; +// } +// AnimationAtoms::marginId AnimationAtoms::addMargin(const HwQubit id) { +// if (marginIds.find(id) == marginIds.end()) { +// marginIdCounter++; +// marginIds[id] = marginIdCounter; +// } else { +// throw std::invalid_argument( +// "Tried to add margin but margin already exists for qubit " + +// std::to_string(id)); +// } +// return marginIds[id]; +// } +// +// std::string +// AnimationAtoms::createCsvOp(const std::unique_ptr& op, +// qc::fp startTime, qc::fp endTime, +// const NeutralAtomArchitecture& arch) { +// std::string csvLine; +// +// for (const auto& coordIdx : op->getUsedQubits()) { +// // if coordIdx unmapped -> continue except it is an AodDeactivate +// if (qc::OpType::AodDeactivate != op->getType() && +// coordIdxToId.find(coordIdx) == coordIdxToId.end()) { +// continue; +// } +// if (op->getType() == qc::OpType::AodDeactivate) { +// // check if there is a qubit at coordIdx +// // if yes -> update coordIdxToId with new coordIdx +// // if not -> throw exception +// for (const auto& idAndCoord : idToCoord) { +// auto id = idAndCoord.first; +// auto coord = idAndCoord.second; +// auto col = coordIdx % arch.getNcolumns(); +// auto row = coordIdx / arch.getNcolumns(); +// if (std::abs(coord.first - (col * arch.getInterQubitDistance())) < +// 0.0001 && +// std::abs(coord.second - (row * arch.getInterQubitDistance())) < +// 0.0001) { +// // remove old coordIdx with same id +// for (const auto& [oldCoordIdx, oldId] : coordIdxToId) { +// if (oldId == id) { +// coordIdxToId.erase(oldCoordIdx); +// break; +// } +// } +// // add new coordIdx with id +// coordIdxToId[coordIdx] = id; +// break; +// } +// } +// } +// if (coordIdxToId.find(coordIdx) == coordIdxToId.end() || +// idToCoord.find(coordIdxToId.at(coordIdx)) == idToCoord.end()) { +// throw std::invalid_argument( +// "Tried to create csv line for qubit at coordIdx " + +// std::to_string(coordIdx) + " but there is no qubit at this +// coordIdx"); +// } +// auto id = coordIdxToId.at(coordIdx); +// auto coord = idToCoord.at(id); +// if (op->getType() == qc::OpType::AodActivate) { +// addAxis(id); +// if (axesIds.find(id) == axesIds.end()) { +// throw std::invalid_argument( +// "Tried to activate qubit at coordIdx " + std::to_string(coordIdx) +// + " but there is no axis for qubit " + std::to_string(id)); +// } +// csvLine += createCsvLine(startTime, id, coord.first, coord.second, 1, +// colorSlm, true, axesIds.at(id)); +// csvLine += createCsvLine(endTime, id, coord.first, coord.second, 1, +// colorAod, true, axesIds.at(id)); +// } else if (op->getType() == qc::OpType::AodDeactivate) { +// if (axesIds.find(id) == axesIds.end()) { +// throw std::invalid_argument("Tried to deactivate qubit at coordIdx " +// + +// std::to_string(coordIdx) + +// " but there is no axis for qubit " + +// std::to_string(id)); +// } +// csvLine += createCsvLine(startTime, id, coord.first, coord.second, 1, +// colorAod, true, axesIds.at(id)); +// csvLine += createCsvLine(endTime, id, coord.first, coord.second, 1, +// colorSlm, true, axesIds.at(id)); +// removeAxis(id); +// +// } else if (op->getType() == qc::OpType::AodMove) { +// if (axesIds.find(id) == axesIds.end()) { +// throw std::invalid_argument( +// "Tried to move qubit at coordIdx " + std::to_string(coordIdx) + +// " but there is no axis for qubit " + std::to_string(id)); +// } +// csvLine += createCsvLine(startTime, id, coord.first, coord.second, 1, +// colorAod, true, axesIds.at(id)); +// +// // update atom coordinates +// auto startsX = +// dynamic_cast(op.get())->getStarts(Dimension::X); +// auto endsX = +// dynamic_cast(op.get())->getEnds(Dimension::X); auto +// startsY = +// dynamic_cast(op.get())->getStarts(Dimension::Y); +// auto endsY = +// dynamic_cast(op.get())->getEnds(Dimension::Y); for +// (size_t i = 0; i < startsX.size(); i++) { +// if (std::abs(startsX[i] - coord.first) < 0.0001) { +// coord.first = endsX[i]; +// } +// } +// for (size_t i = 0; i < startsY.size(); i++) { +// if (std::abs(startsY[i] - coord.second) < 0.0001) { +// coord.second = endsY[i]; +// } +// } +// // save new coordinates +// idToCoord[id] = coord; +// csvLine += createCsvLine(endTime, id, coord.first, coord.second, 1, +// colorAod, true, axesIds.at(id)); +// } else if (op->getUsedQubits().size() > 1) { // multi qubit gates +// addMargin(id); +// csvLine += createCsvLine(startTime, id, coord.first, coord.second, 1, +// colorSlm, false, 0, false, marginIds.at(id)); +// auto midTime = (startTime + endTime) / 2; +// csvLine += +// createCsvLine(midTime, id, coord.first, coord.second, 1, colorCz, +// false, 0, true, marginIds.at(id), +// arch.getBlockingFactor() * +// arch.getInteractionRadius() * +// arch.getInterQubitDistance()); +// csvLine += createCsvLine(endTime, id, coord.first, coord.second, 1, +// colorSlm, false, 0, false, marginIds.at(id)); +// removeMargin(id); +// +// } else { // single qubit gates +// csvLine += +// createCsvLine(startTime, id, coord.first, coord.second, 1, +// colorSlm); +// auto midTime = (startTime + endTime) / 2; +// csvLine += +// createCsvLine(midTime, id, coord.first, coord.second, 1, +// colorLocal); +// csvLine += +// createCsvLine(endTime, id, coord.first, coord.second, 1, colorSlm); +// } +// } +// return csvLine; +// } +// std::string AnimationAtoms::createCsvLine(const qc::fp startTime, +// const HwQubit id, const qc::fp x, +// const qc::fp y, const uint32_t +// size, const uint32_t color, const +// bool axes, const axesId axId, const +// bool margin, const marginId +// marginId, const qc::fp marginSize) +// { +// std::string csvLine; +// csvLine += +// std::to_string(startTime) + ";" + std::to_string(id) + ";" + +// std::to_string(x) + ";" + std::to_string(y) + ";" + +// std::to_string(size) + +// ";" + std::to_string(color) + ";" + std::to_string(color) + ";" + +// std::to_string(static_cast(axes)) + ";" + std::to_string(axId) + +// ";" + std::to_string(static_cast(margin)) + ";" + +// std::to_string(marginId) + ";" + std::to_string(marginSize) + "\n"; +// return csvLine; +// } } // namespace na diff --git a/src/hybridmap/NeutralAtomScheduler.cpp b/src/hybridmap/NeutralAtomScheduler.cpp index ec6c3bd4f..269885f0e 100644 --- a/src/hybridmap/NeutralAtomScheduler.cpp +++ b/src/hybridmap/NeutralAtomScheduler.cpp @@ -27,7 +27,8 @@ na::SchedulerResults na::NeutralAtomScheduler::schedule( const qc::QuantumComputation& qc, - const std::map& initHwPos, const bool verbose, + const std::map& initHwPos, + const std::map& initFaPos, const bool verbose, const bool createAnimationCsv, const qc::fp shuttlingSpeedFactor) { if (verbose) { std::cout << "\n* schedule start!\n"; @@ -41,9 +42,9 @@ na::SchedulerResults na::NeutralAtomScheduler::schedule( qc::fp totalGateTime = 0; qc::fp totalGateFidelities = 1; - AnimationAtoms animationAtoms(initHwPos, *arch); + AnimationAtoms animationAtoms(initHwPos, initFaPos, *arch); if (createAnimationCsv) { - animation += animationAtoms.getInitString(); + animation += animationAtoms.placeInitAtoms(); animationMachine = arch->getAnimationMachine(shuttlingSpeedFactor); animationStyle = arch->getAnimationStyle(""); } @@ -153,8 +154,7 @@ na::SchedulerResults na::NeutralAtomScheduler::schedule( // update animation if (createAnimationCsv) { - animation += - animationAtoms.createCsvOp(op, maxTime, maxTime + opTime, *arch); + animation += ""; } } if (verbose) { @@ -170,9 +170,6 @@ na::SchedulerResults na::NeutralAtomScheduler::schedule( totalGateFidelities * std::exp(-totalIdleTime / arch->getDecoherenceTime()); - if (createAnimationCsv) { - animation += animationAtoms.getEndString(maxExecutionTime); - } if (verbose) { printSchedulerResults(totalExecutionTimes, totalIdleTime, totalGateFidelities, totalFidelities, nCZs); From 621ed28331c66dbcad6947d45d67a93f18892c9b Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 6 Feb 2025 21:51:35 +0100 Subject: [PATCH 115/394] =?UTF-8?q?=E2=9C=A8=20New=20Animation:=20Create?= =?UTF-8?q?=20Move=20animations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridAnimation.hpp | 9 ++- src/hybridmap/HybridAnimation.cpp | 83 ++++++++++++++++++++++---- src/hybridmap/MoveToAodConverter.cpp | 18 +++--- src/hybridmap/NeutralAtomScheduler.cpp | 2 +- test/hybridmap/test_hybridmap.cpp | 16 +---- 5 files changed, 89 insertions(+), 39 deletions(-) diff --git a/include/hybridmap/HybridAnimation.hpp b/include/hybridmap/HybridAnimation.hpp index e170c47bf..bee617aec 100644 --- a/include/hybridmap/HybridAnimation.hpp +++ b/include/hybridmap/HybridAnimation.hpp @@ -21,11 +21,18 @@ class AnimationAtoms { protected: std::map coordIdxToId; std::map> idToCoord; + const NeutralAtomArchitecture& arch; + + void initPositions(const std::map& initHwPos, + const std::map& initFaPos); public: AnimationAtoms(const std::map& initHwPos, const std::map& initFaPos, - const NeutralAtomArchitecture& arch); + const NeutralAtomArchitecture& arch) + : arch(arch) { + initPositions(initHwPos, initFaPos); + } std::string placeInitAtoms(); std::string opToNaViz(const std::unique_ptr& op, diff --git a/src/hybridmap/HybridAnimation.cpp b/src/hybridmap/HybridAnimation.cpp index 825380cb1..f3860c145 100644 --- a/src/hybridmap/HybridAnimation.cpp +++ b/src/hybridmap/HybridAnimation.cpp @@ -20,11 +20,10 @@ #include namespace na { -AnimationAtoms::AnimationAtoms(const std::map& initHwPos, - const std::map& initFaPos, - const NeutralAtomArchitecture& arch) { +void AnimationAtoms::initPositions( + const std::map& initHwPos, + const std::map& initFaPos) { const auto nCols = arch.getNcolumns(); - for (const auto& [id, coord] : initHwPos) { coordIdxToId[coord] = id; const auto column = coord % nCols; @@ -54,11 +53,63 @@ std::string AnimationAtoms::placeInitAtoms() { } return initString; } +std::string AnimationAtoms::opToNaViz(const std::unique_ptr& op, + qc::fp startTime) { + std::string opString = ""; + + if (op->getType() == qc::OpType::AodActivate) { + int a = 0; + } else if (op->getType() == qc::OpType::AodMove) { + // update atom coordinates + const auto startsX = + dynamic_cast(op.get())->getStarts(Dimension::X); + const auto endsX = + dynamic_cast(op.get())->getEnds(Dimension::X); + const auto startsY = + dynamic_cast(op.get())->getStarts(Dimension::Y); + const auto endsY = + dynamic_cast(op.get())->getEnds(Dimension::Y); + const auto CoordIndices = op->getTargets(); + // use that coord indices are pairs of origin and target indices + for (size_t i = 0; i < CoordIndices.size(); i++) { + if (i % 2 == 0) { + const auto coordIdx = CoordIndices[i]; + const auto id = coordIdxToId.at(coordIdx); + auto newX = 0.0; + auto newY = 0.0; + for (size_t i = 0; i < startsX.size(); i++) { + if (std::abs(startsX[i] - idToCoord.at(id).first) < 0.0001) { + newX = endsX[i]; + break; + } + } + for (size_t i = 0; i < startsY.size(); i++) { + if (std::abs(startsY[i] - idToCoord.at(id).second) < 0.0001) { + newY = endsY[i]; + break; + } + } + opString += "@" + std::to_string(startTime) + " move (" + + std::to_string(newX) + ", " + std::to_string(newY) + + ") atom" + std::to_string(coordIdx) + "\n"; + idToCoord.at(id) = {newX, newY}; + } else { + // this is the target index -> update coordIdxToId + const auto coordIdx = CoordIndices[i]; + const auto id = coordIdxToId.at(CoordIndices[i - 1]); + coordIdxToId.erase(CoordIndices[i - 1]); + coordIdxToId[coordIdx] = id; + } + } + } + return opString; +} // std::string AnimationAtoms::getEndString(const qc::fp endTime) { // std::string initString; // for (const auto& [id, coord] : idToCoord) { -// initString += std::to_string(endTime) + ";" + std::to_string(id) + ";" + +// initString += std::to_string(endTime) + ";" + std::to_string(id) + ";" +// + // std::to_string(coord.first) + ";" + // std::to_string(coord.second) + ";1;0;0;0;0;0;0;0\n"; // } @@ -139,7 +190,8 @@ std::string AnimationAtoms::placeInitAtoms() { // addAxis(id); // if (axesIds.find(id) == axesIds.end()) { // throw std::invalid_argument( -// "Tried to activate qubit at coordIdx " + std::to_string(coordIdx) +// "Tried to activate qubit at coordIdx " + +// std::to_string(coordIdx) // + " but there is no axis for qubit " + std::to_string(id)); // } // csvLine += createCsvLine(startTime, id, coord.first, coord.second, 1, @@ -148,7 +200,8 @@ std::string AnimationAtoms::placeInitAtoms() { // colorAod, true, axesIds.at(id)); // } else if (op->getType() == qc::OpType::AodDeactivate) { // if (axesIds.find(id) == axesIds.end()) { -// throw std::invalid_argument("Tried to deactivate qubit at coordIdx " +// throw std::invalid_argument("Tried to deactivate qubit at coordIdx +// " // + // std::to_string(coordIdx) + // " but there is no axis for qubit " + @@ -195,7 +248,8 @@ std::string AnimationAtoms::placeInitAtoms() { // } else if (op->getUsedQubits().size() > 1) { // multi qubit gates // addMargin(id); // csvLine += createCsvLine(startTime, id, coord.first, coord.second, 1, -// colorSlm, false, 0, false, marginIds.at(id)); +// colorSlm, false, 0, false, +// marginIds.at(id)); // auto midTime = (startTime + endTime) / 2; // csvLine += // createCsvLine(midTime, id, coord.first, coord.second, 1, colorCz, @@ -204,7 +258,8 @@ std::string AnimationAtoms::placeInitAtoms() { // arch.getInteractionRadius() * // arch.getInterQubitDistance()); // csvLine += createCsvLine(endTime, id, coord.first, coord.second, 1, -// colorSlm, false, 0, false, marginIds.at(id)); +// colorSlm, false, 0, false, +// marginIds.at(id)); // removeMargin(id); // // } else { // single qubit gates @@ -216,7 +271,8 @@ std::string AnimationAtoms::placeInitAtoms() { // createCsvLine(midTime, id, coord.first, coord.second, 1, // colorLocal); // csvLine += -// createCsvLine(endTime, id, coord.first, coord.second, 1, colorSlm); +// createCsvLine(endTime, id, coord.first, coord.second, 1, +// colorSlm); // } // } // return csvLine; @@ -225,9 +281,10 @@ std::string AnimationAtoms::placeInitAtoms() { // const HwQubit id, const qc::fp x, // const qc::fp y, const uint32_t // size, const uint32_t color, const -// bool axes, const axesId axId, const -// bool margin, const marginId -// marginId, const qc::fp marginSize) +// bool axes, const axesId axId, +// const bool margin, const marginId +// marginId, const qc::fp +// marginSize) // { // std::string csvLine; // csvLine += diff --git a/src/hybridmap/MoveToAodConverter.cpp b/src/hybridmap/MoveToAodConverter.cpp index afce3f00a..4b445c1dd 100644 --- a/src/hybridmap/MoveToAodConverter.cpp +++ b/src/hybridmap/MoveToAodConverter.cpp @@ -397,7 +397,7 @@ AodOperation MoveToAodConverter::MoveGroup::connectAodOperations( // and connect with an aod move operations // all can be done in parallel in a single move std::vector aodOperations; - std::set targetQubits; + std::vector targetQubits; auto d = aodActivationHelper.arch->getInterQubitDistance(); auto interD = aodActivationHelper.arch->getInterQubitDistance() / @@ -412,16 +412,16 @@ AodOperation MoveToAodConverter::MoveGroup::connectAodOperations( // get target qubits for (const auto& move : activation.moves) { if (move.load1) { - targetQubits.insert(move.c1); + targetQubits.emplace_back(move.c1); } else { - targetQubits.insert(move.c1 + - aodActivationHelper.arch->getNpositions()); + targetQubits.emplace_back( + move.c1 + aodActivationHelper.arch->getNpositions()); } if (move.load2) { - targetQubits.insert(move.c2); + targetQubits.emplace_back(move.c2); } else { - targetQubits.insert(move.c2 + - aodActivationHelper.arch->getNpositions()); + targetQubits.emplace_back( + move.c2 + aodActivationHelper.arch->getNpositions()); } } @@ -442,9 +442,7 @@ AodOperation MoveToAodConverter::MoveGroup::connectAodOperations( } } - std::vector targetQubitsVec = {targetQubits.begin(), - targetQubits.end()}; - return {qc::OpType::AodMove, targetQubitsVec, aodOperations}; + return {qc::OpType::AodMove, targetQubits, aodOperations}; } std::vector> diff --git a/src/hybridmap/NeutralAtomScheduler.cpp b/src/hybridmap/NeutralAtomScheduler.cpp index 269885f0e..cb54546cc 100644 --- a/src/hybridmap/NeutralAtomScheduler.cpp +++ b/src/hybridmap/NeutralAtomScheduler.cpp @@ -154,7 +154,7 @@ na::SchedulerResults na::NeutralAtomScheduler::schedule( // update animation if (createAnimationCsv) { - animation += ""; + animation += animationAtoms.opToNaViz(op, maxTime); } } if (verbose) { diff --git a/test/hybridmap/test_hybridmap.cpp b/test/hybridmap/test_hybridmap.cpp index f714a0e59..f6f2d0ae8 100644 --- a/test/hybridmap/test_hybridmap.cpp +++ b/test/hybridmap/test_hybridmap.cpp @@ -140,7 +140,7 @@ class NeutralAtomMapperTest : public ::testing::Test { mapperParameters.decay = 0; mapperParameters.shuttlingTimeWeight = 0.1; mapperParameters.gateWeight = 1; - mapperParameters.shuttlingWeight = 0; + mapperParameters.shuttlingWeight = 1; mapperParameters.seed = 43; mapperParameters.verbose = true; mapperParameters.numFlyingAncillas = 2; @@ -160,19 +160,7 @@ TEST_F(NeutralAtomMapperTest, Output) { qcAodMapped.dumpOpenQASM(std::cout, false); const auto scheduleResults = mapper.schedule(false, true); - const auto animation = mapper.getAnimationViz(); - // save to file - std::ofstream outFile("circuit.csv"); - if (outFile.is_open()) { - outFile << animation; - outFile.close(); - } - const auto archCsv = arch.getAnimationMachine(); - std::ofstream outFile2("arch.csv"); - if (outFile2.is_open()) { - outFile2 << animation; - outFile2.close(); - } + mapper.saveAnimationFiles("test"); // const auto scheduleResults = mapper.schedule(true, true); std::cout << scheduleResults.toCsv(); From 0b6bc95d85f83869225ee8f32a3801e02fc80ff3 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 7 Feb 2025 13:01:09 +0100 Subject: [PATCH 116/394] =?UTF-8?q?=E2=9C=A8=20New=20Animation:=20Fixed=20?= =?UTF-8?q?move=20creation.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridAnimation.cpp | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/src/hybridmap/HybridAnimation.cpp b/src/hybridmap/HybridAnimation.cpp index f3860c145..8b0ea6210 100644 --- a/src/hybridmap/HybridAnimation.cpp +++ b/src/hybridmap/HybridAnimation.cpp @@ -75,23 +75,36 @@ std::string AnimationAtoms::opToNaViz(const std::unique_ptr& op, if (i % 2 == 0) { const auto coordIdx = CoordIndices[i]; const auto id = coordIdxToId.at(coordIdx); - auto newX = 0.0; - auto newY = 0.0; - for (size_t i = 0; i < startsX.size(); i++) { - if (std::abs(startsX[i] - idToCoord.at(id).first) < 0.0001) { - newX = endsX[i]; + bool foundX = false; + auto newX = std::numeric_limits::max(); + bool foundY = false; + auto newY = std::numeric_limits::max(); + for (size_t j = 0; j < startsX.size(); j++) { + if (std::abs(startsX[j] - idToCoord.at(id).first) < 0.0001) { + newX = endsX[j]; + foundX = true; break; } } - for (size_t i = 0; i < startsY.size(); i++) { - if (std::abs(startsY[i] - idToCoord.at(id).second) < 0.0001) { - newY = endsY[i]; + if (!foundX) { + // X coord is the same as before + newX = idToCoord.at(id).first; + } + + for (size_t j = 0; j < startsY.size(); j++) { + if (std::abs(startsY[j] - idToCoord.at(id).second) < 0.0001) { + newY = endsY[j]; + foundY = true; break; } } + if (!foundY) { + // Y coord is the same as before + newY = idToCoord.at(id).second; + } opString += "@" + std::to_string(startTime) + " move (" + std::to_string(newX) + ", " + std::to_string(newY) + - ") atom" + std::to_string(coordIdx) + "\n"; + ") atom" + std::to_string(id) + "\n"; idToCoord.at(id) = {newX, newY}; } else { // this is the target index -> update coordIdxToId From c4918013aaa66dd80090b1fdc49a09b5bd85a4ee Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 7 Feb 2025 13:06:41 +0100 Subject: [PATCH 117/394] =?UTF-8?q?=E2=9C=A8=20New=20Animation:=20Added=20?= =?UTF-8?q?loading=20and=20storing.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridAnimation.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/hybridmap/HybridAnimation.cpp b/src/hybridmap/HybridAnimation.cpp index 8b0ea6210..5a22858a8 100644 --- a/src/hybridmap/HybridAnimation.cpp +++ b/src/hybridmap/HybridAnimation.cpp @@ -55,10 +55,21 @@ std::string AnimationAtoms::placeInitAtoms() { } std::string AnimationAtoms::opToNaViz(const std::unique_ptr& op, qc::fp startTime) { - std::string opString = ""; + std::string opString; if (op->getType() == qc::OpType::AodActivate) { - int a = 0; + opString += "@" + std::to_string(startTime) + " load [\n"; + for (const auto& coordIdx : op->getTargets()) { + const auto id = coordIdxToId.at(coordIdx); + opString += "\t atom" + std::to_string(id) + "\n"; + } + opString += "]\n"; + } else if (op->getType() == qc::OpType::AodDeactivate) { + opString += "@" + std::to_string(startTime) + " store [\n"; + for (const auto& coordIdx : op->getTargets()) { + const auto id = coordIdxToId.at(coordIdx); + opString += "\t atom" + std::to_string(id) + "\n"; + } } else if (op->getType() == qc::OpType::AodMove) { // update atom coordinates const auto startsX = From 369605da039a3a40e618c56d2fafd2cb3879a21a Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 7 Feb 2025 13:10:53 +0100 Subject: [PATCH 118/394] =?UTF-8?q?=E2=9C=A8=20New=20Animation:=20Added=20?= =?UTF-8?q?gates?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridAnimation.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/hybridmap/HybridAnimation.cpp b/src/hybridmap/HybridAnimation.cpp index 5a22858a8..b3ec7dd73 100644 --- a/src/hybridmap/HybridAnimation.cpp +++ b/src/hybridmap/HybridAnimation.cpp @@ -125,7 +125,21 @@ std::string AnimationAtoms::opToNaViz(const std::unique_ptr& op, coordIdxToId[coordIdx] = id; } } + // must be a gate + } else if (op->getNqubits() > 1) { + for (const auto& coordIdx : op->getUsedQubits()) { + const auto id = coordIdxToId.at(coordIdx); + opString += "@" + std::to_string(startTime) + " cz 1" + " atom" + + std::to_string(id) + "\n"; + } + } else { + // single qubit gate + const auto coordIdx = op->getTargets().front(); + const auto id = coordIdxToId.at(coordIdx); + opString += "@" + std::to_string(startTime) + " rz 1" + " atom" + + std::to_string(id) + "\n"; } + return opString; } From b0c689c3c54f97923e5ca4c2d58ae01b03458eca Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 7 Feb 2025 13:52:59 +0100 Subject: [PATCH 119/394] =?UTF-8?q?=F0=9F=90=9B=20Fixed=20wrong=20coordInd?= =?UTF-8?q?ices=20in=20flying=20ancilla=20operations.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridNeutralAtomMapper.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 956627863..64a5c9de0 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -563,7 +563,7 @@ void NeutralAtomMapper::applyFlyingAncilla(NeutralAtomLayer& frontLayer, if (usedQubits.find(passBy.q1) != usedQubits.end()) { usedQubits.erase(passBy.q1); - usedQubits.insert(ancQ1); + usedQubits.insert(ancQ2); } if (this->parameters->verbose) { @@ -583,7 +583,7 @@ void NeutralAtomMapper::applyFlyingAncilla(NeutralAtomLayer& frontLayer, const auto ancQ2 = passBy.q2 + nPos; mappedQc.move(ancQ2, ancQ1); mappedQc.h(ancQ1); - mappedQc.cz(passBy.q2, ancQ1); + mappedQc.cz(passBy.q1, ancQ1); mappedQc.h(ancQ1); // update position of flying ancillas From 58db1702ef0aa1ebacd33dd863bbcf98f9ab362a Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 7 Feb 2025 14:35:56 +0100 Subject: [PATCH 120/394] =?UTF-8?q?=F0=9F=90=9B=20Minor=20animation=20bugs?= =?UTF-8?q?.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridAnimation.cpp | 2 +- src/hybridmap/NeutralAtomArchitecture.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/hybridmap/HybridAnimation.cpp b/src/hybridmap/HybridAnimation.cpp index b3ec7dd73..067a13a3c 100644 --- a/src/hybridmap/HybridAnimation.cpp +++ b/src/hybridmap/HybridAnimation.cpp @@ -129,7 +129,7 @@ std::string AnimationAtoms::opToNaViz(const std::unique_ptr& op, } else if (op->getNqubits() > 1) { for (const auto& coordIdx : op->getUsedQubits()) { const auto id = coordIdxToId.at(coordIdx); - opString += "@" + std::to_string(startTime) + " cz 1" + " atom" + + opString += "@" + std::to_string(startTime) + " cz atom" + std::to_string(id) + "\n"; } } else { diff --git a/src/hybridmap/NeutralAtomArchitecture.cpp b/src/hybridmap/NeutralAtomArchitecture.cpp index 94955d198..aabc6b0a7 100644 --- a/src/hybridmap/NeutralAtomArchitecture.cpp +++ b/src/hybridmap/NeutralAtomArchitecture.cpp @@ -249,6 +249,7 @@ std::string NeutralAtomArchitecture::getAnimationMachine( std::to_string(this->getShuttlingTime(qc::OpType::AodDeactivate) * shuttlingSpeedFactor) + "\n\trz: " + std::to_string(this->getGateTime("x")) + + "\n\try: " + std::to_string(this->getGateTime("x")) + "\n\tcz: " + std::to_string(this->getGateTime("cz")) + "\n\tunit: \"us\"\n}\n"; From 5ae3c60a53c14cd57c1b861386bf5c1b2b33b219 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 7 Feb 2025 15:21:38 +0100 Subject: [PATCH 121/394] =?UTF-8?q?=E2=9C=A8=20New=20Animation:=20Added=20?= =?UTF-8?q?initial=20loading=20of=20the=20flying=20ancilla=20qubits.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HardwareQubits.hpp | 2 +- include/hybridmap/MoveToAodConverter.hpp | 21 +++++++------ src/hybridmap/HybridNeutralAtomMapper.cpp | 2 +- src/hybridmap/MoveToAodConverter.cpp | 36 +++++++++++++++++++++++ 4 files changed, 50 insertions(+), 11 deletions(-) diff --git a/include/hybridmap/HardwareQubits.hpp b/include/hybridmap/HardwareQubits.hpp index 2c3a99217..0f568b508 100644 --- a/include/hybridmap/HardwareQubits.hpp +++ b/include/hybridmap/HardwareQubits.hpp @@ -336,7 +336,7 @@ class HardwareQubits { [[nodiscard]] std::set getBlockedQubits(const std::set& qubits) const; - [[nodiscard]] std::map getInitHwPos() const { + [[nodiscard]] std::map getInitHwPos() const { std::map initialHwPosMap; for (auto const& pair : initialHwPos) { initialHwPosMap[pair.first] = pair.second; diff --git a/include/hybridmap/MoveToAodConverter.hpp b/include/hybridmap/MoveToAodConverter.hpp index 99c074657..280ae331e 100644 --- a/include/hybridmap/MoveToAodConverter.hpp +++ b/include/hybridmap/MoveToAodConverter.hpp @@ -16,6 +16,7 @@ #include "na/NADefinitions.hpp" #include +#include #include #include #include @@ -276,13 +277,9 @@ class MoveToAodConverter { connectAodOperations(const AodActivationHelper& aodActivationHelper, const AodActivationHelper& aodDeactivationHelper); }; - struct AncillaAtom { - bool occupied = false; - uint32_t xOffset = 1; - uint32_t yOffset = 1; - }; - using AncillaAtoms = std::vector; + using AncillaAtoms = + std::map>; const NeutralAtomArchitecture& arch; qc::QuantumComputation qcScheduled; @@ -291,6 +288,9 @@ class MoveToAodConverter { AncillaAtoms ancillaAtoms; AtomMove convertOpToMove(qc::Operation* get); + + void initFlyingAncillas(); + /** * @brief Assigns move operations into groups that can be executed in parallel * @param qc Quantum circuit to schedule @@ -317,11 +317,14 @@ class MoveToAodConverter { MoveToAodConverter(const MoveToAodConverter&) = delete; MoveToAodConverter(MoveToAodConverter&&) = delete; explicit MoveToAodConverter(const NeutralAtomArchitecture& archArg, - const na::HardwareQubits& hardwareQubitsArg) + const na::HardwareQubits& hardwareQubitsArg, + const HardwareQubits& flyingAncillas) : arch(archArg), qcScheduled(arch.getNpositions()), - hardwareQubits(hardwareQubitsArg), - ancillaAtoms(AncillaAtoms(arch.getNpositions(), AncillaAtom())) { + hardwareQubits(hardwareQubitsArg) { qcScheduled.addAncillaryRegister(arch.getNpositions()); + for (const auto& [hwQubit, coord] : flyingAncillas.getInitHwPos()) { + ancillaAtoms.insert({coord + arch.getNpositions(), {1, 1}}); + } } /** diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 64a5c9de0..d8eaad914 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -339,7 +339,7 @@ qc::QuantumComputation NeutralAtomMapper::convertToAod() { qc::CircuitOptimizer::flattenOperations(mappedQc); // decompose AOD moves mappedQc.dumpOpenQASM(std::cout, false); - MoveToAodConverter aodScheduler(*arch, hardwareQubits); + MoveToAodConverter aodScheduler(*arch, hardwareQubits, flyingAncillas); mappedQcAOD = aodScheduler.schedule(mappedQc); if (this->parameters->verbose) { std::cout << "nMoveGroups: " << aodScheduler.getNMoveGroups() << '\n'; diff --git a/src/hybridmap/MoveToAodConverter.cpp b/src/hybridmap/MoveToAodConverter.cpp index 4b445c1dd..bf015d8e1 100644 --- a/src/hybridmap/MoveToAodConverter.cpp +++ b/src/hybridmap/MoveToAodConverter.cpp @@ -28,6 +28,7 @@ namespace na { qc::QuantumComputation MoveToAodConverter::schedule(qc::QuantumComputation& qc) { + initFlyingAncillas(); initMoveGroups(qc); if (moveGroups.empty()) { return qc; @@ -73,6 +74,41 @@ AtomMove MoveToAodConverter::convertOpToMove(qc::Operation* get) { } return {q1, q2, load1, load2}; } +void MoveToAodConverter::initFlyingAncillas() { + std::vector coords; + std::vector dirs; + std::vector starts; + std::vector ends; + std::set rowsActivated; + std::set columnsActivated; + for (const auto& [coord, offsets] : ancillaAtoms) { + coords.emplace_back(coord); + const auto column = (coord % arch.getNcolumns()); + const auto row = (coord / arch.getNcolumns()); + + const auto offset = + arch.getInterQubitDistance() / arch.getNAodIntermediateLevels(); + if (columnsActivated.find(column) == columnsActivated.end()) { + columnsActivated.insert(column); + const auto x = + column * arch.getInterQubitDistance() + (offsets.first * offset); + dirs.emplace_back(Dimension::X); + starts.emplace_back(x); + ends.emplace_back(x); + } + if (rowsActivated.find(row) == rowsActivated.end()) { + rowsActivated.insert(row); + const auto y = + row * arch.getInterQubitDistance() + (offsets.second * offset); + dirs.emplace_back(Dimension::Y); + starts.emplace_back(y); + ends.emplace_back(y); + } + } + const AodOperation aodInit(qc::OpType::AodActivate, coords, dirs, starts, + ends); + qcScheduled.emplace_back(std::make_unique(aodInit)); +} void MoveToAodConverter::initMoveGroups(qc::QuantumComputation& qc) { MoveGroup currentMoveGroup; From 0a5fe1c518b9b64ea919f06da04c0c6e6d7646ce Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 7 Feb 2025 16:01:10 +0100 Subject: [PATCH 122/394] =?UTF-8?q?=F0=9F=94=A5removed=20print=20statement?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridNeutralAtomMapper.cpp | 4 ---- src/hybridmap/MoveToAodConverter.cpp | 1 - 2 files changed, 5 deletions(-) diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index d8eaad914..f4c8aeb4c 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -328,17 +328,13 @@ void NeutralAtomMapper::decomposeBridgeGates(qc::QuantumComputation& qc) const { qc::QuantumComputation NeutralAtomMapper::convertToAod() { // decompose SWAP gates - mappedQc.dumpOpenQASM(std::cout, false); qc::CircuitOptimizer::decomposeSWAP(mappedQc, false); - mappedQc.dumpOpenQASM(std::cout, false); // decompose bridge gates decomposeBridgeGates(mappedQc); - mappedQc.dumpOpenQASM(std::cout, false); qc::CircuitOptimizer::replaceMCXWithMCZ(mappedQc); qc::CircuitOptimizer::singleQubitGateFusion(mappedQc); qc::CircuitOptimizer::flattenOperations(mappedQc); // decompose AOD moves - mappedQc.dumpOpenQASM(std::cout, false); MoveToAodConverter aodScheduler(*arch, hardwareQubits, flyingAncillas); mappedQcAOD = aodScheduler.schedule(mappedQc); if (this->parameters->verbose) { diff --git a/src/hybridmap/MoveToAodConverter.cpp b/src/hybridmap/MoveToAodConverter.cpp index bf015d8e1..733efd1ba 100644 --- a/src/hybridmap/MoveToAodConverter.cpp +++ b/src/hybridmap/MoveToAodConverter.cpp @@ -58,7 +58,6 @@ MoveToAodConverter::schedule(qc::QuantumComputation& qc) { } return qcScheduled; - qcScheduled.print(std::cout); } AtomMove MoveToAodConverter::convertOpToMove(qc::Operation* get) { From 8db33f57db169e41a0cbccfee6035a5717124a4e Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 7 Feb 2025 16:18:27 +0100 Subject: [PATCH 123/394] =?UTF-8?q?=F0=9F=90=9B=20Check=20blocking=20also?= =?UTF-8?q?=20for=20flying=20ancillas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/NeutralAtomScheduler.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/hybridmap/NeutralAtomScheduler.cpp b/src/hybridmap/NeutralAtomScheduler.cpp index cb54546cc..1986f5559 100644 --- a/src/hybridmap/NeutralAtomScheduler.cpp +++ b/src/hybridmap/NeutralAtomScheduler.cpp @@ -37,7 +37,8 @@ na::SchedulerResults na::NeutralAtomScheduler::schedule( std::vector totalExecutionTimes(arch->getNpositions(), 0); // saves for each coord the time slots that are blocked by a multi qubit gate std::vector rydbergBlockedQubitsTimes( - arch->getNpositions(), std::deque>()); + arch->getNpositions() + arch->getNpositions(), + std::deque>()); qc::fp aodLastBlockedTime = 0; qc::fp totalGateTime = 0; qc::fp totalGateFidelities = 1; From 50941d63537a1036f21b92ef99e899c186f670b1 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 7 Feb 2025 16:19:06 +0100 Subject: [PATCH 124/394] =?UTF-8?q?=E2=9C=85=20restructured=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/hybridmap/test_hybridmap.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/hybridmap/test_hybridmap.cpp b/test/hybridmap/test_hybridmap.cpp index f6f2d0ae8..2c95bebd0 100644 --- a/test/hybridmap/test_hybridmap.cpp +++ b/test/hybridmap/test_hybridmap.cpp @@ -152,17 +152,16 @@ class NeutralAtomMapperTest : public ::testing::Test { }; TEST_F(NeutralAtomMapperTest, Output) { - auto qcMapped = mapper.map(qc, initialMapping); setvbuf(stdout, NULL, _IONBF, 0); + auto qcMapped = mapper.map(qc, initialMapping); qcMapped.dumpOpenQASM(std::cout, false); auto qcAodMapped = mapper.convertToAod(); qcAodMapped.dumpOpenQASM(std::cout, false); - const auto scheduleResults = mapper.schedule(false, true); + const auto scheduleResults = mapper.schedule(true, true); mapper.saveAnimationFiles("test"); - // const auto scheduleResults = mapper.schedule(true, true); std::cout << scheduleResults.toCsv(); ASSERT_GT(scheduleResults.totalFidelities, 0); From ff5a0dfb1f470f3f8b4de17bc574210e0d21301e Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Mon, 10 Feb 2025 09:27:48 +0100 Subject: [PATCH 125/394] =?UTF-8?q?=F0=9F=8E=A8=20Fixed=20FA=20init?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/MoveToAodConverter.hpp | 14 +++++-- src/hybridmap/HybridAnimation.cpp | 8 +++- src/hybridmap/MoveToAodConverter.cpp | 49 ++++++++++++++++-------- 3 files changed, 49 insertions(+), 22 deletions(-) diff --git a/include/hybridmap/MoveToAodConverter.hpp b/include/hybridmap/MoveToAodConverter.hpp index 280ae331e..52de94dbf 100644 --- a/include/hybridmap/MoveToAodConverter.hpp +++ b/include/hybridmap/MoveToAodConverter.hpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -285,7 +286,7 @@ class MoveToAodConverter { qc::QuantumComputation qcScheduled; std::vector moveGroups; const na::HardwareQubits& hardwareQubits; - AncillaAtoms ancillaAtoms; + AncillaAtoms ancillaAtomOffsets; AtomMove convertOpToMove(qc::Operation* get); @@ -322,8 +323,15 @@ class MoveToAodConverter { : arch(archArg), qcScheduled(arch.getNpositions()), hardwareQubits(hardwareQubitsArg) { qcScheduled.addAncillaryRegister(arch.getNpositions()); - for (const auto& [hwQubit, coord] : flyingAncillas.getInitHwPos()) { - ancillaAtoms.insert({coord + arch.getNpositions(), {1, 1}}); + if (flyingAncillas.getNumQubits() > arch.getNcolumns() || + flyingAncillas.getNumQubits() > arch.getNrows()) { + throw std::invalid_argument( + "Number of flying ancillas must be smaller than the number of " + "columns and rows of the neutral atom architecture."); + } + for (auto i = 0; i < flyingAncillas.getInitHwPos().size(); ++i) { + const auto coord = flyingAncillas.getInitHwPos().at(i); + ancillaAtomOffsets.insert({coord + arch.getNpositions(), {i + 1, i + 1}}); } } diff --git a/src/hybridmap/HybridAnimation.cpp b/src/hybridmap/HybridAnimation.cpp index 067a13a3c..97841ea14 100644 --- a/src/hybridmap/HybridAnimation.cpp +++ b/src/hybridmap/HybridAnimation.cpp @@ -32,15 +32,19 @@ void AnimationAtoms::initPositions( row * arch.getInterQubitDistance()}; } + auto flyingAncillaidxPlusOne = 0; for (const auto& [id, coord] : initFaPos) { + flyingAncillaidxPlusOne++; coordIdxToId[coord + arch.getNpositions()] = id + initHwPos.size(); const auto column = coord % nCols; const auto row = coord / nCols; const auto offset = arch.getInterQubitDistance() / arch.getNAodIntermediateLevels(); idToCoord[id + initHwPos.size()] = { - (column * arch.getInterQubitDistance()) + offset, - (row * arch.getInterQubitDistance()) + offset}; + (column * arch.getInterQubitDistance()) + + flyingAncillaidxPlusOne * offset, + (row * arch.getInterQubitDistance()) + + flyingAncillaidxPlusOne * offset}; } } diff --git a/src/hybridmap/MoveToAodConverter.cpp b/src/hybridmap/MoveToAodConverter.cpp index 733efd1ba..922c395c5 100644 --- a/src/hybridmap/MoveToAodConverter.cpp +++ b/src/hybridmap/MoveToAodConverter.cpp @@ -80,29 +80,28 @@ void MoveToAodConverter::initFlyingAncillas() { std::vector ends; std::set rowsActivated; std::set columnsActivated; - for (const auto& [coord, offsets] : ancillaAtoms) { + for (const auto& ancilla : ancillaAtomOffsets) { + auto coord = ancilla.first; + const auto offsets = ancilla.second; coords.emplace_back(coord); + coord -= arch.getNpositions(); const auto column = (coord % arch.getNcolumns()); const auto row = (coord / arch.getNcolumns()); const auto offset = arch.getInterQubitDistance() / arch.getNAodIntermediateLevels(); - if (columnsActivated.find(column) == columnsActivated.end()) { - columnsActivated.insert(column); - const auto x = - column * arch.getInterQubitDistance() + (offsets.first * offset); - dirs.emplace_back(Dimension::X); - starts.emplace_back(x); - ends.emplace_back(x); - } - if (rowsActivated.find(row) == rowsActivated.end()) { - rowsActivated.insert(row); - const auto y = - row * arch.getInterQubitDistance() + (offsets.second * offset); - dirs.emplace_back(Dimension::Y); - starts.emplace_back(y); - ends.emplace_back(y); - } + columnsActivated.insert(column); + const auto x = + column * arch.getInterQubitDistance() + (offsets.first * offset); + dirs.emplace_back(Dimension::X); + starts.emplace_back(x); + ends.emplace_back(x); + rowsActivated.insert(row); + const auto y = + row * arch.getInterQubitDistance() + (offsets.second * offset); + dirs.emplace_back(Dimension::Y); + starts.emplace_back(y); + ends.emplace_back(y); } const AodOperation aodInit(qc::OpType::AodActivate, coords, dirs, starts, ends); @@ -404,6 +403,22 @@ MoveToAodConverter::processMoves( origin, v, target, vReverse, Dimension::X); auto canAddY = canAddActivation(aodActivationHelper, aodDeactivationHelper, origin, v, target, vReverse, Dimension::Y); + const bool isFa = !move.load1 && !move.load2; + if (isFa) { + // convert Merge to append for FA + if (canAddX.first == ActivationMergeType::Merge) { + canAddX.first = ActivationMergeType::Append; + } + if (canAddY.first == ActivationMergeType::Merge) { + canAddY.first = ActivationMergeType::Append; + } + if (canAddX.second == ActivationMergeType::Merge) { + canAddX.second = ActivationMergeType::Append; + } + if (canAddY.second == ActivationMergeType::Merge) { + canAddY.second = ActivationMergeType::Append; + } + } auto activationCanAddXY = std::make_pair(canAddX.first, canAddY.first); auto deactivationCanAddXY = std::make_pair(canAddX.second, canAddY.second); if (activationCanAddXY.first == ActivationMergeType::Impossible || From 1beb802377be03289f478572020bae90150b2161 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Mon, 10 Feb 2025 11:12:25 +0100 Subject: [PATCH 126/394] =?UTF-8?q?=F0=9F=90=9B=20Fixed=20wrong=20mapping?= =?UTF-8?q?=20of=20PassBy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridNeutralAtomMapper.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index f4c8aeb4c..18af50fd5 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -345,13 +345,25 @@ qc::QuantumComputation NeutralAtomMapper::convertToAod() { void NeutralAtomMapper::applyPassBy(NeutralAtomLayer& frontLayer, const FlyingAncillaComb& faComb) { + auto usedQubits = faComb.op->getUsedQubits(); for (const auto& passBy : faComb.moves) { mappedQc.move(passBy.q1, passBy.q2 + arch->getNpositions()); if (this->parameters->verbose) { std::cout << "passby " << passBy.q1 << " " << passBy.q2 << '\n'; } + if (usedQubits.find(passBy.q1) != usedQubits.end()) { + usedQubits.erase(passBy.q1); + usedQubits.insert(passBy.q2 + arch->getNpositions()); + } } - mapGate(faComb.op); + const auto opCopy = faComb.op->clone(); + const std::vector usedQubitsVec = {usedQubits.begin(), + usedQubits.end()}; + opCopy->setTargets(usedQubitsVec); + opCopy->setControls({}); + mappedQc.emplace_back(opCopy->clone()); + + // mapGate(faComb.op); for (const auto& passBy : faComb.moves) { mappedQc.move(passBy.q2 + arch->getNpositions(), passBy.q1); if (this->parameters->verbose) { @@ -2416,7 +2428,7 @@ MappingMethod NeutralAtomMapper::compareShuttlingAndFlyingAncilla( const auto move = moveDistReduction * moveFidelity; const auto fa = faDistReduction * faFidelity; const auto passBy = faDistReduction * passByFidelity; - return MappingMethod::FlyingAncillaMethod; + return MappingMethod::PassByMethod; if (move > fa && move > passBy) { return MappingMethod::MoveMethod; From ba008d88bb0b4b38c3c29e9c78185ce41c2e780b Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Mon, 10 Feb 2025 11:12:46 +0100 Subject: [PATCH 127/394] =?UTF-8?q?=F0=9F=8E=A8=20Removed=20target=20from?= =?UTF-8?q?=20offset=20movements.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/MoveToAodConverter.cpp | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/hybridmap/MoveToAodConverter.cpp b/src/hybridmap/MoveToAodConverter.cpp index 922c395c5..74c99d856 100644 --- a/src/hybridmap/MoveToAodConverter.cpp +++ b/src/hybridmap/MoveToAodConverter.cpp @@ -616,13 +616,6 @@ MoveToAodConverter::AodActivationHelper::getAodOperation( } } } - std::set qubitsMoveSet; - for (const auto& move : activation.moves) { - qubitsMoveSet.insert(move.c1); - qubitsMoveSet.insert(move.c2); - } - std::vector const qubitsMove(qubitsMoveSet.begin(), - qubitsMoveSet.end()); std::vector initOperations; std::vector offsetOperations; @@ -645,7 +638,7 @@ MoveToAodConverter::AodActivationHelper::getAodOperation( auto initOp = AodOperation(type, qubitsActivation, initOperations); auto offsetOp = - AodOperation(qc::OpType::AodMove, qubitsMove, offsetOperations); + AodOperation(qc::OpType::AodMove, qubitsActivation, offsetOperations); if (this->type == qc::OpType::AodActivate) { return {initOp, offsetOp}; } From 801c57a7302434d9ae2277c768f6783cdc21d626 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Mon, 10 Feb 2025 13:05:56 +0100 Subject: [PATCH 128/394] =?UTF-8?q?=F0=9F=90=9B=20added=20missing=20store?= =?UTF-8?q?=20animation=20line.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridAnimation.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/hybridmap/HybridAnimation.cpp b/src/hybridmap/HybridAnimation.cpp index 97841ea14..daea39105 100644 --- a/src/hybridmap/HybridAnimation.cpp +++ b/src/hybridmap/HybridAnimation.cpp @@ -74,6 +74,7 @@ std::string AnimationAtoms::opToNaViz(const std::unique_ptr& op, const auto id = coordIdxToId.at(coordIdx); opString += "\t atom" + std::to_string(id) + "\n"; } + opString += "]\n"; } else if (op->getType() == qc::OpType::AodMove) { // update atom coordinates const auto startsX = From 197705233c5779af5fe3d68500f526e5106bb2df Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Mon, 10 Feb 2025 15:55:12 +0100 Subject: [PATCH 129/394] =?UTF-8?q?=E2=9C=A8added=20dodging=20operations?= =?UTF-8?q?=20of=20flying=20ancillas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/MoveToAodConverter.hpp | 13 ++- include/hybridmap/NeutralAtomArchitecture.hpp | 4 + src/hybridmap/MoveToAodConverter.cpp | 94 ++++++++++++++++++- 3 files changed, 103 insertions(+), 8 deletions(-) diff --git a/include/hybridmap/MoveToAodConverter.hpp b/include/hybridmap/MoveToAodConverter.hpp index 52de94dbf..69e4d1eda 100644 --- a/include/hybridmap/MoveToAodConverter.hpp +++ b/include/hybridmap/MoveToAodConverter.hpp @@ -37,6 +37,9 @@ using MergeTypeXY = std::pair; * AODs. */ class MoveToAodConverter { + using AncillaAtoms = + std::map>; + protected: /** * @brief Struct to store information about specific AOD activations. @@ -209,7 +212,12 @@ class MoveToAodConverter { * @brief Converts all activations into AOD operations * @return All activations of the AOD activation helper as AOD operations */ - [[nodiscard]] std::vector getAodOperations() const; + [[nodiscard]] std::vector + getAodOperations(const AncillaAtoms& ancillas) const; + + [[nodiscard]] AodOperation + getDodgingOperation(const AodActivation& aodActivation, + AncillaAtoms ancillas) const; }; [[nodiscard]] static std::pair @@ -279,9 +287,6 @@ class MoveToAodConverter { const AodActivationHelper& aodDeactivationHelper); }; - using AncillaAtoms = - std::map>; - const NeutralAtomArchitecture& arch; qc::QuantumComputation qcScheduled; std::vector moveGroups; diff --git a/include/hybridmap/NeutralAtomArchitecture.hpp b/include/hybridmap/NeutralAtomArchitecture.hpp index 76ab93b61..4c332306c 100644 --- a/include/hybridmap/NeutralAtomArchitecture.hpp +++ b/include/hybridmap/NeutralAtomArchitecture.hpp @@ -245,6 +245,10 @@ class NeutralAtomArchitecture { return properties.getInterQubitDistance(); } + [[nodiscard]] qc::fp getOffsetDistance() const { + return getInterQubitDistance() / getNAodIntermediateLevels(); + } + /** * @brief Get the interaction radius * @return The interaction radius diff --git a/src/hybridmap/MoveToAodConverter.cpp b/src/hybridmap/MoveToAodConverter.cpp index 74c99d856..f33fc0291 100644 --- a/src/hybridmap/MoveToAodConverter.cpp +++ b/src/hybridmap/MoveToAodConverter.cpp @@ -378,8 +378,10 @@ void MoveToAodConverter::processMoveGroups() { possibleNewMoveGroup = MoveGroup(); groupIt--; } - groupIt->processedOpsInit = aodActivationHelper.getAodOperations(); - groupIt->processedOpsFinal = aodDeactivationHelper.getAodOperations(); + groupIt->processedOpsInit = + aodActivationHelper.getAodOperations(ancillaAtomOffsets); + groupIt->processedOpsFinal = + aodDeactivationHelper.getAodOperations(ancillaAtomOffsets); groupIt->processedOpShuttle = MoveGroup::connectAodOperations( aodActivationHelper, aodDeactivationHelper); } @@ -603,7 +605,7 @@ void MoveToAodConverter::AodActivationHelper::mergeActivationDim( std::vector MoveToAodConverter::AodActivationHelper::getAodOperation( const AodActivationHelper::AodActivation& activation) const { - std::vector qubitsActivation; + CoordIndices qubitsActivation; qubitsActivation.reserve(activation.moves.size()); for (const auto& move : activation.moves) { if (type == qc::OpType::AodActivate) { @@ -616,6 +618,12 @@ MoveToAodConverter::AodActivationHelper::getAodOperation( } } } + CoordIndices qubitsOffset; + qubitsOffset.reserve(activation.moves.size() * 2); + for (const auto& qubit : qubitsActivation) { + qubitsOffset.emplace_back(qubit); + qubitsOffset.emplace_back(qubit); + } std::vector initOperations; std::vector offsetOperations; @@ -646,14 +654,92 @@ MoveToAodConverter::AodActivationHelper::getAodOperation( } std::vector -MoveToAodConverter::AodActivationHelper::getAodOperations() const { +MoveToAodConverter::AodActivationHelper::getAodOperations( + const AncillaAtoms& ancillas) const { std::vector aodOperations; for (const auto& activation : allActivations) { auto operations = getAodOperation(activation); + // insert ancilla dodging operations + auto dodgingOperation = getDodgingOperation(activation, ancillas); + if (dodgingOperation.getNqubits() != 0) { + aodOperations.emplace_back(dodgingOperation); + } aodOperations.insert(aodOperations.end(), operations.begin(), operations.end()); + if (dodgingOperation.getNqubits() != 0) { + aodOperations.emplace_back(dodgingOperation.getInverted()); + } } return aodOperations; } +AodOperation MoveToAodConverter::AodActivationHelper::getDodgingOperation( + const AodActivation& aodActivation, AncillaAtoms ancillas) const { + std::vector dodgingOperations; + CoordIndices usedQubits; + // invert order of ancillas to avoid aod crossings of flying Ancillas + // std::reverse(ancillas.begin(), ancillas.end()); + + const std::vector dims = {Dimension::X, Dimension::Y}; + const std::vector>> activations = { + aodActivation.activateXs, aodActivation.activateYs}; + // iterate in parallel over both pairs + for (size_t i = 0; i < dims.size(); i++) { + const auto& dim = dims[i]; + const auto& aodActivations = activations[i]; + std::set usedOffsets; + + for (auto it = ancillas.rbegin(); it != ancillas.rend(); ++it) { + const auto ancillaOffset = *it; + CoordIndex rowOrColumn = 0; + if (dim == Dimension::X) { + rowOrColumn = + (ancillaOffset.first - arch->getNpositions()) % arch->getNcolumns(); + } else { + rowOrColumn = + (ancillaOffset.first - arch->getNpositions()) / arch->getNcolumns(); + } + for (const auto& aodMove : aodActivations) { + if (aodMove->init == rowOrColumn) { + // ancilla is in the same row/column as the activation + // move ancilla away + auto sign = aodMove->delta > 0 ? 1 : -1; + if (type == qc::OpType::AodDeactivate) { + sign *= -1; + } + auto newOffset = aodMove->offset + sign; + if (newOffset == 0) { + newOffset += sign; // always stay in between the atoms + } + // check if same offset is used by other dodging operation in same + // row/column + while (usedOffsets.find(newOffset) != usedOffsets.end()) { + newOffset += sign; + } + // make AodOperation + const auto ancillaOffsetDim = dim == Dimension::X + ? ancillaOffset.second.first + : ancillaOffset.second.second; + const auto start = (rowOrColumn * arch->getInterQubitDistance()) + + (ancillaOffsetDim * arch->getInterQubitDistance() / + arch->getNAodIntermediateLevels()); + const auto end = (rowOrColumn * arch->getInterQubitDistance()) + + (newOffset * arch->getInterQubitDistance() / + arch->getNAodIntermediateLevels()); + if (std::abs(start - end) > 0.0001) { + dodgingOperations.emplace_back(dim, start, end); + if (std::find(usedQubits.begin(), usedQubits.end(), + ancillaOffset.first) == usedQubits.end()) { + usedQubits.emplace_back(ancillaOffset.first); + usedQubits.emplace_back(ancillaOffset.first + + arch->getNpositions()); + } + usedOffsets.insert(newOffset); + } + } + } + } + } + return {qc::OpType::AodMove, usedQubits, dodgingOperations}; +} } // namespace na From 9a94e051674ec40baba7ad44d75ffca296ec2852 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Mon, 10 Feb 2025 16:27:03 +0100 Subject: [PATCH 130/394] =?UTF-8?q?=F0=9F=90=9B=20Fixed=20wrong=20handlich?= =?UTF-8?q?=20of=20non-necessary=20offset=20movement?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/MoveToAodConverter.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/hybridmap/MoveToAodConverter.cpp b/src/hybridmap/MoveToAodConverter.cpp index f33fc0291..577d440df 100644 --- a/src/hybridmap/MoveToAodConverter.cpp +++ b/src/hybridmap/MoveToAodConverter.cpp @@ -659,6 +659,9 @@ MoveToAodConverter::AodActivationHelper::getAodOperations( std::vector aodOperations; for (const auto& activation : allActivations) { auto operations = getAodOperation(activation); + if (operations.empty()) { + continue; + } // insert ancilla dodging operations auto dodgingOperation = getDodgingOperation(activation, ancillas); if (dodgingOperation.getNqubits() != 0) { From 89e4e00cabfb163db3f24fd1745313d735f5b38e Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Mon, 10 Feb 2025 16:33:02 +0100 Subject: [PATCH 131/394] =?UTF-8?q?Revert=20"=F0=9F=90=9B=20Fixed=20wrong?= =?UTF-8?q?=20handlich=20of=20non-necessary=20offset=20movement"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 9a94e051674ec40baba7ad44d75ffca296ec2852. --- src/hybridmap/MoveToAodConverter.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/hybridmap/MoveToAodConverter.cpp b/src/hybridmap/MoveToAodConverter.cpp index 577d440df..f33fc0291 100644 --- a/src/hybridmap/MoveToAodConverter.cpp +++ b/src/hybridmap/MoveToAodConverter.cpp @@ -659,9 +659,6 @@ MoveToAodConverter::AodActivationHelper::getAodOperations( std::vector aodOperations; for (const auto& activation : allActivations) { auto operations = getAodOperation(activation); - if (operations.empty()) { - continue; - } // insert ancilla dodging operations auto dodgingOperation = getDodgingOperation(activation, ancillas); if (dodgingOperation.getNqubits() != 0) { From f49abce3b2460a2e57e4ec7efe9dcba7190da927 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Mon, 10 Feb 2025 21:21:47 +0100 Subject: [PATCH 132/394] =?UTF-8?q?=E2=9C=A8improved=20dodging=20operation?= =?UTF-8?q?s=20of=20flying=20ancillas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/MoveToAodConverter.hpp | 51 +++++-- src/hybridmap/MoveToAodConverter.cpp | 161 ++++++++++++++++------- test/hybridmap/test_hybridmap.cpp | 9 +- 3 files changed, 166 insertions(+), 55 deletions(-) diff --git a/include/hybridmap/MoveToAodConverter.hpp b/include/hybridmap/MoveToAodConverter.hpp index 69e4d1eda..9f08215d5 100644 --- a/include/hybridmap/MoveToAodConverter.hpp +++ b/include/hybridmap/MoveToAodConverter.hpp @@ -37,8 +37,22 @@ using MergeTypeXY = std::pair; * AODs. */ class MoveToAodConverter { - using AncillaAtoms = - std::map>; + struct AncillaAtom { + struct xAndY { + int x; + int y; + xAndY(std::uint32_t x, std::uint32_t y) : x(x), y(y) {} + }; + + xAndY coord; + xAndY coordDodged; + xAndY offset; + xAndY offsetDodged; + AncillaAtom() = delete; + AncillaAtom(xAndY c, xAndY o) + : coord(c), coordDodged(c), offset(o), offsetDodged(o) {} + }; + using AncillaAtoms = std::vector; protected: /** @@ -114,14 +128,15 @@ class MoveToAodConverter { std::vector allActivations; // Differentiate between loading and unloading qc::OpType type; + AncillaAtoms* ancillas; // Constructor AodActivationHelper() = delete; AodActivationHelper(const AodActivationHelper&) = delete; AodActivationHelper(AodActivationHelper&&) = delete; AodActivationHelper(const NeutralAtomArchitecture& architecture, - qc::OpType opType) - : arch(&architecture), type(opType) {} + qc::OpType opType, AncillaAtoms* ancillas) + : arch(&architecture), type(opType), ancillas(ancillas) {} // Methods @@ -212,12 +227,14 @@ class MoveToAodConverter { * @brief Converts all activations into AOD operations * @return All activations of the AOD activation helper as AOD operations */ - [[nodiscard]] std::vector - getAodOperations(const AncillaAtoms& ancillas) const; + [[nodiscard]] std::vector getAodOperations() const; [[nodiscard]] AodOperation - getDodgingOperation(const AodActivation& aodActivation, - AncillaAtoms ancillas) const; + getDodgingOperation(const AodActivation& aodActivation) const; + [[nodiscard]] std::vector getDodgingOperations() const; + + [[nodiscard]] static std::vector + reverseActivations(const std::vector& ops); }; [[nodiscard]] static std::pair @@ -291,7 +308,7 @@ class MoveToAodConverter { qc::QuantumComputation qcScheduled; std::vector moveGroups; const na::HardwareQubits& hardwareQubits; - AncillaAtoms ancillaAtomOffsets; + AncillaAtoms ancillas; AtomMove convertOpToMove(qc::Operation* get); @@ -336,7 +353,12 @@ class MoveToAodConverter { } for (auto i = 0; i < flyingAncillas.getInitHwPos().size(); ++i) { const auto coord = flyingAncillas.getInitHwPos().at(i); - ancillaAtomOffsets.insert({coord + arch.getNpositions(), {i + 1, i + 1}}); + const auto col = coord % arch.getNcolumns(); + const auto row = coord / arch.getNcolumns(); + const AncillaAtom ancillaAtom({col, row}, + {static_cast(i + 1), + static_cast(i + 1)}); + ancillas.emplace_back(ancillaAtom); } } @@ -354,5 +376,14 @@ class MoveToAodConverter { */ [[nodiscard]] auto getNMoveGroups() const { return moveGroups.size(); } }; +inline std::vector +MoveToAodConverter::AodActivationHelper::reverseActivations( + const std::vector& ops) { + std::vector reversedOps; + for (auto it = ops.rbegin(); it != ops.rend(); ++it) { + reversedOps.emplace_back(it->getInverted()); + } + return reversedOps; +} } // namespace na diff --git a/src/hybridmap/MoveToAodConverter.cpp b/src/hybridmap/MoveToAodConverter.cpp index f33fc0291..3fa1f4d61 100644 --- a/src/hybridmap/MoveToAodConverter.cpp +++ b/src/hybridmap/MoveToAodConverter.cpp @@ -80,9 +80,9 @@ void MoveToAodConverter::initFlyingAncillas() { std::vector ends; std::set rowsActivated; std::set columnsActivated; - for (const auto& ancilla : ancillaAtomOffsets) { - auto coord = ancilla.first; - const auto offsets = ancilla.second; + for (const auto& ancilla : ancillas) { + auto coord = ancilla.coord.x + (ancilla.coord.y * arch.getNcolumns()); + const auto offsets = ancilla.offset; coords.emplace_back(coord); coord -= arch.getNpositions(); const auto column = (coord % arch.getNcolumns()); @@ -91,14 +91,12 @@ void MoveToAodConverter::initFlyingAncillas() { const auto offset = arch.getInterQubitDistance() / arch.getNAodIntermediateLevels(); columnsActivated.insert(column); - const auto x = - column * arch.getInterQubitDistance() + (offsets.first * offset); + const auto x = column * arch.getInterQubitDistance() + (offsets.x * offset); dirs.emplace_back(Dimension::X); starts.emplace_back(x); ends.emplace_back(x); rowsActivated.insert(row); - const auto y = - row * arch.getInterQubitDistance() + (offsets.second * offset); + const auto y = row * arch.getInterQubitDistance() + (offsets.y * offset); dirs.emplace_back(Dimension::Y); starts.emplace_back(y); ends.emplace_back(y); @@ -355,8 +353,10 @@ void MoveToAodConverter::processMoveGroups() { // convert the moves from MoveGroup to AodOperations for (auto groupIt = moveGroups.begin(); groupIt != moveGroups.end(); ++groupIt) { - AodActivationHelper aodActivationHelper{arch, qc::OpType::AodActivate}; - AodActivationHelper aodDeactivationHelper{arch, qc::OpType::AodDeactivate}; + AodActivationHelper aodActivationHelper{arch, qc::OpType::AodActivate, + (&ancillas)}; + AodActivationHelper aodDeactivationHelper{arch, qc::OpType::AodDeactivate, + (&ancillas)}; const auto resultMoves = processMoves(groupIt->moves, aodActivationHelper, aodDeactivationHelper); @@ -378,12 +378,26 @@ void MoveToAodConverter::processMoveGroups() { possibleNewMoveGroup = MoveGroup(); groupIt--; } - groupIt->processedOpsInit = - aodActivationHelper.getAodOperations(ancillaAtomOffsets); - groupIt->processedOpsFinal = - aodDeactivationHelper.getAodOperations(ancillaAtomOffsets); + // const auto initFaDodgeOps = aodActivationHelper.getDodgingOperations(); + const auto initActivations = aodActivationHelper.getAodOperations(); + const auto finalFaDodgeOps = aodDeactivationHelper.getDodgingOperations(); + + // append to init ops + // groupIt->processedOpsInit.insert(groupIt->processedOpsInit.end(), + // initFaDodgeOps.begin(), + // initFaDodgeOps.end()); + groupIt->processedOpsInit.insert(groupIt->processedOpsInit.end(), + initActivations.begin(), + initActivations.end()); + groupIt->processedOpsInit.insert(groupIt->processedOpsInit.end(), + finalFaDodgeOps.begin(), + finalFaDodgeOps.end()); + + groupIt->processedOpsFinal = aodDeactivationHelper.getAodOperations(); + groupIt->processedOpShuttle = MoveGroup::connectAodOperations( aodActivationHelper, aodDeactivationHelper); + int a; } } std::pair, MoveToAodConverter::MoveGroup> @@ -632,52 +646,108 @@ MoveToAodConverter::AodActivationHelper::getAodOperation( if (aodMove->load) { computeInitAndOffsetOperations(Dimension::X, aodMove, initOperations, offsetOperations); + } else if (type == qc::OpType::AodActivate) { // flying ancilla + // need to move back if dodged + for (auto& ancilla : *ancillas) { + if (ancilla.coord.x == aodMove->init) { + const auto start = + ancilla.coordDodged.x * arch->getInterQubitDistance() + + ancilla.offsetDodged.x * arch->getOffsetDistance(); + const auto end = ancilla.coord.x * arch->getInterQubitDistance() + + ancilla.offset.x * arch->getOffsetDistance(); + offsetOperations.emplace_back(Dimension::X, start, end); + ancilla.coordDodged.x = aodMove->init; + ancilla.offsetDodged.x = aodMove->offset; + const auto coord = + (ancilla.coord.x + (ancilla.coord.y * arch->getNcolumns())) + + arch->getNpositions(); + if (std::find(qubitsOffset.begin(), qubitsOffset.end(), + coord + arch->getNpositions()) == qubitsOffset.end()) { + qubitsOffset.emplace_back(coord + arch->getNpositions()); + qubitsOffset.emplace_back(coord); + } + } + } } } for (const auto& aodMove : activation.activateYs) { if (aodMove->load) { computeInitAndOffsetOperations(Dimension::Y, aodMove, initOperations, offsetOperations); + } else if (type == qc::OpType::AodActivate) { // flying ancilla + // need to move back if dodged + for (auto& ancilla : *ancillas) { + if (ancilla.coord.y == aodMove->init) { + const auto start = + ancilla.coordDodged.y * arch->getInterQubitDistance() + + ancilla.offsetDodged.y * arch->getOffsetDistance(); + const auto end = ancilla.coord.y * arch->getInterQubitDistance() + + ancilla.offset.y * arch->getOffsetDistance(); + offsetOperations.emplace_back(Dimension::Y, start, end); + ancilla.coordDodged.y = aodMove->init; + ancilla.offsetDodged.y = aodMove->offset; + const auto coord = + (ancilla.coord.x + (ancilla.coord.y * arch->getNcolumns())) + + arch->getNpositions(); + if (std::find(qubitsOffset.begin(), qubitsOffset.end(), + coord + arch->getNpositions()) == qubitsOffset.end()) { + qubitsOffset.emplace_back(coord + arch->getNpositions()); + qubitsOffset.emplace_back(coord); + } + } + } } } if (initOperations.empty() && offsetOperations.empty()) { return {}; } + std::vector aodOperations; - auto initOp = AodOperation(type, qubitsActivation, initOperations); - auto offsetOp = - AodOperation(qc::OpType::AodMove, qubitsActivation, offsetOperations); + if (initOperations.empty()) { + return {AodOperation(qc::OpType::AodMove, qubitsOffset, offsetOperations)}; + } + if (offsetOperations.empty()) { + return {AodOperation(type, qubitsActivation, initOperations)}; + } if (this->type == qc::OpType::AodActivate) { - return {initOp, offsetOp}; + return {AodOperation(type, qubitsActivation, initOperations), + AodOperation(qc::OpType::AodMove, qubitsOffset, offsetOperations)}; } - return {offsetOp, initOp}; + return {AodOperation(qc::OpType::AodMove, qubitsOffset, offsetOperations), + AodOperation(type, qubitsActivation, initOperations)}; } std::vector -MoveToAodConverter::AodActivationHelper::getAodOperations( - const AncillaAtoms& ancillas) const { +MoveToAodConverter::AodActivationHelper::getAodOperations() const { std::vector aodOperations; for (const auto& activation : allActivations) { auto operations = getAodOperation(activation); // insert ancilla dodging operations - auto dodgingOperation = getDodgingOperation(activation, ancillas); - if (dodgingOperation.getNqubits() != 0) { - aodOperations.emplace_back(dodgingOperation); - } aodOperations.insert(aodOperations.end(), operations.begin(), operations.end()); + } + return aodOperations; +} + +std::vector +MoveToAodConverter::AodActivationHelper::getDodgingOperations() const { + std::vector aodOperations; + for (const auto& activation : allActivations) { + auto dodgingOperation = getDodgingOperation(activation); if (dodgingOperation.getNqubits() != 0) { - aodOperations.emplace_back(dodgingOperation.getInverted()); + aodOperations.emplace_back(dodgingOperation); } } + if (type == qc::OpType::AodDeactivate) { + std::reverse(aodOperations.begin(), aodOperations.end()); + } return aodOperations; } + AodOperation MoveToAodConverter::AodActivationHelper::getDodgingOperation( - const AodActivation& aodActivation, AncillaAtoms ancillas) const { + const AodActivation& aodActivation) const { std::vector dodgingOperations; CoordIndices usedQubits; - // invert order of ancillas to avoid aod crossings of flying Ancillas - // std::reverse(ancillas.begin(), ancillas.end()); const std::vector dims = {Dimension::X, Dimension::Y}; const std::vector>> activations = { @@ -688,16 +758,11 @@ AodOperation MoveToAodConverter::AodActivationHelper::getDodgingOperation( const auto& aodActivations = activations[i]; std::set usedOffsets; - for (auto it = ancillas.rbegin(); it != ancillas.rend(); ++it) { - const auto ancillaOffset = *it; + for (auto it = ancillas->rbegin(); it != ancillas->rend(); ++it) { + auto& ancillaOffset = *it; CoordIndex rowOrColumn = 0; - if (dim == Dimension::X) { - rowOrColumn = - (ancillaOffset.first - arch->getNpositions()) % arch->getNcolumns(); - } else { - rowOrColumn = - (ancillaOffset.first - arch->getNpositions()) / arch->getNcolumns(); - } + rowOrColumn = + dim == Dimension::X ? ancillaOffset.coord.x : ancillaOffset.coord.y; for (const auto& aodMove : aodActivations) { if (aodMove->init == rowOrColumn) { // ancilla is in the same row/column as the activation @@ -718,8 +783,8 @@ AodOperation MoveToAodConverter::AodActivationHelper::getDodgingOperation( // make AodOperation const auto ancillaOffsetDim = dim == Dimension::X - ? ancillaOffset.second.first - : ancillaOffset.second.second; + ? ancillaOffset.offset.x + : ancillaOffset.offset.y; const auto start = (rowOrColumn * arch->getInterQubitDistance()) + (ancillaOffsetDim * arch->getInterQubitDistance() / arch->getNAodIntermediateLevels()); @@ -728,13 +793,21 @@ AodOperation MoveToAodConverter::AodActivationHelper::getDodgingOperation( arch->getNAodIntermediateLevels()); if (std::abs(start - end) > 0.0001) { dodgingOperations.emplace_back(dim, start, end); - if (std::find(usedQubits.begin(), usedQubits.end(), - ancillaOffset.first) == usedQubits.end()) { - usedQubits.emplace_back(ancillaOffset.first); - usedQubits.emplace_back(ancillaOffset.first + - arch->getNpositions()); + const auto coord = (ancillaOffset.coord.x + + (ancillaOffset.coord.y * arch->getNcolumns())) + + arch->getNpositions(); + if (std::find(usedQubits.begin(), usedQubits.end(), coord) == + usedQubits.end()) { + usedQubits.emplace_back(coord); + usedQubits.emplace_back(coord + arch->getNpositions()); } usedOffsets.insert(newOffset); + + if (dim == Dimension::X) { + ancillaOffset.offsetDodged.x = newOffset; + } else { + ancillaOffset.offsetDodged.y = newOffset; + } } } } diff --git a/test/hybridmap/test_hybridmap.cpp b/test/hybridmap/test_hybridmap.cpp index 2c95bebd0..687c7c7fe 100644 --- a/test/hybridmap/test_hybridmap.cpp +++ b/test/hybridmap/test_hybridmap.cpp @@ -154,10 +154,17 @@ class NeutralAtomMapperTest : public ::testing::Test { TEST_F(NeutralAtomMapperTest, Output) { setvbuf(stdout, NULL, _IONBF, 0); auto qcMapped = mapper.map(qc, initialMapping); - qcMapped.dumpOpenQASM(std::cout, false); + // qcMapped.dumpOpenQASM(std::cout, false); + // write to file + std::ofstream ofs("test.qasm"); + qcMapped.dumpOpenQASM(ofs, false); + ofs.close(); auto qcAodMapped = mapper.convertToAod(); qcAodMapped.dumpOpenQASM(std::cout, false); + std::ofstream ofsAod("test_aod.qasm"); + qcAodMapped.dumpOpenQASM(ofsAod, false); + ofsAod.close(); const auto scheduleResults = mapper.schedule(true, true); mapper.saveAnimationFiles("test"); From e3cbe75070182104ddaf0b81f18b78890f981f9d Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Tue, 11 Feb 2025 14:14:52 +0100 Subject: [PATCH 133/394] =?UTF-8?q?=F0=9F=94=A5=20removed=20dodging?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/MoveToAodConverter.hpp | 13 -- src/hybridmap/HybridNeutralAtomMapper.cpp | 9 +- src/hybridmap/MoveToAodConverter.cpp | 166 +--------------------- 3 files changed, 7 insertions(+), 181 deletions(-) diff --git a/include/hybridmap/MoveToAodConverter.hpp b/include/hybridmap/MoveToAodConverter.hpp index 9f08215d5..12fb7a836 100644 --- a/include/hybridmap/MoveToAodConverter.hpp +++ b/include/hybridmap/MoveToAodConverter.hpp @@ -229,10 +229,6 @@ class MoveToAodConverter { */ [[nodiscard]] std::vector getAodOperations() const; - [[nodiscard]] AodOperation - getDodgingOperation(const AodActivation& aodActivation) const; - [[nodiscard]] std::vector getDodgingOperations() const; - [[nodiscard]] static std::vector reverseActivations(const std::vector& ops); }; @@ -376,14 +372,5 @@ class MoveToAodConverter { */ [[nodiscard]] auto getNMoveGroups() const { return moveGroups.size(); } }; -inline std::vector -MoveToAodConverter::AodActivationHelper::reverseActivations( - const std::vector& ops) { - std::vector reversedOps; - for (auto it = ops.rbegin(); it != ops.rend(); ++it) { - reversedOps.emplace_back(it->getInverted()); - } - return reversedOps; -} } // namespace na diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 18af50fd5..69dc92a62 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -42,6 +42,7 @@ void NeutralAtomMapper::mapAppend(qc::QuantumComputation& qc, "Not enough qubits in architecture for circuit and flying ancillas"); } mappedQc.addAncillaryRegister(this->arch->getNpositions()); + mappedQc.addAncillaryRegister(this->arch->getNpositions(), "fa"); mapping = std::move(initialMapping); @@ -559,8 +560,8 @@ void NeutralAtomMapper::applyFlyingAncilla(NeutralAtomLayer& frontLayer, auto usedQubits = faComb.op->getUsedQubits(); const auto nPos = this->arch->getNpositions(); for (const auto& passBy : faComb.moves) { - const auto ancQ1 = passBy.q1 + nPos; - const auto ancQ2 = passBy.q2 + nPos; + const auto ancQ1 = passBy.q1 + (nPos * 2); + const auto ancQ2 = passBy.q2 + (nPos * 2); if (passBy.origin + nPos != ancQ1) { mappedQc.move(passBy.origin + nPos, ancQ1); } @@ -587,8 +588,8 @@ void NeutralAtomMapper::applyFlyingAncilla(NeutralAtomLayer& frontLayer, mappedQc.emplace_back(opCopy->clone()); for (const auto& passBy : faComb.moves) { - const auto ancQ1 = passBy.q1 + nPos; - const auto ancQ2 = passBy.q2 + nPos; + const auto ancQ1 = passBy.q1 + (nPos * 2); + const auto ancQ2 = passBy.q2 + (nPos * 2); mappedQc.move(ancQ2, ancQ1); mappedQc.h(ancQ1); mappedQc.cz(passBy.q1, ancQ1); diff --git a/src/hybridmap/MoveToAodConverter.cpp b/src/hybridmap/MoveToAodConverter.cpp index 3fa1f4d61..6d4aaa6eb 100644 --- a/src/hybridmap/MoveToAodConverter.cpp +++ b/src/hybridmap/MoveToAodConverter.cpp @@ -84,7 +84,7 @@ void MoveToAodConverter::initFlyingAncillas() { auto coord = ancilla.coord.x + (ancilla.coord.y * arch.getNcolumns()); const auto offsets = ancilla.offset; coords.emplace_back(coord); - coord -= arch.getNpositions(); + coord -= 2 * arch.getNpositions(); const auto column = (coord % arch.getNcolumns()); const auto row = (coord / arch.getNcolumns()); @@ -378,23 +378,8 @@ void MoveToAodConverter::processMoveGroups() { possibleNewMoveGroup = MoveGroup(); groupIt--; } - // const auto initFaDodgeOps = aodActivationHelper.getDodgingOperations(); - const auto initActivations = aodActivationHelper.getAodOperations(); - const auto finalFaDodgeOps = aodDeactivationHelper.getDodgingOperations(); - - // append to init ops - // groupIt->processedOpsInit.insert(groupIt->processedOpsInit.end(), - // initFaDodgeOps.begin(), - // initFaDodgeOps.end()); - groupIt->processedOpsInit.insert(groupIt->processedOpsInit.end(), - initActivations.begin(), - initActivations.end()); - groupIt->processedOpsInit.insert(groupIt->processedOpsInit.end(), - finalFaDodgeOps.begin(), - finalFaDodgeOps.end()); - + groupIt->processedOpsInit = aodActivationHelper.getAodOperations(); groupIt->processedOpsFinal = aodDeactivationHelper.getAodOperations(); - groupIt->processedOpShuttle = MoveGroup::connectAodOperations( aodActivationHelper, aodDeactivationHelper); int a; @@ -419,22 +404,6 @@ MoveToAodConverter::processMoves( origin, v, target, vReverse, Dimension::X); auto canAddY = canAddActivation(aodActivationHelper, aodDeactivationHelper, origin, v, target, vReverse, Dimension::Y); - const bool isFa = !move.load1 && !move.load2; - if (isFa) { - // convert Merge to append for FA - if (canAddX.first == ActivationMergeType::Merge) { - canAddX.first = ActivationMergeType::Append; - } - if (canAddY.first == ActivationMergeType::Merge) { - canAddY.first = ActivationMergeType::Append; - } - if (canAddX.second == ActivationMergeType::Merge) { - canAddX.second = ActivationMergeType::Append; - } - if (canAddY.second == ActivationMergeType::Merge) { - canAddY.second = ActivationMergeType::Append; - } - } auto activationCanAddXY = std::make_pair(canAddX.first, canAddY.first); auto deactivationCanAddXY = std::make_pair(canAddX.second, canAddY.second); if (activationCanAddXY.first == ActivationMergeType::Impossible || @@ -646,56 +615,12 @@ MoveToAodConverter::AodActivationHelper::getAodOperation( if (aodMove->load) { computeInitAndOffsetOperations(Dimension::X, aodMove, initOperations, offsetOperations); - } else if (type == qc::OpType::AodActivate) { // flying ancilla - // need to move back if dodged - for (auto& ancilla : *ancillas) { - if (ancilla.coord.x == aodMove->init) { - const auto start = - ancilla.coordDodged.x * arch->getInterQubitDistance() + - ancilla.offsetDodged.x * arch->getOffsetDistance(); - const auto end = ancilla.coord.x * arch->getInterQubitDistance() + - ancilla.offset.x * arch->getOffsetDistance(); - offsetOperations.emplace_back(Dimension::X, start, end); - ancilla.coordDodged.x = aodMove->init; - ancilla.offsetDodged.x = aodMove->offset; - const auto coord = - (ancilla.coord.x + (ancilla.coord.y * arch->getNcolumns())) + - arch->getNpositions(); - if (std::find(qubitsOffset.begin(), qubitsOffset.end(), - coord + arch->getNpositions()) == qubitsOffset.end()) { - qubitsOffset.emplace_back(coord + arch->getNpositions()); - qubitsOffset.emplace_back(coord); - } - } - } } } for (const auto& aodMove : activation.activateYs) { if (aodMove->load) { computeInitAndOffsetOperations(Dimension::Y, aodMove, initOperations, offsetOperations); - } else if (type == qc::OpType::AodActivate) { // flying ancilla - // need to move back if dodged - for (auto& ancilla : *ancillas) { - if (ancilla.coord.y == aodMove->init) { - const auto start = - ancilla.coordDodged.y * arch->getInterQubitDistance() + - ancilla.offsetDodged.y * arch->getOffsetDistance(); - const auto end = ancilla.coord.y * arch->getInterQubitDistance() + - ancilla.offset.y * arch->getOffsetDistance(); - offsetOperations.emplace_back(Dimension::Y, start, end); - ancilla.coordDodged.y = aodMove->init; - ancilla.offsetDodged.y = aodMove->offset; - const auto coord = - (ancilla.coord.x + (ancilla.coord.y * arch->getNcolumns())) + - arch->getNpositions(); - if (std::find(qubitsOffset.begin(), qubitsOffset.end(), - coord + arch->getNpositions()) == qubitsOffset.end()) { - qubitsOffset.emplace_back(coord + arch->getNpositions()); - qubitsOffset.emplace_back(coord); - } - } - } } } if (initOperations.empty() && offsetOperations.empty()) { @@ -728,91 +653,4 @@ MoveToAodConverter::AodActivationHelper::getAodOperations() const { } return aodOperations; } - -std::vector -MoveToAodConverter::AodActivationHelper::getDodgingOperations() const { - std::vector aodOperations; - for (const auto& activation : allActivations) { - auto dodgingOperation = getDodgingOperation(activation); - if (dodgingOperation.getNqubits() != 0) { - aodOperations.emplace_back(dodgingOperation); - } - } - if (type == qc::OpType::AodDeactivate) { - std::reverse(aodOperations.begin(), aodOperations.end()); - } - return aodOperations; -} - -AodOperation MoveToAodConverter::AodActivationHelper::getDodgingOperation( - const AodActivation& aodActivation) const { - std::vector dodgingOperations; - CoordIndices usedQubits; - - const std::vector dims = {Dimension::X, Dimension::Y}; - const std::vector>> activations = { - aodActivation.activateXs, aodActivation.activateYs}; - // iterate in parallel over both pairs - for (size_t i = 0; i < dims.size(); i++) { - const auto& dim = dims[i]; - const auto& aodActivations = activations[i]; - std::set usedOffsets; - - for (auto it = ancillas->rbegin(); it != ancillas->rend(); ++it) { - auto& ancillaOffset = *it; - CoordIndex rowOrColumn = 0; - rowOrColumn = - dim == Dimension::X ? ancillaOffset.coord.x : ancillaOffset.coord.y; - for (const auto& aodMove : aodActivations) { - if (aodMove->init == rowOrColumn) { - // ancilla is in the same row/column as the activation - // move ancilla away - auto sign = aodMove->delta > 0 ? 1 : -1; - if (type == qc::OpType::AodDeactivate) { - sign *= -1; - } - auto newOffset = aodMove->offset + sign; - if (newOffset == 0) { - newOffset += sign; // always stay in between the atoms - } - // check if same offset is used by other dodging operation in same - // row/column - while (usedOffsets.find(newOffset) != usedOffsets.end()) { - newOffset += sign; - } - - // make AodOperation - const auto ancillaOffsetDim = dim == Dimension::X - ? ancillaOffset.offset.x - : ancillaOffset.offset.y; - const auto start = (rowOrColumn * arch->getInterQubitDistance()) + - (ancillaOffsetDim * arch->getInterQubitDistance() / - arch->getNAodIntermediateLevels()); - const auto end = (rowOrColumn * arch->getInterQubitDistance()) + - (newOffset * arch->getInterQubitDistance() / - arch->getNAodIntermediateLevels()); - if (std::abs(start - end) > 0.0001) { - dodgingOperations.emplace_back(dim, start, end); - const auto coord = (ancillaOffset.coord.x + - (ancillaOffset.coord.y * arch->getNcolumns())) + - arch->getNpositions(); - if (std::find(usedQubits.begin(), usedQubits.end(), coord) == - usedQubits.end()) { - usedQubits.emplace_back(coord); - usedQubits.emplace_back(coord + arch->getNpositions()); - } - usedOffsets.insert(newOffset); - - if (dim == Dimension::X) { - ancillaOffset.offsetDodged.x = newOffset; - } else { - ancillaOffset.offsetDodged.y = newOffset; - } - } - } - } - } - } - return {qc::OpType::AodMove, usedQubits, dodgingOperations}; -} } // namespace na From 8f9031d5fc8645ad9c9f034b8813901eb0388751 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Tue, 11 Feb 2025 15:17:35 +0100 Subject: [PATCH 134/394] =?UTF-8?q?=E2=9C=A8=20Updated=20animation=20for?= =?UTF-8?q?=20new=20CZ=20gates.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/MoveToAodConverter.hpp | 3 ++- include/hybridmap/NeutralAtomArchitecture.hpp | 3 ++- src/hybridmap/HybridAnimation.cpp | 8 +++++--- src/hybridmap/NeutralAtomArchitecture.cpp | 3 ++- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/include/hybridmap/MoveToAodConverter.hpp b/include/hybridmap/MoveToAodConverter.hpp index 12fb7a836..6cbb914fc 100644 --- a/include/hybridmap/MoveToAodConverter.hpp +++ b/include/hybridmap/MoveToAodConverter.hpp @@ -348,7 +348,8 @@ class MoveToAodConverter { "columns and rows of the neutral atom architecture."); } for (auto i = 0; i < flyingAncillas.getInitHwPos().size(); ++i) { - const auto coord = flyingAncillas.getInitHwPos().at(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}, diff --git a/include/hybridmap/NeutralAtomArchitecture.hpp b/include/hybridmap/NeutralAtomArchitecture.hpp index 4c332306c..5d7ad7666 100644 --- a/include/hybridmap/NeutralAtomArchitecture.hpp +++ b/include/hybridmap/NeutralAtomArchitecture.hpp @@ -569,7 +569,8 @@ class NeutralAtomArchitecture { } } const std::string toReplace = "XXX"; - const std::string replaceWith = std::to_string(getInteractionRadius()); + const std::string replaceWith = + std::to_string(getInteractionRadius() * getInterQubitDistance()); size_t pos = 0; while ((pos = style.find(toReplace, pos)) != std::string::npos) { diff --git a/src/hybridmap/HybridAnimation.cpp b/src/hybridmap/HybridAnimation.cpp index daea39105..2363e793b 100644 --- a/src/hybridmap/HybridAnimation.cpp +++ b/src/hybridmap/HybridAnimation.cpp @@ -35,7 +35,7 @@ void AnimationAtoms::initPositions( auto flyingAncillaidxPlusOne = 0; for (const auto& [id, coord] : initFaPos) { flyingAncillaidxPlusOne++; - coordIdxToId[coord + arch.getNpositions()] = id + initHwPos.size(); + coordIdxToId[coord + (2 * arch.getNpositions())] = id + initHwPos.size(); const auto column = coord % nCols; const auto row = coord / nCols; const auto offset = @@ -132,11 +132,13 @@ std::string AnimationAtoms::opToNaViz(const std::unique_ptr& op, } // must be a gate } else if (op->getNqubits() > 1) { + opString += "@" + std::to_string(startTime) + " cz {"; for (const auto& coordIdx : op->getUsedQubits()) { const auto id = coordIdxToId.at(coordIdx); - opString += "@" + std::to_string(startTime) + " cz atom" + - std::to_string(id) + "\n"; + opString += " atom" + std::to_string(id) + ","; } + opString.pop_back(); + opString += "}\n"; } else { // single qubit gate const auto coordIdx = op->getTargets().front(); diff --git a/src/hybridmap/NeutralAtomArchitecture.cpp b/src/hybridmap/NeutralAtomArchitecture.cpp index aabc6b0a7..6c76ef0bb 100644 --- a/src/hybridmap/NeutralAtomArchitecture.cpp +++ b/src/hybridmap/NeutralAtomArchitecture.cpp @@ -254,7 +254,8 @@ std::string NeutralAtomArchitecture::getAnimationMachine( "\n\tunit: \"us\"\n}\n"; animationMachine += "distance {\n\tinteraction: " + - std::to_string(this->getInteractionRadius()) + + std::to_string(this->getInteractionRadius() * + this->getInterQubitDistance()) + "\n\tunit: \"um\"\n}\n"; const auto zoneStart = -this->getInterQubitDistance(); const auto zoneEndX = this->getNcolumns() * this->getInterQubitDistance() + From 903df9cf21290ce022dae217b9dcde1bec5b5431 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Tue, 11 Feb 2025 16:44:20 +0100 Subject: [PATCH 135/394] =?UTF-8?q?=F0=9F=90=9B=20Fixed=20minor=20animatio?= =?UTF-8?q?n=20bugs.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/NeutralAtomArchitecture.cpp | 6 ++++-- src/hybridmap/NeutralAtomScheduler.cpp | 12 ++++++------ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/hybridmap/NeutralAtomArchitecture.cpp b/src/hybridmap/NeutralAtomArchitecture.cpp index 6c76ef0bb..e3ce03371 100644 --- a/src/hybridmap/NeutralAtomArchitecture.cpp +++ b/src/hybridmap/NeutralAtomArchitecture.cpp @@ -293,7 +293,7 @@ qc::fp NeutralAtomArchitecture::getOpTime(const qc::Operation* op) const { return (distanceX + distanceY) / v; } std::string opName; - for (size_t i = 0; i < op->getNcontrols(); ++i) { + for (size_t i = 0; i < op->getNqubits() - 1; ++i) { opName += "c"; } if (op->getType() == qc::OpType::P) { @@ -317,7 +317,7 @@ qc::fp NeutralAtomArchitecture::getOpFidelity(const qc::Operation* op) const { return getShuttlingAverageFidelity(op->getType()); } std::string opName; - for (size_t i = 0; i < op->getNcontrols(); ++i) { + for (size_t i = 0; i < op->getNqubits() - 1; ++i) { opName += "c"; } opName += op->getName(); @@ -346,6 +346,8 @@ NeutralAtomArchitecture::getBlockedCoordIndices(const qc::Operation* op) const { auto const distance = getEuclideanDistance(coord, i); if (distance <= getBlockingFactor() * getInteractionRadius()) { blockedCoordIndices.emplace(i); + blockedCoordIndices.emplace(i + getNpositions()); + blockedCoordIndices.emplace(i + (2 * getNpositions())); } } } diff --git a/src/hybridmap/NeutralAtomScheduler.cpp b/src/hybridmap/NeutralAtomScheduler.cpp index 1986f5559..b0cb85128 100644 --- a/src/hybridmap/NeutralAtomScheduler.cpp +++ b/src/hybridmap/NeutralAtomScheduler.cpp @@ -34,11 +34,10 @@ na::SchedulerResults na::NeutralAtomScheduler::schedule( std::cout << "\n* schedule start!\n"; } - std::vector totalExecutionTimes(arch->getNpositions(), 0); + std::vector totalExecutionTimes(3 * arch->getNpositions(), 0); // saves for each coord the time slots that are blocked by a multi qubit gate std::vector rydbergBlockedQubitsTimes( - arch->getNpositions() + arch->getNpositions(), - std::deque>()); + 3 * arch->getNpositions(), std::deque>()); qc::fp aodLastBlockedTime = 0; qc::fp totalGateTime = 0; qc::fp totalGateFidelities = 1; @@ -112,7 +111,7 @@ na::SchedulerResults na::NeutralAtomScheduler::schedule( (start <= maxTime + opTime && end > maxTime + opTime)) { rydbergBlocked = true; // update maxTime to the end of the blocking - maxTime = end; + maxTime = std::max(maxTime, end); // remove the blocking break; } @@ -149,8 +148,9 @@ na::SchedulerResults na::NeutralAtomScheduler::schedule( totalGateFidelities *= opFidelity; totalGateTime += opTime; if (verbose) { - std::cout << "\n"; - printTotalExecutionTimes(totalExecutionTimes, rydbergBlockedQubitsTimes); + // std::cout << "\n"; + // printTotalExecutionTimes(totalExecutionTimes, + // rydbergBlockedQubitsTimes); } // update animation From c370c5c90dc9067c1a98ef5ac1a0c6300b4370f1 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Tue, 11 Feb 2025 16:47:22 +0100 Subject: [PATCH 136/394] =?UTF-8?q?=F0=9F=94=A5=20removed=20debug=20statem?= =?UTF-8?q?ent.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/MoveToAodConverter.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/hybridmap/MoveToAodConverter.cpp b/src/hybridmap/MoveToAodConverter.cpp index 6d4aaa6eb..56fd504cc 100644 --- a/src/hybridmap/MoveToAodConverter.cpp +++ b/src/hybridmap/MoveToAodConverter.cpp @@ -382,7 +382,6 @@ void MoveToAodConverter::processMoveGroups() { groupIt->processedOpsFinal = aodDeactivationHelper.getAodOperations(); groupIt->processedOpShuttle = MoveGroup::connectAodOperations( aodActivationHelper, aodDeactivationHelper); - int a; } } std::pair, MoveToAodConverter::MoveGroup> From 6ec93515aabe2252dd065a5158ab193e0d85de9d Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Tue, 11 Feb 2025 17:03:46 +0100 Subject: [PATCH 137/394] =?UTF-8?q?=E2=9C=A8=20added=20minor=20postprocess?= =?UTF-8?q?ing=20to=20avoid=20unneeded=20loading.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/MoveToAodConverter.hpp | 1 + src/hybridmap/MoveToAodConverter.cpp | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/include/hybridmap/MoveToAodConverter.hpp b/include/hybridmap/MoveToAodConverter.hpp index 6cbb914fc..2eb9f39f0 100644 --- a/include/hybridmap/MoveToAodConverter.hpp +++ b/include/hybridmap/MoveToAodConverter.hpp @@ -325,6 +325,7 @@ class MoveToAodConverter { * is created for the remaining moves. */ void processMoveGroups(); + void postProcessMoveGroups(); std::pair, MoveGroup> processMoves(const std::vector>& moves, diff --git a/src/hybridmap/MoveToAodConverter.cpp b/src/hybridmap/MoveToAodConverter.cpp index 56fd504cc..067ef2f6a 100644 --- a/src/hybridmap/MoveToAodConverter.cpp +++ b/src/hybridmap/MoveToAodConverter.cpp @@ -34,6 +34,7 @@ MoveToAodConverter::schedule(qc::QuantumComputation& qc) { return qc; } processMoveGroups(); + postProcessMoveGroups(); // create new quantum circuit and insert AOD operations at the correct // indices @@ -384,6 +385,29 @@ void MoveToAodConverter::processMoveGroups() { aodActivationHelper, aodDeactivationHelper); } } +void MoveToAodConverter::postProcessMoveGroups() { + for (auto groupIt = moveGroups.begin(); groupIt != moveGroups.end() - 1; + ++groupIt) { + auto nextGroupIt = groupIt + 1; + for (const auto& move : groupIt->moves) { + for (const auto& moveNext : nextGroupIt->moves) { + if (move.first.c2 == moveNext.first.c1 && move.first.load2 && + moveNext.first.load1) { + // moveNext is dependent on move + // moveNext can only be executed + if (groupIt->processedOpsFinal.size() == 2 && + groupIt->processedOpsInit.size() == 2) { + groupIt->processedOpsFinal.pop_back(); + // next remove first move from next group + nextGroupIt->processedOpsInit.erase( + nextGroupIt->processedOpsInit.begin()); + } + } + } + } + } +} + std::pair, MoveToAodConverter::MoveGroup> MoveToAodConverter::processMoves( const std::vector>& moves, From 90ed70cd3d7c2edf05ef4f16f3684005ef5c0d97 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Tue, 11 Feb 2025 17:04:57 +0100 Subject: [PATCH 138/394] =?UTF-8?q?=F0=9F=92=84=20Removed=20ticks=20from?= =?UTF-8?q?=20style.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/default_style.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/hybridmap/default_style.hpp b/include/hybridmap/default_style.hpp index f9f977c6a..3a6f25677 100644 --- a/include/hybridmap/default_style.hpp +++ b/include/hybridmap/default_style.hpp @@ -34,7 +34,7 @@ inline const char* defaultStyle = "\"AOD\"\n }\n legend {\n display: true\n title: " "\"Atom States\"\n }\n}\n\ncoordinate {\n tick {\n x: " "20\n y: 20\n color: #999999\n line " - "{\n thickness: 0.5\n dash " + "{\n thickness: 0.0\n dash " "{\n length: 0\n " " duty: 100%\n }\n }\n }\n number " "{\n x {\n distance: 40\n " From bd339e82011a2dbef1b03589b46b447ab97599de Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Tue, 11 Feb 2025 21:04:28 +0100 Subject: [PATCH 139/394] =?UTF-8?q?=E2=9C=A8First=20draft=20for=20seperate?= =?UTF-8?q?d=20Fa=20Moves.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/MoveToAodConverter.hpp | 18 ++++++- src/hybridmap/MoveToAodConverter.cpp | 61 ++++++++++++++++++++---- 2 files changed, 70 insertions(+), 9 deletions(-) diff --git a/include/hybridmap/MoveToAodConverter.hpp b/include/hybridmap/MoveToAodConverter.hpp index 2eb9f39f0..cdeb37f1a 100644 --- a/include/hybridmap/MoveToAodConverter.hpp +++ b/include/hybridmap/MoveToAodConverter.hpp @@ -165,6 +165,9 @@ class MoveToAodConverter { addActivation(std::pair merge, const Point& origin, const AtomMove& move, MoveVector v, bool needLoad); + + void addActivationFa(const Point& origin, const AtomMove& move, + MoveVector v, bool needLoad); /** * @brief Merges the given activation into the current activations * @param dim The dimension/direction of the activation @@ -250,6 +253,7 @@ class MoveToAodConverter { // the moves and the index they appear in the original quantum circuit (to // insert them back later) std::vector> moves; + std::vector> movesFa; std::vector processedOpsInit; std::vector processedOpsFinal; AodOperation processedOpShuttle; @@ -277,7 +281,15 @@ class MoveToAodConverter { * @return Circuit index of the first move in the move group */ - [[nodiscard]] uint32_t getFirstIdx() const { return moves.front().second; } + [[nodiscard]] uint32_t getFirstIdx() const { + if (moves.size() == 0) { + return movesFa.front().second; + } + if (movesFa.size() == 0) { + return moves.front().second; + } + return std::min(moves.front().second, movesFa.front().second); + } /** * @brief Checks if the two moves can be executed in parallel * @param v1 The first move @@ -331,6 +343,9 @@ class MoveToAodConverter { processMoves(const std::vector>& moves, AodActivationHelper& aodActivationHelper, AodActivationHelper& aodDeactivationHelper); + void processMovesFa(const std::vector>& movesFa, + AodActivationHelper& aodActivationHelper, + AodActivationHelper& aodDeactivationHelper) const; public: MoveToAodConverter() = delete; @@ -342,6 +357,7 @@ class MoveToAodConverter { : arch(archArg), qcScheduled(arch.getNpositions()), hardwareQubits(hardwareQubitsArg) { qcScheduled.addAncillaryRegister(arch.getNpositions()); + qcScheduled.addAncillaryRegister(arch.getNpositions(), "fa"); if (flyingAncillas.getNumQubits() > arch.getNcolumns() || flyingAncillas.getNumQubits() > arch.getNrows()) { throw std::invalid_argument( diff --git a/src/hybridmap/MoveToAodConverter.cpp b/src/hybridmap/MoveToAodConverter.cpp index 067ef2f6a..5c35dff03 100644 --- a/src/hybridmap/MoveToAodConverter.cpp +++ b/src/hybridmap/MoveToAodConverter.cpp @@ -66,10 +66,10 @@ AtomMove MoveToAodConverter::convertOpToMove(qc::Operation* get) { auto q2 = get->getTargets().back(); const auto load1 = q1 < arch.getNpositions(); const auto load2 = q2 < arch.getNpositions(); - if (!load1) { + while (q1 > arch.getNpositions()) { q1 -= arch.getNpositions(); } - if (!load2) { + while (q2 > arch.getNpositions()) { q2 -= arch.getNpositions(); } return {q1, q2, load1, load2}; @@ -147,8 +147,14 @@ bool MoveToAodConverter::MoveGroup::canAddMove( } // checks if the op can be executed in parallel auto moveVector = archArg.getVector(move.c1, move.c2); + std::vector>* movesToCheck; + if (move.load1 || move.load2) { + movesToCheck = &moves; + } else { + movesToCheck = &movesFa; + } return std::all_of( - moves.begin(), moves.end(), + movesToCheck->begin(), movesToCheck->end(), [&moveVector, &archArg](const std::pair opPair) { auto moveGroup = opPair.first; auto opVector = archArg.getVector(moveGroup.c1, moveGroup.c2); @@ -174,7 +180,11 @@ bool MoveToAodConverter::MoveGroup::parallelCheck(const MoveVector& v1, void MoveToAodConverter::MoveGroup::addMove(const AtomMove& move, const uint32_t idx) { - moves.emplace_back(move, idx); + if (move.load1 || move.load2) { + moves.emplace_back(move, idx); + } else { + movesFa.emplace_back(move, idx); + } qubitsUsedByGates.emplace_back(move.c2); } @@ -271,6 +281,18 @@ void MoveToAodConverter::AodActivationHelper::addActivation( break; } } +void MoveToAodConverter::AodActivationHelper::addActivationFa( + const Point& origin, const AtomMove& move, MoveVector v, bool needLoad) { + const auto x = static_cast(origin.x); + const auto y = static_cast(origin.y); + const auto signX = v.direction.getSignX(); + const auto signY = v.direction.getSignY(); + const auto deltaX = v.xEnd - v.xStart; + const auto deltaY = v.yEnd - v.yStart; + + allActivations.emplace_back(AodActivation{ + {x, deltaX, signX, needLoad}, {y, deltaY, signY, needLoad}, move}); +} [[nodiscard]] std::pair MoveToAodConverter::canAddActivation( @@ -364,6 +386,9 @@ void MoveToAodConverter::processMoveGroups() { auto movesToRemove = resultMoves.first; auto possibleNewMoveGroup = resultMoves.second; + processMovesFa(groupIt->movesFa, aodActivationHelper, + aodDeactivationHelper); + // remove from current move group for (const auto& moveToRemove : movesToRemove) { groupIt->moves.erase( @@ -447,6 +472,23 @@ MoveToAodConverter::processMoves( return {movesToRemove, possibleNewMoveGroup}; } +void MoveToAodConverter::processMovesFa( + const std::vector>& movesFa, + AodActivationHelper& aodActivationHelper, + AodActivationHelper& aodDeactivationHelper) const { + for (const auto& moveFaPair : movesFa) { + const auto& moveFa = moveFaPair.first; + const auto idx = moveFaPair.second; + auto origin = arch.getCoordinate(moveFa.c1); + auto target = arch.getCoordinate(moveFa.c2); + const auto v = arch.getVector(moveFa.c1, moveFa.c2); + const auto vReverse = arch.getVector(moveFa.c2, moveFa.c1); + + aodActivationHelper.addActivationFa(origin, moveFa, v, moveFa.load1); + aodDeactivationHelper.addActivationFa(target, moveFa, vReverse, + moveFa.load2); + } +} AodOperation MoveToAodConverter::MoveGroup::connectAodOperations( const AodActivationHelper& aodActivationHelper, @@ -468,18 +510,21 @@ AodOperation MoveToAodConverter::MoveGroup::connectAodOperations( for (const auto& deactivation : aodDeactivationHelper.allActivations) { if (activation.moves == deactivation.moves) { // get target qubits + const auto nPos = aodActivationHelper.arch->getNpositions(); for (const auto& move : activation.moves) { if (move.load1) { targetQubits.emplace_back(move.c1); + } else if (move.load2) { + targetQubits.emplace_back(move.c1 + nPos); } else { - targetQubits.emplace_back( - move.c1 + aodActivationHelper.arch->getNpositions()); + targetQubits.emplace_back(move.c1 + (2 * nPos)); } if (move.load2) { targetQubits.emplace_back(move.c2); + } else if (move.load1) { + targetQubits.emplace_back(move.c2 + nPos); } else { - targetQubits.emplace_back( - move.c2 + aodActivationHelper.arch->getNpositions()); + targetQubits.emplace_back(move.c2 + (2 * nPos)); } } From a676459eb1acc411c6a470255664a016d843919e Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Tue, 11 Feb 2025 21:44:56 +0100 Subject: [PATCH 140/394] =?UTF-8?q?=F0=9F=90=9B=20Fixed=20FA=20coord=20pro?= =?UTF-8?q?blems.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/MoveToAodConverter.cpp | 4 ++-- src/hybridmap/NeutralAtomArchitecture.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/hybridmap/MoveToAodConverter.cpp b/src/hybridmap/MoveToAodConverter.cpp index 5c35dff03..27c83dc0c 100644 --- a/src/hybridmap/MoveToAodConverter.cpp +++ b/src/hybridmap/MoveToAodConverter.cpp @@ -66,10 +66,10 @@ AtomMove MoveToAodConverter::convertOpToMove(qc::Operation* get) { auto q2 = get->getTargets().back(); const auto load1 = q1 < arch.getNpositions(); const auto load2 = q2 < arch.getNpositions(); - while (q1 > arch.getNpositions()) { + while (q1 >= arch.getNpositions()) { q1 -= arch.getNpositions(); } - while (q2 > arch.getNpositions()) { + while (q2 >= arch.getNpositions()) { q2 -= arch.getNpositions(); } return {q1, q2, load1, load2}; diff --git a/src/hybridmap/NeutralAtomArchitecture.cpp b/src/hybridmap/NeutralAtomArchitecture.cpp index e3ce03371..4fd0838df 100644 --- a/src/hybridmap/NeutralAtomArchitecture.cpp +++ b/src/hybridmap/NeutralAtomArchitecture.cpp @@ -334,7 +334,7 @@ NeutralAtomArchitecture::getBlockedCoordIndices(const qc::Operation* op) const { std::set blockedCoordIndices; for (auto coord : op->getUsedQubits()) { // qubits in ancilla register - if (coord >= getNpositions()) { + while (coord >= getNpositions()) { coord -= getNpositions(); } for (uint32_t i = 0; i < getNpositions(); ++i) { From 52a08ed56a78d45709db81577cb174645f4cb81e Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Tue, 11 Feb 2025 21:57:02 +0100 Subject: [PATCH 141/394] =?UTF-8?q?=F0=9F=90=9B=20Fixed=20missing=20move?= =?UTF-8?q?=20in=20last=20move=20group?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/MoveToAodConverter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hybridmap/MoveToAodConverter.cpp b/src/hybridmap/MoveToAodConverter.cpp index 27c83dc0c..ce8ba1f5c 100644 --- a/src/hybridmap/MoveToAodConverter.cpp +++ b/src/hybridmap/MoveToAodConverter.cpp @@ -132,7 +132,7 @@ void MoveToAodConverter::initMoveGroups(qc::QuantumComputation& qc) { } idx++; } - if (!currentMoveGroup.moves.empty()) { + if (!currentMoveGroup.moves.empty() || !currentMoveGroup.movesFa.empty()) { moveGroups.emplace_back(std::move(currentMoveGroup)); } } From fb4c1852f19d3021b1f51335c80ba2656a6bc67b Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Wed, 12 Feb 2025 10:28:17 +0100 Subject: [PATCH 142/394] =?UTF-8?q?=F0=9F=8E=A8=20Restructured=20graph=20m?= =?UTF-8?q?atching=20algorithm.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HardwareQubits.hpp | 1 - include/hybridmap/HybridNeutralAtomMapper.hpp | 23 +- include/hybridmap/HybridSynthesisMapper.hpp | 5 +- include/hybridmap/Mapping.hpp | 29 ++- include/hybridmap/NeutralAtomUtils.hpp | 10 +- src/hybridmap/HybridNeutralAtomMapper.cpp | 210 +----------------- src/hybridmap/Mapping.cpp | 181 +++++++++++++++ test/hybridmap/test_hybridmap.cpp | 2 +- 8 files changed, 237 insertions(+), 224 deletions(-) diff --git a/include/hybridmap/HardwareQubits.hpp b/include/hybridmap/HardwareQubits.hpp index 0f568b508..92e65a768 100644 --- a/include/hybridmap/HardwareQubits.hpp +++ b/include/hybridmap/HardwareQubits.hpp @@ -98,7 +98,6 @@ class HardwareQubits { switch (initialCoordinateMapping) { case Trivial: - case Graph: for (uint32_t i = 0; i < this->nQubits; ++i) { hwToCoordIdx.emplace(i, i); } diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index fe84d41e7..5d16a5577 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -7,6 +7,7 @@ #include "Definitions.hpp" #include "NeutralAtomLayer.hpp" +#include "circuit_optimizer/CircuitOptimizer.hpp" #include "hybridmap/HardwareQubits.hpp" #include "hybridmap/Mapping.hpp" #include "hybridmap/NeutralAtomArchitecture.hpp" @@ -113,16 +114,8 @@ class NeutralAtomMapper { // The current mapping between circuit qubits and hardware qubits Mapping mapping; - qc::DAG dag; - // Methods for mapping - /** - * @brief GraphMatching for initCoordMapping - */ - void graphMatching(std::vector& qubitIndices, - std::vector& hwIndices, const qc::DAG& dag); - /** * @brief Maps the gate to the mapped quantum circuit. * @param op The gate to map @@ -448,7 +441,8 @@ class NeutralAtomMapper { hardwareQubits(*arch, arch->getNqubits() - p->numFlyingAncillas, p->initialCoordMapping, p->seed), flyingAncillas(*arch, p->numFlyingAncillas, - InitialCoordinateMapping::Trivial, p->seed) { + InitialCoordinateMapping::Trivial, p->seed), + mapping() { if (arch->getNpositions() - arch->getNqubits() < 1 && p->shuttlingWeight > 0) { throw std::runtime_error( @@ -533,20 +527,23 @@ class NeutralAtomMapper { * operations */ qc::QuantumComputation map(qc::QuantumComputation& qc, - Mapping initialMapping) { + const Mapping& initialMapping) { mappedQc = qc::QuantumComputation(arch->getNpositions()); mappedQcAOD = qc::QuantumComputation(arch->getNpositions()); nMoves = 0; nSwaps = 0; nBridges = 0; nFAncillas = 0; - mapAppend(qc, std::move(initialMapping)); + mapAppend(qc, initialMapping); return mappedQc; } qc::QuantumComputation map(qc::QuantumComputation& qc, const InitialMapping initialMapping) { - return map(qc, Mapping(qc.getNqubits(), initialMapping)); + const auto dag = qc::CircuitOptimizer::constructDAG(qc); + const auto actualMapping = + Mapping(qc.getNqubits(), initialMapping, qc, hardwareQubits); + return map(qc, actualMapping); } /** @@ -555,7 +552,7 @@ class NeutralAtomMapper { * @param initialMapping The initial mapping of the circuit qubits to the * hardware qubits */ - void mapAppend(qc::QuantumComputation& qc, Mapping initialMapping); + void mapAppend(qc::QuantumComputation& qc, const Mapping& initialMapping); /** * @brief Maps the given quantum circuit to the given architecture and diff --git a/include/hybridmap/HybridSynthesisMapper.hpp b/include/hybridmap/HybridSynthesisMapper.hpp index 20d53af9f..3880cbc97 100644 --- a/include/hybridmap/HybridSynthesisMapper.hpp +++ b/include/hybridmap/HybridSynthesisMapper.hpp @@ -63,11 +63,10 @@ class HybridSynthesisMapper : public NeutralAtomMapper { * @param nQubits The number of qubits to be mapped. * @param initialMapping The initial mapping to be used. */ - void initMapping(size_t nQubits, - InitialMapping initialMapping = InitialMapping::Identity) { + void initMapping(size_t nQubits) { mappedQc = qc::QuantumComputation(arch->getNpositions()); synthesizedQc = qc::QuantumComputation(nQubits); - mapping = Mapping(nQubits, initialMapping); + mapping = Mapping(nQubits); } /** diff --git a/include/hybridmap/Mapping.hpp b/include/hybridmap/Mapping.hpp index c309f7e4f..14efdd487 100644 --- a/include/hybridmap/Mapping.hpp +++ b/include/hybridmap/Mapping.hpp @@ -6,6 +6,8 @@ #pragma once #include "Definitions.hpp" +#include "HardwareQubits.hpp" +#include "NeutralAtomArchitecture.hpp" #include "hybridmap/NeutralAtomDefinitions.hpp" #include "hybridmap/NeutralAtomUtils.hpp" #include "ir/Permutation.hpp" @@ -16,6 +18,7 @@ #include #include #include +#include namespace na { @@ -27,18 +30,38 @@ class Mapping { protected: // std::map qc::Permutation circToHw; + HardwareQubits hwQubits; + qc::DAG dag; + + /** + * @brief GraphMatching for initCoordMapping + */ + [[nodiscard]] + std::vector graphMatching() const; public: Mapping() = default; - Mapping(const size_t nQubits, const InitialMapping initialMapping) { + explicit Mapping(const size_t nQubits) { + for (size_t i = 0; i < nQubits; ++i) { + circToHw.emplace(i, i); + } + } + Mapping(const size_t nQubits, const InitialMapping initialMapping, + qc::QuantumComputation qc, const HardwareQubits& hwQubits) + : dag(qc::CircuitOptimizer::constructDAG(qc)), hwQubits(hwQubits) { + switch (initialMapping) { case Identity: for (size_t i = 0; i < nQubits; ++i) { circToHw.emplace(i, i); } break; - default: - qc::unreachable(); + case Graph: + const auto qubitIndices = graphMatching(); + for (size_t i = 0; i < nQubits; i++) { + circToHw.emplace(i, qubitIndices[i]); + } + break; } } /** diff --git a/include/hybridmap/NeutralAtomUtils.hpp b/include/hybridmap/NeutralAtomUtils.hpp index bf5dd0865..4e80610a3 100644 --- a/include/hybridmap/NeutralAtomUtils.hpp +++ b/include/hybridmap/NeutralAtomUtils.hpp @@ -36,8 +36,8 @@ class NeutralAtomException final : public std::runtime_error { }; // Enums for the different initial mappings strategies -enum InitialCoordinateMapping : uint8_t { Trivial, Random, Graph }; -enum InitialMapping : uint8_t { Identity }; +enum InitialCoordinateMapping : uint8_t { Trivial, Random }; +enum InitialMapping : uint8_t { Identity, Graph }; enum MappingMethod : uint8_t { SwapMethod, BridgeMethod, @@ -56,9 +56,6 @@ initialCoordinateMappingFromString( if (initialCoordinateMapping == "random" || initialCoordinateMapping == "1") { return InitialCoordinateMapping::Random; } - if (initialCoordinateMapping == "graph" || initialCoordinateMapping == "2") { - return InitialCoordinateMapping::Graph; - } throw std::invalid_argument("Invalid initial coordinate mapping value: " + initialCoordinateMapping); } @@ -68,6 +65,9 @@ initialMappingFromString(const std::string& initialMapping) { if (initialMapping == "identity" || initialMapping == "0") { return InitialMapping::Identity; } + if (initialMapping == "graph" || initialMapping == "1") { + return InitialMapping::Graph; + } throw std::invalid_argument("Invalid initial mapping value: " + initialMapping); } diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 69dc92a62..8f1ebaa54 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -35,7 +35,7 @@ namespace na { void NeutralAtomMapper::mapAppend(qc::QuantumComputation& qc, - Mapping initialMapping) { + const Mapping& initialMapping) { if (qc.getNqubits() + this->parameters->numFlyingAncillas > arch->getNqubits()) { throw std::runtime_error( @@ -44,8 +44,6 @@ void NeutralAtomMapper::mapAppend(qc::QuantumComputation& qc, mappedQc.addAncillaryRegister(this->arch->getNpositions()); mappedQc.addAncillaryRegister(this->arch->getNpositions(), "fa"); - mapping = std::move(initialMapping); - qc::CircuitOptimizer::replaceMCXWithMCZ(qc); qc::CircuitOptimizer::singleQubitGateFusion(qc); qc::CircuitOptimizer::flattenOperations(qc); @@ -53,33 +51,19 @@ void NeutralAtomMapper::mapAppend(qc::QuantumComputation& qc, const auto dag = qc::CircuitOptimizer::constructDAG(qc); - /* - // init mapping - this->mapping = Mapping(nQubits, initialMapping); - - // init coord mapping - std::vector qubitIndices( - nQubits, std::numeric_limits::max()); - std::vector hwIndices(arch.getNpositions(), - std::numeric_limits::max()); - - if (initialCoordinateMapping == Graph) { - graphMatching(qubitIndices, hwIndices, dag); - this->hardwareQubits = - HardwareQubits(arch, initialCoordinateMapping, qubitIndices, hwIndices, - parameters.seed); - } + mapping = initialMapping; - if(this->parameters.verbose){ - std::cout << "* Init Coord Mapping w/ [row:" << arch.getNrows() << " X col:" - << arch.getNcolumns() << "] hardware" << std::endl; for(uint32_t q=0; - q h " << std::setw(3) << this->mapping.getHwQubit(q); std::cout << " -> c " - << std::setw(3) << this->hardwareQubits.getCoordIndex(q) << std::endl; + if (this->parameters->verbose) { + std::cout << "* Init Coord Mapping w/ [row:" << arch->getNrows() + << " X col:" << arch->getNcolumns() << "] hardware" << std::endl; + for (uint32_t q = 0; q < qc.getNqubits(); q++) { + std::cout << "q " << std::setw(3) << q; + std::cout << " -> h " << std::setw(3) << this->mapping.getHwQubit(q); + std::cout << " -> c " << std::setw(3) + << this->hardwareQubits.getCoordIndex(q) << std::endl; } std::cout << std::endl; } - */ // init layers NeutralAtomLayer lookaheadLayer(dag); @@ -118,177 +102,6 @@ void NeutralAtomMapper::mapAppend(qc::QuantumComputation& qc, mappedQc.print(std::cout); } } -// -// void NeutralAtomMapper::graphMatching(std::vector& qubitIndices, -// std::vector& hwIndices, -// const qc::DAG& dag) { -// auto archNqubits = arch->getNqubits(); -// auto archNpositions = arch->getNpositions(); -// auto archNrows = arch->getNrows(); -// auto archNcolumns = arch->getNcolumns(); -// // interaction graph -// std::vector> circGraph( -// dag.size(), std::vector(dag.size(), 0.0)); -// std::vector>> circGraph_degree( -// dag.size()); -// std::vector>> circGraph_neighbor( -// dag.size()); -// for (uint32_t qubit = 0; qubit < dag.size(); ++qubit) { -// for (auto opPtr : dag[qubit]) { -// auto* op = opPtr->get(); -// if (op->getUsedQubits().size() > 1) { -// for (auto i : op->getUsedQubits()) { -// if (i != qubit) { -// circGraph[qubit][i] += 1; -// } -// } -// } -// } -// } -// -// // generate graph matching queue -// for (uint32_t qubit = 0; qubit < dag.size(); qubit++) { -// int cnt = 0; -// double sum = 0; -// for (uint32_t i = 0; i < dag.size(); i++) { -// double weight = circGraph[qubit][i]; -// if (weight > 0) { -// cnt++; -// sum += weight; -// circGraph_neighbor[qubit].emplace_back(i, weight); -// } -// } -// circGraph_degree[qubit] = std::make_pair(qubit, std::make_pair(cnt, -// sum)); -// } -// sort(circGraph_degree.begin(), circGraph_degree.end(), -// [](std::pair> a, -// std::pair> b) { -// if (a.second.first == b.second.first) -// return a.second.second > b.second.second; -// else -// return a.second.first > b.second.first; -// }); -// std::queue circGraph_queue; -// for (const auto i : circGraph_degree) { -// circGraph_queue.push(i.first); -// } -// for (auto& innerVec : circGraph_neighbor) { -// sort(innerVec.begin(), innerVec.end(), -// [](std::pair& a, std::pair& b) { -// return a.second > b.second; -// }); -// } -// -// // graph matching -// bool firstCenter = true; -// uint32_t nMapped = 0; -// uint32_t archCenterX = -// (archNcolumns % 2 == 0) ? (archNcolumns / 2 - 1) : (archNcolumns - 1) / -// 2; -// uint32_t archCenterY = -// (archNrows % 2 == 0) ? (archNrows / 2 - 1) : (archNrows - 1) / 2; -// uint32_t archCenter = archCenterY * archNcolumns + archCenterX; -// -// while (!circGraph_queue.empty() && nMapped != dag.size()) { -// uint32_t qc = circGraph_queue.front(); -// uint32_t hc = std::numeric_limits::max(); // hardwarCenter -// // center mapping -// if (firstCenter) { -// hc = archCenter; -// qubitIndices[qc] = hc; -// hwIndices[hc] = qc; -// firstCenter = false; -// nMapped++; -// } else if (qubitIndices[qc] == std::numeric_limits::max()) -// { -// -// // ref loc -// std::vector refLoc; -// for (auto i : circGraph_neighbor[qc]) { -// if (qubitIndices[i.first] != std::numeric_limits::max()) { -// refLoc.push_back(qubitIndices[i.first]); -// } -// } -// -// // candidate loc -// std::vector> distCandiLoc; -// std::vector initCandiLoc; -// for (int i = 0; i < archNpositions; i++) { -// if (hwIndices[i] == std::numeric_limits::max()) { -// initCandiLoc.push_back(i); -// } -// } -// for (auto v : initCandiLoc) { -// int distSum = 0; -// for (auto r : refLoc) { -// int dist = std::abs(v % archNcolumns - r % archNcolumns) + -// std::abs(v / archNcolumns - r / archNcolumns); -// distSum += dist; -// } -// distCandiLoc.emplace_back(v, distSum); -// } -// sort(distCandiLoc.begin(), distCandiLoc.end(), -// [](std::pair a, std::pair b) { -// return a.second < b.second; -// }); -// -// // find position -// hc = distCandiLoc[0].first; -// qubitIndices[qc] = hc; -// hwIndices[hc] = qc; -// nMapped++; -// } else { -// hc = qubitIndices[qc]; -// } -// -// // neighbor mapping -// if (!circGraph_neighbor[qc].empty()) { -// int idx_qc_n = 0; -// std::vector qc_n; -// for (auto i : circGraph_neighbor[qc]) { -// if (qubitIndices[i.first] != std::numeric_limits::max()) -// continue; -// if (idx_qc_n >= 4) -// continue; -// else { -// qc_n.push_back(i.first); -// idx_qc_n++; -// } -// } -// std::vector hw_n; -// if (hc != std::numeric_limits::max() && qc_n.size() > 0) -// { -// if ((hc + 1) % archNcolumns != 0 && -// hwIndices[hc + 1] == std::numeric_limits::max()) -// hw_n.push_back(hc + 1); // right -// if (hc / archNcolumns < (archNrows - 1) && -// hwIndices[hc + archNcolumns] == -// std::numeric_limits::max()) -// hw_n.push_back(hc + archNcolumns); // down -// if (hc % archNcolumns != 0 && -// hwIndices[hc - 1] == std::numeric_limits::max()) -// hw_n.push_back(hc - 1); // left -// if (hc > archNcolumns && hwIndices[hc - archNcolumns] == -// std::numeric_limits::max()) -// hw_n.push_back(hc - archNcolumns); // up -// } -// -// int minSize = std::min(qc_n.size(), hw_n.size()); -// for (int i = 0; i < minSize; i++) { -// int qc_i = qc_n[i]; -// int hw_i = hw_n[i]; -// qubitIndices[qc_i] = hw_i; -// hwIndices[hw_i] = qc_i; -// nMapped++; -// } -// } -// circGraph_queue.pop(); -// } -// } void NeutralAtomMapper::mapAllPossibleGates(NeutralAtomLayer& frontLayer, NeutralAtomLayer& lookaheadLayer) { @@ -2429,7 +2242,8 @@ MappingMethod NeutralAtomMapper::compareShuttlingAndFlyingAncilla( const auto move = moveDistReduction * moveFidelity; const auto fa = faDistReduction * faFidelity; const auto passBy = faDistReduction * passByFidelity; - return MappingMethod::PassByMethod; + // return MappingMethod::PassByMethod; + return MappingMethod::FlyingAncillaMethod; if (move > fa && move > passBy) { return MappingMethod::MoveMethod; diff --git a/src/hybridmap/Mapping.cpp b/src/hybridmap/Mapping.cpp index 128477c95..09ee4ed06 100644 --- a/src/hybridmap/Mapping.cpp +++ b/src/hybridmap/Mapping.cpp @@ -27,4 +27,185 @@ void Mapping::applySwap(Swap swap) { } } +std::vector Mapping::graphMatching() const { + + // init coord mapping + std::vector qubitIndices( + dag.size(), std::numeric_limits::max()); + std::vector hwIndices(hwQubits.getNumQubits(), + std::numeric_limits::max()); + + for (size_t i = 0; i < dag.size(); ++i) { + qubitIndices[i] = i; + } + return qubitIndices; + + // use hwQubits instead of positions + + // // interaction graph + // std::vector> circGraph( + // dag.size(), std::vector(dag.size(), 0.0)); + // std::vector>> circGraph_degree( + // dag.size()); + // std::vector>> circGraph_neighbor( + // dag.size()); + // for (uint32_t qubit = 0; qubit < dag.size(); ++qubit) { + // for (const auto& opPtr : dag[qubit]) { + // const auto* op = opPtr->get(); + // if (op->getUsedQubits().size() > 1) { + // for (auto i : op->getUsedQubits()) { + // if (i != qubit) { + // circGraph[qubit][i] += 1; + // } + // } + // } + // } + // } + // + // // generate graph matching queue + // for (uint32_t qubit = 0; qubit < dag.size(); qubit++) { + // int cnt = 0; + // double sum = 0; + // for (uint32_t i = 0; i < dag.size(); i++) { + // double weight = circGraph[qubit][i]; + // if (weight > 0) { + // cnt++; + // sum += weight; + // circGraph_neighbor[qubit].emplace_back(i, weight); + // } + // } + // circGraph_degree[qubit] = std::make_pair(qubit, std::make_pair(cnt, + // sum)); + // } + // sort(circGraph_degree.begin(), circGraph_degree.end(), + // [](std::pair> a, + // std::pair> b) { + // if (a.second.first == b.second.first) + // return a.second.second > b.second.second; + // else + // return a.second.first > b.second.first; + // }); + // std::queue circGraph_queue; + // for (const auto i : circGraph_degree) { + // circGraph_queue.push(i.first); + // } + // for (auto& innerVec : circGraph_neighbor) { + // sort(innerVec.begin(), innerVec.end(), + // [](std::pair& a, std::pair& b) { + // return a.second > b.second; + // }); + // } + // + // // graph matching + // bool firstCenter = true; + // uint32_t nMapped = 0; + // uint32_t archCenterX = + // (archNcolumns % 2 == 0) ? (archNcolumns / 2 - 1) : (archNcolumns - 1) / + // 2; + // uint32_t archCenterY = + // (archNrows % 2 == 0) ? (archNrows / 2 - 1) : (archNrows - 1) / 2; + // uint32_t archCenter = archCenterY * archNcolumns + archCenterX; + // const auto start = hwQubits.getClosestQubit(archCenter, {}); + // + // while (!circGraph_queue.empty() && nMapped != dag.size()) { + // uint32_t qc = circGraph_queue.front(); + // uint32_t hc = std::numeric_limits::max(); // hardwarCenter + // // center mapping + // if (firstCenter) { + // hc = archCenter; + // qubitIndices[qc] = hc; + // hwIndices[hc] = qc; + // firstCenter = false; + // nMapped++; + // } else if (qubitIndices[qc] == std::numeric_limits::max()) + // { + // + // // ref loc + // std::vector refLoc; + // for (auto i : circGraph_neighbor[qc]) { + // if (qubitIndices[i.first] != std::numeric_limits::max()) { + // refLoc.push_back(qubitIndices[i.first]); + // } + // } + // + // // candidate loc + // std::vector> distCandiLoc; + // std::vector initCandiLoc; + // for (int i = 0; i < archNpositions; i++) { + // if (hwIndices[i] == std::numeric_limits::max()) { + // initCandiLoc.push_back(i); + // } + // } + // for (auto v : initCandiLoc) { + // int distSum = 0; + // for (auto r : refLoc) { + // int dist = std::abs(v % archNcolumns - r % archNcolumns) + + // std::abs(v / archNcolumns - r / archNcolumns); + // distSum += dist; + // } + // distCandiLoc.emplace_back(v, distSum); + // } + // sort(distCandiLoc.begin(), distCandiLoc.end(), + // [](std::pair a, std::pair b) { + // return a.second < b.second; + // }); + // + // // find position + // hc = distCandiLoc[0].first; + // qubitIndices[qc] = hc; + // hwIndices[hc] = qc; + // nMapped++; + // } else { + // hc = qubitIndices[qc]; + // } + // + // // neighbor mapping + // if (!circGraph_neighbor[qc].empty()) { + // int idx_qc_n = 0; + // std::vector qc_n; + // for (auto i : circGraph_neighbor[qc]) { + // if (qubitIndices[i.first] != std::numeric_limits::max()) + // continue; + // if (idx_qc_n >= 4) + // continue; + // else { + // qc_n.push_back(i.first); + // idx_qc_n++; + // } + // } + // std::vector hw_n; + // if (hc != std::numeric_limits::max() && qc_n.size() > 0) + // { + // if ((hc + 1) % archNcolumns != 0 && + // hwIndices[hc + 1] == std::numeric_limits::max()) + // hw_n.push_back(hc + 1); // right + // if (hc / archNcolumns < (archNrows - 1) && + // hwIndices[hc + archNcolumns] == + // std::numeric_limits::max()) + // hw_n.push_back(hc + archNcolumns); // down + // if (hc % archNcolumns != 0 && + // hwIndices[hc - 1] == std::numeric_limits::max()) + // hw_n.push_back(hc - 1); // left + // if (hc > archNcolumns && hwIndices[hc - archNcolumns] == + // std::numeric_limits::max()) + // hw_n.push_back(hc - archNcolumns); // up + // } + // + // int minSize = std::min(qc_n.size(), hw_n.size()); + // for (int i = 0; i < minSize; i++) { + // int qc_i = qc_n[i]; + // int hw_i = hw_n[i]; + // qubitIndices[qc_i] = hw_i; + // hwIndices[hw_i] = qc_i; + // nMapped++; + // } + // } + // circGraph_queue.pop(); + // } + // return qubitIndices; +} + } // namespace na diff --git a/test/hybridmap/test_hybridmap.cpp b/test/hybridmap/test_hybridmap.cpp index 687c7c7fe..0992980c2 100644 --- a/test/hybridmap/test_hybridmap.cpp +++ b/test/hybridmap/test_hybridmap.cpp @@ -126,7 +126,7 @@ class NeutralAtomMapperTest : public ::testing::Test { std::string testArchitecturePath = "architectures/rubidium_shuttling.json"; const na::NeutralAtomArchitecture arch = na::NeutralAtomArchitecture(testArchitecturePath); - na::InitialMapping const initialMapping = na::InitialMapping::Identity; + na::InitialMapping const initialMapping = na::InitialMapping::Graph; na::MapperParameters mapperParameters; na::NeutralAtomMapper mapper; qc::QuantumComputation qc; From 4f5c3c91f5ea3c019fb3497c21e8a9ee340dd98f Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Wed, 12 Feb 2025 10:29:41 +0100 Subject: [PATCH 143/394] =?UTF-8?q?=F0=9F=94=A5=20removed=20debugging=20st?= =?UTF-8?q?atement.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/hybridmap/main.cpp | 162 ---------------------------------------- 1 file changed, 162 deletions(-) delete mode 100644 test/hybridmap/main.cpp diff --git a/test/hybridmap/main.cpp b/test/hybridmap/main.cpp deleted file mode 100644 index a59e082b2..000000000 --- a/test/hybridmap/main.cpp +++ /dev/null @@ -1,162 +0,0 @@ -// -// Created by Ludwig Schmid on 06.11.23. -// - -#include "Definitions.hpp" -#include "QuantumComputation.hpp" -#include "filesystem" -#include "hybridmap/HybridNeutralAtomMapper.hpp" -#include "hybridmap/NeutralAtomArchitecture.hpp" -#include "hybridmap/NeutralAtomScheduler.hpp" -#include "hybridmap/NeutralAtomUtils.hpp" - -#include -#include -#include -#include -#include -#include - -int main(int argc, char* argv[]) { - if (argc != 14) { - std::cerr - << "Usage: " << argv[0] - << " " - " " - " " - " " - " \n"; - return 1; - } - - const int runIdx = std::atoi(argv[1]); - const std::string input_directory = argv[2]; - const std::string output_directory = argv[3]; - const double lookaheadGate = std::stod(argv[4]); - const double lookaheadShuttling = std::stod(argv[5]); - const double gateDecay = std::stod(argv[6]); - const double shuttlingTimeWeight = std::stod(argv[7]); - const double gateWeight = std::stod(argv[8]); - const double shuttlingWeight = std::stod(argv[9]); - const bool verbose = std::atoi(argv[10]) != 0; - const std::string json_config_file_path = argv[11]; - const std::string initialCoordinateMapping = argv[12]; - const std::string initialCircuitMapping = argv[13]; - - // Check if the output directory exists and create it if it doesn't. - if (!std::filesystem::exists(output_directory)) { - if (!std::filesystem::create_directory(output_directory)) { - std::cerr << "Failed to create the output directory.\n"; - return 1; - } - } - - // init Mappings - na::InitialCoordinateMapping initialCoordinateMappingEnum = - na::InitialCoordinateMapping::Trivial; - if (initialCoordinateMapping == "trivial") { - initialCoordinateMappingEnum = na::InitialCoordinateMapping::Trivial; - } else if (initialCoordinateMapping == "graph") { - initialCoordinateMappingEnum = na::InitialCoordinateMapping::Graph; - } else if (initialCoordinateMapping == "random") { - initialCoordinateMappingEnum = na::InitialCoordinateMapping::Random; - } else { - std::cerr << "Unknown initial coordinate mapping: " - << initialCoordinateMapping << "\n"; - return 1; - } - na::InitialMapping initialCircuitMappingEnum = na::InitialMapping::Identity; - if (initialCircuitMapping == "identity") { - initialCircuitMappingEnum = na::InitialMapping::Identity; - } else { - std::cerr << "Unknown initial circuit mapping: " << initialCircuitMapping - << "\n"; - return 1; - } - - // read files - std::vector qasmFiles; - for (const auto& entry : - std::filesystem::directory_iterator(input_directory)) { - if (entry.is_regular_file() && entry.path().extension() == ".qasm") { - qasmFiles.push_back(entry.path().string()); - } - } - - // output file - std::ofstream ofsResults(output_directory + "/" + std::to_string(runIdx) + - ".csv"); - - for (const auto& qasmFile : qasmFiles) { - // create arch - na::NeutralAtomArchitecture const arch = - na::NeutralAtomArchitecture(json_config_file_path); - // start mapping - auto startTime = std::chrono::high_resolution_clock::now(); - na::MapperParameters mapperParameters; - mapperParameters.lookaheadWeightSwaps = lookaheadGate; - mapperParameters.lookaheadWeightMoves = lookaheadShuttling; - mapperParameters.decay = gateDecay; - mapperParameters.shuttlingTimeWeight = shuttlingTimeWeight; - mapperParameters.gateWeight = gateWeight; - mapperParameters.shuttlingWeight = shuttlingWeight; - mapperParameters.initialMapping = initialCoordinateMappingEnum; - mapperParameters.verbose = verbose; - na::NeutralAtomMapper mapper = - na::NeutralAtomMapper(arch, mapperParameters); - - std::cout << "Mapping " << qasmFile << "\n"; - qc::QuantumComputation qc = qc::QuantumComputation(qasmFile); - auto qcMapped = - mapper.map(qc, initialCircuitMappingEnum, initialCoordinateMappingEnum); - std::ofstream ofs(output_directory + "/" + - std::filesystem::path(qasmFile).filename().string() + - "_" + std::to_string(runIdx) + ".qasm_ext"); - bool openQASM3 = false; - qcMapped.dumpOpenQASM(ofs, openQASM3); - auto qcAodMapped = mapper.convertToAod(qcMapped); - std::ofstream ofs_aod(output_directory + "/" + - std::filesystem::path(qasmFile).filename().string() + - "_" + std::to_string(runIdx) + ".qasm_aod"); - qcAodMapped.dumpOpenQASM(ofs_aod, openQASM3); - auto endTime = std::chrono::high_resolution_clock::now(); - auto timeTaken = std::chrono::duration_cast( - endTime - startTime) - .count(); - // do the scheduling - na::NeutralAtomScheduler scheduler = na::NeutralAtomScheduler(arch); - bool const createAnimationCsv = true; - qc::fp const shuttlingSpeedFactor = 0.1; - auto schedulerResults = - scheduler.schedule(qcAodMapped, mapper.getInitHwPos(), verbose, - createAnimationCsv, shuttlingSpeedFactor); - scheduler.saveAnimationCsv( - output_directory + "/" + - std::filesystem::path(qasmFile).filename().string() + "_" + - std::to_string(runIdx) + "_animate.csv"); - - // dump the results - ofsResults << std::filesystem::path(qasmFile).filename().string() + ", " + - schedulerResults.toCsv() + "\n"; - - // dump the parameters - std::ofstream ofs_params(output_directory + "/parameters_" + - std::to_string(runIdx) + ".txt"); - ofs_params << "lookaheadGate: " << lookaheadGate << "\n"; - ofs_params << "lookaheadShuttling: " << lookaheadShuttling << "\n"; - ofs_params << "gateDecay: " << gateDecay << "\n"; - ofs_params << "shuttlingTimeWeight: " << shuttlingTimeWeight << "\n"; - ofs_params << "gateWeight: " << gateWeight << "\n"; - ofs_params << "shuttlingWeight: " << shuttlingWeight << "\n"; - ofs_params << "verbose: " << verbose << "\n"; - ofs_params << "json_config_file_path: " << json_config_file_path << "\n"; - ofs_params << "initialCoordinateMapping: " << initialCoordinateMapping - << "\n"; - ofs_params << "initialCircuitMapping: " << initialCircuitMapping << "\n"; - // close the file - ofs_params.close(); - std::cout << "* runtime: " << timeTaken << '\n'; - } - - return 0; -} From daaf4da0eb0d7d38d08d869c232daa1ee899a4f4 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Wed, 12 Feb 2025 10:45:32 +0100 Subject: [PATCH 144/394] =?UTF-8?q?=F0=9F=94=A5=20removed=20debugging=20st?= =?UTF-8?q?atement.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridNeutralAtomMapper.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 8f1ebaa54..b42ffd836 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -2243,7 +2243,7 @@ MappingMethod NeutralAtomMapper::compareShuttlingAndFlyingAncilla( const auto fa = faDistReduction * faFidelity; const auto passBy = faDistReduction * passByFidelity; // return MappingMethod::PassByMethod; - return MappingMethod::FlyingAncillaMethod; + // return MappingMethod::FlyingAncillaMethod; if (move > fa && move > passBy) { return MappingMethod::MoveMethod; From 333db6e8f8572aa387965fae647147080f24e711 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Tue, 18 Feb 2025 09:55:46 +0100 Subject: [PATCH 145/394] =?UTF-8?q?=F0=9F=90=9B=20fixed=20missing=20mappin?= =?UTF-8?q?g=20for=20flyingAncillas=20and=20PassBys?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridNeutralAtomMapper.cpp | 33 +++++++++++++---------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index b42ffd836..67bd57cfd 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -159,21 +159,23 @@ qc::QuantumComputation NeutralAtomMapper::convertToAod() { void NeutralAtomMapper::applyPassBy(NeutralAtomLayer& frontLayer, const FlyingAncillaComb& faComb) { - auto usedQubits = faComb.op->getUsedQubits(); + const auto usedQubits = faComb.op->getUsedQubits(); + auto usedCoords = + hardwareQubits.getCoordIndices(mapping.getHwQubits(usedQubits)); for (const auto& passBy : faComb.moves) { mappedQc.move(passBy.q1, passBy.q2 + arch->getNpositions()); if (this->parameters->verbose) { std::cout << "passby " << passBy.q1 << " " << passBy.q2 << '\n'; } - if (usedQubits.find(passBy.q1) != usedQubits.end()) { - usedQubits.erase(passBy.q1); - usedQubits.insert(passBy.q2 + arch->getNpositions()); + if (usedCoords.find(passBy.q1) != usedCoords.end()) { + usedCoords.erase(passBy.q1); + usedCoords.insert(passBy.q2 + arch->getNpositions()); } } const auto opCopy = faComb.op->clone(); - const std::vector usedQubitsVec = {usedQubits.begin(), - usedQubits.end()}; - opCopy->setTargets(usedQubitsVec); + const std::vector usedCoordsVec = {usedCoords.begin(), + usedCoords.end()}; + opCopy->setTargets(usedCoordsVec); opCopy->setControls({}); mappedQc.emplace_back(opCopy->clone()); @@ -370,7 +372,10 @@ void NeutralAtomMapper::applyBridge(NeutralAtomLayer& frontLayer, } void NeutralAtomMapper::applyFlyingAncilla(NeutralAtomLayer& frontLayer, const FlyingAncillaComb& faComb) { - auto usedQubits = faComb.op->getUsedQubits(); + const auto usedQubits = faComb.op->getUsedQubits(); + auto usedCoords = + hardwareQubits.getCoordIndices(mapping.getHwQubits(usedQubits)); + const auto nPos = this->arch->getNpositions(); for (const auto& passBy : faComb.moves) { const auto ancQ1 = passBy.q1 + (nPos * 2); @@ -383,9 +388,9 @@ void NeutralAtomMapper::applyFlyingAncilla(NeutralAtomLayer& frontLayer, mappedQc.h(ancQ1); mappedQc.move(ancQ1, ancQ2); - if (usedQubits.find(passBy.q1) != usedQubits.end()) { - usedQubits.erase(passBy.q1); - usedQubits.insert(ancQ2); + if (usedCoords.find(passBy.q1) != usedCoords.end()) { + usedCoords.erase(passBy.q1); + usedCoords.insert(ancQ2); } if (this->parameters->verbose) { @@ -394,9 +399,9 @@ void NeutralAtomMapper::applyFlyingAncilla(NeutralAtomLayer& frontLayer, } } const auto opCopy = faComb.op->clone(); - const std::vector usedQubitsVec = {usedQubits.begin(), - usedQubits.end()}; - opCopy->setTargets(usedQubitsVec); + const std::vector usedCoordsVec = {usedCoords.begin(), + usedCoords.end()}; + opCopy->setTargets(usedCoordsVec); opCopy->setControls({}); mappedQc.emplace_back(opCopy->clone()); From e9c202d2d66facaf13030188fc3513ff14ba40fe Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Tue, 18 Feb 2025 10:56:43 +0100 Subject: [PATCH 146/394] =?UTF-8?q?=F0=9F=90=9B=20also=20single=20qubit=20?= =?UTF-8?q?gates=20can=20break=20move=20groups?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/MoveToAodConverter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hybridmap/MoveToAodConverter.cpp b/src/hybridmap/MoveToAodConverter.cpp index ce8ba1f5c..226d6f222 100644 --- a/src/hybridmap/MoveToAodConverter.cpp +++ b/src/hybridmap/MoveToAodConverter.cpp @@ -121,7 +121,7 @@ void MoveToAodConverter::initMoveGroups(qc::QuantumComputation& qc) { currentMoveGroup = MoveGroup(); currentMoveGroup.addMove(move, idx); } - } else if (op->getNqubits() > 1 && (!currentMoveGroup.moves.empty())) { + } else if ((!currentMoveGroup.moves.empty())) { for (const auto& qubit : op->getUsedQubits()) { if (std::find(currentMoveGroup.qubitsUsedByGates.begin(), currentMoveGroup.qubitsUsedByGates.end(), From a8a8e95062eebc287acd590bce3579e9029fdc07 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Tue, 18 Feb 2025 13:45:10 +0100 Subject: [PATCH 147/394] =?UTF-8?q?=E2=9C=A8=20added=20initial=20mapping?= =?UTF-8?q?=20based=20on=20graph=20matching?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridNeutralAtomMapper.cpp | 9 +- src/hybridmap/Mapping.cpp | 347 +++++++++++----------- 2 files changed, 183 insertions(+), 173 deletions(-) diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 67bd57cfd..76cc5e5fa 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -55,14 +55,15 @@ void NeutralAtomMapper::mapAppend(qc::QuantumComputation& qc, if (this->parameters->verbose) { std::cout << "* Init Coord Mapping w/ [row:" << arch->getNrows() - << " X col:" << arch->getNcolumns() << "] hardware" << std::endl; + << " X col:" << arch->getNcolumns() << "] hardware" << "\n"; for (uint32_t q = 0; q < qc.getNqubits(); q++) { std::cout << "q " << std::setw(3) << q; - std::cout << " -> h " << std::setw(3) << this->mapping.getHwQubit(q); + const auto hwQubit = this->mapping.getHwQubit(q); + std::cout << " -> h " << std::setw(3) << hwQubit; std::cout << " -> c " << std::setw(3) - << this->hardwareQubits.getCoordIndex(q) << std::endl; + << hardwareQubits.getCoordIndex(hwQubit) << "\n"; } - std::cout << std::endl; + std::cout << "\n"; } // init layers diff --git a/src/hybridmap/Mapping.cpp b/src/hybridmap/Mapping.cpp index 09ee4ed06..78290962b 100644 --- a/src/hybridmap/Mapping.cpp +++ b/src/hybridmap/Mapping.cpp @@ -28,184 +28,193 @@ void Mapping::applySwap(Swap swap) { } std::vector Mapping::graphMatching() const { + std::cout << "\n* initialMapping: Graph Matching\n"; - // init coord mapping std::vector qubitIndices( dag.size(), std::numeric_limits::max()); std::vector hwIndices(hwQubits.getNumQubits(), std::numeric_limits::max()); + // make hardware graph + std::unordered_map> hwGraph; + for (size_t i = 0; i < hwQubits.getNumQubits(); ++i) { + auto neighbors = hwQubits.getNearbyQubits(i); + hwGraph[i] = std::vector(neighbors.begin(), neighbors.end()); + } + for (auto& [qubit, neighbors] : hwGraph) { + std::sort(neighbors.begin(), neighbors.end(), + [this](uint32_t a, uint32_t b) { + return hwQubits.getNearbyQubits(a).size() > + hwQubits.getNearbyQubits(b).size(); + }); + } + + uint32_t hwCenter = std::numeric_limits::max(); + size_t maxHwConnections = 0; + for (const auto& [qubit, neighbors] : hwGraph) { + if (neighbors.size() > maxHwConnections) { + maxHwConnections = neighbors.size(); + hwCenter = qubit; + } + } + + /* + //for debug// + for (size_t i = 0; i < hwQubits.getNumQubits(); ++i){ + auto coordIdx = hwQubits.getCoordIndex(i); + std::cout << "hwQubit " << i << " -> coordIdx " << coordIdx << "\t"; + std::cout << "-> neighbors: "; + for(auto j : hwGraph[i]){ + std::cout << j << " "; + } + std::cout << "\n"; + } + std::cout << "=> hwCenter: " << hwCenter << "\n"; + */ + + // make circuit graph + std::vector>> circGraph(dag.size()); + for (qc::Qubit qubit = 0; qubit < dag.size(); ++qubit) { + std::unordered_map weightMap; + for (const auto& opPtr : dag[qubit]) { + const auto* op = opPtr->get(); + auto usedQubits = op->getUsedQubits(); + if (usedQubits.size() > 1) { + for (auto i : usedQubits) { + if (i != qubit) { + weightMap[i] += 1.0; + } + } + } + } + std::vector> neighbors(weightMap.begin(), + weightMap.end()); + std::sort( + neighbors.begin(), neighbors.end(), + [](const std::pair& a, + std::pair& b) { return a.second > b.second; }); + circGraph[qubit] = std::move(neighbors); + } + + /* + //for debug// + for (qc::Qubit qubit = 0; qubit "; + for(const auto& [neighbor, weight] : circGraph[qubit]){ + std::cout << "(q" << neighbor << " - weight " << weight << ") "; + } + std::cout << "\n"; + } + */ + + // circuit queue for graph matching + std::vector>> nodes; + for (size_t i = 0; i < circGraph.size(); ++i) { + int degree = circGraph[i].size(); + double weight_sum = 0; + for (const auto& neighbor : circGraph[i]) { + weight_sum += neighbor.second; + } + nodes.emplace_back(i, std::make_pair(degree, weight_sum)); + } + std::sort(nodes.begin(), nodes.end(), + [](const std::pair>& a, + const std::pair>& b) { + if (a.second.first == b.second.first) { + return a.second.second > b.second.second; + } + return a.second.first > b.second.first; + }); + std::queue circGraphQueue; + for (const auto& node : nodes) { + circGraphQueue.push(node.first); + } + + // graph matching -> return qubit Indices + uint32_t nMapped = 0; + bool firstCenter = true; + int i = 0; + while (!circGraphQueue.empty() && nMapped != dag.size()) { + auto qi = circGraphQueue.front(); + HwQubit Qi = std::numeric_limits::max(); + // std::cout << "*" << ++i << "th mapping: q" << qi << "\n"; + // center mapping + if (qubitIndices[qi] == std::numeric_limits::max()) { + // first center + if (firstCenter) { + Qi = hwCenter; + firstCenter = false; + } + // next.. + else { + int minDistance = std::numeric_limits::max(); + for (HwQubit Qcandi = 0; Qcandi < hwQubits.getNumQubits(); ++Qcandi) { + if (hwIndices[Qcandi] != std::numeric_limits::max()) { + continue; + } + int weightDistance = 0; + for (auto qn_pair : circGraph[qi]) { + auto qn = qn_pair.first; + auto qn_weight = qn_pair.second; + HwQubit Qn = qubitIndices[qn]; + if (Qn == std::numeric_limits::max()) { + continue; + } + weightDistance += + const_cast(hwQubits).getSwapDistance( + Qn, Qcandi, true) * + qn_weight; + } + if (weightDistance < minDistance) { + minDistance = weightDistance; + Qi = Qcandi; + } + } + } + qubitIndices[qi] = Qi; + hwIndices[Qi] = qi; + nMapped++; + // std::cout << "q" << qi << "-> Q" << Qi << "\n"; + } else { + Qi = qubitIndices[qi]; + } + // neighbor mapping + for (auto& qn_pair : circGraph[qi]) { + uint32_t qn = qn_pair.first; + if (qubitIndices[qn] != std::numeric_limits::max()) { + continue; + } + HwQubit Qn = std::numeric_limits::max(); + for (const auto& Qcandi : hwGraph[Qi]) { + if (hwIndices[Qcandi] == std::numeric_limits::max()) { + Qn = Qcandi; + break; + } + } + if (Qn != std::numeric_limits::max()) { + qubitIndices[qn] = Qn; + hwIndices[Qn] = qn; + nMapped++; + // std::cout << "q" << qn << "-> Q" << Qn << "\n"; + } + } + circGraphQueue.pop(); + } + + // for debug for (size_t i = 0; i < dag.size(); ++i) { - qubitIndices[i] = i; + if (qubitIndices[i] == std::numeric_limits::max()) { + for (HwQubit hw = 0; hw < hwQubits.getNumQubits(); ++hw) { + if (hwIndices[hw] == std::numeric_limits::max()) { + qubitIndices[i] = hw; + hwIndices[hw] = i; + break; + } + } + } } - return qubitIndices; - // use hwQubits instead of positions - - // // interaction graph - // std::vector> circGraph( - // dag.size(), std::vector(dag.size(), 0.0)); - // std::vector>> circGraph_degree( - // dag.size()); - // std::vector>> circGraph_neighbor( - // dag.size()); - // for (uint32_t qubit = 0; qubit < dag.size(); ++qubit) { - // for (const auto& opPtr : dag[qubit]) { - // const auto* op = opPtr->get(); - // if (op->getUsedQubits().size() > 1) { - // for (auto i : op->getUsedQubits()) { - // if (i != qubit) { - // circGraph[qubit][i] += 1; - // } - // } - // } - // } - // } - // - // // generate graph matching queue - // for (uint32_t qubit = 0; qubit < dag.size(); qubit++) { - // int cnt = 0; - // double sum = 0; - // for (uint32_t i = 0; i < dag.size(); i++) { - // double weight = circGraph[qubit][i]; - // if (weight > 0) { - // cnt++; - // sum += weight; - // circGraph_neighbor[qubit].emplace_back(i, weight); - // } - // } - // circGraph_degree[qubit] = std::make_pair(qubit, std::make_pair(cnt, - // sum)); - // } - // sort(circGraph_degree.begin(), circGraph_degree.end(), - // [](std::pair> a, - // std::pair> b) { - // if (a.second.first == b.second.first) - // return a.second.second > b.second.second; - // else - // return a.second.first > b.second.first; - // }); - // std::queue circGraph_queue; - // for (const auto i : circGraph_degree) { - // circGraph_queue.push(i.first); - // } - // for (auto& innerVec : circGraph_neighbor) { - // sort(innerVec.begin(), innerVec.end(), - // [](std::pair& a, std::pair& b) { - // return a.second > b.second; - // }); - // } - // - // // graph matching - // bool firstCenter = true; - // uint32_t nMapped = 0; - // uint32_t archCenterX = - // (archNcolumns % 2 == 0) ? (archNcolumns / 2 - 1) : (archNcolumns - 1) / - // 2; - // uint32_t archCenterY = - // (archNrows % 2 == 0) ? (archNrows / 2 - 1) : (archNrows - 1) / 2; - // uint32_t archCenter = archCenterY * archNcolumns + archCenterX; - // const auto start = hwQubits.getClosestQubit(archCenter, {}); - // - // while (!circGraph_queue.empty() && nMapped != dag.size()) { - // uint32_t qc = circGraph_queue.front(); - // uint32_t hc = std::numeric_limits::max(); // hardwarCenter - // // center mapping - // if (firstCenter) { - // hc = archCenter; - // qubitIndices[qc] = hc; - // hwIndices[hc] = qc; - // firstCenter = false; - // nMapped++; - // } else if (qubitIndices[qc] == std::numeric_limits::max()) - // { - // - // // ref loc - // std::vector refLoc; - // for (auto i : circGraph_neighbor[qc]) { - // if (qubitIndices[i.first] != std::numeric_limits::max()) { - // refLoc.push_back(qubitIndices[i.first]); - // } - // } - // - // // candidate loc - // std::vector> distCandiLoc; - // std::vector initCandiLoc; - // for (int i = 0; i < archNpositions; i++) { - // if (hwIndices[i] == std::numeric_limits::max()) { - // initCandiLoc.push_back(i); - // } - // } - // for (auto v : initCandiLoc) { - // int distSum = 0; - // for (auto r : refLoc) { - // int dist = std::abs(v % archNcolumns - r % archNcolumns) + - // std::abs(v / archNcolumns - r / archNcolumns); - // distSum += dist; - // } - // distCandiLoc.emplace_back(v, distSum); - // } - // sort(distCandiLoc.begin(), distCandiLoc.end(), - // [](std::pair a, std::pair b) { - // return a.second < b.second; - // }); - // - // // find position - // hc = distCandiLoc[0].first; - // qubitIndices[qc] = hc; - // hwIndices[hc] = qc; - // nMapped++; - // } else { - // hc = qubitIndices[qc]; - // } - // - // // neighbor mapping - // if (!circGraph_neighbor[qc].empty()) { - // int idx_qc_n = 0; - // std::vector qc_n; - // for (auto i : circGraph_neighbor[qc]) { - // if (qubitIndices[i.first] != std::numeric_limits::max()) - // continue; - // if (idx_qc_n >= 4) - // continue; - // else { - // qc_n.push_back(i.first); - // idx_qc_n++; - // } - // } - // std::vector hw_n; - // if (hc != std::numeric_limits::max() && qc_n.size() > 0) - // { - // if ((hc + 1) % archNcolumns != 0 && - // hwIndices[hc + 1] == std::numeric_limits::max()) - // hw_n.push_back(hc + 1); // right - // if (hc / archNcolumns < (archNrows - 1) && - // hwIndices[hc + archNcolumns] == - // std::numeric_limits::max()) - // hw_n.push_back(hc + archNcolumns); // down - // if (hc % archNcolumns != 0 && - // hwIndices[hc - 1] == std::numeric_limits::max()) - // hw_n.push_back(hc - 1); // left - // if (hc > archNcolumns && hwIndices[hc - archNcolumns] == - // std::numeric_limits::max()) - // hw_n.push_back(hc - archNcolumns); // up - // } - // - // int minSize = std::min(qc_n.size(), hw_n.size()); - // for (int i = 0; i < minSize; i++) { - // int qc_i = qc_n[i]; - // int hw_i = hw_n[i]; - // qubitIndices[qc_i] = hw_i; - // hwIndices[hw_i] = qc_i; - // nMapped++; - // } - // } - // circGraph_queue.pop(); - // } - // return qubitIndices; + return qubitIndices; } } // namespace na From 9a4940ce95ecbbf27114d48a2884d52556a1c8c1 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Tue, 18 Feb 2025 14:17:26 +0100 Subject: [PATCH 148/394] =?UTF-8?q?=F0=9F=8E=A8=20fixed=20clang=20tidy=20w?= =?UTF-8?q?arnings.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/Mapping.hpp | 11 ++- src/hybridmap/Mapping.cpp | 134 ++++++++++++++-------------------- 2 files changed, 63 insertions(+), 82 deletions(-) diff --git a/include/hybridmap/Mapping.hpp b/include/hybridmap/Mapping.hpp index 14efdd487..e12a60b5b 100644 --- a/include/hybridmap/Mapping.hpp +++ b/include/hybridmap/Mapping.hpp @@ -8,9 +8,11 @@ #include "Definitions.hpp" #include "HardwareQubits.hpp" #include "NeutralAtomArchitecture.hpp" +#include "circuit_optimizer/CircuitOptimizer.hpp" #include "hybridmap/NeutralAtomDefinitions.hpp" #include "hybridmap/NeutralAtomUtils.hpp" #include "ir/Permutation.hpp" +#include "ir/QuantumComputation.hpp" #include "ir/operations/Operation.hpp" #include @@ -37,7 +39,7 @@ class Mapping { * @brief GraphMatching for initCoordMapping */ [[nodiscard]] - std::vector graphMatching() const; + std::vector graphMatching(); public: Mapping() = default; @@ -47,8 +49,9 @@ class Mapping { } } Mapping(const size_t nQubits, const InitialMapping initialMapping, - qc::QuantumComputation qc, const HardwareQubits& hwQubits) - : dag(qc::CircuitOptimizer::constructDAG(qc)), hwQubits(hwQubits) { + qc::QuantumComputation qc, HardwareQubits hwQubits) + : hwQubits(std::move(hwQubits)), + dag(qc::CircuitOptimizer::constructDAG(qc)) { switch (initialMapping) { case Identity: @@ -144,7 +147,7 @@ class Mapping { * @param swap The two circuit qubits to be swapped * @throws std::runtime_error if hardware qubits are not mapped */ - void applySwap(Swap swap); + void applySwap(const Swap& swap); }; } // namespace na diff --git a/src/hybridmap/Mapping.cpp b/src/hybridmap/Mapping.cpp index 78290962b..8874bb336 100644 --- a/src/hybridmap/Mapping.cpp +++ b/src/hybridmap/Mapping.cpp @@ -5,17 +5,26 @@ #include "hybridmap/Mapping.hpp" +#include "Definitions.hpp" #include "hybridmap/NeutralAtomDefinitions.hpp" +#include +#include +#include +#include +#include #include +#include +#include +#include namespace na { -void Mapping::applySwap(Swap swap) { - auto q1 = swap.first; - auto q2 = swap.second; +void Mapping::applySwap(const Swap& swap) { + const auto q1 = swap.first; + const auto q2 = swap.second; if (this->isMapped(q1) && this->isMapped(q2)) { - auto circQ1 = this->getCircQubit(q1); - auto circQ2 = this->getCircQubit(q2); + const auto circQ1 = this->getCircQubit(q1); + const auto circQ2 = this->getCircQubit(q2); this->setCircuitQubit(circQ2, q1); this->setCircuitQubit(circQ1, q2); } else if (this->isMapped(q1) && !this->isMapped(q2)) { @@ -27,23 +36,22 @@ void Mapping::applySwap(Swap swap) { } } -std::vector Mapping::graphMatching() const { - std::cout << "\n* initialMapping: Graph Matching\n"; +std::vector Mapping::graphMatching() { - std::vector qubitIndices( - dag.size(), std::numeric_limits::max()); - std::vector hwIndices(hwQubits.getNumQubits(), - std::numeric_limits::max()); + std::vector qubitIndices(dag.size(), + std::numeric_limits::max()); + std::vector hwIndices(hwQubits.getNumQubits(), + std::numeric_limits::max()); // make hardware graph std::unordered_map> hwGraph; - for (size_t i = 0; i < hwQubits.getNumQubits(); ++i) { + for (qc::Qubit i = 0; i < hwQubits.getNumQubits(); ++i) { auto neighbors = hwQubits.getNearbyQubits(i); - hwGraph[i] = std::vector(neighbors.begin(), neighbors.end()); + hwGraph[i] = std::vector(neighbors.begin(), neighbors.end()); } for (auto& [qubit, neighbors] : hwGraph) { std::sort(neighbors.begin(), neighbors.end(), - [this](uint32_t a, uint32_t b) { + [this](const uint32_t a, const uint32_t b) { return hwQubits.getNearbyQubits(a).size() > hwQubits.getNearbyQubits(b).size(); }); @@ -58,20 +66,6 @@ std::vector Mapping::graphMatching() const { } } - /* - //for debug// - for (size_t i = 0; i < hwQubits.getNumQubits(); ++i){ - auto coordIdx = hwQubits.getCoordIndex(i); - std::cout << "hwQubit " << i << " -> coordIdx " << coordIdx << "\t"; - std::cout << "-> neighbors: "; - for(auto j : hwGraph[i]){ - std::cout << j << " "; - } - std::cout << "\n"; - } - std::cout << "=> hwCenter: " << hwCenter << "\n"; - */ - // make circuit graph std::vector>> circGraph(dag.size()); for (qc::Qubit qubit = 0; qubit < dag.size(); ++qubit) { @@ -89,33 +83,23 @@ std::vector Mapping::graphMatching() const { } std::vector> neighbors(weightMap.begin(), weightMap.end()); - std::sort( - neighbors.begin(), neighbors.end(), - [](const std::pair& a, - std::pair& b) { return a.second > b.second; }); + std::sort(neighbors.begin(), neighbors.end(), + [](const std::pair& a, + const std::pair& b) { + return a.second > b.second; + }); circGraph[qubit] = std::move(neighbors); } - /* - //for debug// - for (qc::Qubit qubit = 0; qubit "; - for(const auto& [neighbor, weight] : circGraph[qubit]){ - std::cout << "(q" << neighbor << " - weight " << weight << ") "; - } - std::cout << "\n"; - } - */ - // circuit queue for graph matching std::vector>> nodes; for (size_t i = 0; i < circGraph.size(); ++i) { - int degree = circGraph[i].size(); - double weight_sum = 0; + const auto degree = circGraph[i].size(); + double weightSum = 0; for (const auto& neighbor : circGraph[i]) { - weight_sum += neighbor.second; + weightSum += neighbor.second; } - nodes.emplace_back(i, std::make_pair(degree, weight_sum)); + nodes.emplace_back(i, std::make_pair(degree, weightSum)); } std::sort(nodes.begin(), nodes.end(), [](const std::pair>& a, @@ -133,69 +117,63 @@ std::vector Mapping::graphMatching() const { // graph matching -> return qubit Indices uint32_t nMapped = 0; bool firstCenter = true; - int i = 0; while (!circGraphQueue.empty() && nMapped != dag.size()) { auto qi = circGraphQueue.front(); - HwQubit Qi = std::numeric_limits::max(); - // std::cout << "*" << ++i << "th mapping: q" << qi << "\n"; + HwQubit qI = std::numeric_limits::max(); // center mapping if (qubitIndices[qi] == std::numeric_limits::max()) { // first center if (firstCenter) { - Qi = hwCenter; + qI = hwCenter; firstCenter = false; } // next.. else { - int minDistance = std::numeric_limits::max(); - for (HwQubit Qcandi = 0; Qcandi < hwQubits.getNumQubits(); ++Qcandi) { - if (hwIndices[Qcandi] != std::numeric_limits::max()) { + auto minDistance = std::numeric_limits::max(); + for (HwQubit qCandi = 0; qCandi < hwQubits.getNumQubits(); ++qCandi) { + if (hwIndices[qCandi] != std::numeric_limits::max()) { continue; } - int weightDistance = 0; - for (auto qn_pair : circGraph[qi]) { - auto qn = qn_pair.first; - auto qn_weight = qn_pair.second; - HwQubit Qn = qubitIndices[qn]; - if (Qn == std::numeric_limits::max()) { + auto weightDistance = 0.0; + for (auto qnPair : circGraph[qi]) { + auto qn = qnPair.first; + auto qnWeight = qnPair.second; + HwQubit const qN = qubitIndices[qn]; + if (qN == std::numeric_limits::max()) { continue; } weightDistance += - const_cast(hwQubits).getSwapDistance( - Qn, Qcandi, true) * - qn_weight; + qnWeight * hwQubits.getSwapDistance(qN, qCandi, true); } if (weightDistance < minDistance) { minDistance = weightDistance; - Qi = Qcandi; + qI = qCandi; } } } - qubitIndices[qi] = Qi; - hwIndices[Qi] = qi; + qubitIndices[qi] = qI; + hwIndices[qI] = qi; nMapped++; - // std::cout << "q" << qi << "-> Q" << Qi << "\n"; } else { - Qi = qubitIndices[qi]; + qI = qubitIndices[qi]; } // neighbor mapping - for (auto& qn_pair : circGraph[qi]) { - uint32_t qn = qn_pair.first; + for (auto& qnPair : circGraph[qi]) { + auto const qn = qnPair.first; if (qubitIndices[qn] != std::numeric_limits::max()) { continue; } - HwQubit Qn = std::numeric_limits::max(); - for (const auto& Qcandi : hwGraph[Qi]) { - if (hwIndices[Qcandi] == std::numeric_limits::max()) { - Qn = Qcandi; + HwQubit qN = std::numeric_limits::max(); + for (const auto& qCandi : hwGraph[qI]) { + if (hwIndices[qCandi] == std::numeric_limits::max()) { + qN = qCandi; break; } } - if (Qn != std::numeric_limits::max()) { - qubitIndices[qn] = Qn; - hwIndices[Qn] = qn; + if (qN != std::numeric_limits::max()) { + qubitIndices[qn] = qN; + hwIndices[qN] = qn; nMapped++; - // std::cout << "q" << qn << "-> Q" << Qn << "\n"; } } circGraphQueue.pop(); From d5abb698fad0f4eb3e412e0beb2490b1e1d00738 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Wed, 19 Feb 2025 09:55:16 +0100 Subject: [PATCH 149/394] =?UTF-8?q?=E2=9C=A8=20added=20limit=20to=20number?= =?UTF-8?q?=20of=20gates=20in=20shuttling=20layer=20which=20are=20evaluate?= =?UTF-8?q?d?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridNeutralAtomMapper.hpp | 1 + src/hybridmap/HybridNeutralAtomMapper.cpp | 5 +++++ test/hybridmap/test_hybridmap.cpp | 1 + 3 files changed, 7 insertions(+) diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index 5d16a5577..6a1022002 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -41,6 +41,7 @@ struct MapperParameters { qc::fp shuttlingWeight = 1; uint32_t seed = 0; uint32_t numFlyingAncillas = 0; + uint32_t limitShuttlingLayer = std::numeric_limits::max(); bool verbose = false; InitialCoordinateMapping initialCoordMapping; }; diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 76cc5e5fa..c6f2a97f8 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -1339,6 +1339,7 @@ NeutralAtomMapper::getMovePositionRec(MultiQubitMovePos currentPos, MoveCombs NeutralAtomMapper::getAllMoveCombinations() { MoveCombs allMoves; + size_t i = 0; for (const auto& op : this->frontLayerShuttling) { auto usedQubits = op->getUsedQubits(); auto usedHwQubits = this->mapping.getHwQubits(usedQubits); @@ -1355,6 +1356,10 @@ MoveCombs NeutralAtomMapper::getAllMoveCombinations() { auto moves = getMoveCombinationsToPosition(usedHwQubits, bestPos); moves.setOperation(op, bestPos); allMoves.addMoveCombs(moves); + ++i; + if (i >= parameters->limitShuttlingLayer) { + break; + } } allMoves.removeLongerMoveCombs(); return allMoves; diff --git a/test/hybridmap/test_hybridmap.cpp b/test/hybridmap/test_hybridmap.cpp index 0992980c2..536d7b00b 100644 --- a/test/hybridmap/test_hybridmap.cpp +++ b/test/hybridmap/test_hybridmap.cpp @@ -144,6 +144,7 @@ class NeutralAtomMapperTest : public ::testing::Test { mapperParameters.seed = 43; mapperParameters.verbose = true; mapperParameters.numFlyingAncillas = 2; + mapperParameters.limitShuttlingLayer = 2; mapper.setParameters(mapperParameters); qc = qc::QuantumComputation( // "circuits/dj_nativegates_rigetti_qiskit_opt3_10.qasm"); From fb394c2146e8eb6964d4508b39f267a87572f48a Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Wed, 19 Feb 2025 10:01:50 +0100 Subject: [PATCH 150/394] =?UTF-8?q?=F0=9F=A5=85=20catch=20error=20when=20a?= =?UTF-8?q?n=20architecture=20with=20not=20sufficient=20number=20of=20qubi?= =?UTF-8?q?ts=20is=20selected?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/Mapping.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/hybridmap/Mapping.hpp b/include/hybridmap/Mapping.hpp index e12a60b5b..a2518bfd9 100644 --- a/include/hybridmap/Mapping.hpp +++ b/include/hybridmap/Mapping.hpp @@ -53,6 +53,10 @@ class Mapping { : hwQubits(std::move(hwQubits)), dag(qc::CircuitOptimizer::constructDAG(qc)) { + if (qc.getNqubits() > hwQubits.getNumQubits()) { + throw std::runtime_error("Not enough qubits in architecture for circuit"); + } + switch (initialMapping) { case Identity: for (size_t i = 0; i < nQubits; ++i) { From 105af70e9b5f1036124a690a61a874f6aad59dd9 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Wed, 19 Feb 2025 13:49:00 +0100 Subject: [PATCH 151/394] =?UTF-8?q?=F0=9F=90=9B=20Fixed=20bug=20when=20pos?= =?UTF-8?q?ition=20is=20loaded=20and=20unloaded=20within=20same=20move=20g?= =?UTF-8?q?roup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/MoveToAodConverter.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/hybridmap/MoveToAodConverter.cpp b/src/hybridmap/MoveToAodConverter.cpp index 226d6f222..470d95401 100644 --- a/src/hybridmap/MoveToAodConverter.cpp +++ b/src/hybridmap/MoveToAodConverter.cpp @@ -304,6 +304,7 @@ MoveToAodConverter::canAddActivation( static_cast(dim == Dimension::X ? origin.x : origin.y); auto end = static_cast(dim == Dimension::X ? final.x : final.y); + auto delta = end - start; // Get Moves that start/end at the same position as the current move auto aodMovesActivation = activationHelper.getAodMovesFromInit(dim, start); @@ -338,7 +339,9 @@ MoveToAodConverter::canAddActivation( for (const auto& aodMoveActivation : aodMovesActivation) { for (const auto& aodMoveDeactivation : aodMovesDeactivation) { if (aodMoveActivation->init == start && - aodMoveDeactivation->init == end) { + aodMoveDeactivation->init == end && + aodMoveActivation->delta == delta && + aodMoveDeactivation->delta == delta) { return std::make_pair(ActivationMergeType::Merge, ActivationMergeType::Merge); } From edebd416d4355209edd9a79911764fdf0c728e6c Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Wed, 19 Feb 2025 14:08:13 +0100 Subject: [PATCH 152/394] =?UTF-8?q?=E2=9C=A8=20added=20option=20to=20toggl?= =?UTF-8?q?e=20bridge=20and=20passBy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridNeutralAtomMapper.hpp | 2 ++ src/hybridmap/HybridNeutralAtomMapper.cpp | 22 ++++++++++++------- test/hybridmap/test_hybridmap.cpp | 2 ++ 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index 6a1022002..a05c5e42c 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -42,6 +42,8 @@ struct MapperParameters { uint32_t seed = 0; uint32_t numFlyingAncillas = 0; uint32_t limitShuttlingLayer = std::numeric_limits::max(); + bool useBridge = true; + bool usePassBy = true; bool verbose = false; InitialCoordinateMapping initialCoordMapping; }; diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index c6f2a97f8..4c93e0e81 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -1747,17 +1747,20 @@ size_t NeutralAtomMapper::gateBasedMapping(NeutralAtomLayer& frontLayer, } auto bestSwap = findBestSwap(lastSwap); - auto bestBridge = findBestBridge(); - - if (compareSwapAndBridge(bestSwap, bestBridge) == - MappingMethod::SwapMethod) { + MappingMethod bestMethod = MappingMethod::SwapMethod; + if (parameters->useBridge) { + auto bestBridge = findBestBridge(); + bestMethod = compareSwapAndBridge(bestSwap, bestBridge); + if (bestMethod == MappingMethod::BridgeMethod) { + updateBlockedQubits( + {bestBridge.second.begin(), bestBridge.second.end()}); + applyBridge(frontLayer, bestBridge); + } + } + if (bestMethod == MappingMethod::SwapMethod) { lastSwap = bestSwap; updateBlockedQubits(bestSwap); applySwap(bestSwap); - } else { - updateBlockedQubits( - {bestBridge.second.begin(), bestBridge.second.end()}); - applyBridge(frontLayer, bestBridge); } gatesToExecute = getExecutableGates(frontLayer.getGates()); @@ -2189,6 +2192,9 @@ NeutralAtomMapper::compareSwapAndBridge(const Swap& bestSwap, MappingMethod NeutralAtomMapper::compareShuttlingAndFlyingAncilla( const MoveComb& bestMoveComb, const FlyingAncillaComb& bestFaComb) const { + if (!parameters->usePassBy) { + return MappingMethod::MoveMethod; + } // move distance reduction auto const moveDistReduction = moveCombDistanceReduction(bestMoveComb, this->frontLayerShuttling) + diff --git a/test/hybridmap/test_hybridmap.cpp b/test/hybridmap/test_hybridmap.cpp index 536d7b00b..490d9b124 100644 --- a/test/hybridmap/test_hybridmap.cpp +++ b/test/hybridmap/test_hybridmap.cpp @@ -145,6 +145,8 @@ class NeutralAtomMapperTest : public ::testing::Test { mapperParameters.verbose = true; mapperParameters.numFlyingAncillas = 2; mapperParameters.limitShuttlingLayer = 2; + mapperParameters.useBridge = false; + mapperParameters.usePassBy = true; mapper.setParameters(mapperParameters); qc = qc::QuantumComputation( // "circuits/dj_nativegates_rigetti_qiskit_opt3_10.qasm"); From 92065839cd7b1501cd52c9ffd26731ef16cd0c03 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Wed, 19 Feb 2025 14:52:10 +0100 Subject: [PATCH 153/394] =?UTF-8?q?=F0=9F=90=9B=20fixed=20bridge=20gate=20?= =?UTF-8?q?cost=20calculation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridNeutralAtomMapper.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 4c93e0e81..d20630f10 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -2184,7 +2184,8 @@ NeutralAtomMapper::compareSwapAndBridge(const Swap& bestSwap, qc::fp const bridgeFidelity = this->arch->getGateAverageFidelity(bridgeName) * std::exp(-this->arch->getGateTime(bridgeName) / this->arch->getDecoherenceTime()); - if (swapDistReduction * swapFidelity > bridgeDistReduction * bridgeFidelity) { + if (bridgeDistReduction * std::log(swapFidelity) > + swapDistReduction * std::log(bridgeFidelity)) { return MappingMethod::SwapMethod; } return MappingMethod::BridgeMethod; From 2711cafe844ee68014e1a5a8102b46946b4118a6 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Wed, 19 Feb 2025 16:23:23 +0100 Subject: [PATCH 154/394] =?UTF-8?q?=E2=9C=A8=20Changed=20construction=20of?= =?UTF-8?q?=20lookahead=20layer=20->=20now=20with=20defined=20depth?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridNeutralAtomMapper.hpp | 1 + include/hybridmap/NeutralAtomLayer.hpp | 7 ++++- src/hybridmap/HybridNeutralAtomMapper.cpp | 4 +-- src/hybridmap/NeutralAtomLayer.cpp | 31 ++++++++++++++----- test/hybridmap/test_hybridmap.cpp | 3 +- 5 files changed, 34 insertions(+), 12 deletions(-) diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index a05c5e42c..6171e1835 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -33,6 +33,7 @@ namespace na { * @brief Struct to store the runtime parameters of the mapper. */ struct MapperParameters { + uint32_t lookaheadDepth = 1; qc::fp lookaheadWeightSwaps = 0.1; qc::fp lookaheadWeightMoves = 0.1; qc::fp decay = 0.1; diff --git a/include/hybridmap/NeutralAtomLayer.hpp b/include/hybridmap/NeutralAtomLayer.hpp index 87e063d0b..abe7428ed 100644 --- a/include/hybridmap/NeutralAtomLayer.hpp +++ b/include/hybridmap/NeutralAtomLayer.hpp @@ -31,6 +31,8 @@ class NeutralAtomLayer { GateList newGates; GateList mappedSingleQubitGates; GateLists candidates; + uint32_t lookaheadDepth; + bool isFrontLayer; /** * @brief Updates the gates for the given qubits @@ -53,7 +55,10 @@ class NeutralAtomLayer { public: // Constructor - explicit NeutralAtomLayer(qc::DAG graph) : dag(std::move(graph)) { + explicit NeutralAtomLayer(qc::DAG graph, bool isFrontLayer, + uint32_t lookaheadDepth = 1) + : dag(std::move(graph)), lookaheadDepth(lookaheadDepth), + isFrontLayer(isFrontLayer) { iterators.reserve(dag.size()); candidates.reserve(dag.size()); for (auto& i : dag) { diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index d20630f10..8cf384ccb 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -67,9 +67,9 @@ void NeutralAtomMapper::mapAppend(qc::QuantumComputation& qc, } // init layers - NeutralAtomLayer lookaheadLayer(dag); + NeutralAtomLayer lookaheadLayer(dag, false, this->parameters->lookaheadDepth); lookaheadLayer.initAllQubits(); - NeutralAtomLayer frontLayer(dag); + NeutralAtomLayer frontLayer(dag, true, this->parameters->lookaheadDepth); frontLayer.initAllQubits(); lookaheadLayer.removeGatesAndUpdate(frontLayer.getGates()); mapAllPossibleGates(frontLayer, lookaheadLayer); diff --git a/src/hybridmap/NeutralAtomLayer.cpp b/src/hybridmap/NeutralAtomLayer.cpp index 582908b79..15d7a1a10 100644 --- a/src/hybridmap/NeutralAtomLayer.cpp +++ b/src/hybridmap/NeutralAtomLayer.cpp @@ -45,16 +45,31 @@ void NeutralAtomLayer::initAllQubits() { void NeutralAtomLayer::updateCandidatesByQubits( const std::set& qubitsToUpdate) { for (const auto& qubit : qubitsToUpdate) { - while (iterators[qubit] < ends[qubit]) { - auto* op = (*iterators[qubit])->get(); - // check if operation commutes with gates and candidates - auto commutes = commutesWithAtQubit(gates, op, qubit) && - commutesWithAtQubit(candidates[qubit], op, qubit); - if (commutes) { + if (isFrontLayer) { + while (iterators[qubit] < ends[qubit]) { + auto* op = (*iterators[qubit])->get(); + // check if operation commutes with gates and candidates + auto commutes = commutesWithAtQubit(gates, op, qubit) && + commutesWithAtQubit(candidates[qubit], op, qubit); + if (commutes) { + candidates[qubit].emplace_back(op); + iterators[qubit]++; + } else { + break; + } + } + } + // for lookahead layer, take the next k multi-qubit gates + else { + size_t multiQubitGatesFound = 0; + while (iterators[qubit] < ends[qubit] && + multiQubitGatesFound < lookaheadDepth) { + auto* op = (*iterators[qubit])->get(); candidates[qubit].emplace_back(op); iterators[qubit]++; - } else { - break; + if (op->getUsedQubits().size() > 1) { + multiQubitGatesFound++; + } } } } diff --git a/test/hybridmap/test_hybridmap.cpp b/test/hybridmap/test_hybridmap.cpp index 490d9b124..977b3bb38 100644 --- a/test/hybridmap/test_hybridmap.cpp +++ b/test/hybridmap/test_hybridmap.cpp @@ -135,6 +135,7 @@ class NeutralAtomMapperTest : public ::testing::Test { mapper = na::NeutralAtomMapper(arch); mapperParameters.initialCoordMapping = na::InitialCoordinateMapping::Trivial; + mapperParameters.lookaheadDepth = 1; mapperParameters.lookaheadWeightSwaps = 0.1; mapperParameters.lookaheadWeightMoves = 0.1; mapperParameters.decay = 0; @@ -145,7 +146,7 @@ class NeutralAtomMapperTest : public ::testing::Test { mapperParameters.verbose = true; mapperParameters.numFlyingAncillas = 2; mapperParameters.limitShuttlingLayer = 2; - mapperParameters.useBridge = false; + mapperParameters.useBridge = true; mapperParameters.usePassBy = true; mapper.setParameters(mapperParameters); qc = qc::QuantumComputation( From df71b0d25d3b269680106030874bc505a42f67a7 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Wed, 19 Feb 2025 17:03:20 +0100 Subject: [PATCH 155/394] =?UTF-8?q?=F0=9F=90=9B=20fixed=20passBy=20cost=20?= =?UTF-8?q?calculation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridNeutralAtomMapper.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 8cf384ccb..3b9d76d3c 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -2257,11 +2257,9 @@ MappingMethod NeutralAtomMapper::compareShuttlingAndFlyingAncilla( faCombSize) * std::exp(-passByTime / this->arch->getDecoherenceTime()); - const auto move = moveDistReduction * moveFidelity; - const auto fa = faDistReduction * faFidelity; - const auto passBy = faDistReduction * passByFidelity; - // return MappingMethod::PassByMethod; - // return MappingMethod::FlyingAncillaMethod; + const auto move = std::log(moveFidelity) / moveDistReduction; + const auto fa = std::log(faFidelity) / faDistReduction; + const auto passBy = std::log(passByFidelity) / faDistReduction; if (move > fa && move > passBy) { return MappingMethod::MoveMethod; From 70b91c8149c53c9a73d0da0e8186be39f2bd0827 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Wed, 19 Feb 2025 17:12:48 +0100 Subject: [PATCH 156/394] =?UTF-8?q?=F0=9F=94=A5=20removed=20AOD=20post=20o?= =?UTF-8?q?ptimizations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/MoveToAodConverter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hybridmap/MoveToAodConverter.cpp b/src/hybridmap/MoveToAodConverter.cpp index 470d95401..e9bac2f08 100644 --- a/src/hybridmap/MoveToAodConverter.cpp +++ b/src/hybridmap/MoveToAodConverter.cpp @@ -34,7 +34,7 @@ MoveToAodConverter::schedule(qc::QuantumComputation& qc) { return qc; } processMoveGroups(); - postProcessMoveGroups(); + // postProcessMoveGroups(); // create new quantum circuit and insert AOD operations at the correct // indices From c85d58af245b2754529b86263eb56b0087833766 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Wed, 19 Feb 2025 18:38:26 +0100 Subject: [PATCH 157/394] =?UTF-8?q?=F0=9F=90=9B=20avoid=20move=20and=20pas?= =?UTF-8?q?sby=20in=20same=20moveGroup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/MoveToAodConverter.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/hybridmap/MoveToAodConverter.cpp b/src/hybridmap/MoveToAodConverter.cpp index e9bac2f08..6035c4a5a 100644 --- a/src/hybridmap/MoveToAodConverter.cpp +++ b/src/hybridmap/MoveToAodConverter.cpp @@ -146,7 +146,6 @@ bool MoveToAodConverter::MoveGroup::canAddMove( return false; } // checks if the op can be executed in parallel - auto moveVector = archArg.getVector(move.c1, move.c2); std::vector>* movesToCheck; if (move.load1 || move.load2) { movesToCheck = &moves; @@ -155,8 +154,14 @@ bool MoveToAodConverter::MoveGroup::canAddMove( } return std::all_of( movesToCheck->begin(), movesToCheck->end(), - [&moveVector, &archArg](const std::pair opPair) { + [&move, &archArg](const std::pair opPair) { auto moveGroup = opPair.first; + // check that passby and move are not in same group + if (move.load1 != moveGroup.load1 || move.load2 != moveGroup.load2) { + return false; + } + // check if parallel executable + auto moveVector = archArg.getVector(move.c1, move.c2); auto opVector = archArg.getVector(moveGroup.c1, moveGroup.c2); return parallelCheck(moveVector, opVector); }); From a615144b6f43bf558f70b0a3117e6c3d36ff9b84 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Wed, 19 Feb 2025 18:39:30 +0100 Subject: [PATCH 158/394] =?UTF-8?q?=F0=9F=90=9B=20fixed=20aod=20arrangemen?= =?UTF-8?q?t=20bugs.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/MoveToAodConverter.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/hybridmap/MoveToAodConverter.cpp b/src/hybridmap/MoveToAodConverter.cpp index 6035c4a5a..692bc332f 100644 --- a/src/hybridmap/MoveToAodConverter.cpp +++ b/src/hybridmap/MoveToAodConverter.cpp @@ -309,7 +309,7 @@ MoveToAodConverter::canAddActivation( static_cast(dim == Dimension::X ? origin.x : origin.y); auto end = static_cast(dim == Dimension::X ? final.x : final.y); - auto delta = end - start; + const qc::fp delta = static_cast(end - start); // Get Moves that start/end at the same position as the current move auto aodMovesActivation = activationHelper.getAodMovesFromInit(dim, start); @@ -345,8 +345,8 @@ MoveToAodConverter::canAddActivation( for (const auto& aodMoveDeactivation : aodMovesDeactivation) { if (aodMoveActivation->init == start && aodMoveDeactivation->init == end && - aodMoveActivation->delta == delta && - aodMoveDeactivation->delta == delta) { + std::abs(aodMoveActivation->delta) == std::abs(delta) && + std::abs(aodMoveDeactivation->delta) == std::abs(delta)) { return std::make_pair(ActivationMergeType::Merge, ActivationMergeType::Merge); } @@ -710,12 +710,8 @@ MoveToAodConverter::AodActivationHelper::getAodOperation( if (offsetOperations.empty()) { return {AodOperation(type, qubitsActivation, initOperations)}; } - if (this->type == qc::OpType::AodActivate) { - return {AodOperation(type, qubitsActivation, initOperations), - AodOperation(qc::OpType::AodMove, qubitsOffset, offsetOperations)}; - } - return {AodOperation(qc::OpType::AodMove, qubitsOffset, offsetOperations), - AodOperation(type, qubitsActivation, initOperations)}; + return {AodOperation(type, qubitsActivation, initOperations), + AodOperation(qc::OpType::AodMove, qubitsOffset, offsetOperations)}; } std::vector @@ -727,6 +723,10 @@ MoveToAodConverter::AodActivationHelper::getAodOperations() const { aodOperations.insert(aodOperations.end(), operations.begin(), operations.end()); } + if (type == qc::OpType::AodActivate) { + return aodOperations; + } + std::reverse(aodOperations.begin(), aodOperations.end()); return aodOperations; } } // namespace na From 00832efca734f445effece6f6a8f409dce2d43e3 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Tue, 22 Apr 2025 13:02:25 +0200 Subject: [PATCH 159/394] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20slightly=20speed?= =?UTF-8?q?=20up=20bridge=20gate=20computation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridNeutralAtomMapper.hpp | 4 ++-- src/hybridmap/HybridNeutralAtomMapper.cpp | 9 +++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index 6171e1835..3b1d32b96 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -232,8 +232,8 @@ class NeutralAtomMapper { // Methods for bridge operations mapping - [[nodiscard]] Bridge findBestBridge() const; - [[nodiscard]] Bridges getShortestBridges() const; + [[nodiscard]] Bridge findBestBridge(); + [[nodiscard]] Bridges getShortestBridges(); [[nodiscard]] CoordIndices computeCurrentCoordUsages() const; diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 3b9d76d3c..f8e1661a3 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -503,7 +503,7 @@ std::set NeutralAtomMapper::getAllPossibleSwaps( } return swaps; } -Bridge NeutralAtomMapper::findBestBridge() const { +Bridge NeutralAtomMapper::findBestBridge() { auto allBridges = getShortestBridges(); if (allBridges.empty()) { return {}; @@ -528,13 +528,18 @@ Bridge NeutralAtomMapper::findBestBridge() const { return allBridges[bestBridgeIdx]; } -Bridges NeutralAtomMapper::getShortestBridges() const { +Bridges NeutralAtomMapper::getShortestBridges() { Bridges allBridges; size_t minBridgeLength = std::numeric_limits::max(); for (const auto* const op : this->frontLayerGate) { if (op->getUsedQubits().size() == 2) { auto usedQuBits = op->getUsedQubits(); auto usedHwQubits = this->mapping.getHwQubits(usedQuBits); + // shortcut if distance already larger than minBridgeLength + if (this->hardwareQubits.getAllToAllSwapDistance(usedHwQubits) > + minBridgeLength) { + continue; + } const auto bridges = this->hardwareQubits.computeAllShortestPaths( *usedHwQubits.begin(), *usedHwQubits.rbegin()); if (bridges.empty()) { From bd43bcf2bfaa6cff3c9c95ca88546964f912e3ea Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Tue, 22 Apr 2025 13:31:06 +0200 Subject: [PATCH 160/394] Changed useBridge to a parameter maxDistanceBridge --- include/hybridmap/HybridNeutralAtomMapper.hpp | 2 +- src/hybridmap/HybridNeutralAtomMapper.cpp | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index 3b1d32b96..a5579490a 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -43,7 +43,7 @@ struct MapperParameters { uint32_t seed = 0; uint32_t numFlyingAncillas = 0; uint32_t limitShuttlingLayer = std::numeric_limits::max(); - bool useBridge = true; + uint32_t maxBridgeDistance = 1; bool usePassBy = true; bool verbose = false; InitialCoordinateMapping initialCoordMapping; diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index f8e1661a3..2866d2bb3 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -536,8 +536,10 @@ Bridges NeutralAtomMapper::getShortestBridges() { auto usedQuBits = op->getUsedQubits(); auto usedHwQubits = this->mapping.getHwQubits(usedQuBits); // shortcut if distance already larger than minBridgeLength - if (this->hardwareQubits.getAllToAllSwapDistance(usedHwQubits) > - minBridgeLength) { + const auto dist = + this->hardwareQubits.getAllToAllSwapDistance(usedHwQubits); + if (dist > this->parameters->maxBridgeDistance || + dist > static_cast(minBridgeLength)) { continue; } const auto bridges = this->hardwareQubits.computeAllShortestPaths( @@ -1753,7 +1755,7 @@ size_t NeutralAtomMapper::gateBasedMapping(NeutralAtomLayer& frontLayer, auto bestSwap = findBestSwap(lastSwap); MappingMethod bestMethod = MappingMethod::SwapMethod; - if (parameters->useBridge) { + if (parameters->maxBridgeDistance > 0) { auto bestBridge = findBestBridge(); bestMethod = compareSwapAndBridge(bestSwap, bestBridge); if (bestMethod == MappingMethod::BridgeMethod) { From 1f64624f2eb97a2313b9946337d1ba4096cdcc02 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Tue, 22 Apr 2025 16:59:47 +0200 Subject: [PATCH 161/394] =?UTF-8?q?=F0=9F=90=9B=20Fixed=20bug=20where=20tw?= =?UTF-8?q?o=20moves=20using=20same=20CoordIdx=20hat=20wrong=20ordering?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/MoveToAodConverter.cpp | 30 ++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/src/hybridmap/MoveToAodConverter.cpp b/src/hybridmap/MoveToAodConverter.cpp index 692bc332f..d1f2d3f3d 100644 --- a/src/hybridmap/MoveToAodConverter.cpp +++ b/src/hybridmap/MoveToAodConverter.cpp @@ -518,21 +518,39 @@ AodOperation MoveToAodConverter::MoveGroup::connectAodOperations( for (const auto& deactivation : aodDeactivationHelper.allActivations) { if (activation.moves == deactivation.moves) { // get target qubits + qc::Targets starts; + qc::Targets ends; const auto nPos = aodActivationHelper.arch->getNpositions(); for (const auto& move : activation.moves) { if (move.load1) { - targetQubits.emplace_back(move.c1); + starts.emplace_back(move.c1); } else if (move.load2) { - targetQubits.emplace_back(move.c1 + nPos); + starts.emplace_back(move.c1 + nPos); } else { - targetQubits.emplace_back(move.c1 + (2 * nPos)); + starts.emplace_back(move.c1 + (2 * nPos)); } if (move.load2) { - targetQubits.emplace_back(move.c2); + ends.emplace_back(move.c2); } else if (move.load1) { - targetQubits.emplace_back(move.c2 + nPos); + ends.emplace_back(move.c2 + nPos); } else { - targetQubits.emplace_back(move.c2 + (2 * nPos)); + ends.emplace_back(move.c2 + (2 * nPos)); + } + } + + // Ensure that the ordering of the target qubits such that atoms are + // moved away before used as a target + for (size_t i = 0; i < starts.size(); i++) { + const auto pos = + std::find(targetQubits.begin(), targetQubits.end(), starts[i]); + if (pos == targetQubits.end()) { + // if the start qubit is not already in the target qubits + targetQubits.emplace_back(starts[i]); + targetQubits.emplace_back(ends[i]); + } else { + // insert before one before the found position + targetQubits.insert(pos - 1, ends[i]); + targetQubits.insert(pos - 1, starts[i]); } } From 62519491247de9db22fdde26c38a291524291ccf Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Tue, 22 Apr 2025 17:03:10 +0200 Subject: [PATCH 162/394] =?UTF-8?q?=F0=9F=90=9B=20Fixed=20missing=20qubits?= =?UTF-8?q?=20at=20schedule=20output=20due=20to=20use=20of=20set?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/NeutralAtomScheduler.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/hybridmap/NeutralAtomScheduler.cpp b/src/hybridmap/NeutralAtomScheduler.cpp index b0cb85128..ffed6a8d6 100644 --- a/src/hybridmap/NeutralAtomScheduler.cpp +++ b/src/hybridmap/NeutralAtomScheduler.cpp @@ -74,8 +74,13 @@ na::SchedulerResults na::NeutralAtomScheduler::schedule( // DEBUG info if (verbose) { std::cout << op->getName() << " "; - for (const auto& qubit : qubits) { - std::cout << "q" << qubit << " "; + // print control qubits + for (const auto& c : op->getControls()) { + std::cout << "c" << c.qubit << " "; + } + // print target qubits + for (const auto& t : op->getTargets()) { + std::cout << "q" << t << " "; } std::cout << "-> time: " << opTime << ", fidelity: " << opFidelity << "\n"; From 21252ebac82b078f5db35339f91d0a4441383986 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Wed, 23 Apr 2025 09:42:21 +0200 Subject: [PATCH 163/394] =?UTF-8?q?=F0=9F=90=9B=20fixed=20missing=20contro?= =?UTF-8?q?ls=20during=20passby?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridNeutralAtomMapper.cpp | 37 ++++++++++++++++------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 2866d2bb3..5ae532269 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -13,6 +13,7 @@ #include "hybridmap/NeutralAtomLayer.hpp" #include "hybridmap/NeutralAtomUtils.hpp" #include "ir/QuantumComputation.hpp" +#include "ir/operations/Control.hpp" #include "ir/operations/OpType.hpp" #include "ir/operations/Operation.hpp" #include "ir/operations/StandardOperation.hpp" @@ -160,24 +161,38 @@ qc::QuantumComputation NeutralAtomMapper::convertToAod() { void NeutralAtomMapper::applyPassBy(NeutralAtomLayer& frontLayer, const FlyingAncillaComb& faComb) { - const auto usedQubits = faComb.op->getUsedQubits(); - auto usedCoords = - hardwareQubits.getCoordIndices(mapping.getHwQubits(usedQubits)); + auto opTargets = faComb.op->getTargets(); + auto targetHwQubits = mapping.getHwQubits(opTargets); + auto targetCoords = hardwareQubits.getCoordIndices(targetHwQubits); + auto opControls = faComb.op->getControls(); + HwQubitsVector controlQubits; + for (const auto& control : opControls) { + controlQubits.emplace_back(control.qubit); + } + auto controlHwQubits = mapping.getHwQubits(controlQubits); + auto controlCoords = hardwareQubits.getCoordIndices(controlHwQubits); + for (const auto& passBy : faComb.moves) { mappedQc.move(passBy.q1, passBy.q2 + arch->getNpositions()); if (this->parameters->verbose) { std::cout << "passby " << passBy.q1 << " " << passBy.q2 << '\n'; } - if (usedCoords.find(passBy.q1) != usedCoords.end()) { - usedCoords.erase(passBy.q1); - usedCoords.insert(passBy.q2 + arch->getNpositions()); + auto itT = std::find(targetCoords.begin(), targetCoords.end(), passBy.q1); + if (itT != targetCoords.end()) { + *itT = passBy.q2 + arch->getNpositions(); + } + auto itC = std::find(controlCoords.begin(), controlCoords.end(), passBy.q1); + if (itC != controlCoords.end()) { + *itC = passBy.q2 + arch->getNpositions(); } } - const auto opCopy = faComb.op->clone(); - const std::vector usedCoordsVec = {usedCoords.begin(), - usedCoords.end()}; - opCopy->setTargets(usedCoordsVec); - opCopy->setControls({}); + auto opCopy = faComb.op->clone(); + opCopy->setTargets(targetCoords); + qc::Controls controls; + for (const auto& control : controlCoords) { + controls.emplace(control); + } + opCopy->setControls(controls); mappedQc.emplace_back(opCopy->clone()); // mapGate(faComb.op); From 79459e9459c041f97c51cc64fa2f1a9d2fbb1942 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Wed, 23 Apr 2025 10:04:28 +0200 Subject: [PATCH 164/394] =?UTF-8?q?=E2=9C=A8=20addec=20getHwQubits=20for?= =?UTF-8?q?=20vector=20additional=20to=20set?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/Mapping.hpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/include/hybridmap/Mapping.hpp b/include/hybridmap/Mapping.hpp index a2518bfd9..4f2967cbf 100644 --- a/include/hybridmap/Mapping.hpp +++ b/include/hybridmap/Mapping.hpp @@ -103,6 +103,20 @@ class Mapping { return hwQubits; } + /** + * @brief Returns the hardware qubits assigned to the given circuit qubits. + * @param qubits The circuit qubits to be queried + * @return The hardware qubits assigned to the given circuit qubits + */ + [[nodiscard]] std::vector + getHwQubits(const std::vector& qubits) const { + std::vector hwQubits; + for (const auto& qubit : qubits) { + hwQubits.emplace_back(this->getHwQubit(qubit)); + } + return hwQubits; + } + /** * @brief Returns the circuit qubit assigned to the given hardware qubit. * @details Throws an exception if the hardware qubit is not assigned to any From 13d22f23daaf31926939ee548a27720b21938985 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Wed, 23 Apr 2025 10:05:09 +0200 Subject: [PATCH 165/394] =?UTF-8?q?=F0=9F=90=9B=20fixed=20bug=20where=20sw?= =?UTF-8?q?ap=20distance=20instead=20of=20euclidean=20distance=20is=20calc?= =?UTF-8?q?ulated?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/NeutralAtomArchitecture.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/hybridmap/NeutralAtomArchitecture.hpp b/include/hybridmap/NeutralAtomArchitecture.hpp index 5d7ad7666..24c50926f 100644 --- a/include/hybridmap/NeutralAtomArchitecture.hpp +++ b/include/hybridmap/NeutralAtomArchitecture.hpp @@ -414,7 +414,7 @@ class NeutralAtomArchitecture { if (c1 == c2) { continue; } - dist += getSwapDistance(c1, c2); + dist += getEuclideanDistance(c1, c2); } } return dist; From 9a6cb96fd8e3d489b417bd5905aea561c09a9744 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Wed, 23 Apr 2025 10:05:46 +0200 Subject: [PATCH 166/394] =?UTF-8?q?=F0=9F=90=9B=20fixed=20bug=20where=20pa?= =?UTF-8?q?ssBy=20and=20move=20on=20same=20atom=20was=20in=20one=20moveGro?= =?UTF-8?q?up?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/MoveToAodConverter.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/hybridmap/MoveToAodConverter.cpp b/src/hybridmap/MoveToAodConverter.cpp index d1f2d3f3d..7f351acdb 100644 --- a/src/hybridmap/MoveToAodConverter.cpp +++ b/src/hybridmap/MoveToAodConverter.cpp @@ -160,6 +160,10 @@ bool MoveToAodConverter::MoveGroup::canAddMove( if (move.load1 != moveGroup.load1 || move.load2 != moveGroup.load2) { return false; } + // if start or end is same -> false + if (move.c1 == moveGroup.c1 || move.c2 == moveGroup.c2) { + return false; + } // check if parallel executable auto moveVector = archArg.getVector(move.c1, move.c2); auto opVector = archArg.getVector(moveGroup.c1, moveGroup.c2); From a7a3a2869d1f1940b3b2961917ca2c105fbb27d3 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Wed, 23 Apr 2025 10:31:39 +0200 Subject: [PATCH 167/394] =?UTF-8?q?=E2=9C=A8=20added=20weight=20paramter?= =?UTF-8?q?=20to=20control=20static=20vs=20dynamic=20mapping?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridNeutralAtomMapper.hpp | 1 + src/hybridmap/HybridNeutralAtomMapper.cpp | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index a5579490a..2c823b20e 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -38,6 +38,7 @@ struct MapperParameters { qc::fp lookaheadWeightMoves = 0.1; qc::fp decay = 0.1; qc::fp shuttlingTimeWeight = 1; + qc::fp dynamicMappingWeight = 2; qc::fp gateWeight = 1; qc::fp shuttlingWeight = 1; uint32_t seed = 0; diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 5ae532269..1f164da47 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -2206,7 +2206,8 @@ NeutralAtomMapper::compareSwapAndBridge(const Swap& bestSwap, qc::fp const bridgeFidelity = this->arch->getGateAverageFidelity(bridgeName) * std::exp(-this->arch->getGateTime(bridgeName) / this->arch->getDecoherenceTime()); - if (bridgeDistReduction * std::log(swapFidelity) > + if (bridgeDistReduction * std::log(swapFidelity) / + parameters->dynamicMappingWeight > swapDistReduction * std::log(bridgeFidelity)) { return MappingMethod::SwapMethod; } @@ -2279,7 +2280,8 @@ MappingMethod NeutralAtomMapper::compareShuttlingAndFlyingAncilla( faCombSize) * std::exp(-passByTime / this->arch->getDecoherenceTime()); - const auto move = std::log(moveFidelity) / moveDistReduction; + const auto move = std::log(moveFidelity) / moveDistReduction / + parameters->dynamicMappingWeight; const auto fa = std::log(faFidelity) / faDistReduction; const auto passBy = std::log(passByFidelity) / faDistReduction; From 20592849cc7c8d281f8d93551e9eec1841a60aee Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Wed, 23 Apr 2025 13:06:37 +0200 Subject: [PATCH 168/394] =?UTF-8?q?=F0=9F=90=9B=20fixed=20missing=20contro?= =?UTF-8?q?ls=20during=20flying=20ancilla?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridNeutralAtomMapper.cpp | 33 ++++++++++++++++------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 1f164da47..0f09787d6 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -388,9 +388,16 @@ void NeutralAtomMapper::applyBridge(NeutralAtomLayer& frontLayer, } void NeutralAtomMapper::applyFlyingAncilla(NeutralAtomLayer& frontLayer, const FlyingAncillaComb& faComb) { - const auto usedQubits = faComb.op->getUsedQubits(); - auto usedCoords = - hardwareQubits.getCoordIndices(mapping.getHwQubits(usedQubits)); + auto targetCoords = hardwareQubits.getCoordIndices( + mapping.getHwQubits(faComb.op->getTargets())); + // get control vector + auto opControls = faComb.op->getControls(); + HwQubitsVector controlQubits; + for (const auto& control : opControls) { + controlQubits.emplace_back(control.qubit); + } + auto controlCoords = + hardwareQubits.getCoordIndices(mapping.getHwQubits(controlQubits)); const auto nPos = this->arch->getNpositions(); for (const auto& passBy : faComb.moves) { @@ -404,9 +411,13 @@ void NeutralAtomMapper::applyFlyingAncilla(NeutralAtomLayer& frontLayer, mappedQc.h(ancQ1); mappedQc.move(ancQ1, ancQ2); - if (usedCoords.find(passBy.q1) != usedCoords.end()) { - usedCoords.erase(passBy.q1); - usedCoords.insert(ancQ2); + auto itT = std::find(targetCoords.begin(), targetCoords.end(), passBy.q1); + if (itT != targetCoords.end()) { + *itT = ancQ2; + } + auto itC = std::find(controlCoords.begin(), controlCoords.end(), passBy.q1); + if (itC != controlCoords.end()) { + *itC = ancQ2; } if (this->parameters->verbose) { @@ -415,10 +426,12 @@ void NeutralAtomMapper::applyFlyingAncilla(NeutralAtomLayer& frontLayer, } } const auto opCopy = faComb.op->clone(); - const std::vector usedCoordsVec = {usedCoords.begin(), - usedCoords.end()}; - opCopy->setTargets(usedCoordsVec); - opCopy->setControls({}); + opCopy->setTargets(targetCoords); + qc::Controls controls; + for (const auto& control : controlCoords) { + controls.emplace(control); + } + opCopy->setControls(controls); mappedQc.emplace_back(opCopy->clone()); for (const auto& passBy : faComb.moves) { From 646a37f9146e460b085fefbda185bada6088f543 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Wed, 23 Apr 2025 14:17:07 +0200 Subject: [PATCH 169/394] =?UTF-8?q?=F0=9F=90=9B=20Throw=20error=20if=20mor?= =?UTF-8?q?e=20than=20one=20FA=20is=20used?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridNeutralAtomMapper.hpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index 2c823b20e..dfe898571 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -454,6 +454,9 @@ class NeutralAtomMapper { "No free coordinates for shuttling but shuttling " "weight is greater than 0."); } + if (parameters->numFlyingAncillas > 1) { + throw std::runtime_error("Only one flying ancilla is supported for now."); + } // precompute exponential decay weights this->decayWeights.reserve(this->arch->getNcolumns()); for (uint32_t i = this->arch->getNcolumns(); i > 0; --i) { From 7f1fb026232e25ba7da275ff43b12e301a84f35c Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Wed, 23 Apr 2025 14:47:15 +0200 Subject: [PATCH 170/394] =?UTF-8?q?=F0=9F=90=9B=20Fixed=20bug=20where=20mo?= =?UTF-8?q?ve=20is=20added=20to=20previous=20movegroup=20although=20atom?= =?UTF-8?q?=20still=20used=20later.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/MoveToAodConverter.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/hybridmap/MoveToAodConverter.cpp b/src/hybridmap/MoveToAodConverter.cpp index 7f351acdb..ac724070f 100644 --- a/src/hybridmap/MoveToAodConverter.cpp +++ b/src/hybridmap/MoveToAodConverter.cpp @@ -121,7 +121,8 @@ void MoveToAodConverter::initMoveGroups(qc::QuantumComputation& qc) { currentMoveGroup = MoveGroup(); currentMoveGroup.addMove(move, idx); } - } else if ((!currentMoveGroup.moves.empty())) { + } else if (!currentMoveGroup.moves.empty() || + !currentMoveGroup.movesFa.empty()) { for (const auto& qubit : op->getUsedQubits()) { if (std::find(currentMoveGroup.qubitsUsedByGates.begin(), currentMoveGroup.qubitsUsedByGates.end(), From c7f11cad837efa84a3c7ea8102a746292892f464 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Tue, 9 Sep 2025 14:55:04 +0200 Subject: [PATCH 171/394] =?UTF-8?q?=E2=9C=85=20updated=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/hybridmap/test_hybridmap.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/test/hybridmap/test_hybridmap.cpp b/test/hybridmap/test_hybridmap.cpp index 977b3bb38..c1c43a033 100644 --- a/test/hybridmap/test_hybridmap.cpp +++ b/test/hybridmap/test_hybridmap.cpp @@ -137,21 +137,22 @@ class NeutralAtomMapperTest : public ::testing::Test { na::InitialCoordinateMapping::Trivial; mapperParameters.lookaheadDepth = 1; mapperParameters.lookaheadWeightSwaps = 0.1; - mapperParameters.lookaheadWeightMoves = 0.1; + mapperParameters.lookaheadWeightMoves = 0.5; mapperParameters.decay = 0; mapperParameters.shuttlingTimeWeight = 0.1; mapperParameters.gateWeight = 1; - mapperParameters.shuttlingWeight = 1; + mapperParameters.shuttlingWeight = 0; mapperParameters.seed = 43; - mapperParameters.verbose = true; + mapperParameters.verbose = false; mapperParameters.numFlyingAncillas = 2; - mapperParameters.limitShuttlingLayer = 2; - mapperParameters.useBridge = true; + mapperParameters.limitShuttlingLayer = 1; + // mapperParameters.useBridge = true; mapperParameters.usePassBy = true; mapper.setParameters(mapperParameters); qc = qc::QuantumComputation( - // "circuits/dj_nativegates_rigetti_qiskit_opt3_10.qasm"); - "circuits/modulo_2.qasm"); + "circuits/dj_nativegates_rigetti_qiskit_opt3_10.qasm"); + // "circuits/modulo_2.qasm"); + // "circuits/random_nativegates_ibm_qiskit_opt3_50.qasm"); } }; From 52fedc1cda46c2be6634938789557fcd7418bc5f Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Tue, 9 Sep 2025 15:46:43 +0200 Subject: [PATCH 172/394] =?UTF-8?q?=F0=9F=90=9B=20Fixed=20independence=20o?= =?UTF-8?q?f=20PassBy=20and=20FA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridNeutralAtomMapper.hpp | 5 +- include/hybridmap/NeutralAtomArchitecture.hpp | 6 +- include/hybridmap/NeutralAtomUtils.hpp | 5 + src/hybridmap/HybridNeutralAtomMapper.cpp | 125 +++++++++++------- 4 files changed, 92 insertions(+), 49 deletions(-) diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index dfe898571..105f41fc9 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -240,6 +240,8 @@ class NeutralAtomMapper { [[nodiscard]] FlyingAncillaComb convertMoveCombToFlyingAncillaComb(const MoveComb& moveComb) const; + [[nodiscard]] PassByComb + convertMoveCombToPassByComb(const MoveComb& moveComb) const; // std::vector> // findAllBridges(qc::QuantumComputation& qc); @@ -303,7 +305,8 @@ class NeutralAtomMapper { const Bridge& bestBridge); [[nodiscard]] MappingMethod compareShuttlingAndFlyingAncilla(const MoveComb& bestMoveComb, - const FlyingAncillaComb& bestFaComb) const; + const FlyingAncillaComb& bestFaComb, + const PassByComb& bestPbComb) const; // Helper methods /** diff --git a/include/hybridmap/NeutralAtomArchitecture.hpp b/include/hybridmap/NeutralAtomArchitecture.hpp index 24c50926f..f4dae5792 100644 --- a/include/hybridmap/NeutralAtomArchitecture.hpp +++ b/include/hybridmap/NeutralAtomArchitecture.hpp @@ -439,10 +439,10 @@ class NeutralAtomArchitecture { } [[nodiscard]] qc::fp - getPassByEuclideanDistance(const FlyingAncillaComb& faComb) const { + getPassByEuclideanDistance(const PassByComb& pbComb) const { qc::fp dist = 0; - for (const auto& fa : faComb.moves) { - dist += getEuclideanDistance(fa.q1, fa.q2) * 2; + for (const auto& fa : pbComb.moves) { + dist += getEuclideanDistance(fa.c1, fa.c2) * 2; } return dist; } diff --git a/include/hybridmap/NeutralAtomUtils.hpp b/include/hybridmap/NeutralAtomUtils.hpp index 4e80610a3..2d0522c49 100644 --- a/include/hybridmap/NeutralAtomUtils.hpp +++ b/include/hybridmap/NeutralAtomUtils.hpp @@ -145,6 +145,11 @@ struct FlyingAncillaComb { const qc::Operation* op; }; +struct PassByComb { + std::vector moves; + const qc::Operation* op; +}; + /** * @brief Helper class to manage multiple atom moves which belong together. * @details E.g. a move-away combined with the actual move. These are combined diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 0f09787d6..78da615fc 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -649,6 +649,10 @@ FlyingAncillaComb NeutralAtomMapper::convertMoveCombToFlyingAncillaComb( bestFA.q2 = move.c1; bestFA.origin = nearSecond; bestFA.index = nearSecondIdx; + + usedFA.emplace(bestFA.index); + bestFAs.emplace_back(bestFA); + continue; } } bestFA.q1 = move.c1; @@ -663,6 +667,24 @@ FlyingAncillaComb NeutralAtomMapper::convertMoveCombToFlyingAncillaComb( return {bestFAs, moveComb.op}; } +PassByComb +NeutralAtomMapper::convertMoveCombToPassByComb(const MoveComb& moveComb) const { + if (this->flyingAncillas.getNumQubits() == 0) { + return {}; + } + const auto usedQubits = moveComb.op->getUsedQubits(); + const auto hwQubits = this->mapping.getHwQubits(usedQubits); + const auto usedCoords = this->hardwareQubits.getCoordIndices(hwQubits); + + std::vector bestPbs; + for (const auto move : moveComb.moves) { + if (usedCoords.find(move.c1) != usedCoords.end()) { + bestPbs.emplace_back(AtomMove{move.c1, move.c2, true, false}); + } + } + return {bestPbs, moveComb.op}; +} + qc::fp NeutralAtomMapper::swapCost( const Swap& swap, const std::pair& swapsFront, const std::pair& swapsLookahead) { @@ -1590,8 +1612,10 @@ size_t NeutralAtomMapper::shuttlingBasedMapping( } auto bestComb = findBestAtomMove(); auto bestFaComb = convertMoveCombToFlyingAncillaComb(bestComb); + auto bestPbComb = convertMoveCombToPassByComb(bestComb); - switch (compareShuttlingAndFlyingAncilla(bestComb, bestFaComb)) { + switch ( + compareShuttlingAndFlyingAncilla(bestComb, bestFaComb, bestPbComb)) { case MappingMethod::MoveMethod: // apply whole move combination at once for (const auto& move : bestComb.moves) { @@ -2228,23 +2252,13 @@ NeutralAtomMapper::compareSwapAndBridge(const Swap& bestSwap, } MappingMethod NeutralAtomMapper::compareShuttlingAndFlyingAncilla( - const MoveComb& bestMoveComb, const FlyingAncillaComb& bestFaComb) const { - if (!parameters->usePassBy) { - return MappingMethod::MoveMethod; - } + const MoveComb& bestMoveComb, const FlyingAncillaComb& bestFaComb, + const PassByComb& bestPbComb) const { // move distance reduction auto const moveDistReduction = moveCombDistanceReduction(bestMoveComb, this->frontLayerShuttling) + (this->parameters->lookaheadWeightMoves * moveCombDistanceReduction(bestMoveComb, this->lookaheadLayerShuttling)); - - // flying ancilla distance reduction - auto const faCoords = this->hardwareQubits.getCoordIndices( - this->mapping.getHwQubits(bestFaComb.op->getUsedQubits())); - auto const faDistReduction = - this->arch->getAllToAllEuclideanDistance(faCoords); - - // fidelity comparison // move auto const moveDist = this->arch->getMoveCombEuclideanDistance(bestMoveComb); auto const moveCombSize = bestMoveComb.size(); @@ -2262,41 +2276,62 @@ MappingMethod NeutralAtomMapper::compareShuttlingAndFlyingAncilla( auto const moveDecoherence = std::exp(-moveTime / this->arch->getDecoherenceTime()); auto const moveFidelity = moveOpFidelity * moveDecoherence; + const auto move = std::log(moveFidelity) / moveDistReduction / + parameters->dynamicMappingWeight; - // flying ancilla - auto const faDist = this->arch->getFaEuclideanDistance(bestFaComb); - auto const faCombSize = bestFaComb.moves.size(); - auto const faOpFidelity = - std::pow(this->arch->getShuttlingAverageFidelity(qc::OpType::AodMove) * - std::pow(this->arch->getGateAverageFidelity("cz"), 2) * - std::pow(this->arch->getGateAverageFidelity("h"), 4), - faCombSize); - auto const faDecoherence = - std::exp(-faDist / this->arch->getShuttlingTime(qc::OpType::AodMove) / - this->arch->getDecoherenceTime()); - auto const faFidelity = faOpFidelity * faDecoherence; + // fa + auto fa = -std::numeric_limits::infinity(); + auto faDistReduction = 0.0; + if (flyingAncillas.getNumQubits() != 0) { + // flying ancilla distance reduction + auto const faCoords = this->hardwareQubits.getCoordIndices( + this->mapping.getHwQubits(bestFaComb.op->getUsedQubits())); + faDistReduction = this->arch->getAllToAllEuclideanDistance(faCoords); + + // flying ancilla + auto const faDist = this->arch->getFaEuclideanDistance(bestFaComb); + auto const faCombSize = bestFaComb.moves.size(); + auto const faOpFidelity = + std::pow(this->arch->getShuttlingAverageFidelity(qc::OpType::AodMove) * + std::pow(this->arch->getGateAverageFidelity("cz"), 2) * + std::pow(this->arch->getGateAverageFidelity("h"), 4), + faCombSize); + auto const faDecoherence = + std::exp(-faDist / this->arch->getShuttlingTime(qc::OpType::AodMove) / + this->arch->getDecoherenceTime()); + auto const faFidelity = faOpFidelity * faDecoherence; + fa = std::log(faFidelity) / faDistReduction; + } // passby - auto const passByDist = this->arch->getPassByEuclideanDistance(bestFaComb); - auto const passByTime = - (passByDist / this->arch->getShuttlingTime(qc::OpType::AodMove)) + - (this->arch->getShuttlingTime(qc::OpType::AodActivate) * - static_cast(faCombSize)) + - (this->arch->getShuttlingTime(qc::OpType::AodDeactivate) * - static_cast(faCombSize)); - auto const passByFidelity = - std::pow( - this->arch->getShuttlingAverageFidelity(qc::OpType::AodMove) * - this->arch->getShuttlingAverageFidelity(qc::OpType::AodActivate) * - this->arch->getShuttlingAverageFidelity( - qc::OpType::AodDeactivate), - faCombSize) * - std::exp(-passByTime / this->arch->getDecoherenceTime()); - - const auto move = std::log(moveFidelity) / moveDistReduction / - parameters->dynamicMappingWeight; - const auto fa = std::log(faFidelity) / faDistReduction; - const auto passBy = std::log(passByFidelity) / faDistReduction; + auto pbDistReduction = 0.0; + auto passBy = -std::numeric_limits::infinity(); + if (parameters->usePassBy) { + auto const pbCoords = this->hardwareQubits.getCoordIndices( + this->mapping.getHwQubits(bestPbComb.op->getUsedQubits())); + faDistReduction = this->arch->getAllToAllEuclideanDistance(pbCoords); + const auto pbCombSize = bestPbComb.moves.size(); + + auto const passByDist = this->arch->getPassByEuclideanDistance(bestPbComb); + auto const passByTime = + (passByDist / this->arch->getShuttlingTime(qc::OpType::AodMove)) + + (this->arch->getShuttlingTime(qc::OpType::AodActivate) * + static_cast(pbCombSize)) + + (this->arch->getShuttlingTime(qc::OpType::AodDeactivate) * + static_cast(pbCombSize)); + auto const passByFidelity = + std::pow(this->arch->getShuttlingAverageFidelity(qc::OpType::AodMove) * + this->arch->getShuttlingAverageFidelity( + qc::OpType::AodActivate) * + this->arch->getShuttlingAverageFidelity( + qc::OpType::AodDeactivate), + pbCombSize) * + std::exp(-passByTime / this->arch->getDecoherenceTime()); + + const auto move = std::log(moveFidelity) / moveDistReduction / + parameters->dynamicMappingWeight; + passBy = std::log(passByFidelity) / faDistReduction; + } if (move > fa && move > passBy) { return MappingMethod::MoveMethod; From 4ba54c8e13fcfc695244815ba120bd457a15e6f7 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 11 Sep 2025 13:04:34 +0200 Subject: [PATCH 173/394] =?UTF-8?q?=F0=9F=90=9B=20Fixed=20error=20where=20?= =?UTF-8?q?PassBy=20used=20FA=20info=20for=20swapped=20directions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridNeutralAtomMapper.hpp | 3 +- src/hybridmap/HybridNeutralAtomMapper.cpp | 34 +++++++++---------- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index 105f41fc9..fe7b681d3 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -168,8 +168,7 @@ class NeutralAtomMapper { void applyBridge(NeutralAtomLayer& frontLayer, const Bridge& bridge); void applyFlyingAncilla(NeutralAtomLayer& frontLayer, const FlyingAncillaComb& faComb); - void applyPassBy(NeutralAtomLayer& frontLayer, - const FlyingAncillaComb& faComb); + void applyPassBy(NeutralAtomLayer& frontLayer, const PassByComb& pbComb); // Methods for gate vs. shuttling /** diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 78da615fc..541f8960f 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -160,11 +160,11 @@ qc::QuantumComputation NeutralAtomMapper::convertToAod() { } void NeutralAtomMapper::applyPassBy(NeutralAtomLayer& frontLayer, - const FlyingAncillaComb& faComb) { - auto opTargets = faComb.op->getTargets(); + const PassByComb& pbComb) { + auto opTargets = pbComb.op->getTargets(); auto targetHwQubits = mapping.getHwQubits(opTargets); auto targetCoords = hardwareQubits.getCoordIndices(targetHwQubits); - auto opControls = faComb.op->getControls(); + auto opControls = pbComb.op->getControls(); HwQubitsVector controlQubits; for (const auto& control : opControls) { controlQubits.emplace_back(control.qubit); @@ -172,21 +172,21 @@ void NeutralAtomMapper::applyPassBy(NeutralAtomLayer& frontLayer, auto controlHwQubits = mapping.getHwQubits(controlQubits); auto controlCoords = hardwareQubits.getCoordIndices(controlHwQubits); - for (const auto& passBy : faComb.moves) { - mappedQc.move(passBy.q1, passBy.q2 + arch->getNpositions()); + for (const auto& passBy : pbComb.moves) { + mappedQc.move(passBy.c1, passBy.c2 + arch->getNpositions()); if (this->parameters->verbose) { - std::cout << "passby " << passBy.q1 << " " << passBy.q2 << '\n'; + std::cout << "passby " << passBy.c1 << " " << passBy.c2 << '\n'; } - auto itT = std::find(targetCoords.begin(), targetCoords.end(), passBy.q1); + auto itT = std::find(targetCoords.begin(), targetCoords.end(), passBy.c1); if (itT != targetCoords.end()) { - *itT = passBy.q2 + arch->getNpositions(); + *itT = passBy.c2 + arch->getNpositions(); } - auto itC = std::find(controlCoords.begin(), controlCoords.end(), passBy.q1); + auto itC = std::find(controlCoords.begin(), controlCoords.end(), passBy.c1); if (itC != controlCoords.end()) { - *itC = passBy.q2 + arch->getNpositions(); + *itC = passBy.c2 + arch->getNpositions(); } } - auto opCopy = faComb.op->clone(); + auto opCopy = pbComb.op->clone(); opCopy->setTargets(targetCoords); qc::Controls controls; for (const auto& control : controlCoords) { @@ -196,15 +196,15 @@ void NeutralAtomMapper::applyPassBy(NeutralAtomLayer& frontLayer, mappedQc.emplace_back(opCopy->clone()); // mapGate(faComb.op); - for (const auto& passBy : faComb.moves) { - mappedQc.move(passBy.q2 + arch->getNpositions(), passBy.q1); + for (const auto& passBy : pbComb.moves) { + mappedQc.move(passBy.c2 + arch->getNpositions(), passBy.c1); if (this->parameters->verbose) { - std::cout << "passby " << passBy.q2 << " " << passBy.q1 << '\n'; + std::cout << "passby " << passBy.c2 << " " << passBy.c1 << '\n'; } } - frontLayer.removeGatesAndUpdate({faComb.op}); - nPassBy += faComb.moves.size(); + frontLayer.removeGatesAndUpdate({pbComb.op}); + nPassBy += pbComb.moves.size(); } void NeutralAtomMapper::reassignGatesToLayers(const GateList& frontGates, @@ -1627,7 +1627,7 @@ size_t NeutralAtomMapper::shuttlingBasedMapping( applyFlyingAncilla(frontLayer, bestFaComb); break; case MappingMethod::PassByMethod: - applyPassBy(frontLayer, bestFaComb); + applyPassBy(frontLayer, bestPbComb); break; default: break; From 7caa6b80fc26fa638d1d39579b2f53970117bc11 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 11 Sep 2025 14:20:38 +0200 Subject: [PATCH 174/394] =?UTF-8?q?=F0=9F=90=9B=20Fixed=20error=20where=20?= =?UTF-8?q?PassBy=20is=20not=20created=20when=20there=20are=20not=20FA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HardwareQubits.hpp | 6 +----- src/hybridmap/HybridNeutralAtomMapper.cpp | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/include/hybridmap/HardwareQubits.hpp b/include/hybridmap/HardwareQubits.hpp index 92e65a768..0f67cea7e 100644 --- a/include/hybridmap/HardwareQubits.hpp +++ b/include/hybridmap/HardwareQubits.hpp @@ -89,11 +89,7 @@ class HardwareQubits { InitialCoordinateMapping::Trivial, uint32_t seed = 0) : arch(&architecture) { - if (nQubits == 0) { - this->nQubits = architecture.getNqubits(); - } else { - this->nQubits = nQubits; - } + this->nQubits = nQubits; swapDistances = SymmetricMatrix(this->nQubits); switch (initialCoordinateMapping) { diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 541f8960f..7c6621109 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -669,7 +669,7 @@ FlyingAncillaComb NeutralAtomMapper::convertMoveCombToFlyingAncillaComb( PassByComb NeutralAtomMapper::convertMoveCombToPassByComb(const MoveComb& moveComb) const { - if (this->flyingAncillas.getNumQubits() == 0) { + if (!this->parameters->usePassBy) { return {}; } const auto usedQubits = moveComb.op->getUsedQubits(); From fa649a492eb1ee5d762f7871b4c3df847009fda3 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 11 Sep 2025 14:37:23 +0200 Subject: [PATCH 175/394] =?UTF-8?q?=E2=9C=A8=20Improved=20result=20output?= =?UTF-8?q?=20including=20AOD=20parameters?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/NeutralAtomScheduler.hpp | 10 +++++++--- src/hybridmap/NeutralAtomScheduler.cpp | 23 +++++++++++----------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/include/hybridmap/NeutralAtomScheduler.hpp b/include/hybridmap/NeutralAtomScheduler.hpp index 94a48dd3e..c1a5dd5a4 100644 --- a/include/hybridmap/NeutralAtomScheduler.hpp +++ b/include/hybridmap/NeutralAtomScheduler.hpp @@ -30,13 +30,16 @@ struct SchedulerResults { qc::fp totalGateFidelities; qc::fp totalFidelities; uint32_t nCZs = 0; + uint32_t nAodActivate = 0; + uint32_t nAodMove = 0; SchedulerResults(const qc::fp executionTime, const qc::fp idleTime, const qc::fp gateFidelities, const qc::fp fidelities, - const uint32_t cZs) + const uint32_t cZs, const uint32_t aodActivate, + const uint32_t aodMove) : totalExecutionTime(executionTime), totalIdleTime(idleTime), totalGateFidelities(gateFidelities), totalFidelities(fidelities), - nCZs(cZs) {} + nCZs(cZs), nAodActivate(aodActivate), nAodMove(aodMove) {} [[nodiscard]] std::string toString() const { std::stringstream ss; @@ -128,7 +131,8 @@ class NeutralAtomScheduler { static void printSchedulerResults(std::vector& totalExecutionTimes, qc::fp totalIdleTime, qc::fp totalGateFidelities, - qc::fp totalFidelities, uint32_t nCZs); + qc::fp totalFidelities, uint32_t nCZs, + uint32_t nAodActivate, uint32_t nAodMove); static void printTotalExecutionTimes( const std::vector& totalExecutionTimes, const std::vector>>& diff --git a/src/hybridmap/NeutralAtomScheduler.cpp b/src/hybridmap/NeutralAtomScheduler.cpp index ffed6a8d6..134132295 100644 --- a/src/hybridmap/NeutralAtomScheduler.cpp +++ b/src/hybridmap/NeutralAtomScheduler.cpp @@ -50,7 +50,8 @@ na::SchedulerResults na::NeutralAtomScheduler::schedule( } int index = 0; - int nAodActivate = 0; + uint32_t nAodActivate = 0; + uint32_t nAodMove = 0; uint32_t nCZs = 0; for (const auto& op : qc) { index++; @@ -59,6 +60,8 @@ na::SchedulerResults na::NeutralAtomScheduler::schedule( } if (op->getType() == qc::AodActivate) { nAodActivate++; + } else if (op->getType() == qc::AodMove) { + nAodMove++; } else if (op->getType() == qc::OpType::Z && op->getNcontrols() == 1) { nCZs++; } @@ -152,11 +155,6 @@ na::SchedulerResults na::NeutralAtomScheduler::schedule( totalGateFidelities *= opFidelity; totalGateTime += opTime; - if (verbose) { - // std::cout << "\n"; - // printTotalExecutionTimes(totalExecutionTimes, - // rydbergBlockedQubitsTimes); - } // update animation if (createAnimationCsv) { @@ -165,7 +163,6 @@ na::SchedulerResults na::NeutralAtomScheduler::schedule( } if (verbose) { std::cout << "\n* schedule end!\n"; - std::cout << "nAodActivate: " << nAodActivate << "\n"; } const auto maxExecutionTime = @@ -178,16 +175,18 @@ na::SchedulerResults na::NeutralAtomScheduler::schedule( if (verbose) { printSchedulerResults(totalExecutionTimes, totalIdleTime, - totalGateFidelities, totalFidelities, nCZs); + totalGateFidelities, totalFidelities, nCZs, + nAodActivate, nAodMove); } - return {maxExecutionTime, totalIdleTime, totalGateFidelities, totalFidelities, - nCZs}; + return {maxExecutionTime, totalIdleTime, totalGateFidelities, + totalFidelities, nCZs, nAodActivate, + nAodMove}; } void na::NeutralAtomScheduler::printSchedulerResults( std::vector& totalExecutionTimes, const qc::fp totalIdleTime, const qc::fp totalGateFidelities, const qc::fp totalFidelities, - const uint32_t nCZs) { + const uint32_t nCZs, const uint32_t nAodActivate, const uint32_t nAodMove) { const auto totalExecutionTime = *std::max_element(totalExecutionTimes.begin(), totalExecutionTimes.end()); std::cout << "\ntotalExecutionTimes: " << totalExecutionTime << "\n"; @@ -195,6 +194,8 @@ void na::NeutralAtomScheduler::printSchedulerResults( std::cout << "totalGateFidelities: " << totalGateFidelities << "\n"; std::cout << "totalFidelities: " << totalFidelities << "\n"; std::cout << "totalNumCZs: " << nCZs << "\n"; + std::cout << "nAodActivate: " << nAodActivate << "\n"; + std::cout << "nAodMove: " << nAodMove << "\n"; } void na::NeutralAtomScheduler::printTotalExecutionTimes( From 635adbcdcc53a945490718403aa652250d9d1faa Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 11 Sep 2025 17:15:08 +0200 Subject: [PATCH 176/394] =?UTF-8?q?=F0=9F=90=9B=20Fixed=20Bridge=20gates?= =?UTF-8?q?=20not=20correctly=20removed=20from=20FrontLayerGates?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridNeutralAtomMapper.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 7c6621109..20792aa21 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -1814,6 +1814,7 @@ size_t NeutralAtomMapper::gateBasedMapping(NeutralAtomLayer& frontLayer, updateBlockedQubits( {bestBridge.second.begin(), bestBridge.second.end()}); applyBridge(frontLayer, bestBridge); + break; } } if (bestMethod == MappingMethod::SwapMethod) { From cadabad583aed88942888333f4000b9ad459e5dc Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 11 Sep 2025 18:51:18 +0200 Subject: [PATCH 177/394] =?UTF-8?q?=F0=9F=90=9B=20Avoid=20unnecessary=20mo?= =?UTF-8?q?ves?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridNeutralAtomMapper.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 20792aa21..403a6f013 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -403,8 +403,8 @@ void NeutralAtomMapper::applyFlyingAncilla(NeutralAtomLayer& frontLayer, for (const auto& passBy : faComb.moves) { const auto ancQ1 = passBy.q1 + (nPos * 2); const auto ancQ2 = passBy.q2 + (nPos * 2); - if (passBy.origin + nPos != ancQ1) { - mappedQc.move(passBy.origin + nPos, ancQ1); + if (passBy.origin + 2 * nPos != ancQ1) { + mappedQc.move(passBy.origin + 2 * nPos, ancQ1); } mappedQc.h(ancQ1); mappedQc.cz(passBy.q1, ancQ1); From 94fbebc7b9cdbe3a114233dc42bf23c4236d4efc Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 12 Sep 2025 10:58:43 +0200 Subject: [PATCH 178/394] =?UTF-8?q?=F0=9F=90=9B=20Fixed/improved=20routing?= =?UTF-8?q?=20comparisons?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridNeutralAtomMapper.cpp | 38 ++++++++++++++--------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 403a6f013..cbab053b7 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -2244,9 +2244,10 @@ NeutralAtomMapper::compareSwapAndBridge(const Swap& bestSwap, qc::fp const bridgeFidelity = this->arch->getGateAverageFidelity(bridgeName) * std::exp(-this->arch->getGateTime(bridgeName) / this->arch->getDecoherenceTime()); - if (bridgeDistReduction * std::log(swapFidelity) / - parameters->dynamicMappingWeight > - swapDistReduction * std::log(bridgeFidelity)) { + const auto swap = -swapDistReduction * std::log(1 - swapFidelity) * + parameters->dynamicMappingWeight; + const auto bridge = -bridgeDistReduction * std::log(1 - bridgeFidelity); + if (swap >= bridge) { return MappingMethod::SwapMethod; } return MappingMethod::BridgeMethod; @@ -2256,7 +2257,7 @@ MappingMethod NeutralAtomMapper::compareShuttlingAndFlyingAncilla( const MoveComb& bestMoveComb, const FlyingAncillaComb& bestFaComb, const PassByComb& bestPbComb) const { // move distance reduction - auto const moveDistReduction = + auto moveDistReduction = moveCombDistanceReduction(bestMoveComb, this->frontLayerShuttling) + (this->parameters->lookaheadWeightMoves * moveCombDistanceReduction(bestMoveComb, this->lookaheadLayerShuttling)); @@ -2277,12 +2278,10 @@ MappingMethod NeutralAtomMapper::compareShuttlingAndFlyingAncilla( auto const moveDecoherence = std::exp(-moveTime / this->arch->getDecoherenceTime()); auto const moveFidelity = moveOpFidelity * moveDecoherence; - const auto move = std::log(moveFidelity) / moveDistReduction / - parameters->dynamicMappingWeight; // fa - auto fa = -std::numeric_limits::infinity(); auto faDistReduction = 0.0; + auto faFidelity = 0.0; if (flyingAncillas.getNumQubits() != 0) { // flying ancilla distance reduction auto const faCoords = this->hardwareQubits.getCoordIndices( @@ -2300,17 +2299,16 @@ MappingMethod NeutralAtomMapper::compareShuttlingAndFlyingAncilla( auto const faDecoherence = std::exp(-faDist / this->arch->getShuttlingTime(qc::OpType::AodMove) / this->arch->getDecoherenceTime()); - auto const faFidelity = faOpFidelity * faDecoherence; - fa = std::log(faFidelity) / faDistReduction; + faFidelity = faOpFidelity * faDecoherence; } // passby auto pbDistReduction = 0.0; - auto passBy = -std::numeric_limits::infinity(); + auto passByFidelity = 0.0; if (parameters->usePassBy) { auto const pbCoords = this->hardwareQubits.getCoordIndices( this->mapping.getHwQubits(bestPbComb.op->getUsedQubits())); - faDistReduction = this->arch->getAllToAllEuclideanDistance(pbCoords); + pbDistReduction = this->arch->getAllToAllEuclideanDistance(pbCoords); const auto pbCombSize = bestPbComb.moves.size(); auto const passByDist = this->arch->getPassByEuclideanDistance(bestPbComb); @@ -2320,7 +2318,7 @@ MappingMethod NeutralAtomMapper::compareShuttlingAndFlyingAncilla( static_cast(pbCombSize)) + (this->arch->getShuttlingTime(qc::OpType::AodDeactivate) * static_cast(pbCombSize)); - auto const passByFidelity = + passByFidelity = std::pow(this->arch->getShuttlingAverageFidelity(qc::OpType::AodMove) * this->arch->getShuttlingAverageFidelity( qc::OpType::AodActivate) * @@ -2328,12 +2326,22 @@ MappingMethod NeutralAtomMapper::compareShuttlingAndFlyingAncilla( qc::OpType::AodDeactivate), pbCombSize) * std::exp(-passByTime / this->arch->getDecoherenceTime()); + } - const auto move = std::log(moveFidelity) / moveDistReduction / - parameters->dynamicMappingWeight; - passBy = std::log(passByFidelity) / faDistReduction; + auto const minDistanceReduction = + std::min({moveDistReduction, faDistReduction, pbDistReduction}); + if (minDistanceReduction < 0) { + moveDistReduction -= minDistanceReduction; + faDistReduction -= minDistanceReduction; + pbDistReduction -= minDistanceReduction; } + // higher is better + const auto move = -std::log(1 - moveFidelity) * moveDistReduction * + parameters->dynamicMappingWeight; + const auto fa = -std::log(1 - faFidelity) * faDistReduction; + const auto passBy = -std::log(1 - passByFidelity) * faDistReduction; + if (move > fa && move > passBy) { return MappingMethod::MoveMethod; } From b54f4513a8f1c01a68cda344b76d9a099088246a Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 12 Sep 2025 12:56:35 +0200 Subject: [PATCH 179/394] =?UTF-8?q?=F0=9F=90=9B=20Fixed=20problem=20where?= =?UTF-8?q?=20move=20does=20not=20provide=20distance=20reduction=20but=20o?= =?UTF-8?q?thers=20are=20forbidden.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridNeutralAtomMapper.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index cbab053b7..b4e317540 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -2256,6 +2256,10 @@ NeutralAtomMapper::compareSwapAndBridge(const Swap& bestSwap, MappingMethod NeutralAtomMapper::compareShuttlingAndFlyingAncilla( const MoveComb& bestMoveComb, const FlyingAncillaComb& bestFaComb, const PassByComb& bestPbComb) const { + if (flyingAncillas.getNumQubits() == 0 && !parameters->usePassBy) { + return MappingMethod::MoveMethod; + } + // move distance reduction auto moveDistReduction = moveCombDistanceReduction(bestMoveComb, this->frontLayerShuttling) + From a7b27205e68e54d89890c21990e4e41330381ed0 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 12 Sep 2025 13:38:19 +0200 Subject: [PATCH 180/394] =?UTF-8?q?=F0=9F=90=9B=20Fixed=20error=20where=20?= =?UTF-8?q?FA=20CZ=20is=20applied=20to=20qubit=20at=20position=20not=20qub?= =?UTF-8?q?it=20from=20operation.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridNeutralAtomMapper.cpp | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index b4e317540..d17285eb7 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -398,7 +398,16 @@ void NeutralAtomMapper::applyFlyingAncilla(NeutralAtomLayer& frontLayer, } auto controlCoords = hardwareQubits.getCoordIndices(mapping.getHwQubits(controlQubits)); + // merge target and control coords + auto allCoords = targetCoords; + allCoords.insert(allCoords.end(), controlCoords.begin(), controlCoords.end()); + if (allCoords.size() / 2 != faComb.moves.size()) { + throw std::runtime_error( + "Not enough flying ancilla moves for the given operation"); + } + + uint32_t i = 0; const auto nPos = this->arch->getNpositions(); for (const auto& passBy : faComb.moves) { const auto ancQ1 = passBy.q1 + (nPos * 2); @@ -407,15 +416,18 @@ void NeutralAtomMapper::applyFlyingAncilla(NeutralAtomLayer& frontLayer, mappedQc.move(passBy.origin + 2 * nPos, ancQ1); } mappedQc.h(ancQ1); - mappedQc.cz(passBy.q1, ancQ1); + mappedQc.cz(allCoords[i], ancQ1); + i++; mappedQc.h(ancQ1); mappedQc.move(ancQ1, ancQ2); - auto itT = std::find(targetCoords.begin(), targetCoords.end(), passBy.q1); + auto itT = + std::find(targetCoords.begin(), targetCoords.end(), allCoords[i]); if (itT != targetCoords.end()) { *itT = ancQ2; } - auto itC = std::find(controlCoords.begin(), controlCoords.end(), passBy.q1); + auto itC = + std::find(controlCoords.begin(), controlCoords.end(), allCoords[i]); if (itC != controlCoords.end()) { *itC = ancQ2; } @@ -439,7 +451,8 @@ void NeutralAtomMapper::applyFlyingAncilla(NeutralAtomLayer& frontLayer, const auto ancQ2 = passBy.q2 + (nPos * 2); mappedQc.move(ancQ2, ancQ1); mappedQc.h(ancQ1); - mappedQc.cz(passBy.q1, ancQ1); + mappedQc.cz(allCoords[i + 1], ancQ1); + i += 2; mappedQc.h(ancQ1); // update position of flying ancillas From f00dd69272bf5477e4d1dd8e6c9b7e3f568d273c Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Mon, 15 Sep 2025 16:14:54 +0200 Subject: [PATCH 181/394] =?UTF-8?q?=F0=9F=90=9B=20Fixed=20error=20of=20wro?= =?UTF-8?q?ng=20Flying=20Ancilla=20indexing.=20Also,=20add=20AOD=20movemen?= =?UTF-8?q?t=20if=20staying=20still=20to=20avoid=20problems=20while=20Move?= =?UTF-8?q?Group=20generation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridNeutralAtomMapper.cpp | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index d17285eb7..990f7b715 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -101,7 +101,7 @@ void NeutralAtomMapper::mapAppend(qc::QuantumComputation& qc, std::cout << "nMoves: " << nMoves << '\n'; std::cout << "nPassBy: " << nPassBy << '\n'; - mappedQc.print(std::cout); + // mappedQc.print(std::cout); } } @@ -144,12 +144,12 @@ void NeutralAtomMapper::decomposeBridgeGates(qc::QuantumComputation& qc) const { qc::QuantumComputation NeutralAtomMapper::convertToAod() { // decompose SWAP gates - qc::CircuitOptimizer::decomposeSWAP(mappedQc, false); + // qc::CircuitOptimizer::decomposeSWAP(mappedQc, false); // decompose bridge gates - decomposeBridgeGates(mappedQc); - qc::CircuitOptimizer::replaceMCXWithMCZ(mappedQc); - qc::CircuitOptimizer::singleQubitGateFusion(mappedQc); - qc::CircuitOptimizer::flattenOperations(mappedQc); + // decomposeBridgeGates(mappedQc); + // qc::CircuitOptimizer::replaceMCXWithMCZ(mappedQc); + // qc::CircuitOptimizer::singleQubitGateFusion(mappedQc); + // qc::CircuitOptimizer::flattenOperations(mappedQc); // decompose AOD moves MoveToAodConverter aodScheduler(*arch, hardwareQubits, flyingAncillas); mappedQcAOD = aodScheduler.schedule(mappedQc); @@ -412,12 +412,9 @@ void NeutralAtomMapper::applyFlyingAncilla(NeutralAtomLayer& frontLayer, for (const auto& passBy : faComb.moves) { const auto ancQ1 = passBy.q1 + (nPos * 2); const auto ancQ2 = passBy.q2 + (nPos * 2); - if (passBy.origin + 2 * nPos != ancQ1) { - mappedQc.move(passBy.origin + 2 * nPos, ancQ1); - } + mappedQc.move(passBy.origin + 2 * nPos, ancQ1); mappedQc.h(ancQ1); mappedQc.cz(allCoords[i], ancQ1); - i++; mappedQc.h(ancQ1); mappedQc.move(ancQ1, ancQ2); @@ -431,6 +428,7 @@ void NeutralAtomMapper::applyFlyingAncilla(NeutralAtomLayer& frontLayer, if (itC != controlCoords.end()) { *itC = ancQ2; } + i += 2; if (this->parameters->verbose) { std::cout << "passby (flying ancilla) " << passBy.origin << " " @@ -446,12 +444,13 @@ void NeutralAtomMapper::applyFlyingAncilla(NeutralAtomLayer& frontLayer, opCopy->setControls(controls); mappedQc.emplace_back(opCopy->clone()); + i = 0; for (const auto& passBy : faComb.moves) { const auto ancQ1 = passBy.q1 + (nPos * 2); const auto ancQ2 = passBy.q2 + (nPos * 2); mappedQc.move(ancQ2, ancQ1); mappedQc.h(ancQ1); - mappedQc.cz(allCoords[i + 1], ancQ1); + mappedQc.cz(allCoords[i], ancQ1); i += 2; mappedQc.h(ancQ1); From e11f0b9fc056b92b8f3f9cc6a3e7bb7e03fdcebd Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Mon, 15 Sep 2025 16:30:49 +0200 Subject: [PATCH 182/394] =?UTF-8?q?=F0=9F=90=9B=20fixed=20wrong=20indexing?= =?UTF-8?q?=20when=20combining=20AOD=20moves.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/MoveToAodConverter.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/hybridmap/MoveToAodConverter.cpp b/src/hybridmap/MoveToAodConverter.cpp index ac724070f..e92397f75 100644 --- a/src/hybridmap/MoveToAodConverter.cpp +++ b/src/hybridmap/MoveToAodConverter.cpp @@ -554,8 +554,8 @@ AodOperation MoveToAodConverter::MoveGroup::connectAodOperations( targetQubits.emplace_back(ends[i]); } else { // insert before one before the found position - targetQubits.insert(pos - 1, ends[i]); - targetQubits.insert(pos - 1, starts[i]); + const auto newPos = targetQubits.insert(pos - 1, ends[i]); + targetQubits.insert(newPos, starts[i]); } } @@ -568,6 +568,9 @@ AodOperation MoveToAodConverter::MoveGroup::connectAodOperations( const auto& end = deactivationDim[i]->init * d + deactivationDim[i]->offset * interD; if (std::abs(start - end) > 0.0001) { + if (start > 10000) { + int i = 0; + } aodOperations.emplace_back(dim, start, end); } } From 18a9cc1263f07956ca7652922bcf1622c94155f5 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Mon, 15 Sep 2025 17:08:20 +0200 Subject: [PATCH 183/394] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20Speed=20up=20findi?= =?UTF-8?q?ng=20move=20position=20by=20only=20checking=20a=20subset=20of?= =?UTF-8?q?=20the=20possible=20positions.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridNeutralAtomMapper.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 990f7b715..e6b4b1711 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -1497,12 +1497,6 @@ CoordIndices NeutralAtomMapper::getBestMovePos(const CoordIndices& gateCoords) { if (!bestPos.coords.empty()) { nMovesGate = std::min(nMovesGate, bestPos.nMoves); } - for (const auto& nearbyCoord : this->arch->getNearbyCoordinates(coord)) { - if (std::find(visited.begin(), visited.end(), nearbyCoord) == - visited.end()) { - q.push(nearbyCoord); - } - } } if (finalBestPos.coords.empty()) { // check if interaction radius too small From e480fa70e37d6df87123ab3d17e157bb9f195445 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Mon, 15 Sep 2025 17:17:31 +0200 Subject: [PATCH 184/394] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20Only=20compute=20s?= =?UTF-8?q?ingle=20Bridge-Path.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HardwareQubits.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/hybridmap/HardwareQubits.cpp b/src/hybridmap/HardwareQubits.cpp index cb7a32d75..430df4270 100644 --- a/src/hybridmap/HardwareQubits.cpp +++ b/src/hybridmap/HardwareQubits.cpp @@ -104,6 +104,7 @@ HardwareQubits::computeAllShortestPaths(const HwQubit q1, currentPath.size() == shortestPathLength) { shortestPathLength = currentPath.size(); allPaths.push_back(currentPath); + break; } else if (currentPath.size() > shortestPathLength) { // Since we use BFS, once a path longer than the shortest length is // found, stop exploring From bdb4152042517590243a2bb2a409feb3d06840cd Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Mon, 15 Sep 2025 18:00:09 +0200 Subject: [PATCH 185/394] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20Speed=20up=20compu?= =?UTF-8?q?tation=20of=20free=20Coordinats=20by=20book-keeping=20a=20list?= =?UTF-8?q?=20of=20free=20ones.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HardwareQubits.hpp | 22 +++++++++-- src/hybridmap/HardwareQubits.cpp | 59 ++++++++++++++-------------- 2 files changed, 48 insertions(+), 33 deletions(-) diff --git a/include/hybridmap/HardwareQubits.hpp b/include/hybridmap/HardwareQubits.hpp index 0f67cea7e..b31d45e75 100644 --- a/include/hybridmap/HardwareQubits.hpp +++ b/include/hybridmap/HardwareQubits.hpp @@ -40,6 +40,8 @@ class HardwareQubits { qc::Permutation hwToCoordIdx; SymmetricMatrix swapDistances; std::map nearbyQubits; + std::vector freeCoordinates; + std::vector occupiedCoordinates; qc::Permutation initialHwPos; /** @@ -96,6 +98,7 @@ class HardwareQubits { case Trivial: for (uint32_t i = 0; i < this->nQubits; ++i) { hwToCoordIdx.emplace(i, i); + occupiedCoordinates.emplace_back(i); } initTrivialSwapDistances(); break; @@ -109,11 +112,20 @@ class HardwareQubits { std::shuffle(indices.begin(), indices.end(), g); for (uint32_t i = 0; i < this->nQubits; ++i) { hwToCoordIdx.emplace(i, indices[i]); + occupiedCoordinates.emplace_back(indices[i]); } swapDistances = SymmetricMatrix(this->nQubits, -1); } initNearbyQubits(); + + for (uint32_t i = 0; i < architecture.getNpositions(); ++i) { + if (std::find(occupiedCoordinates.begin(), occupiedCoordinates.end(), + i) == occupiedCoordinates.end()) { + freeCoordinates.emplace_back(i); + } + } + initialHwPos = hwToCoordIdx; } @@ -133,9 +145,11 @@ class HardwareQubits { * @return Boolean indicating if the hardware qubit is mapped to a coordinate. */ [[nodiscard]] bool isMapped(CoordIndex idx) const { - return !std::none_of( - hwToCoordIdx.begin(), hwToCoordIdx.end(), - [idx](const auto& pair) { return pair.second == idx; }); + if (std::find(occupiedCoordinates.begin(), occupiedCoordinates.end(), + idx) != occupiedCoordinates.end()) { + return true; + } + return false; } /** * @brief Updates mapping after moving a hardware qubit to a coordinate. @@ -148,6 +162,8 @@ class HardwareQubits { void removeHwQubit(const HwQubit hwQubit) { hwToCoordIdx.erase(hwQubit); + freeCoordinates.emplace_back(initialHwPos.at(hwQubit)); + occupiedCoordinates.emplace_back(initialHwPos.at(hwQubit)); initialHwPos.erase(hwQubit); // set swap distances to -1 for (uint32_t i = 0; i < swapDistances.size(); ++i) { diff --git a/src/hybridmap/HardwareQubits.cpp b/src/hybridmap/HardwareQubits.cpp index 430df4270..60a8c582b 100644 --- a/src/hybridmap/HardwareQubits.cpp +++ b/src/hybridmap/HardwareQubits.cpp @@ -144,6 +144,14 @@ void HardwareQubits::move(HwQubit hwQubit, const CoordIndex newCoord) { } } + const auto oldCoord = hwToCoordIdx.at(hwQubit); + occupiedCoordinates.erase(std::find(occupiedCoordinates.begin(), + occupiedCoordinates.end(), oldCoord)); + occupiedCoordinates.emplace_back(newCoord); + freeCoordinates.emplace_back(oldCoord); + freeCoordinates.erase( + std::find(freeCoordinates.begin(), freeCoordinates.end(), newCoord)); + // remove qubit from old nearby qubits const auto prevNearbyQubits = nearbyQubits.at(hwQubit); for (const auto& qubit : prevNearbyQubits) { @@ -249,38 +257,29 @@ HardwareQubits::findClosestFreeCoord(CoordIndex coord, const CoordIndices& excludedCoords) const { // return the closest free coord in general // and the closest free coord in the given direction - std::vector closestFreeCoords; - std::queue queue; - queue.push(coord); - std::set visited; - visited.emplace(coord); - bool foundClosest = false; - while (!queue.empty()) { - const auto currentCoord = queue.front(); - queue.pop(); - auto nearbyCoords = this->arch->getNN(currentCoord); - for (const auto& nearbyCoord : nearbyCoords) { - if (std::find(visited.rbegin(), visited.rend(), nearbyCoord) == - visited.rend()) { - visited.emplace(nearbyCoord); - if (!this->isMapped(nearbyCoord) && - std::find(excludedCoords.begin(), excludedCoords.end(), - nearbyCoord) == excludedCoords.end()) { - if (!foundClosest) { - closestFreeCoords.emplace_back(nearbyCoord); - } - foundClosest = true; - if (direction == arch->getVector(coord, nearbyCoord).direction) { - closestFreeCoords.emplace_back(nearbyCoord); - return closestFreeCoords; - } - } else { - queue.push(nearbyCoord); - } - } + std::vector freeCoordsInDirection; + for (const auto& freeCoord : freeCoordinates) { + if (std::find(excludedCoords.begin(), excludedCoords.end(), freeCoord) != + excludedCoords.end()) { + continue; + } + if (direction == arch->getVector(coord, freeCoord).direction) { + freeCoordsInDirection.emplace_back(freeCoord); } } - return closestFreeCoords; + if (freeCoordsInDirection.empty()) { + freeCoordsInDirection = freeCoordinates; + } + auto minDistance = std::numeric_limits::max(); + CoordIndex minCoord = freeCoordsInDirection.front(); + for (const auto& freeCoord : freeCoordsInDirection) { + if (const auto distance = arch->getEuclideanDistance(coord, freeCoord); + distance < minDistance) { + minDistance = distance; + minCoord = freeCoord; + } + } + return {minCoord}; } std::vector HardwareQubits::findClosestAncillaCoord( From f07f27f221b6a11fa5fdc476c0eb62a750f50839 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 18 Sep 2025 18:26:37 +0200 Subject: [PATCH 186/394] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20use=20modified=20m?= =?UTF-8?q?ain=20version=20of=20mqt-core?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmake/ExternalDependencies.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/ExternalDependencies.cmake b/cmake/ExternalDependencies.cmake index a14ac2e15..c976c3c6a 100644 --- a/cmake/ExternalDependencies.cmake +++ b/cmake/ExternalDependencies.cmake @@ -42,8 +42,8 @@ endif() # cmake-format: off set(MQT_CORE_VERSION 3.0.0 CACHE STRING "MQT Core version") -set(MQT_CORE_REV "c2701a0747781eeca43b71dec43ffb0531022a51" -#set(MQT_CORE_REV "na-hybrid-extension" +#set(MQT_CORE_REV "c2701a0747781eeca43b71dec43ffb0531022a51" +set(MQT_CORE_REV "na-hybrid-extension" CACHE STRING "MQT Core identifier (tag, branch or commit hash)") set(MQT_CORE_REPO_OWNER "cda-tum" CACHE STRING "MQT Core repository owner (change when using a fork)") From f69c31fefc7dc8a63033a1f433aba9a18a038aa9 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 18 Sep 2025 19:08:17 +0200 Subject: [PATCH 187/394] =?UTF-8?q?=F0=9F=94=80=20fixed=20merge=20bugs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HardwareQubits.hpp | 2 +- include/hybridmap/HybridSynthesisMapper.hpp | 12 +++++++++++- include/hybridmap/Mapping.hpp | 5 +++-- include/hybridmap/NeutralAtomArchitecture.hpp | 4 ++-- include/hybridmap/NeutralAtomDefinitions.hpp | 2 +- src/hybridmap/HybridNeutralAtomMapper.cpp | 2 +- src/hybridmap/HybridSynthesisMapper.cpp | 12 +++++++++++- src/hybridmap/Mapping.cpp | 2 +- 8 files changed, 31 insertions(+), 10 deletions(-) diff --git a/include/hybridmap/HardwareQubits.hpp b/include/hybridmap/HardwareQubits.hpp index 9604154d4..077a63938 100644 --- a/include/hybridmap/HardwareQubits.hpp +++ b/include/hybridmap/HardwareQubits.hpp @@ -97,7 +97,7 @@ class HardwareQubits { uint32_t seed = 0) : arch(&architecture) { this->nQubits = nQubits; - swapDistances = SymmetricMatrix(this->nQubits); + swapDistances = qc::SymmetricMatrix(this->nQubits); switch (initialCoordinateMapping) { case Trivial: diff --git a/include/hybridmap/HybridSynthesisMapper.hpp b/include/hybridmap/HybridSynthesisMapper.hpp index 3880cbc97..869b21f80 100644 --- a/include/hybridmap/HybridSynthesisMapper.hpp +++ b/include/hybridmap/HybridSynthesisMapper.hpp @@ -1,3 +1,13 @@ +/* + * 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 + */ + // // This file is part of the MQT QMAP library released under the MIT license. // See README.md or go to https://github.com/cda-tum/qmap for more information. @@ -5,11 +15,11 @@ #pragma once -#include "Definitions.hpp" #include "HybridNeutralAtomMapper.hpp" #include "NeutralAtomArchitecture.hpp" #include "NeutralAtomUtils.hpp" #include "hybridmap/NeutralAtomDefinitions.hpp" +#include "ir/Definitions.hpp" #include "ir/QuantumComputation.hpp" #include diff --git a/include/hybridmap/Mapping.hpp b/include/hybridmap/Mapping.hpp index 4a4808254..c6af1c715 100644 --- a/include/hybridmap/Mapping.hpp +++ b/include/hybridmap/Mapping.hpp @@ -35,10 +35,11 @@ namespace na { */ class Mapping { protected: - // std::map + using DAG = std::vector*>>; + qc::Permutation circToHw; HardwareQubits hwQubits; - qc::DAG dag; + DAG dag; /** * @brief GraphMatching for initCoordMapping diff --git a/include/hybridmap/NeutralAtomArchitecture.hpp b/include/hybridmap/NeutralAtomArchitecture.hpp index 8c73dc16e..aefebd4f3 100644 --- a/include/hybridmap/NeutralAtomArchitecture.hpp +++ b/include/hybridmap/NeutralAtomArchitecture.hpp @@ -408,7 +408,7 @@ class NeutralAtomArchitecture { */ [[nodiscard]] qc::fp getEuclideanDistance(const CoordIndex idx1, const CoordIndex idx2) const { - return this->coordinates.at(idx1).getEuclideanDistanceFp( + return this->coordinates.at(idx1).getEuclideanDistance( this->coordinates.at(idx2)); } [[nodiscard]] qc::fp @@ -460,7 +460,7 @@ class NeutralAtomArchitecture { */ [[nodiscard]] static qc::fp getEuclideanDistance(const Location& c1, const Location& c2) { - return c1.getEuclideanDistanceFp(c2); + return c1.getEuclideanDistance(c2); } /** * @brief Get the Manhattan distance between two coordinate indices diff --git a/include/hybridmap/NeutralAtomDefinitions.hpp b/include/hybridmap/NeutralAtomDefinitions.hpp index 18edc3ded..fc0bb05c1 100644 --- a/include/hybridmap/NeutralAtomDefinitions.hpp +++ b/include/hybridmap/NeutralAtomDefinitions.hpp @@ -29,7 +29,7 @@ namespace na { // (or not). using CoordIndex = std::uint32_t; using CoordIndices = std::vector; -using AdjacencyMatrix = SymmetricMatrix; +using AdjacencyMatrix = qc::SymmetricMatrix; // A HwQubit corresponds to an atom in the neutral atom architecture. It can be // used as qubit or not and occupies a certain position in the architecture. diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 8ca51dfcc..75ee108ee 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -2162,7 +2162,7 @@ std::set> NeutralAtomMapper::findQtargetSet(std::set& usedQubits) { std::set> qTargetSet; const auto numUsedQubits = usedQubits.size(); - SymmetricMatrix gateQubitDistances(numUsedQubits); + qc::SymmetricMatrix gateQubitDistances(numUsedQubits); for (uint32_t i = 0; i < numUsedQubits; ++i) { for (uint32_t j = 0; j <= i; ++j) { if (i == j) { diff --git a/src/hybridmap/HybridSynthesisMapper.cpp b/src/hybridmap/HybridSynthesisMapper.cpp index f8cc64793..d4e437047 100644 --- a/src/hybridmap/HybridSynthesisMapper.cpp +++ b/src/hybridmap/HybridSynthesisMapper.cpp @@ -1,12 +1,22 @@ +/* + * 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 + */ + // // This file is part of the MQT QMAP library released under the MIT license. // See README.md or go to https://github.com/cda-tum/qmap for more information. // #include "hybridmap/HybridSynthesisMapper.hpp" -#include "Definitions.hpp" #include "hybridmap/HybridNeutralAtomMapper.hpp" #include "hybridmap/NeutralAtomDefinitions.hpp" +#include "ir/Definitions.hpp" #include "ir/QuantumComputation.hpp" #include diff --git a/src/hybridmap/Mapping.cpp b/src/hybridmap/Mapping.cpp index 36d3f4f0d..3bde71030 100644 --- a/src/hybridmap/Mapping.cpp +++ b/src/hybridmap/Mapping.cpp @@ -10,8 +10,8 @@ #include "hybridmap/Mapping.hpp" -#include "Definitions.hpp" #include "hybridmap/NeutralAtomDefinitions.hpp" +#include "ir/Definitions.hpp" #include #include From 337b26324f387cae862a518b7a21cc3dfc95afac Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 19 Sep 2025 16:19:29 +0200 Subject: [PATCH 188/394] =?UTF-8?q?=E2=9C=A8=20updated=20bindings=20and=20?= =?UTF-8?q?add=20return=20of=20mapping=20stats?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bindings/hybrid_mapper/hybrid_mapper.cpp | 148 ++++++++++-------- include/hybridmap/HybridNeutralAtomMapper.hpp | 48 ++++-- src/hybridmap/HybridNeutralAtomMapper.cpp | 20 +-- 3 files changed, 122 insertions(+), 94 deletions(-) diff --git a/bindings/hybrid_mapper/hybrid_mapper.cpp b/bindings/hybrid_mapper/hybrid_mapper.cpp index 51335c7ca..ef4d6a0b6 100644 --- a/bindings/hybrid_mapper/hybrid_mapper.cpp +++ b/bindings/hybrid_mapper/hybrid_mapper.cpp @@ -42,56 +42,73 @@ PYBIND11_MODULE(MQT_QMAP_MODULE_NAME, m, py::mod_gil_not_used()) { m, "InitialCircuitMapping", "enum.Enum", "Initial mapping between circuit qubits and hardware qubits.") .value("identity", na::InitialMapping::Identity, "Identity mapping.") + .value("graph", na::InitialMapping::Graph, "Graph matching mapping.") .export_values() .finalize(); - py::class_( - m, "HybridMapperParameters", - "Parameters for the Neutral Atom Hybrid Mapper.") - .def(py::init<>()) - .def(py::init(), - "lookahead_weight_swaps"_a = 0.1, "lookahead_weight_moves"_a = 0.1, - "decay"_a = 0.1, "shuttling_time_weight"_a = 1, "gate_weight"_a = 1, - "shuttling_weight"_a = 1) - .def_readwrite("lookahead_weight_swaps", + py::class_(m, "MapperParameters", + "Parameters controlling the mapper behavior") + .def(py::init<>(), + "Create a MapperParameters instance with default values") + .def_readwrite("lookaheadDepth", &na::MapperParameters::lookaheadDepth, + "Depth of lookahead for mapping decisions") + .def_readwrite("lookaheadWeightSwaps", &na::MapperParameters::lookaheadWeightSwaps, - "Weight for the lookahead for the SWAP gates. 0 means no " - "lookahead is considered.") - .def_readwrite("lookahead_weight_moves", + "Weight assigned to swap operations during lookahead") + .def_readwrite("lookaheadWeightMoves", &na::MapperParameters::lookaheadWeightMoves, - "Weight for the lookahead for the MOVE gates. 0 means no " - "lookahead is considered.") + "Weight assigned to move operations during lookahead") .def_readwrite("decay", &na::MapperParameters::decay, - "Decay factor for the blocking constraint to avoid SWAPs " - "that block each other. 0 means no decay.") - .def_readwrite( - "shuttling_time_weight", &na::MapperParameters::shuttlingTimeWeight, - "Weight how much the shuttling Times should be considered.") - .def_readwrite( - "gate_weight", &na::MapperParameters::gateWeight, - "Weight for the SWAP gates. Higher means mapper will prefer SWAP " - "gates over shuttling. 0 means only shuttling is used.") + "Decay factor for gate blocking") + .def_readwrite("shuttlingTimeWeight", + &na::MapperParameters::shuttlingTimeWeight, + "Weight for shuttling time in cost evaluation") .def_readwrite( - "shuttling_weight", &na::MapperParameters::shuttlingWeight, - "Weight for the shuttling. Higher means mapper will prefer " - "shuttling over SWAP gates. 0 means only SWAP gates are " - "used.") + "dynamicMappingWeight", &na::MapperParameters::dynamicMappingWeight, + "Weight for dynamic remapping (SWAPs or MOVEs) in cost evaluation") + .def_readwrite("gateWeight", &na::MapperParameters::gateWeight, + "Weight for gate execution in cost evaluation") + .def_readwrite("shuttlingWeight", &na::MapperParameters::shuttlingWeight, + "Weight for shuttling operations in cost evaluation") .def_readwrite( "seed", &na::MapperParameters::seed, - "Seed for the random number generator. 0 means random seed.") + "Random seed for stochastic decisions (initial mapping, etc.)") + .def_readwrite("numFlyingAncillas", + &na::MapperParameters::numFlyingAncillas, + "Number of ancilla qubits to be used (0 or 1 for now)") + .def_readwrite("limitShuttlingLayer", + &na::MapperParameters::limitShuttlingLayer, + "Maximum allowed shuttling layer (default: unlimited)") + .def_readwrite("maxBridgeDistance", + &na::MapperParameters::maxBridgeDistance, + "Maximum distance for bridge operations") + .def_readwrite("usePassBy", &na::MapperParameters::usePassBy, + "Enable or disable pass-by operations") .def_readwrite("verbose", &na::MapperParameters::verbose, - "Print additional information during the mapping process.") - .def_readwrite("initial_mapping", &na::MapperParameters::initialMapping, - "Initial mapping between circuit qubits and hardware " - "qubits."); + "Enable verbose logging for debugging") + .def_readwrite("initialCoordMapping", + &na::MapperParameters::initialCoordMapping, + "Strategy for initial coordinate mapping"); + + py::class_(m, "MapperStats") + .def(py::init<>()) + .def_readwrite("nSwaps", &na::MapperStats::nSwaps, + "Number of swap operations performed") + .def_readwrite("nBridges", &na::MapperStats::nBridges, + "Number of bridge operations performed") + .def_readwrite("nFAncillas", &na::MapperStats::nFAncillas, + "Number of fresh ancilla qubits used") + .def_readwrite("nMoves", &na::MapperStats::nMoves, + "Number of move operations performed") + .def_readwrite("nPassBy", &na::MapperStats::nPassBy, + "Number of pass-by operations performed"); py::class_(m, "NeutralAtomHybridArchitecture") .def(py::init(), "filename"_a) .def("load_json", &na::NeutralAtomArchitecture::loadJson, "json_filename"_a) .def_readwrite("name", &na::NeutralAtomArchitecture::name, - "Name of the " - "architecture") + "Name of the architecture") .def_property_readonly( "nrows", &na::NeutralAtomArchitecture::getNrows, "Number of rows in a rectangular grid SLM arrangement") @@ -106,7 +123,7 @@ PYBIND11_MODULE(MQT_QMAP_MODULE_NAME, m, py::mod_gil_not_used()) { "Number of independent 2D acousto-optic deflectors") .def_property_readonly("naod_coordinates", &na::NeutralAtomArchitecture::getNAodCoordinates, - "Maximal number of AOD rows/columns (NOT USED)") + "Maximal number of AOD rows/columns (NOT USED) ") .def_property_readonly("nqubits", &na::NeutralAtomArchitecture::getNqubits, "Number of atoms in the neutral atom quantum " @@ -114,10 +131,7 @@ PYBIND11_MODULE(MQT_QMAP_MODULE_NAME, m, py::mod_gil_not_used()) { .def_property_readonly( "inter_qubit_distance", &na::NeutralAtomArchitecture::getInterQubitDistance, - "Distance " - "between " - "SLM traps in " - "micrometers") + "Distance between SLM traps in micrometers") .def_property_readonly("interaction_radius", &na::NeutralAtomArchitecture::getInteractionRadius, "Interaction radius in inter-qubit distances") @@ -146,50 +160,47 @@ PYBIND11_MODULE(MQT_QMAP_MODULE_NAME, m, py::mod_gil_not_used()) { &na::NeutralAtomArchitecture::getNearbyCoordinates, "Positions that are within the interaction radius of the passed " "position", - "idx"_a) - .def("get_animation_csv", &na::NeutralAtomArchitecture::getAnimationCsv, - "Returns string representation of the architecture used for " - "animation") - .def("save_animation_csv", &na::NeutralAtomArchitecture::saveAnimationCsv, - "filename"_a, "Saves the animation csv string to a file"); + "idx"_a); py::class_( m, "HybridNAMapper", "Neutral Atom Hybrid Mapper that can use both SWAP gates and AOD " - "movements to map a quantum circuit to a neutral atom quantum computer.") - .def(py::init(), - "Create Hybrid NA Mapper with mapper parameters", - py::keep_alive<1, 2>(), py::keep_alive<1, 3>(), "arch"_a, - "params"_a = na::MapperParameters()) + "movements to map a quantum circuit to a neutral atom quantum " + "computer.") + .def( + py::init(), + "Create Hybrid NA Mapper with mapper parameters", + py::keep_alive<1, 2>(), py::keep_alive<1, 3>(), "arch"_a, "params"_a) .def("set_parameters", &na::NeutralAtomMapper::setParameters, "Set the parameters for the Hybrid NA Mapper", "params"_a) .def( "get_init_hw_pos", &na::NeutralAtomMapper::getInitHwPos, "Get the initial hardware positions, required to create an animation") - .def("map", &na::NeutralAtomMapper::mapAndConvert, + .def("map", &na::NeutralAtomMapper::mapWithoutReturn, "Map a quantum circuit to the neutral atom quantum computer", - "circ"_a, "initial_mapping"_a = na::InitialMapping::Identity, - "verbose"_a = false) + "circ"_a, "initial_mapping"_a = na::InitialMapping::Identity) .def( "map_qasm_file", [](na::NeutralAtomMapper& mapper, const std::string& filename, const na::InitialMapping initialMapping) { - auto qc = qasm3::Importer::importf(filename); - mapper.map(qc, initialMapping); + auto circ = qasm3::Importer::importf(filename); + mapper.map(circ, initialMapping); }, "Map a quantum circuit to the neutral atom quantum computer", "filename"_a, "initial_mapping"_a = na::InitialMapping::Identity) + .def("get_stats", &na::NeutralAtomMapper::getStatsMap, + "Returns the statistics of the mapping") .def("get_mapped_qc", &na::NeutralAtomMapper::getMappedQc, "Returns the mapped circuit as an extended qasm2 string") - .def("save_mapped_qc", &na::NeutralAtomMapper::saveMappedQc, - "Saves the mapped circuit as an extended qasm2 string to a file", - "filename"_a) - .def("get_mapped_qc_aod", &na::NeutralAtomMapper::getMappedQcAOD, - "Returns the mapped circuit as an extended qasm2 string with native " - "AOD movements") - .def("save_mapped_qc_aod", &na::NeutralAtomMapper::saveMappedQcAOD, - "Saves the mapped circuit as an extended qasm2 string with native " - "AOD movements to a file", + .def("get_mapped_qc_qasm", &na::NeutralAtomMapper::getMappedQcQasm, + "Returns the mapped circuit as an extended qasm2 string") + .def("save_mapped_qc_aod_qasm", + &na::NeutralAtomMapper::getMappedQcAodQasm, + "Returns the mapped circuit with AOD operations as an extended " + "qasm2 string") + .def("saveMappedQcAodQasm", &na::NeutralAtomMapper::saveMappedQcAodQasm, + "Saves the mapped circuit with AOD operations as an extended qasm2 " + "string", "filename"_a) .def( "schedule", @@ -201,8 +212,9 @@ PYBIND11_MODULE(MQT_QMAP_MODULE_NAME, m, py::mod_gil_not_used()) { }, "Schedule the mapped circuit", "verbose"_a = false, "create_animation_csv"_a = false, "shuttling_speed_factor"_a = 1.0) - .def("get_animation_csv", &na::NeutralAtomMapper::getAnimationCsv, - "Returns the animation csv string") - .def("save_animation_csv", &na::NeutralAtomMapper::saveAnimationCsv, - "Saves the animation csv string to a file", "filename"_a); + .def("save_animation_files", &na::NeutralAtomMapper::saveAnimationFiles, + "Saves the animation files (csv and html) for the last scheduling", + "filename"_a) + .def("get_animation_viz", &na::NeutralAtomMapper::getAnimationViz, + "Returns the animation csv string for the last scheduling"); } diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index afc0e5e3d..82f60874f 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -10,12 +10,12 @@ #pragma once -#include "NeutralAtomLayer.hpp" #include "circuit_optimizer/CircuitOptimizer.hpp" #include "hybridmap/HardwareQubits.hpp" #include "hybridmap/Mapping.hpp" #include "hybridmap/NeutralAtomArchitecture.hpp" #include "hybridmap/NeutralAtomDefinitions.hpp" +#include "hybridmap/NeutralAtomLayer.hpp" #include "hybridmap/NeutralAtomScheduler.hpp" #include "hybridmap/NeutralAtomUtils.hpp" #include "ir/Definitions.hpp" @@ -48,11 +48,20 @@ struct MapperParameters { qc::fp shuttlingWeight = 1; uint32_t seed = 0; uint32_t numFlyingAncillas = 0; - uint32_t limitShuttlingLayer = std::numeric_limits::max(); + uint32_t limitShuttlingLayer = 10; uint32_t maxBridgeDistance = 1; bool usePassBy = true; bool verbose = false; - InitialCoordinateMapping initialCoordMapping; + InitialCoordinateMapping initialCoordMapping = + InitialCoordinateMapping::Trivial; +}; + +struct MapperStats { + uint32_t nSwaps = 0; + uint32_t nBridges = 0; + uint32_t nFAncillas = 0; + uint32_t nMoves = 0; + uint32_t nPassBy = 0; }; enum RoutingType : uint8_t { @@ -83,6 +92,7 @@ enum RoutingType : uint8_t { */ class NeutralAtomMapper { protected: + MapperStats stats; // The considered architecture const NeutralAtomArchitecture* arch = nullptr; // The mapped quantum circuit @@ -111,12 +121,6 @@ class NeutralAtomMapper { std::deque lastMoves; // Precomputed decay weights std::vector decayWeights; - // Counter variables - uint32_t nSwaps = 0; - uint32_t nBridges = 0; - uint32_t nFAncillas = 0; - uint32_t nMoves = 0; - uint32_t nPassBy = 0; // The current placement of the hardware qubits onto the coordinates HardwareQubits hardwareQubits; @@ -545,10 +549,6 @@ class NeutralAtomMapper { const Mapping& initialMapping) { mappedQc = qc::QuantumComputation(arch->getNpositions()); mappedQcAOD = qc::QuantumComputation(arch->getNpositions()); - nMoves = 0; - nSwaps = 0; - nBridges = 0; - nFAncillas = 0; mapAppend(qc, initialMapping); return mappedQc; } @@ -576,10 +576,26 @@ class NeutralAtomMapper { * @param initialMapping The initial mapping of the circuit qubits to the * hardware qubits */ - [[maybe_unused]] void mapAndConvert(qc::QuantumComputation& qc, - const InitialMapping initialMapping) { + [[maybe_unused]] void mapWithoutReturn(qc::QuantumComputation& qc, + const InitialMapping initialMapping) { map(qc, initialMapping); - convertToAod(); + } + + /** + * @brief Returns the statistics of the mapping. + * @return The statistics of the mapping + */ + [[nodiscard]] MapperStats getStats() const { return stats; } + + [[maybe_unused]] [[nodiscard]] std::unordered_map + getStatsMap() const { + std::unordered_map result; + result["nSwaps"] = stats.nSwaps; + result["nBridges"] = stats.nBridges; + result["nFAncillas"] = stats.nFAncillas; + result["nMoves"] = stats.nMoves; + result["nPassBy"] = stats.nPassBy; + return result; } /** diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 75ee108ee..9c75520f8 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -100,11 +100,11 @@ void NeutralAtomMapper::mapAppend(qc::QuantumComputation& qc, } if (this->parameters->verbose) { - std::cout << "nSwaps: " << nSwaps << '\n'; - std::cout << "nBridges: " << nBridges << '\n'; - std::cout << "nFAncillas: " << nFAncillas << '\n'; - std::cout << "nMoves: " << nMoves << '\n'; - std::cout << "nPassBy: " << nPassBy << '\n'; + std::cout << "nSwaps: " << stats.nSwaps << '\n'; + std::cout << "nBridges: " << stats.nBridges << '\n'; + std::cout << "nFAncillas: " << stats.nFAncillas << '\n'; + std::cout << "nMoves: " << stats.nMoves << '\n'; + std::cout << "nPassBy: " << stats.nPassBy << '\n'; // mappedQc.print(std::cout); } @@ -209,7 +209,7 @@ void NeutralAtomMapper::applyPassBy(NeutralAtomLayer& frontLayer, } frontLayer.removeGatesAndUpdate({pbComb.op}); - nPassBy += pbComb.moves.size(); + stats.nPassBy += pbComb.moves.size(); } void NeutralAtomMapper::reassignGatesToLayers(const GateList& frontGates, @@ -329,7 +329,7 @@ void NeutralAtomMapper::updateBlockedQubits(const HwQubits& qubits) { } void NeutralAtomMapper::applySwap(const Swap& swap) { - nSwaps++; + stats.nSwaps++; this->mapping.applySwap(swap); // convert circuit qubits to CoordIndex and append to mappedQc @@ -370,7 +370,7 @@ void NeutralAtomMapper::applyMove(AtomMove move) { std::cout << " not mapped" << '\n'; } } - nMoves++; + stats.nMoves++; } void NeutralAtomMapper::applyBridge(NeutralAtomLayer& frontLayer, const Bridge& bridge) { @@ -389,7 +389,7 @@ void NeutralAtomMapper::applyBridge(NeutralAtomLayer& frontLayer, const auto* op = bridge.first; frontLayer.removeGatesAndUpdate({op}); - nBridges++; + stats.nBridges++; } void NeutralAtomMapper::applyFlyingAncilla(NeutralAtomLayer& frontLayer, const FlyingAncillaComb& faComb) { @@ -479,7 +479,7 @@ void NeutralAtomMapper::applyFlyingAncilla(NeutralAtomLayer& frontLayer, } frontLayer.removeGatesAndUpdate({faComb.op}); - nFAncillas += faComb.moves.size(); + stats.nFAncillas += faComb.moves.size(); } Swap NeutralAtomMapper::findBestSwap(const Swap& lastSwap) { From 98742d04e541f23d2105b2d55f80817ae93125b4 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Mon, 22 Sep 2025 12:15:16 +0200 Subject: [PATCH 189/394] Removed comments on SWAP/Bridge decomposition --- src/hybridmap/HybridNeutralAtomMapper.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 9c75520f8..6e46b5c41 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -149,12 +149,12 @@ void NeutralAtomMapper::decomposeBridgeGates(qc::QuantumComputation& qc) const { qc::QuantumComputation NeutralAtomMapper::convertToAod() { // decompose SWAP gates - // qc::CircuitOptimizer::decomposeSWAP(mappedQc, false); + qc::CircuitOptimizer::decomposeSWAP(mappedQc, false); // decompose bridge gates - // decomposeBridgeGates(mappedQc); - // qc::CircuitOptimizer::replaceMCXWithMCZ(mappedQc); - // qc::CircuitOptimizer::singleQubitGateFusion(mappedQc); - // qc::CircuitOptimizer::flattenOperations(mappedQc); + decomposeBridgeGates(mappedQc); + qc::CircuitOptimizer::replaceMCXWithMCZ(mappedQc); + qc::CircuitOptimizer::singleQubitGateFusion(mappedQc); + qc::CircuitOptimizer::flattenOperations(mappedQc); // decompose AOD moves MoveToAodConverter aodScheduler(*arch, hardwareQubits, flyingAncillas); mappedQcAOD = aodScheduler.schedule(mappedQc); From 3f216d3975cfe96b08519da601b1585f28937946 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Mon, 22 Sep 2025 12:43:05 +0200 Subject: [PATCH 190/394] updated python bindings --- bindings/hybrid_mapper/hybrid_mapper.cpp | 58 +++++++-------- python/mqt/qmap/hybrid_mapper.pyi | 89 ++++++++++++------------ 2 files changed, 73 insertions(+), 74 deletions(-) diff --git a/bindings/hybrid_mapper/hybrid_mapper.cpp b/bindings/hybrid_mapper/hybrid_mapper.cpp index ef4d6a0b6..a019e8df8 100644 --- a/bindings/hybrid_mapper/hybrid_mapper.cpp +++ b/bindings/hybrid_mapper/hybrid_mapper.cpp @@ -50,57 +50,57 @@ PYBIND11_MODULE(MQT_QMAP_MODULE_NAME, m, py::mod_gil_not_used()) { "Parameters controlling the mapper behavior") .def(py::init<>(), "Create a MapperParameters instance with default values") - .def_readwrite("lookaheadDepth", &na::MapperParameters::lookaheadDepth, + .def_readwrite("lookahead_depth", &na::MapperParameters::lookaheadDepth, "Depth of lookahead for mapping decisions") - .def_readwrite("lookaheadWeightSwaps", + .def_readwrite("lookahead_weight_swaps", &na::MapperParameters::lookaheadWeightSwaps, "Weight assigned to swap operations during lookahead") - .def_readwrite("lookaheadWeightMoves", + .def_readwrite("lookahead_weight_moves", &na::MapperParameters::lookaheadWeightMoves, "Weight assigned to move operations during lookahead") .def_readwrite("decay", &na::MapperParameters::decay, "Decay factor for gate blocking") - .def_readwrite("shuttlingTimeWeight", + .def_readwrite("shuttling_time_weight", &na::MapperParameters::shuttlingTimeWeight, "Weight for shuttling time in cost evaluation") .def_readwrite( - "dynamicMappingWeight", &na::MapperParameters::dynamicMappingWeight, + "dynamic_mapping_weight", &na::MapperParameters::dynamicMappingWeight, "Weight for dynamic remapping (SWAPs or MOVEs) in cost evaluation") - .def_readwrite("gateWeight", &na::MapperParameters::gateWeight, + .def_readwrite("gate_weight", &na::MapperParameters::gateWeight, "Weight for gate execution in cost evaluation") - .def_readwrite("shuttlingWeight", &na::MapperParameters::shuttlingWeight, + .def_readwrite("shuttling_weight", &na::MapperParameters::shuttlingWeight, "Weight for shuttling operations in cost evaluation") .def_readwrite( "seed", &na::MapperParameters::seed, "Random seed for stochastic decisions (initial mapping, etc.)") - .def_readwrite("numFlyingAncillas", + .def_readwrite("num_flying_ancillas", &na::MapperParameters::numFlyingAncillas, "Number of ancilla qubits to be used (0 or 1 for now)") - .def_readwrite("limitShuttlingLayer", + .def_readwrite("limit_shuttling_layer", &na::MapperParameters::limitShuttlingLayer, "Maximum allowed shuttling layer (default: unlimited)") - .def_readwrite("maxBridgeDistance", + .def_readwrite("max_bridge_distance", &na::MapperParameters::maxBridgeDistance, "Maximum distance for bridge operations") - .def_readwrite("usePassBy", &na::MapperParameters::usePassBy, + .def_readwrite("use_pass_by", &na::MapperParameters::usePassBy, "Enable or disable pass-by operations") .def_readwrite("verbose", &na::MapperParameters::verbose, "Enable verbose logging for debugging") - .def_readwrite("initialCoordMapping", + .def_readwrite("initial_coord_mapping", &na::MapperParameters::initialCoordMapping, "Strategy for initial coordinate mapping"); py::class_(m, "MapperStats") .def(py::init<>()) - .def_readwrite("nSwaps", &na::MapperStats::nSwaps, + .def_readwrite("num_swaps", &na::MapperStats::nSwaps, "Number of swap operations performed") - .def_readwrite("nBridges", &na::MapperStats::nBridges, + .def_readwrite("num_bridges", &na::MapperStats::nBridges, "Number of bridge operations performed") - .def_readwrite("nFAncillas", &na::MapperStats::nFAncillas, + .def_readwrite("num_f_ancillas", &na::MapperStats::nFAncillas, "Number of fresh ancilla qubits used") - .def_readwrite("nMoves", &na::MapperStats::nMoves, + .def_readwrite("num_moves", &na::MapperStats::nMoves, "Number of move operations performed") - .def_readwrite("nPassBy", &na::MapperStats::nPassBy, + .def_readwrite("num_pass_by", &na::MapperStats::nPassBy, "Number of pass-by operations performed"); py::class_(m, "NeutralAtomHybridArchitecture") @@ -110,21 +110,21 @@ PYBIND11_MODULE(MQT_QMAP_MODULE_NAME, m, py::mod_gil_not_used()) { .def_readwrite("name", &na::NeutralAtomArchitecture::name, "Name of the architecture") .def_property_readonly( - "nrows", &na::NeutralAtomArchitecture::getNrows, + "num_rows", &na::NeutralAtomArchitecture::getNrows, "Number of rows in a rectangular grid SLM arrangement") .def_property_readonly( - "ncolumns", &na::NeutralAtomArchitecture::getNcolumns, + "num_columns", &na::NeutralAtomArchitecture::getNcolumns, "Number of columns in a rectangular grid SLM arrangement") .def_property_readonly( - "npositions", &na::NeutralAtomArchitecture::getNpositions, + "num_positions", &na::NeutralAtomArchitecture::getNpositions, "Total number of positions in a rectangular grid SLM arrangement") .def_property_readonly( - "naods", &na::NeutralAtomArchitecture::getNAods, + "num_aods", &na::NeutralAtomArchitecture::getNAods, "Number of independent 2D acousto-optic deflectors") .def_property_readonly("naod_coordinates", &na::NeutralAtomArchitecture::getNAodCoordinates, "Maximal number of AOD rows/columns (NOT USED) ") - .def_property_readonly("nqubits", + .def_property_readonly("num_qubits", &na::NeutralAtomArchitecture::getNqubits, "Number of atoms in the neutral atom quantum " "computer that can be used as qubits") @@ -182,9 +182,9 @@ PYBIND11_MODULE(MQT_QMAP_MODULE_NAME, m, py::mod_gil_not_used()) { .def( "map_qasm_file", [](na::NeutralAtomMapper& mapper, const std::string& filename, - const na::InitialMapping initialMapping) { + const na::InitialMapping initial_mapping) { auto circ = qasm3::Importer::importf(filename); - mapper.map(circ, initialMapping); + mapper.map(circ, initial_mapping); }, "Map a quantum circuit to the neutral atom quantum computer", "filename"_a, "initial_mapping"_a = na::InitialMapping::Identity) @@ -198,16 +198,18 @@ PYBIND11_MODULE(MQT_QMAP_MODULE_NAME, m, py::mod_gil_not_used()) { &na::NeutralAtomMapper::getMappedQcAodQasm, "Returns the mapped circuit with AOD operations as an extended " "qasm2 string") - .def("saveMappedQcAodQasm", &na::NeutralAtomMapper::saveMappedQcAodQasm, + .def("save_mapped_qc_aod_qasm", + &na::NeutralAtomMapper::saveMappedQcAodQasm, "Saves the mapped circuit with AOD operations as an extended qasm2 " "string", "filename"_a) .def( "schedule", [](na::NeutralAtomMapper& mapper, const bool verbose, - const bool createAnimationCsv, const double shuttlingSpeedFactor) { - auto results = mapper.schedule(verbose, createAnimationCsv, - shuttlingSpeedFactor); + const bool create_animation_csv, + const double shuttling_speed_factor) { + auto results = mapper.schedule(verbose, create_animation_csv, + shuttling_speed_factor); return results.toMap(); }, "Schedule the mapped circuit", "verbose"_a = false, diff --git a/python/mqt/qmap/hybrid_mapper.pyi b/python/mqt/qmap/hybrid_mapper.pyi index 268bce68b..9a376eefe 100644 --- a/python/mqt/qmap/hybrid_mapper.pyi +++ b/python/mqt/qmap/hybrid_mapper.pyi @@ -8,8 +8,8 @@ """Python bindings for hybrid mapper module.""" +import typing from enum import Enum -from typing import overload from mqt.core.ir import QuantumComputation @@ -19,64 +19,42 @@ class InitialCoordinateMapping(Enum): class InitialCircuitMapping(Enum): identity = ... + graph = ... -class HybridMapperParameters: +class MapperParameters: decay: float + dynamic_mapping_weight: float gate_weight: float - initial_mapping: InitialCoordinateMapping + initial_coord_mapping: InitialCoordinateMapping + limit_shuttling_layer: int + lookahead_depth: int lookahead_weight_moves: float lookahead_weight_swaps: float + max_bridge_distance: int + num_flying_ancillas: int seed: int shuttling_time_weight: float shuttling_weight: float + use_pass_by: bool verbose: bool - @overload def __init__(self) -> None: ... - @overload - def __init__( - self, - lookahead_weight_swaps: float = ..., - lookahead_weight_moves: float = ..., - decay: float = ..., - shuttling_time_weight: float = ..., - gate_weight: float = ..., - shuttling_weight: float = ..., - ) -> None: ... -class HybridNAMapper: - """The hybrid mapper for Neutral Atom Quantum Computers.""" - def __init__(self, arch: NeutralAtomHybridArchitecture, params: HybridMapperParameters = ...) -> None: ... - def get_animation_csv(self) -> str: ... - def get_init_hw_pos(self) -> dict[int, int]: ... - def get_mapped_qc(self) -> str: ... - def get_mapped_qc_aod(self) -> str: ... - def map( - self, circ: QuantumComputation, initial_mapping: InitialCircuitMapping = ..., verbose: bool = ... - ) -> None: ... - def map_qasm_file( - self, filename: str, initial_mapping: InitialCircuitMapping = ..., verbose: bool = ... - ) -> None: ... - def save_animation_csv(self, filename: str) -> None: ... - def save_mapped_qc(self, filename: str) -> None: ... - def save_mapped_qc_aod(self, filename: str) -> None: ... - def schedule( - self, verbose: bool = ..., create_animation_csv: bool = ..., shuttling_speed_factor: float = ... - ) -> dict[str, float]: ... - def set_parameters(self, params: HybridMapperParameters) -> None: ... +class MapperStats: + num_bridges: int + num_f_ancillas: int + num_moves: int + num_pass_by: int + num_swaps: int + def __init__(self) -> None: ... class NeutralAtomHybridArchitecture: - """Class representing the architecture of a Neutral Atom Quantum Computer.""" - name: str - def __init__(self, filename: str) -> None: ... - def compute_swap_distance(self, idx1: int, idx2: int) -> float: ... - def get_animation_csv(self) -> str: ... + def compute_swap_distance(self, idx1: typing.SupportsInt, idx2: typing.SupportsInt) -> int: ... def get_gate_average_fidelity(self, s: str) -> float: ... def get_gate_time(self, s: str) -> float: ... - def get_nearby_coordinates(self, idx: int) -> set[int]: ... + def get_nearby_coordinates(self, idx: typing.SupportsInt) -> set[int]: ... def load_json(self, json_filename: str) -> None: ... - def save_animation_csv(self, filename: str) -> None: ... @property def blocking_factor(self) -> float: ... @property @@ -90,12 +68,31 @@ class NeutralAtomHybridArchitecture: @property def naod_intermediate_levels(self) -> int: ... @property - def naods(self) -> int: ... + def num_aods(self) -> int: ... @property - def ncolumns(self) -> int: ... + def num_columns(self) -> int: ... @property - def npositions(self) -> int: ... + def num_positions(self) -> int: ... @property - def nqubits(self) -> int: ... + def num_qubits(self) -> int: ... @property - def nrows(self) -> int: ... + def num_rows(self) -> int: ... + +class HybridNAMapper: + def __init__(self, arch: NeutralAtomHybridArchitecture, params: MapperParameters) -> None: ... + def get_animation_viz(self) -> str: ... + def get_init_hw_pos(self) -> dict[int, int]: ... + def get_mapped_qc(self) -> QuantumComputation: ... + def get_mapped_qc_qasm(self) -> str: ... + def get_stats(self) -> dict[str, float]: ... + def map(self, circ: QuantumComputation, initial_mapping: InitialCircuitMapping = ...) -> None: ... + def map_qasm_file(self, filename: str, initial_mapping: InitialCircuitMapping = ...) -> None: ... + def save_animation_files(self, filename: str) -> None: ... + def save_mapped_qc_aod_qasm(self) -> str: ... + def schedule( + self, + verbose: bool = ..., + create_animation_csv: bool = ..., + shuttling_speed_factor: typing.SupportsFloat = ..., + ) -> dict[str, float]: ... + def set_parameters(self, params: MapperParameters) -> None: ... From 7afa6ddbd1e5d183f80ca2d35ed19de77bab236f Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 26 Sep 2025 14:48:16 +0200 Subject: [PATCH 191/394] fixed error where one move is done for free by other activations (merge in both directions). --- src/hybridmap/MoveToAodConverter.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/hybridmap/MoveToAodConverter.cpp b/src/hybridmap/MoveToAodConverter.cpp index 32670e8d2..2e79880f9 100644 --- a/src/hybridmap/MoveToAodConverter.cpp +++ b/src/hybridmap/MoveToAodConverter.cpp @@ -250,7 +250,11 @@ void MoveToAodConverter::AodActivationHelper::addActivation( reAssignOffsets(aodMovesX, signX); break; case ActivationMergeType::Merge: - throw std::runtime_error("Merge in both dimensions should never happen."); + mergeActivationDim( + Dimension::X, + AodActivation{Dimension::X, {x, deltaX, signX, needLoad}, move}, + AodActivation{Dimension::Y, {y, deltaY, signY, needLoad}, move}); + break; case ActivationMergeType::Append: mergeActivationDim( Dimension::X, From b7e7013c33f7b54de87bac26eaf2206dabb3308d Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Wed, 1 Oct 2025 09:27:53 +0200 Subject: [PATCH 192/394] fixed dynamic fidelity comparison --- src/hybridmap/HybridNeutralAtomMapper.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 6e46b5c41..03c0d9947 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -2228,6 +2228,9 @@ NeutralAtomMapper::compareSwapAndBridge(const Swap& bestSwap, if (bestBridge == Bridge()) { return MappingMethod::SwapMethod; } + if (this->parameters->dynamicMappingWeight == 0) { + return MappingMethod::BridgeMethod; + } // swap distance reduction qc::fp const swapDistReduction = swapDistanceReduction(bestSwap, this->frontLayerGate) + @@ -2246,9 +2249,9 @@ NeutralAtomMapper::compareSwapAndBridge(const Swap& bestSwap, qc::fp const bridgeFidelity = this->arch->getGateAverageFidelity(bridgeName) * std::exp(-this->arch->getGateTime(bridgeName) / this->arch->getDecoherenceTime()); - const auto swap = -swapDistReduction * std::log(1 - swapFidelity) * + const auto swap = std::log(swapFidelity) / swapDistReduction / parameters->dynamicMappingWeight; - const auto bridge = -bridgeDistReduction * std::log(1 - bridgeFidelity); + const auto bridge = std::log(bridgeFidelity) / bridgeDistReduction; if (swap >= bridge) { return MappingMethod::SwapMethod; } @@ -2343,10 +2346,10 @@ MappingMethod NeutralAtomMapper::compareShuttlingAndFlyingAncilla( } // higher is better - const auto move = -std::log(1 - moveFidelity) * moveDistReduction * + const auto move = std::log(moveFidelity) / moveDistReduction / parameters->dynamicMappingWeight; - const auto fa = -std::log(1 - faFidelity) * faDistReduction; - const auto passBy = -std::log(1 - passByFidelity) * faDistReduction; + const auto fa = std::log(faFidelity) / faDistReduction; + const auto passBy = std::log(passByFidelity) / faDistReduction; if (move > fa && move > passBy) { return MappingMethod::MoveMethod; From dbe866a01b2c4325423d9b787c41a42d2ea4d34c Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Wed, 1 Oct 2025 10:20:40 +0200 Subject: [PATCH 193/394] only compute bridges comparable to swaps --- src/hybridmap/HybridNeutralAtomMapper.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 03c0d9947..36331e4b1 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -547,8 +547,8 @@ std::set NeutralAtomMapper::getAllPossibleSwaps( } return swaps; } -Bridge NeutralAtomMapper::findBestBridge() { - auto allBridges = getShortestBridges(); +Bridge NeutralAtomMapper::findBestBridge(const Swap& bestSwap) { + auto allBridges = getShortestBridges(bestSwap); if (allBridges.empty()) { return {}; } @@ -572,13 +572,18 @@ Bridge NeutralAtomMapper::findBestBridge() { return allBridges[bestBridgeIdx]; } -Bridges NeutralAtomMapper::getShortestBridges() { +Bridges NeutralAtomMapper::getShortestBridges(const Swap& bestSwap) { Bridges allBridges; size_t minBridgeLength = std::numeric_limits::max(); for (const auto* const op : this->frontLayerGate) { if (op->getUsedQubits().size() == 2) { + // only consider gates which involve at least one of the swapped qubits auto usedQuBits = op->getUsedQubits(); auto usedHwQubits = this->mapping.getHwQubits(usedQuBits); + if (!usedHwQubits.contains(bestSwap.first) && + !usedHwQubits.contains(bestSwap.second)) { + continue; + } // shortcut if distance already larger than minBridgeLength const auto dist = this->hardwareQubits.getAllToAllSwapDistance(usedHwQubits); @@ -1810,7 +1815,7 @@ size_t NeutralAtomMapper::gateBasedMapping(NeutralAtomLayer& frontLayer, auto bestSwap = findBestSwap(lastSwap); MappingMethod bestMethod = MappingMethod::SwapMethod; if (parameters->maxBridgeDistance > 0) { - auto bestBridge = findBestBridge(); + auto bestBridge = findBestBridge(bestSwap); bestMethod = compareSwapAndBridge(bestSwap, bestBridge); if (bestMethod == MappingMethod::BridgeMethod) { updateBlockedQubits( From 95172cac2c30579894249bf01f6fc4cdad008853 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 2 Oct 2025 12:55:52 +0200 Subject: [PATCH 194/394] updated bridge search --- include/hybridmap/HybridNeutralAtomMapper.hpp | 4 +-- src/hybridmap/HybridNeutralAtomMapper.cpp | 26 +++++++------------ 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index 82f60874f..08268c01b 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -241,8 +241,8 @@ class NeutralAtomMapper { // Methods for bridge operations mapping - [[nodiscard]] Bridge findBestBridge(); - [[nodiscard]] Bridges getShortestBridges(); + [[nodiscard]] Bridge findBestBridge(const Swap& bestSwap); + [[nodiscard]] Bridges getShortestBridges(const Swap& bestSwap); [[nodiscard]] CoordIndices computeCurrentCoordUsages() const; diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 36331e4b1..66ccea15b 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -721,7 +721,8 @@ qc::fp NeutralAtomMapper::swapCost( swapCostPerLayer(swap, swapCloseByLookahead, swapExactLookahead) / static_cast(this->lookaheadLayerGate.size()); } - auto cost = (parameters->lookaheadWeightSwaps * distanceChangeLookahead) + + auto cost = (parameters->lookaheadWeightSwaps * distanceChangeLookahead / + this->parameters->lookaheadDepth) + distanceChangeFront; // compute the last time one of the swap qubits was used if (this->parameters->decay != 0) { @@ -1209,7 +1210,8 @@ qc::fp NeutralAtomMapper::moveCost(const AtomMove& move) const { const auto lookaheadCost = moveCostPerLayer(move, this->lookaheadLayerShuttling) / static_cast(this->lookaheadLayerShuttling.size()); - cost += parameters->lookaheadWeightMoves * lookaheadCost; + cost += parameters->lookaheadWeightMoves * lookaheadCost / + static_cast(this->parameters->lookaheadDepth); } if (!this->lastMoves.empty()) { const auto parallelCost = @@ -1654,18 +1656,8 @@ NeutralAtomMapper::estimateNumSwapGates(const qc::Operation* opPointer) { const auto usedHwQubits = this->mapping.getHwQubits(usedQubits); qc::fp minNumSwaps = 0; if (usedHwQubits.size() == 2) { - SwapDistance minDistance = std::numeric_limits::max(); - for (const auto& hwQubit : usedHwQubits) { - for (const auto& otherHwQubit : usedHwQubits) { - if (hwQubit == otherHwQubit) { - continue; - } - auto distance = - this->hardwareQubits.getSwapDistance(hwQubit, otherHwQubit); - minDistance = std::min(distance, minDistance); - } - } - minNumSwaps = minDistance; + minNumSwaps = this->hardwareQubits.getSwapDistance( + *usedHwQubits.begin(), *(usedHwQubits.rbegin()), true); } else { // multi-qubit gates const auto bestPos = getBestMultiQubitPosition(opPointer); if (bestPos.empty()) { @@ -2240,7 +2232,8 @@ NeutralAtomMapper::compareSwapAndBridge(const Swap& bestSwap, qc::fp const swapDistReduction = swapDistanceReduction(bestSwap, this->frontLayerGate) + (this->parameters->lookaheadWeightSwaps * - swapDistanceReduction(bestSwap, this->lookaheadLayerGate)); + swapDistanceReduction(bestSwap, this->lookaheadLayerGate) / + this->parameters->lookaheadDepth); // bridge distance reduction qc::fp const bridgeDistReduction = bestBridge.second.size() - 2; @@ -2274,7 +2267,8 @@ MappingMethod NeutralAtomMapper::compareShuttlingAndFlyingAncilla( auto moveDistReduction = moveCombDistanceReduction(bestMoveComb, this->frontLayerShuttling) + (this->parameters->lookaheadWeightMoves * - moveCombDistanceReduction(bestMoveComb, this->lookaheadLayerShuttling)); + moveCombDistanceReduction(bestMoveComb, this->lookaheadLayerShuttling) / + this->parameters->lookaheadDepth); // move auto const moveDist = this->arch->getMoveCombEuclideanDistance(bestMoveComb); auto const moveCombSize = bestMoveComb.size(); From ef9e356f2dd3ea67e44b39c8884065baa28d6f15 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Wed, 8 Oct 2025 10:59:21 +0200 Subject: [PATCH 195/394] Collect more moves by considering more best positions --- src/hybridmap/HybridNeutralAtomMapper.cpp | 41 ++++++++++++++++------- 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 66ccea15b..2b8bad322 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -1420,21 +1420,38 @@ MoveCombs NeutralAtomMapper::getAllMoveCombinations() { auto usedHwQubits = this->mapping.getHwQubits(usedQubits); auto usedCoordsSet = this->hardwareQubits.getCoordIndices(usedHwQubits); auto usedCoords = std::vector(usedCoordsSet.begin(), usedCoordsSet.end()); - auto bestPos = getBestMovePos(usedCoords); - if (this->parameters->verbose) { - std::cout << "bestPos: "; - for (const auto qubit : bestPos) { - std::cout << qubit << " "; + std::set bestPositions; + if (usedCoords.size() == 2) { + // check vecinity + for (const auto& coord : usedCoords) { + auto const nearbyFreeCoords = + this->hardwareQubits.getNearbyFreeCoordinatesByCoord(coord); + for (const auto& freeCoord : nearbyFreeCoords) { + bestPositions.insert({coord, freeCoord}); + } + } + // none free nearby coords + if (bestPositions.empty()) { + bestPositions.insert(getBestMovePos(usedCoords)); + bestPositions.insert(getBestMovePos({usedCoords[1], usedCoords[0]})); + } + } else { + // iterate over all possible permutations of the usedCoords + // to find different best positions + std::sort(usedCoords.begin(), usedCoords.end()); + do { + bestPositions.insert(getBestMovePos(usedCoords)); + } while (std::next_permutation(usedCoords.begin(), usedCoords.end())); + } + for (const auto& bestPos : bestPositions) { + auto moves = getMoveCombinationsToPosition(usedHwQubits, bestPos); + moves.setOperation(op, bestPos); + if (allMoves.size() > this->parameters->limitShuttlingLayer) { + break; } - std::cout << '\n'; + allMoves.addMoveCombs(moves); } - auto moves = getMoveCombinationsToPosition(usedHwQubits, bestPos); - moves.setOperation(op, bestPos); - allMoves.addMoveCombs(moves); ++i; - if (i >= parameters->limitShuttlingLayer) { - break; - } } allMoves.removeLongerMoveCombs(); return allMoves; From 181f028fd092d7dcbc5406b97f6b3abcbd69e1d3 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Wed, 8 Oct 2025 13:33:45 +0200 Subject: [PATCH 196/394] compute move dist reduction only if one of the qubits is moved --- src/hybridmap/HybridNeutralAtomMapper.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 2b8bad322..4cb298164 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -774,17 +774,17 @@ NeutralAtomMapper::moveCombDistanceReduction(const MoveComb& moveComb, auto usedQubits = op->getUsedQubits(); auto hwQubits = this->mapping.getHwQubits(usedQubits); auto coordIndices = this->hardwareQubits.getCoordIndices(hwQubits); - const auto& distBefore = - this->arch->getAllToAllEuclideanDistance(coordIndices); for (const auto& move : moveComb.moves) { if (coordIndices.find(move.c1) != coordIndices.end()) { + const auto& distBefore = + this->arch->getAllToAllEuclideanDistance(coordIndices); coordIndices.erase(move.c1); coordIndices.insert(move.c2); + const auto& distAfter = + this->arch->getAllToAllEuclideanDistance(coordIndices); + moveDistReduction += distBefore - distAfter; } } - const auto& distAfter = - this->arch->getAllToAllEuclideanDistance(coordIndices); - moveDistReduction += distBefore - distAfter; } return moveDistReduction; } From 61d7b77cfff55c2cda24d9a976be36c21599337f Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 10 Oct 2025 08:56:55 +0200 Subject: [PATCH 197/394] Improved/fixed comparison between move and passby --- src/hybridmap/HybridNeutralAtomMapper.cpp | 74 ++++++++++++++--------- 1 file changed, 44 insertions(+), 30 deletions(-) diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 4cb298164..56be67a5f 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -2281,11 +2281,16 @@ MappingMethod NeutralAtomMapper::compareShuttlingAndFlyingAncilla( } // move distance reduction - auto moveDistReduction = - moveCombDistanceReduction(bestMoveComb, this->frontLayerShuttling) + - (this->parameters->lookaheadWeightMoves * - moveCombDistanceReduction(bestMoveComb, this->lookaheadLayerShuttling) / - this->parameters->lookaheadDepth); + auto moveDistReductionf = + moveCombDistanceReduction(bestMoveComb, this->frontLayerShuttling); + + auto const moveDistReductionl = 0.0; + if (this->lookaheadLayerShuttling.size() != 0) { + (this->parameters->lookaheadWeightMoves * + moveCombDistanceReduction(bestMoveComb, this->lookaheadLayerShuttling) / + this->lookaheadLayerShuttling.size()); + } + auto moveDistReduction = moveDistReductionf + moveDistReductionl; // move auto const moveDist = this->arch->getMoveCombEuclideanDistance(bestMoveComb); auto const moveCombSize = bestMoveComb.size(); @@ -2300,8 +2305,8 @@ MappingMethod NeutralAtomMapper::compareShuttlingAndFlyingAncilla( static_cast(moveCombSize)) + (this->arch->getShuttlingTime(qc::OpType::AodDeactivate) * static_cast(moveCombSize)); - auto const moveDecoherence = - std::exp(-moveTime / this->arch->getDecoherenceTime()); + auto const moveDecoherence = std::exp(-moveTime * this->arch->getNqubits() / + this->arch->getDecoherenceTime()); auto const moveFidelity = moveOpFidelity * moveDecoherence; // fa @@ -2316,14 +2321,15 @@ MappingMethod NeutralAtomMapper::compareShuttlingAndFlyingAncilla( // flying ancilla auto const faDist = this->arch->getFaEuclideanDistance(bestFaComb); auto const faCombSize = bestFaComb.moves.size(); - auto const faOpFidelity = - std::pow(this->arch->getShuttlingAverageFidelity(qc::OpType::AodMove) * - std::pow(this->arch->getGateAverageFidelity("cz"), 2) * - std::pow(this->arch->getGateAverageFidelity("h"), 4), - faCombSize); - auto const faDecoherence = - std::exp(-faDist / this->arch->getShuttlingTime(qc::OpType::AodMove) / - this->arch->getDecoherenceTime()); + auto const faOpFidelity = std::pow( + std::pow(this->arch->getShuttlingAverageFidelity(qc::OpType::AodMove), + 3) * + std::pow(this->arch->getGateAverageFidelity("cz"), 2) * + std::pow(this->arch->getGateAverageFidelity("h"), 4), + faCombSize); + auto const faDecoherence = std::exp( + -faDist / this->arch->getShuttlingTime(qc::OpType::AodMove) * + this->arch->getNqubits() * 2 / this->arch->getDecoherenceTime()); faFidelity = faOpFidelity * faDecoherence; } @@ -2338,34 +2344,42 @@ MappingMethod NeutralAtomMapper::compareShuttlingAndFlyingAncilla( auto const passByDist = this->arch->getPassByEuclideanDistance(bestPbComb); auto const passByTime = - (passByDist / this->arch->getShuttlingTime(qc::OpType::AodMove)) + + (passByDist / this->arch->getShuttlingTime(qc::OpType::AodMove)) * 2 + (this->arch->getShuttlingTime(qc::OpType::AodActivate) * static_cast(pbCombSize)) + (this->arch->getShuttlingTime(qc::OpType::AodDeactivate) * static_cast(pbCombSize)); - passByFidelity = - std::pow(this->arch->getShuttlingAverageFidelity(qc::OpType::AodMove) * - this->arch->getShuttlingAverageFidelity( - qc::OpType::AodActivate) * - this->arch->getShuttlingAverageFidelity( - qc::OpType::AodDeactivate), - pbCombSize) * - std::exp(-passByTime / this->arch->getDecoherenceTime()); + passByFidelity = std::pow(std::pow(this->arch->getShuttlingAverageFidelity( + qc::OpType::AodMove), + 2) * + this->arch->getShuttlingAverageFidelity( + qc::OpType::AodActivate) * + this->arch->getShuttlingAverageFidelity( + qc::OpType::AodDeactivate), + pbCombSize) * + std::exp(-passByTime * this->arch->getNqubits() / + this->arch->getDecoherenceTime()); } auto const minDistanceReduction = std::min({moveDistReduction, faDistReduction, pbDistReduction}); + qc::fp const constant = 1; if (minDistanceReduction < 0) { - moveDistReduction -= minDistanceReduction; - faDistReduction -= minDistanceReduction; - pbDistReduction -= minDistanceReduction; + moveDistReduction -= minDistanceReduction - constant; + faDistReduction -= minDistanceReduction - constant; + pbDistReduction -= minDistanceReduction - constant; + } else { + moveDistReduction += constant; + faDistReduction += constant; + pbDistReduction += constant; } // higher is better - const auto move = std::log(moveFidelity) / moveDistReduction / - parameters->dynamicMappingWeight; + auto a = parameters->dynamicMappingWeight; + const auto move = std::log(moveFidelity) / moveDistReduction / a; + const auto fa = std::log(faFidelity) / faDistReduction; - const auto passBy = std::log(passByFidelity) / faDistReduction; + const auto passBy = std::log(passByFidelity) / pbDistReduction; if (move > fa && move > passBy) { return MappingMethod::MoveMethod; From 613cdf1fa9539eb2f7cb727d32c43907a066bd0e Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 10 Oct 2025 09:55:18 +0200 Subject: [PATCH 198/394] reworked moveComb cost computation --- include/hybridmap/HybridNeutralAtomMapper.hpp | 22 +-- src/hybridmap/HybridNeutralAtomMapper.cpp | 182 ++++++------------ 2 files changed, 58 insertions(+), 146 deletions(-) diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index 08268c01b..6b910eb8e 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -407,16 +407,6 @@ class NeutralAtomMapper { qc::fp moveCombDistanceReduction(const MoveComb& moveComb, const GateList& layer) const; qc::fp swapDistanceReduction(const Swap& swap, const GateList& layer); - /** - * @brief Calculates the cost of a move operation. - * @details Assumes the move is executed and computes the distance reduction - * for the layer. - * @param move The move operation to compute the cost for - * @param layer The layer to compute the distance reduction for - * @return The distance reduction cost - */ - [[nodiscard]] qc::fp moveCostPerLayer(const AtomMove& move, - const GateList& layer) const; /** * @brief Calculates a parallelization cost if the move operation can be @@ -424,17 +414,7 @@ class NeutralAtomMapper { * @param move The move operation to compute the cost for * @return The parallelization cost */ - [[nodiscard]] qc::fp parallelMoveCost(const AtomMove& move) const; - /** - * @brief Calculates the cost of a move operation. - * @details The cost of a move operation is computed with the following terms: - * - distance reduction for front + lookahead layers using moveCostPerLayer - * - parallelization term based on last moves using parallelMoveCost - * The three contributions are weighted with the runtime parameters. - * @param move The move operation to compute the cost for - * @return The cost of the move operation - */ - [[nodiscard]] qc::fp moveCost(const AtomMove& move) const; + [[nodiscard]] qc::fp parallelMoveCost(const MoveComb& moveComb) const; /** * @brief Calculates the cost of a series of move operations by summing up the * cost of each move. diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 56be67a5f..6fcfaa03c 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -1195,122 +1195,69 @@ MoveComb NeutralAtomMapper::findBestAtomMove() { qc::fp NeutralAtomMapper::moveCostComb(const MoveComb& moveComb) const { qc::fp costComb = 0; - for (const auto& move : moveComb.moves) { - costComb += moveCost(move); - } - return costComb; -} - -qc::fp NeutralAtomMapper::moveCost(const AtomMove& move) const { - qc::fp cost = 0; - const auto frontCost = moveCostPerLayer(move, this->frontLayerShuttling) / - static_cast(this->frontLayerShuttling.size()); - cost += frontCost; + const auto frontDistReduction = + moveCombDistanceReduction(moveComb, this->frontLayerShuttling) / + static_cast(this->frontLayerShuttling.size()); + costComb -= frontDistReduction; if (!lookaheadLayerShuttling.empty()) { - const auto lookaheadCost = - moveCostPerLayer(move, this->lookaheadLayerShuttling) / + const auto lookaheadDistReduction = + moveCombDistanceReduction(moveComb, this->lookaheadLayerShuttling) / static_cast(this->lookaheadLayerShuttling.size()); - cost += parameters->lookaheadWeightMoves * lookaheadCost / - static_cast(this->parameters->lookaheadDepth); + costComb -= parameters->lookaheadWeightMoves * lookaheadDistReduction / + static_cast(this->parameters->lookaheadDepth); } if (!this->lastMoves.empty()) { - const auto parallelCost = - parameters->shuttlingTimeWeight * parallelMoveCost(move) / - static_cast(this->lastMoves.size()) / - static_cast(this->frontLayerShuttling.size()); - cost += parallelCost; + const auto parallelMovecost = + parameters->shuttlingTimeWeight * parallelMoveCost(moveComb); + costComb += parallelMovecost; } - - return cost; + return costComb; } -qc::fp NeutralAtomMapper::moveCostPerLayer(const AtomMove& move, - const GateList& layer) const { - // compute cost assuming the move was applied - qc::fp distChange = 0; - if (const auto toMoveHwQubit = this->hardwareQubits.getHwQubit(move.c1); - this->mapping.isMapped(toMoveHwQubit)) { - const auto toMoveCircuitQubit = this->mapping.getCircQubit(toMoveHwQubit); - for (const auto& gate : layer) { - if (auto const usedQubits = gate->getUsedQubits(); - usedQubits.contains(toMoveCircuitQubit)) { - // check distance reduction - qc::fp distanceBefore = 0; - for (const auto& qubit : usedQubits) { - if (qubit == toMoveCircuitQubit) { - continue; - } - const auto hwQubit = this->mapping.getHwQubit(qubit); - const auto dist = this->arch->getEuclideanDistance( - this->hardwareQubits.getCoordIndex(hwQubit), - this->hardwareQubits.getCoordIndex(toMoveHwQubit)); - distanceBefore += dist; - } - qc::fp distanceAfter = 0; - for (const auto& qubit : usedQubits) { - if (qubit == toMoveCircuitQubit) { - continue; +qc::fp NeutralAtomMapper::parallelMoveCost(const MoveComb& moveComb) const { + qc::fp parallelCost = 0; + for (const auto& move : moveComb.moves) { + const auto moveVector = this->arch->getVector(move.c1, move.c2); + std::vector lastEndingCoords; + if (this->lastMoves.empty()) { + parallelCost += arch->getVectorShuttlingTime(moveVector); + } + for (const auto& lastMove : this->lastMoves) { + lastEndingCoords.emplace_back(lastMove.c2); + // decide of shuttling can be done in parallel + auto lastMoveVector = this->arch->getVector(lastMove.c1, lastMove.c2); + if (moveVector.overlap(lastMoveVector)) { + if (moveVector.direction != lastMoveVector.direction) { + parallelCost += arch->getVectorShuttlingTime(moveVector); + } else { + // check if move can be done in parallel + if (moveVector.include(lastMoveVector)) { + parallelCost += arch->getVectorShuttlingTime(moveVector); } - const auto hwQubit = this->mapping.getHwQubit(qubit); - const auto dist = this->arch->getEuclideanDistance( - this->hardwareQubits.getCoordIndex(hwQubit), move.c2); - distanceAfter += dist; } - distChange += distanceAfter - distanceBefore; } } - } - return distChange; -} - -qc::fp NeutralAtomMapper::parallelMoveCost(const AtomMove& move) const { - qc::fp parallelCost = 0; - const auto moveVector = this->arch->getVector(move.c1, move.c2); - std::vector lastEndingCoords; - if (this->lastMoves.empty()) { - parallelCost += arch->getVectorShuttlingTime(moveVector); - } - for (const auto& lastMove : this->lastMoves) { - lastEndingCoords.emplace_back(lastMove.c2); - // decide of shuttling can be done in parallel - auto lastMoveVector = this->arch->getVector(lastMove.c1, lastMove.c2); - if (moveVector.overlap(lastMoveVector)) { - if (moveVector.direction != lastMoveVector.direction) { - parallelCost += arch->getVectorShuttlingTime(moveVector); - } else { - // check if move can be done in parallel - if (moveVector.include(lastMoveVector)) { - parallelCost += arch->getVectorShuttlingTime(moveVector); - } + // check if in same row/column like last moves + // then can may be loaded in parallel + const auto moveCoordInit = this->arch->getCoordinate(move.c1); + const auto moveCoordEnd = this->arch->getCoordinate(move.c2); + parallelCost += arch->getShuttlingTime(qc::OpType::AodActivate) + + arch->getShuttlingTime(qc::OpType::AodDeactivate); + for (const auto& lastMove : this->lastMoves) { + const auto lastMoveCoordInit = this->arch->getCoordinate(lastMove.c1); + const auto lastMoveCoordEnd = this->arch->getCoordinate(lastMove.c2); + if (moveCoordInit.x == lastMoveCoordInit.x || + moveCoordInit.y == lastMoveCoordInit.y) { + parallelCost -= arch->getShuttlingTime(qc::OpType::AodActivate); + } + if (moveCoordEnd.x == lastMoveCoordEnd.x || + moveCoordEnd.y == lastMoveCoordEnd.y) { + parallelCost -= arch->getShuttlingTime(qc::OpType::AodDeactivate); } } } - // check if in same row/column like last moves - // then can may be loaded in parallel - const auto moveCoordInit = this->arch->getCoordinate(move.c1); - const auto moveCoordEnd = this->arch->getCoordinate(move.c2); - parallelCost += arch->getShuttlingTime(qc::OpType::AodActivate) + - arch->getShuttlingTime(qc::OpType::AodDeactivate); - for (const auto& lastMove : this->lastMoves) { - const auto lastMoveCoordInit = this->arch->getCoordinate(lastMove.c1); - const auto lastMoveCoordEnd = this->arch->getCoordinate(lastMove.c2); - if (moveCoordInit.x == lastMoveCoordInit.x || - moveCoordInit.y == lastMoveCoordInit.y) { - parallelCost -= arch->getShuttlingTime(qc::OpType::AodActivate); - } - if (moveCoordEnd.x == lastMoveCoordEnd.x || - moveCoordEnd.y == lastMoveCoordEnd.y) { - parallelCost -= arch->getShuttlingTime(qc::OpType::AodDeactivate); - } - } - // check if move can use AOD atom from last moves - // if (std::find(lastEndingCoords.begin(), lastEndingCoords.end(), - // move.c1) == - // lastEndingCoords.end()) { - // parallelCost += arch->getShuttlingTime(qc::OpType::AodActivate) + - // arch->getShuttlingTime(qc::OpType::AodDeactivate); - // } - return parallelCost; + return parallelCost / static_cast(this->lastMoves.size()) / + static_cast(this->frontLayerShuttling.size()); } MultiQubitMovePos @@ -1421,28 +1368,12 @@ MoveCombs NeutralAtomMapper::getAllMoveCombinations() { auto usedCoordsSet = this->hardwareQubits.getCoordIndices(usedHwQubits); auto usedCoords = std::vector(usedCoordsSet.begin(), usedCoordsSet.end()); std::set bestPositions; - if (usedCoords.size() == 2) { - // check vecinity - for (const auto& coord : usedCoords) { - auto const nearbyFreeCoords = - this->hardwareQubits.getNearbyFreeCoordinatesByCoord(coord); - for (const auto& freeCoord : nearbyFreeCoords) { - bestPositions.insert({coord, freeCoord}); - } - } - // none free nearby coords - if (bestPositions.empty()) { - bestPositions.insert(getBestMovePos(usedCoords)); - bestPositions.insert(getBestMovePos({usedCoords[1], usedCoords[0]})); - } - } else { - // iterate over all possible permutations of the usedCoords - // to find different best positions - std::sort(usedCoords.begin(), usedCoords.end()); - do { - bestPositions.insert(getBestMovePos(usedCoords)); - } while (std::next_permutation(usedCoords.begin(), usedCoords.end())); - } + // iterate over all possible permutations of the usedCoords + // to find different best positions + std::sort(usedCoords.begin(), usedCoords.end()); + do { + bestPositions.insert(getBestMovePos(usedCoords)); + } while (std::next_permutation(usedCoords.begin(), usedCoords.end())); for (const auto& bestPos : bestPositions) { auto moves = getMoveCombinationsToPosition(usedHwQubits, bestPos); moves.setOperation(op, bestPos); @@ -1576,7 +1507,8 @@ MoveCombs NeutralAtomMapper::getMoveCombinationsToPosition( costs.emplace_back(remainingCoord, cost); } } else { - auto cost = moveCost({currentGateQubit, remainingCoord}); + MoveComb moveComb({AtomMove{currentGateQubit, remainingCoord}}); + auto cost = moveCostComb(moveComb); costs.emplace_back(remainingCoord, cost); } } From e6757fd776bdfa4609c013efffd2bcb5c47a7c92 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 10 Oct 2025 10:33:19 +0200 Subject: [PATCH 199/394] Reworked parallel move cost computation --- include/hybridmap/HybridNeutralAtomMapper.hpp | 2 +- src/hybridmap/HybridNeutralAtomMapper.cpp | 78 +++++++++---------- 2 files changed, 40 insertions(+), 40 deletions(-) diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index 6b910eb8e..6cc9b3837 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -42,7 +42,7 @@ struct MapperParameters { qc::fp lookaheadWeightSwaps = 0.1; qc::fp lookaheadWeightMoves = 0.1; qc::fp decay = 0.1; - qc::fp shuttlingTimeWeight = 1; + qc::fp shuttlingTimeWeight = 0.1; qc::fp dynamicMappingWeight = 2; qc::fp gateWeight = 1; qc::fp shuttlingWeight = 1; diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 6fcfaa03c..58a94e8e4 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -1208,7 +1208,8 @@ qc::fp NeutralAtomMapper::moveCostComb(const MoveComb& moveComb) const { } if (!this->lastMoves.empty()) { const auto parallelMovecost = - parameters->shuttlingTimeWeight * parallelMoveCost(moveComb); + parameters->shuttlingTimeWeight * parallelMoveCost(moveComb) / + static_cast(this->frontLayerShuttling.size()); costComb += parallelMovecost; } return costComb; @@ -1216,48 +1217,47 @@ qc::fp NeutralAtomMapper::moveCostComb(const MoveComb& moveComb) const { qc::fp NeutralAtomMapper::parallelMoveCost(const MoveComb& moveComb) const { qc::fp parallelCost = 0; - for (const auto& move : moveComb.moves) { - const auto moveVector = this->arch->getVector(move.c1, move.c2); - std::vector lastEndingCoords; - if (this->lastMoves.empty()) { - parallelCost += arch->getVectorShuttlingTime(moveVector); - } - for (const auto& lastMove : this->lastMoves) { - lastEndingCoords.emplace_back(lastMove.c2); - // decide of shuttling can be done in parallel - auto lastMoveVector = this->arch->getVector(lastMove.c1, lastMove.c2); - if (moveVector.overlap(lastMoveVector)) { - if (moveVector.direction != lastMoveVector.direction) { - parallelCost += arch->getVectorShuttlingTime(moveVector); - } else { - // check if move can be done in parallel - if (moveVector.include(lastMoveVector)) { - parallelCost += arch->getVectorShuttlingTime(moveVector); - } - } + // only first move matters for parallelization + auto move = moveComb.moves.front(); + const auto moveVector = this->arch->getVector(move.c1, move.c2); + parallelCost += arch->getVectorShuttlingTime(moveVector); + bool canBeDoneInParallel = true; + for (const auto& lastMove : this->lastMoves) { + // decide of shuttling can be done in parallel + auto lastMoveVector = this->arch->getVector(lastMove.c1, lastMove.c2); + if (moveVector.overlap(lastMoveVector)) { + if (moveVector.direction != lastMoveVector.direction) { + canBeDoneInParallel = false; + break; + } // check if move can be done in parallel + if (moveVector.include(lastMoveVector)) { + canBeDoneInParallel = false; + break; } } - // check if in same row/column like last moves - // then can may be loaded in parallel - const auto moveCoordInit = this->arch->getCoordinate(move.c1); - const auto moveCoordEnd = this->arch->getCoordinate(move.c2); - parallelCost += arch->getShuttlingTime(qc::OpType::AodActivate) + - arch->getShuttlingTime(qc::OpType::AodDeactivate); - for (const auto& lastMove : this->lastMoves) { - const auto lastMoveCoordInit = this->arch->getCoordinate(lastMove.c1); - const auto lastMoveCoordEnd = this->arch->getCoordinate(lastMove.c2); - if (moveCoordInit.x == lastMoveCoordInit.x || - moveCoordInit.y == lastMoveCoordInit.y) { - parallelCost -= arch->getShuttlingTime(qc::OpType::AodActivate); - } - if (moveCoordEnd.x == lastMoveCoordEnd.x || - moveCoordEnd.y == lastMoveCoordEnd.y) { - parallelCost -= arch->getShuttlingTime(qc::OpType::AodDeactivate); - } + } + if (canBeDoneInParallel) { + parallelCost -= arch->getVectorShuttlingTime(moveVector); + } + // check if in same row/column like last moves + // then can may be loaded in parallel + const auto moveCoordInit = this->arch->getCoordinate(move.c1); + const auto moveCoordEnd = this->arch->getCoordinate(move.c2); + parallelCost += arch->getShuttlingTime(qc::OpType::AodActivate) + + arch->getShuttlingTime(qc::OpType::AodDeactivate); + for (const auto& lastMove : this->lastMoves) { + const auto lastMoveCoordInit = this->arch->getCoordinate(lastMove.c1); + const auto lastMoveCoordEnd = this->arch->getCoordinate(lastMove.c2); + if (moveCoordInit.x == lastMoveCoordInit.x || + moveCoordInit.y == lastMoveCoordInit.y) { + parallelCost -= arch->getShuttlingTime(qc::OpType::AodActivate); + } + if (moveCoordEnd.x == lastMoveCoordEnd.x || + moveCoordEnd.y == lastMoveCoordEnd.y) { + parallelCost -= arch->getShuttlingTime(qc::OpType::AodDeactivate); } } - return parallelCost / static_cast(this->lastMoves.size()) / - static_cast(this->frontLayerShuttling.size()); + return parallelCost; } MultiQubitMovePos From 5e2cc7012d2783a4470cc6b8f1fd35b6fb3cf0ec Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Wed, 5 Nov 2025 16:40:52 +0100 Subject: [PATCH 200/394] =?UTF-8?q?=E2=9C=85=20updated=20test=20because=20?= =?UTF-8?q?of=20changed=20file=20name?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/hybridmap/test_hybrid_synthesis_map.cpp | 14 ++++++++++++-- test/hybridmap/test_hybridmap.cpp | 5 +++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/test/hybridmap/test_hybrid_synthesis_map.cpp b/test/hybridmap/test_hybrid_synthesis_map.cpp index b2eec0435..b52cbe3f3 100644 --- a/test/hybridmap/test_hybrid_synthesis_map.cpp +++ b/test/hybridmap/test_hybrid_synthesis_map.cpp @@ -1,3 +1,13 @@ +/* + * 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 + */ + // // This file is part of the MQT QMAP library released under the MIT license. // See README.md or go to https://github.com/cda-tum/qmap for more information. @@ -57,13 +67,13 @@ TEST_P(TestParametrizedHybridSynthesisMapper, EvaluateSynthesisStep) { INSTANTIATE_TEST_SUITE_P(HybridSynthesisMapperTestSuite, TestParametrizedHybridSynthesisMapper, - ::testing::Values("rubidium", "rubidium_hybrid", + ::testing::Values("rubidium_gate", "rubidium_hybrid", "rubidium_shuttling")); class TestHybridSynthesisMapper : public ::testing::Test { protected: NeutralAtomArchitecture arch = - NeutralAtomArchitecture("architectures/rubidium.json"); + NeutralAtomArchitecture("architectures/rubidium_gate.json"); HybridSynthesisMapper mapper = HybridSynthesisMapper(arch); qc::QuantumComputation qc; diff --git a/test/hybridmap/test_hybridmap.cpp b/test/hybridmap/test_hybridmap.cpp index 6a2a2e822..a3b529131 100644 --- a/test/hybridmap/test_hybridmap.cpp +++ b/test/hybridmap/test_hybridmap.cpp @@ -54,7 +54,7 @@ TEST_P(NeutralAtomArchitectureTest, LoadArchitectures) { INSTANTIATE_TEST_SUITE_P(NeutralAtomArchitectureTestSuite, NeutralAtomArchitectureTest, - ::testing::Values("rubidium", "rubidium_hybrid", + ::testing::Values("rubidium_gate", "rubidium_hybrid", "rubidium_shuttling")); class NeutralAtomMapperTestParams // parameters are architecture, circuit, gateWeight, shuttlingWeight, @@ -117,7 +117,8 @@ TEST_P(NeutralAtomMapperTestParams, MapCircuitsIdentity) { INSTANTIATE_TEST_SUITE_P( NeutralAtomMapperTestSuite, NeutralAtomMapperTestParams, ::testing::Combine( - ::testing::Values("rubidium", "rubidium_hybrid", "rubidium_shuttling"), + ::testing::Values("rubidium_gate", "rubidium_hybrid", + "rubidium_shuttling"), ::testing::Values("dj_nativegates_rigetti_qiskit_opt3_10", "modulo_2", "multiply_2", "qft_nativegates_rigetti_qiskit_opt3_10", From 47c8c7027997beb56ca713452020d2b3d60bd3d5 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Wed, 5 Nov 2025 16:53:04 +0100 Subject: [PATCH 201/394] =?UTF-8?q?=E2=9C=85=20updated=20tests=20for=20hyb?= =?UTF-8?q?rid=20synthesis=20mapper?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/hybridmap/test_hybrid_synthesis_map.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/hybridmap/test_hybrid_synthesis_map.cpp b/test/hybridmap/test_hybrid_synthesis_map.cpp index b52cbe3f3..30070b15e 100644 --- a/test/hybridmap/test_hybrid_synthesis_map.cpp +++ b/test/hybridmap/test_hybrid_synthesis_map.cpp @@ -57,9 +57,11 @@ TEST_P(TestParametrizedHybridSynthesisMapper, AdjaencyMatrix) { TEST_P(TestParametrizedHybridSynthesisMapper, EvaluateSynthesisStep) { auto arch = NeutralAtomArchitecture(testArchitecturePath); - auto mapper = HybridSynthesisMapper(arch); + auto params = MapperParameters(); + params.verbose = true; + auto mapper = HybridSynthesisMapper(arch, params); mapper.initMapping(3); - auto best = mapper.evaluateSynthesisSteps(circuits, false); + auto best = mapper.evaluateSynthesisSteps(circuits, true); EXPECT_EQ(best.size(), 2); EXPECT_GE(best[0], 0); EXPECT_GE(best[1], 0); From 756ec97aa45516c01588696bd8858ef48b17e661 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Wed, 5 Nov 2025 17:16:43 +0100 Subject: [PATCH 202/394] =?UTF-8?q?=E2=9C=85=20updated=20mapping=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/Mapping.cpp | 14 -------------- test/hybridmap/test_hybridmap.cpp | 6 ++++-- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/src/hybridmap/Mapping.cpp b/src/hybridmap/Mapping.cpp index 3bde71030..a54674fb8 100644 --- a/src/hybridmap/Mapping.cpp +++ b/src/hybridmap/Mapping.cpp @@ -184,20 +184,6 @@ std::vector Mapping::graphMatching() { circGraphQueue.pop(); } - // for debug - for (size_t i = 0; i < dag.size(); ++i) { - if (qubitIndices[i] == std::numeric_limits::max()) { - for (HwQubit hw = 0; hw < hwQubits.getNumQubits(); ++hw) { - if (hwIndices[hw] == std::numeric_limits::max()) { - qubitIndices[i] = hw; - hwIndices[hw] = i; - break; - } - } - } - } - return qubitIndices; } - } // namespace na diff --git a/test/hybridmap/test_hybridmap.cpp b/test/hybridmap/test_hybridmap.cpp index a3b529131..bc5720592 100644 --- a/test/hybridmap/test_hybridmap.cpp +++ b/test/hybridmap/test_hybridmap.cpp @@ -21,6 +21,8 @@ #include #include #include +// additional include for direct Mapping test +#include "hybridmap/Mapping.hpp" class NeutralAtomArchitectureTest : public ::testing::TestWithParam { @@ -156,7 +158,7 @@ class NeutralAtomMapperTest : public ::testing::Test { // mapperParameters.useBridge = true; mapperParameters.usePassBy = true; mapper.setParameters(mapperParameters); - auto qc = qasm3::Importer::importf( + qc = qasm3::Importer::importf( "circuits/dj_nativegates_rigetti_qiskit_opt3_10.qasm"); } }; @@ -164,7 +166,7 @@ class NeutralAtomMapperTest : public ::testing::Test { TEST_F(NeutralAtomMapperTest, Output) { setvbuf(stdout, NULL, _IONBF, 0); auto qcMapped = mapper.map(qc, initialMapping); - // qcMapped.dumpOpenQASM(std::cout, false); + qcMapped.dumpOpenQASM(std::cout, false); // write to file std::ofstream ofs("test.qasm"); qcMapped.dumpOpenQASM(ofs, false); From 3c61748cbc6188ff989de68a7ecb033a94a8203d Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Wed, 5 Nov 2025 18:45:07 +0100 Subject: [PATCH 203/394] =?UTF-8?q?=F0=9F=90=9B=20Fixed=20bug=20where=20ex?= =?UTF-8?q?cluded=20free=20coordinates=20are=20returned.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HardwareQubits.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/hybridmap/HardwareQubits.cpp b/src/hybridmap/HardwareQubits.cpp index 288d866b9..3c50cbffa 100644 --- a/src/hybridmap/HardwareQubits.cpp +++ b/src/hybridmap/HardwareQubits.cpp @@ -274,7 +274,16 @@ HardwareQubits::findClosestFreeCoord(CoordIndex coord, } } if (freeCoordsInDirection.empty()) { - freeCoordsInDirection = freeCoordinates; + // return all free coords except excluded + auto allFreeCoords = freeCoordinates; + for (const auto& excludedCoord : excludedCoords) { + if (const auto pos = std::find(allFreeCoords.begin(), allFreeCoords.end(), + excludedCoord); + pos != allFreeCoords.end()) { + allFreeCoords.erase(pos); + } + } + return allFreeCoords; } auto minDistance = std::numeric_limits::max(); CoordIndex minCoord = freeCoordsInDirection.front(); From ca581bc58b8857d6e90dc01ec95419e759013284 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Wed, 5 Nov 2025 18:52:50 +0100 Subject: [PATCH 204/394] =?UTF-8?q?=F0=9F=90=9B=20Fixed=20bug=20multiple?= =?UTF-8?q?=20atoms=20are=20moved=20to=20same=20free=20coord.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridNeutralAtomMapper.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 58a94e8e4..49677cdc2 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -1494,6 +1494,8 @@ MoveCombs NeutralAtomMapper::getMoveCombinationsToPosition( } } + // save coords where atoms have been moved away to + CoordIndices movedAwayCoords = remainingCoords; while (!remainingGateCoords.empty()) { auto currentGateQubit = *remainingGateCoords.begin(); // compute costs and find best coord @@ -1501,7 +1503,7 @@ MoveCombs NeutralAtomMapper::getMoveCombinationsToPosition( for (const auto& remainingCoord : remainingCoords) { if (this->hardwareQubits.isMapped(remainingCoord)) { const auto moveAwayComb = getMoveAwayCombinations( - currentGateQubit, remainingCoord, remainingCoords); + currentGateQubit, remainingCoord, movedAwayCoords); for (const auto& moveAway : moveAwayComb) { auto cost = moveCostComb(moveAway); costs.emplace_back(remainingCoord, cost); @@ -1520,11 +1522,9 @@ MoveCombs NeutralAtomMapper::getMoveCombinationsToPosition( auto bestCoord = bestCost->first; if (this->hardwareQubits.isMapped(bestCoord)) { auto moveAwayComb = - getMoveAwayCombinations(currentGateQubit, bestCoord, remainingCoords); - // for (const auto& moveAway : moveAwayComb) { - // moveComb.append(moveAway); - // } + getMoveAwayCombinations(currentGateQubit, bestCoord, movedAwayCoords); moveComb.append(moveAwayComb.moveCombs[0]); + movedAwayCoords.emplace_back(moveAwayComb.moveCombs[0].moves[0].c2); } else { moveComb.append(AtomMove{currentGateQubit, bestCoord}); } From 8c3cc231817c5f792c9422d2decf53cfa84b2675 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Wed, 5 Nov 2025 19:49:37 +0100 Subject: [PATCH 205/394] =?UTF-8?q?=E2=9C=85=20modified=20architecture=20t?= =?UTF-8?q?o=20make=20it=20use=20flying=20ancillas.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/hybridmap/architectures/rubidium_gate.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/hybridmap/architectures/rubidium_gate.json b/test/hybridmap/architectures/rubidium_gate.json index 60a9de429..4ef84219c 100644 --- a/test/hybridmap/architectures/rubidium_gate.json +++ b/test/hybridmap/architectures/rubidium_gate.json @@ -53,8 +53,8 @@ "shuttlingAverageFidelities": { "move": 1, "aod_move": 1, - "aod_activate": 1, - "aod_deactivate": 1 + "aod_activate": 0.98, + "aod_deactivate": 0.98 } } } From 7d4378d4149dd54febab1afdc08f79499191ae2c Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Wed, 5 Nov 2025 19:50:52 +0100 Subject: [PATCH 206/394] =?UTF-8?q?=F0=9F=90=9B=20Fixed=20missing=20copyin?= =?UTF-8?q?g=20of=20decay=20weights=20for=20the=20Synthesis.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridNeutralAtomMapper.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index 6cc9b3837..95aabfaf4 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -485,6 +485,7 @@ class NeutralAtomMapper { this->lastMoves = mapper.lastMoves; this->lastBlockedQubits = mapper.lastBlockedQubits; this->scheduler = mapper.scheduler; + this->decayWeights = mapper.decayWeights; } /** From 1582b65c51bc286ea7456a4dc1f0ec57ddc590ee Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Wed, 5 Nov 2025 19:51:39 +0100 Subject: [PATCH 207/394] =?UTF-8?q?=F0=9F=90=9B=20deactivate=20most=20rout?= =?UTF-8?q?ing=20methods=20for=20>2=20qubit=20gates.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridNeutralAtomMapper.hpp | 2 ++ src/hybridmap/HybridNeutralAtomMapper.cpp | 13 ++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index 95aabfaf4..a414cb3c3 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -121,6 +121,8 @@ class NeutralAtomMapper { std::deque lastMoves; // Precomputed decay weights std::vector decayWeights; + // indicates if multi-qubit gates are in the circuit + bool multiQubitGates = false; // The current placement of the hardware qubits onto the coordinates HardwareQubits hardwareQubits; diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 49677cdc2..539222993 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -47,6 +47,14 @@ void NeutralAtomMapper::mapAppend(qc::QuantumComputation& qc, throw std::runtime_error( "Not enough qubits in architecture for circuit and flying ancillas"); } + // check if multi-qubit gates are present + for (const auto& op : qc) { + if (op->getUsedQubits().size() > 2) { + // deactivate static mapping + multiQubitGates = true; + break; + } + } mappedQc.addAncillaryRegister(this->arch->getNpositions()); mappedQc.addAncillaryRegister(this->arch->getNpositions(), "fa"); @@ -1755,7 +1763,7 @@ size_t NeutralAtomMapper::gateBasedMapping(NeutralAtomLayer& frontLayer, auto bestSwap = findBestSwap(lastSwap); MappingMethod bestMethod = MappingMethod::SwapMethod; - if (parameters->maxBridgeDistance > 0) { + if (parameters->maxBridgeDistance > 0 && !multiQubitGates) { auto bestBridge = findBestBridge(bestSwap); bestMethod = compareSwapAndBridge(bestSwap, bestBridge); if (bestMethod == MappingMethod::BridgeMethod) { @@ -2211,6 +2219,9 @@ MappingMethod NeutralAtomMapper::compareShuttlingAndFlyingAncilla( if (flyingAncillas.getNumQubits() == 0 && !parameters->usePassBy) { return MappingMethod::MoveMethod; } + if (multiQubitGates) { + return MappingMethod::MoveMethod; + } // move distance reduction auto moveDistReductionf = From 5d03e851c109216fa2128c0816103c1f8bcd7746 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Wed, 5 Nov 2025 19:52:03 +0100 Subject: [PATCH 208/394] =?UTF-8?q?=E2=9C=85=20reduced=20number=20of=20tes?= =?UTF-8?q?ts.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/hybridmap/test_hybridmap.cpp | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/test/hybridmap/test_hybridmap.cpp b/test/hybridmap/test_hybridmap.cpp index bc5720592..36c87330f 100644 --- a/test/hybridmap/test_hybridmap.cpp +++ b/test/hybridmap/test_hybridmap.cpp @@ -60,9 +60,9 @@ INSTANTIATE_TEST_SUITE_P(NeutralAtomArchitectureTestSuite, "rubidium_shuttling")); class NeutralAtomMapperTestParams // parameters are architecture, circuit, gateWeight, shuttlingWeight, - // lookAheadWeight, initialCoordinateMapping + // lookAheadWeight, dynamicMappingWeight, initialCoordinateMapping : public ::testing::TestWithParam< - std::tuple> { protected: std::string testArchitecturePath = "architectures/"; @@ -70,8 +70,9 @@ class NeutralAtomMapperTestParams qc::fp gateWeight = 1; qc::fp shuttlingWeight = 1; qc::fp lookAheadWeight = 1; + qc::fp dynamicMappingWeight = 2; na::InitialCoordinateMapping initialCoordinateMapping = - na::InitialCoordinateMapping::Trivial; + na::InitialCoordinateMapping::Random; // fixed qc::fp decay = 0.1; qc::fp shuttlingTimeWeight = 0.1; @@ -84,7 +85,8 @@ class NeutralAtomMapperTestParams gateWeight = std::get<2>(params); shuttlingWeight = std::get<3>(params); lookAheadWeight = std::get<4>(params); - initialCoordinateMapping = std::get<5>(params); + dynamicMappingWeight = std::get<5>(params); + initialCoordinateMapping = std::get<6>(params); } }; @@ -100,13 +102,16 @@ TEST_P(NeutralAtomMapperTestParams, MapCircuitsIdentity) { mapperParameters.shuttlingTimeWeight = shuttlingTimeWeight; mapperParameters.gateWeight = gateWeight; mapperParameters.shuttlingWeight = shuttlingWeight; + mapperParameters.dynamicMappingWeight = dynamicMappingWeight; mapperParameters.seed = seed; mapperParameters.verbose = true; + mapperParameters.maxBridgeDistance = 2; + mapperParameters.numFlyingAncillas = 1; mapper.setParameters(mapperParameters); auto qc = qasm3::Importer::importf(testQcPath); const auto qcMapped = mapper.map(qc, initialMapping); - ASSERT_GT(qcMapped.size(), qc.size()); + ASSERT_GE(qcMapped.size(), qc.size()); mapper.convertToAod(); const auto scheduleResults = mapper.schedule(true, true); @@ -119,16 +124,14 @@ TEST_P(NeutralAtomMapperTestParams, MapCircuitsIdentity) { INSTANTIATE_TEST_SUITE_P( NeutralAtomMapperTestSuite, NeutralAtomMapperTestParams, ::testing::Combine( - ::testing::Values("rubidium_gate", "rubidium_hybrid", - "rubidium_shuttling"), + ::testing::Values("rubidium_gate", "rubidium_hybrid"), ::testing::Values("dj_nativegates_rigetti_qiskit_opt3_10", "modulo_2", "multiply_2", "qft_nativegates_rigetti_qiskit_opt3_10", "random_nativegates_rigetti_qiskit_opt3_10"), ::testing::Values(1, 0.), ::testing::Values(1, 0.), - ::testing::Values(0, 0.1), - ::testing::Values(na::InitialCoordinateMapping::Trivial, - na::InitialCoordinateMapping::Random))); + ::testing::Values(0.1), ::testing::Values(0), + ::testing::Values(na::InitialCoordinateMapping::Trivial))); class NeutralAtomMapperTest : public ::testing::Test { protected: From 7ee6d123a53bfd9e71a7097ffd28152058878fca Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Wed, 5 Nov 2025 19:56:56 +0100 Subject: [PATCH 209/394] =?UTF-8?q?=F0=9F=90=9B=20Remove=20unused=20findCl?= =?UTF-8?q?osestAncillaCoord=20method=20and=20related=20references.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HardwareQubits.hpp | 5 --- include/hybridmap/HybridNeutralAtomMapper.hpp | 4 -- src/hybridmap/HardwareQubits.cpp | 39 ------------------- src/hybridmap/HybridNeutralAtomMapper.cpp | 10 ----- 4 files changed, 58 deletions(-) diff --git a/include/hybridmap/HardwareQubits.hpp b/include/hybridmap/HardwareQubits.hpp index 077a63938..c41cdb59e 100644 --- a/include/hybridmap/HardwareQubits.hpp +++ b/include/hybridmap/HardwareQubits.hpp @@ -334,11 +334,6 @@ class HardwareQubits { findClosestFreeCoord(HwQubit coord, Direction direction, const CoordIndices& excludedCoords = {}) const; - [[nodiscard]] std::vector - findClosestAncillaCoord(CoordIndex coord, Direction direction, - int circQubitSize, - const CoordIndices& excludedCoords = {}) const; - [[nodiscard]] HwQubit getClosestQubit(CoordIndex coord, HwQubits ignored) const; diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index a414cb3c3..a5deb7eac 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -307,10 +307,6 @@ class NeutralAtomMapper { std::vector findBestFlyingAncillaComb(const qc::Operation* targetOp); std::set> findQtargetSet(std::set& usedQubits); - [[nodiscard]] CoordIndex - returnClosestAncillaCoord(const CoordIndex& cTarget, - const CoordIndices& excludeCoords, - const qc::QuantumComputation& qc) const; [[nodiscard]] MappingMethod compareSwapAndBridge(const Swap& bestSwap, const Bridge& bestBridge); [[nodiscard]] MappingMethod diff --git a/src/hybridmap/HardwareQubits.cpp b/src/hybridmap/HardwareQubits.cpp index 3c50cbffa..ae0775fd3 100644 --- a/src/hybridmap/HardwareQubits.cpp +++ b/src/hybridmap/HardwareQubits.cpp @@ -297,45 +297,6 @@ HardwareQubits::findClosestFreeCoord(CoordIndex coord, return {minCoord}; } -std::vector HardwareQubits::findClosestAncillaCoord( - const CoordIndex coord, const Direction direction, const int circQubitSize, - const CoordIndices& excludedCoords) const { - // return the closest ancilla coord in general - // and the closest free ancilla in the given direction - std::vector closestFreeCoords; - std::queue queue; - queue.push(coord); - std::set visited; - visited.insert(coord); - bool foundClosest = false; - while (!queue.empty()) { - const auto currentCoord = queue.front(); - queue.pop(); - auto nearbyCoords = this->arch->getNN(currentCoord); - for (const auto& nearbyCoord : nearbyCoords) { - if (std::ranges::find(std::ranges::reverse_view(visited), nearbyCoord) == - visited.rend()) { - visited.insert(nearbyCoord); - if (this->isMapped(nearbyCoord) && - this->getHwQubit(nearbyCoord) >= circQubitSize && - std::find(excludedCoords.begin(), excludedCoords.end(), - nearbyCoord) == excludedCoords.end()) { - if (!foundClosest) { - closestFreeCoords.push_back(nearbyCoord); - } - foundClosest = true; - if (direction == arch->getVector(coord, nearbyCoord).direction) { - closestFreeCoords.emplace_back(nearbyCoord); - return closestFreeCoords; - } - } else { - queue.push(nearbyCoord); - } - } - } - } - return closestFreeCoords; -} HwQubit HardwareQubits::getClosestQubit(const CoordIndex coord, HwQubits ignored) const { HwQubit closestQubit = 0; diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 539222993..465ccd072 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -2166,16 +2166,6 @@ NeutralAtomMapper::findQtargetSet(std::set& usedQubits) { return qTargetSet; } -CoordIndex NeutralAtomMapper::returnClosestAncillaCoord( - const CoordIndex& cTarget, const CoordIndices& excludeCoords, - const qc::QuantumComputation& qc) const { - auto const originalVector = this->arch->getVector( - cTarget + arch->getNcolumns(), cTarget); // startCoord, targetCoord - auto const originalDirection = originalVector.direction; - const auto ancillaTargets = this->hardwareQubits.findClosestAncillaCoord( - cTarget, originalDirection, qc.getNqubits(), excludeCoords); - return ancillaTargets[0]; -} MappingMethod NeutralAtomMapper::compareSwapAndBridge(const Swap& bestSwap, const Bridge& bestBridge) { From 750da0ac891f2c1eca3c66c2714e7d8f1dbfe437 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Wed, 5 Nov 2025 19:59:38 +0100 Subject: [PATCH 210/394] =?UTF-8?q?=F0=9F=94=A5=20Remove=20unused=20findQt?= =?UTF-8?q?argetSet=20method=20and=20related=20references.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridNeutralAtomMapper.hpp | 1 - src/hybridmap/HybridNeutralAtomMapper.cpp | 54 ------------------- 2 files changed, 55 deletions(-) diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index a5deb7eac..bc185d7d4 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -306,7 +306,6 @@ class NeutralAtomMapper { // Methods for flying ancilla operations mapping std::vector findBestFlyingAncillaComb(const qc::Operation* targetOp); - std::set> findQtargetSet(std::set& usedQubits); [[nodiscard]] MappingMethod compareSwapAndBridge(const Swap& bestSwap, const Bridge& bestBridge); [[nodiscard]] MappingMethod diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 465ccd072..8eac27094 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -2112,60 +2112,6 @@ size_t NeutralAtomMapper::gateBasedMapping(NeutralAtomLayer& frontLayer, // } // } -std::set> -NeutralAtomMapper::findQtargetSet(std::set& usedQubits) { - std::set> qTargetSet; - const auto numUsedQubits = usedQubits.size(); - qc::SymmetricMatrix gateQubitDistances(numUsedQubits); - for (uint32_t i = 0; i < numUsedQubits; ++i) { - for (uint32_t j = 0; j <= i; ++j) { - if (i == j) { - gateQubitDistances(i, j) = 0; - } - const qc::Qubit qi = *(std::next(usedQubits.begin(), i)); - const qc::Qubit qj = *(std::next(usedQubits.begin(), j)); - gateQubitDistances(i, j) = this->hardwareQubits.getSwapDistance( - this->mapping.getHwQubit(qi), this->mapping.getHwQubit(qj)); - } - } - - size_t maxSize = 0; - for (int i = 0; i < numUsedQubits; ++i) { - std::vector currentVec; - const qc::Qubit qi = *(std::next(usedQubits.begin(), i)); - currentVec.push_back(qi); - for (int j = 0; j < numUsedQubits; ++j) { - if (i != j) { - const qc::Qubit qj = *(std::next(usedQubits.begin(), j)); - bool isInteractable = true; - for (auto& q : currentVec) { - auto it = usedQubits.find(q); - uint32_t idx = 0; - if (it != usedQubits.end()) { - idx = std::distance(usedQubits.begin(), it); - } - if (gateQubitDistances(idx, j) != 0) { - isInteractable = false; - break; - } - } - if (isInteractable) { - currentVec.push_back(qj); - } - } - } - if (const std::set currentSet(currentVec.begin(), currentVec.end()); - currentSet.size() > maxSize) { - maxSize = currentSet.size(); - qTargetSet.clear(); - qTargetSet.insert(currentSet); - } else if (currentSet.size() == maxSize) { - qTargetSet.insert(currentSet); - } - } - return qTargetSet; -} - MappingMethod NeutralAtomMapper::compareSwapAndBridge(const Swap& bestSwap, const Bridge& bestBridge) { From 05e3c0316b94ca66a1f44f1afabc88b471871bfb Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 6 Nov 2025 09:02:58 +0100 Subject: [PATCH 211/394] =?UTF-8?q?=E2=9C=85=20added=20HardwareQubit=20tes?= =?UTF-8?q?t=20and=20removed=20some=20not=20used=20lines.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HardwareQubits.hpp | 17 ----- test/hybridmap/test_hardware_qubits.cpp | 86 +++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 17 deletions(-) create mode 100644 test/hybridmap/test_hardware_qubits.cpp diff --git a/include/hybridmap/HardwareQubits.hpp b/include/hybridmap/HardwareQubits.hpp index c41cdb59e..3182aeb1d 100644 --- a/include/hybridmap/HardwareQubits.hpp +++ b/include/hybridmap/HardwareQubits.hpp @@ -137,11 +137,6 @@ class HardwareQubits { [[nodiscard]] std::vector computeAllShortestPaths(HwQubit q1, HwQubit q2) const; - // Mapping - [[nodiscard]] const qc::Permutation& getHwToCoordIdx() const { - return hwToCoordIdx; - } - [[nodiscard]] CoordIndex getNumQubits() const { return nQubits; } /** @@ -241,18 +236,6 @@ class HardwareQubits { std::to_string(coordIndex)); } - // Forwards from architecture class - - /** - * @brief Returns the nearby coordinates of a hardware qubit. - * @param q The hardware qubit. - * @return The nearby coordinates of the hardware qubit. - */ - [[nodiscard]] [[maybe_unused]] std::set - getNearbyCoordinates(const HwQubit q) const { - return this->arch->getNearbyCoordinates(this->getCoordIndex(q)); - } - // Swap Distances and Nearby qc::Qubits /** diff --git a/test/hybridmap/test_hardware_qubits.cpp b/test/hybridmap/test_hardware_qubits.cpp new file mode 100644 index 000000000..3098efc3a --- /dev/null +++ b/test/hybridmap/test_hardware_qubits.cpp @@ -0,0 +1,86 @@ +/* + * 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 + */ + +#include "hybridmap/HardwareQubits.hpp" +#include "hybridmap/NeutralAtomArchitecture.hpp" + +#include + +namespace { + +TEST(HardwareQubitsExceptions, AccessEmptyCoordThrows) { + const auto arch = + na::NeutralAtomArchitecture("architectures/rubidium_shuttling.json"); + na::HardwareQubits const hw(arch, 2, na::InitialCoordinateMapping::Trivial, + 0); + + constexpr auto emptyCoord = static_cast(3); + EXPECT_THROW(auto result = hw.getHwQubit(emptyCoord), std::runtime_error); +} + +TEST(HardwareQubitsExceptions, MoveInvalidCoordinateThrows) { + const auto arch = + na::NeutralAtomArchitecture("architectures/rubidium_shuttling.json"); + na::HardwareQubits hw(arch, /*nQubits*/ 2, + na::InitialCoordinateMapping::Trivial, /*seed*/ 0); + + // Coordinate equal to Npositions is out of range + const auto invalidCoord = arch.getNpositions(); + EXPECT_THROW(hw.move(/*hwQubit*/ 0, invalidCoord), std::runtime_error); +} + +TEST(HardwareQubitsExceptions, MoveToOccupiedCoordinateThrows) { + const auto arch = + na::NeutralAtomArchitecture("architectures/rubidium_shuttling.json"); + na::HardwareQubits hw(arch, /*nQubits*/ 2, + na::InitialCoordinateMapping::Trivial, /*seed*/ 0); + + const auto occupied = hw.getCoordIndex(/*hwQubit*/ 1); + EXPECT_THROW(hw.move(/*hwQubit*/ 0, occupied), std::runtime_error); +} + +TEST(HardwareQubitsBehavior, RemoveHwQubitRemovesMappingsAndNeighbors) { + const auto arch = + na::NeutralAtomArchitecture("architectures/rubidium_shuttling.json"); + na::HardwareQubits hw(arch, /*nQubits*/ 3, + na::InitialCoordinateMapping::Trivial, /*seed*/ 0); + + // Remove middle qubit (1) and verify it is no longer addressable + hw.removeHwQubit(/*hwQubit*/ 1); + + // getCoordIndex should throw since the qubit was erased from the mapping + EXPECT_THROW((void)hw.getCoordIndex(1), std::out_of_range); + + // Remaining qubits neighbor lists should not contain the removed qubit + for (na::HwQubit q : {0U, 2U}) { + const auto neighbors = hw.getNearbyQubits(q); + EXPECT_TRUE(!neighbors.contains(1U)); + } +} + +TEST(HardwareQubitsBehavior, RandomInitializationIsDeterministicPerSeed) { + const auto arch = + na::NeutralAtomArchitecture("architectures/rubidium_shuttling.json"); + constexpr na::CoordIndex nQ = 4; + + na::HardwareQubits hw(arch, nQ, na::InitialCoordinateMapping::Random, + /*seed*/ 0); + + // All assigned coordinates are unique and within bounds + std::set coords; + for (na::HwQubit q = 0; q < nQ; ++q) { + const auto c = hw.getCoordIndex(q); + EXPECT_LT(c, arch.getNpositions()); + coords.insert(c); + } + EXPECT_EQ(coords.size(), static_cast(nQ)); +} + +} // namespace From fd9a2257f42e28b6f5393ae74b661d8966b58f75 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 6 Nov 2025 09:12:15 +0100 Subject: [PATCH 212/394] =?UTF-8?q?=E2=9C=85=20added=20Mapping=20tests.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/hybridmap/test_mapping.cpp | 67 +++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 test/hybridmap/test_mapping.cpp diff --git a/test/hybridmap/test_mapping.cpp b/test/hybridmap/test_mapping.cpp new file mode 100644 index 000000000..ffef0161e --- /dev/null +++ b/test/hybridmap/test_mapping.cpp @@ -0,0 +1,67 @@ +/* + * 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 + */ + +#include "hybridmap/Mapping.hpp" +#include "hybridmap/NeutralAtomArchitecture.hpp" + +#include + +// These tests focus only on exception behavior in Mapping (mapping.cpp/.hpp). + +TEST(MappingExceptions, CircuitExceedsHardwareThrows) { + const auto arch = + na::NeutralAtomArchitecture("architectures/rubidium_shuttling.json"); + // Hardware has 1 available logical qubit, circuit needs 2 + na::HardwareQubits hw(arch, /*nQubits*/ 1, + na::InitialCoordinateMapping::Trivial, /*seed*/ 0); + qc::QuantumComputation qc(2); + + EXPECT_THROW((void)na::Mapping(2, na::InitialMapping::Identity, qc, hw), + std::runtime_error); +} + +TEST(MappingExceptions, GetCircQubitThrowsIfHardwareNotMapped) { + const auto arch = + na::NeutralAtomArchitecture("architectures/rubidium_shuttling.json"); + // Hardware has 4 logical spots, but circuit uses only 2 (identity mapping) + na::HardwareQubits hw(arch, /*nQubits*/ 4, + na::InitialCoordinateMapping::Trivial, /*seed*/ 0); + qc::QuantumComputation qc(2); + na::Mapping m(2, na::InitialMapping::Identity, qc, hw); + + // hw qubits 0 and 1 are mapped; 2 and 3 are not -> getCircQubit(2) should + // throw + EXPECT_THROW((void)m.getCircQubit(2), std::runtime_error); +} + +TEST(MappingExceptions, ApplySwapThrowsIfBothHardwareQubitsUnmapped) { + const auto arch = + na::NeutralAtomArchitecture("architectures/rubidium_shuttling.json"); + // Hardware has 4, circuit maps only 2 via identity + na::HardwareQubits hw(arch, /*nQubits*/ 4, + na::InitialCoordinateMapping::Trivial, /*seed*/ 0); + qc::QuantumComputation qc(2); + na::Mapping m(2, na::InitialMapping::Identity, qc, hw); + + // Swap two unmapped hardware qubits (2 and 3) -> should throw + EXPECT_THROW(m.applySwap({2, 3}), std::runtime_error); +} + +TEST(MappingExceptions, GetHwQubitThrowsOutOfRangeForInvalidCircuitIndex) { + const auto arch = + na::NeutralAtomArchitecture("architectures/rubidium_shuttling.json"); + na::HardwareQubits hw(arch, /*nQubits*/ 2, + na::InitialCoordinateMapping::Trivial, /*seed*/ 0); + qc::QuantumComputation qc(2); + na::Mapping m(2, na::InitialMapping::Identity, qc, hw); + + // Access circuit qubit index outside [0, nQubits) -> std::out_of_range + EXPECT_THROW((void)m.getHwQubit(2), std::out_of_range); +} From ffce9c3dcab077ef000111e169127600c67b353f Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 6 Nov 2025 09:31:42 +0100 Subject: [PATCH 213/394] =?UTF-8?q?=E2=9C=85=20added=20Utils=20tests.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/NeutralAtomUtils.hpp | 36 -------- test/hybridmap/test_utils.cpp | 118 +++++++++++++++++++++++++ 2 files changed, 118 insertions(+), 36 deletions(-) create mode 100644 test/hybridmap/test_utils.cpp diff --git a/include/hybridmap/NeutralAtomUtils.hpp b/include/hybridmap/NeutralAtomUtils.hpp index 23c8d91ff..2e35f26bd 100644 --- a/include/hybridmap/NeutralAtomUtils.hpp +++ b/include/hybridmap/NeutralAtomUtils.hpp @@ -27,19 +27,6 @@ namespace na { -class NeutralAtomException final : public std::runtime_error { - std::string msg; - -public: - explicit NeutralAtomException(std::string m) - : std::runtime_error("Neutral Atom Mapper Exception"), msg(std::move(m)) { - } - - [[nodiscard]] const char* what() const noexcept override { - return msg.c_str(); - } -}; - // Enums for the different initial mappings strategies enum InitialCoordinateMapping : uint8_t { Trivial, Random }; enum InitialMapping : uint8_t { Identity, Graph }; @@ -85,8 +72,6 @@ struct Direction { bool x; bool y; - [[maybe_unused]] Direction(const bool xDir, const bool yDir) - : x(xDir), y(yDir) {} Direction(const qc::fp deltaX, const qc::fp deltaY) : x(deltaX >= 0), y(deltaY >= 0) {} @@ -119,13 +104,6 @@ struct MoveVector { const qc::fp yEnd) : xStart(xStart), yStart(yStart), xEnd(xEnd), yEnd(yEnd), direction(xEnd - xStart, yEnd - yStart) {} - MoveVector(const std::int64_t xStart, const std::int64_t yStart, - const std::int64_t xEnd, const std::int64_t yEnd) - : xStart(static_cast(xStart)), - yStart(static_cast(yStart)), xEnd(static_cast(xEnd)), - yEnd(static_cast(yEnd)), - direction(static_cast(xEnd - xStart), - static_cast(yEnd - yStart)) {} [[nodiscard]] [[maybe_unused]] bool sameDirection(const MoveVector& other) const { @@ -177,20 +155,6 @@ struct MoveComb { explicit MoveComb(std::vector mov) : moves(std::move(mov)) {} explicit MoveComb(AtomMove mov) : moves({std::move(mov)}) {} - /** - * @brief Get the first move of the combination - * @return The first move of the combination - */ - [[nodiscard]] AtomMove getFirstMove() const { return moves.front(); } - - /** - * @brief Get the last move of the combination - * @return The last move of the combination - */ - [[nodiscard]] [[maybe_unused]] AtomMove getLastMove() const { - return moves.back(); - } - // implement == operator for AtomMove [[nodiscard]] bool operator==(const MoveComb& other) const { return moves == other.moves; diff --git a/test/hybridmap/test_utils.cpp b/test/hybridmap/test_utils.cpp new file mode 100644 index 000000000..d74efa65a --- /dev/null +++ b/test/hybridmap/test_utils.cpp @@ -0,0 +1,118 @@ +/* + * 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 + */ + +#include "hybridmap/NeutralAtomDefinitions.hpp" +#include "hybridmap/NeutralAtomUtils.hpp" + +#include + +using namespace na; + +TEST(NeutralAtomUtils, InitialCoordinateMappingFromStringTest) { + EXPECT_EQ(initialCoordinateMappingFromString("trivial"), + InitialCoordinateMapping::Trivial); + EXPECT_EQ(initialCoordinateMappingFromString("0"), + InitialCoordinateMapping::Trivial); + EXPECT_EQ(initialCoordinateMappingFromString("random"), + InitialCoordinateMapping::Random); + EXPECT_EQ(initialCoordinateMappingFromString("1"), + InitialCoordinateMapping::Random); +} + +TEST(NeutralAtomUtils, InitialCoordinateMappingFromStringThrow) { + EXPECT_THROW((void)initialCoordinateMappingFromString("foobar"), + std::invalid_argument); +} + +TEST(NeutralAtomUtils, InitialMappingFromString) { + EXPECT_EQ(initialMappingFromString("identity"), InitialMapping::Identity); + EXPECT_EQ(initialMappingFromString("0"), InitialMapping::Identity); + EXPECT_EQ(initialMappingFromString("graph"), InitialMapping::Graph); + EXPECT_EQ(initialMappingFromString("1"), InitialMapping::Graph); +} + +TEST(NeutralAtomUtils, InitialMappingFromStringThrow) { + EXPECT_THROW((void)initialMappingFromString("baz"), std::invalid_argument); +} + +TEST(NeutralAtomUtils, MoveCombConstructorsAndEquality) { + constexpr AtomMove m1{.c1 = 1, .c2 = 2, .load1 = true, .load2 = false}; + constexpr AtomMove m2{.c1 = 3, .c2 = 4, .load1 = false, .load2 = true}; + const CoordIndices pos{5, 6}; + + // vector-based constructor + const MoveComb cvec({m1, m2}, /*cost*/ 1.23, /*op*/ nullptr, pos); + EXPECT_FALSE(cvec.empty()); + EXPECT_EQ(cvec.moves.size(), 2u); + EXPECT_DOUBLE_EQ(cvec.cost, 1.23); + EXPECT_EQ(cvec.op, nullptr); + EXPECT_EQ(cvec.bestPos, pos); + + // single-move constructor + const MoveComb cone(m1, /*cost*/ 0.5, /*op*/ nullptr, CoordIndices{7}); + EXPECT_FALSE(cone.empty()); + EXPECT_EQ(cone.moves.size(), 1U); + EXPECT_DOUBLE_EQ(cone.cost, 0.5); + EXPECT_EQ(cone.op, nullptr); + EXPECT_EQ(cone.bestPos, (CoordIndices{7})); + + // equality compares only the moves vector (per operator== definition) + const MoveComb cvecSameMoves( + {m1, m2}, /*cost*/ 9.99, + /*op*/ reinterpret_cast(0x1), CoordIndices{42}); + EXPECT_TRUE(cvec == cvecSameMoves); + EXPECT_FALSE(cvec != cvecSameMoves); + + const MoveComb cvecDiffMoves({m2, m1}, /*cost*/ 1.23, /*op*/ nullptr, pos); + EXPECT_FALSE(cvec == cvecDiffMoves); + EXPECT_TRUE(cvec != cvecDiffMoves); +} + +TEST(NeutralAtomUtils, MoveCombEmpty) { + const MoveComb emptyComb; + EXPECT_TRUE(emptyComb.empty()); +} + +TEST(NeutralAtomUtils, MoveVectorLengthAndDirection) { + // 3-4-5 triangle + const MoveVector mv(0, 0, 3, 4); + EXPECT_DOUBLE_EQ(mv.getLength(), 5.0); + // Direction should be positive in both axes + EXPECT_TRUE(mv.direction.x); + EXPECT_TRUE(mv.direction.y); +} + +TEST(NeutralAtomUtils, MoveVectorOverlapAndInclude) { + // Overlap: same row (y=0), x-ranges sharing points + const MoveVector a(0, 0, 5, 0); + const MoveVector b(3, 0, 10, 0); + EXPECT_TRUE(a.overlap(b)); + EXPECT_TRUE(b.overlap(a)); + EXPECT_TRUE(a.sameDirection(b)); + EXPECT_TRUE(b.sameDirection(a)); + + // No overlap in either X or Y ranges -> should be false + const MoveVector c(0, 0, 0, 2); // vertical at x=0, y in [0,2] + const MoveVector d(10, 5, 12, 5); // horizontal at y=5, x in [10,12] + EXPECT_FALSE(c.overlap(d)); + EXPECT_FALSE(d.overlap(c)); + + // Include + const MoveVector inner(2, 0, 4, 0); + const MoveVector outer(1, 0, 5, 0); + EXPECT_TRUE(inner.include(outer)); + EXPECT_FALSE(outer.include(inner)); + + // Non-include: disjoint segments + const MoveVector e(0, 0, 2, 0); + const MoveVector f(3, 0, 5, 0); + EXPECT_FALSE(e.include(f)); + EXPECT_FALSE(f.include(e)); +} From 97be84db56a6210f60d734c9ba52962a04378fc0 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 6 Nov 2025 10:23:38 +0100 Subject: [PATCH 214/394] =?UTF-8?q?=E2=9C=85=20added=20architecture=20test?= =?UTF-8?q?s.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridSynthesisMapper.hpp | 8 - include/hybridmap/NeutralAtomArchitecture.hpp | 22 --- src/hybridmap/NeutralAtomArchitecture.cpp | 2 +- test/hybridmap/test_architecture.cpp | 157 ++++++++++++++++++ 4 files changed, 158 insertions(+), 31 deletions(-) create mode 100644 test/hybridmap/test_architecture.cpp diff --git a/include/hybridmap/HybridSynthesisMapper.hpp b/include/hybridmap/HybridSynthesisMapper.hpp index 869b21f80..790b83cc4 100644 --- a/include/hybridmap/HybridSynthesisMapper.hpp +++ b/include/hybridmap/HybridSynthesisMapper.hpp @@ -148,13 +148,5 @@ class HybridSynthesisMapper : public NeutralAtomMapper { * @return The current adjacency matrix of the neutral atom hardware. */ [[nodiscard]] AdjacencyMatrix getCircuitAdjacencyMatrix() const; - - /** - * @brief Returns the maximum gate size of the neutral atom hardware. - * @return The maximum gate size of the neutral atom hardware. - */ - [[nodiscard]] size_t getMaxGateSize() const { - return this->arch->getMaxGateSize(); - } }; } // namespace na diff --git a/include/hybridmap/NeutralAtomArchitecture.hpp b/include/hybridmap/NeutralAtomArchitecture.hpp index aefebd4f3..8b83afe00 100644 --- a/include/hybridmap/NeutralAtomArchitecture.hpp +++ b/include/hybridmap/NeutralAtomArchitecture.hpp @@ -505,28 +505,6 @@ class NeutralAtomArchitecture { */ [[nodiscard]] std::vector getNN(CoordIndex idx) const; - /** - * @brief Get the maximum gate size for multi-qubit size. Gets derived - * from the interaction radius. - * @return The maximum gate size for multi-qubit size - */ - [[nodiscard]] size_t getMaxGateSize() const { - size_t maxGateSize = 0; - const auto intRad = getInteractionRadius(); - const auto xMax = static_cast(intRad); - auto y = static_cast(intRad); - size_t x = 0; - while (x <= xMax) { - if (static_cast((x * x) + (y * y)) > intRad * intRad) { - y--; - } else { - maxGateSize += y + 1; - x++; - } - } - return maxGateSize; - } - // MoveVector functions /** * @brief Get the MoveVector between two coordinate indices diff --git a/src/hybridmap/NeutralAtomArchitecture.cpp b/src/hybridmap/NeutralAtomArchitecture.cpp index b0a34f19c..dc014b9b6 100644 --- a/src/hybridmap/NeutralAtomArchitecture.cpp +++ b/src/hybridmap/NeutralAtomArchitecture.cpp @@ -308,7 +308,7 @@ qc::fp NeutralAtomArchitecture::getOpTime(const qc::Operation* op) const { for (size_t i = 0; i < op->getNqubits() - 1; ++i) { opName += "c"; } - if (op->getType() == qc::OpType::P) { + if (op->getType() == qc::OpType::P || op->getType() == qc::OpType::RZ) { // use time of theta = pi and linearly scale opName += "z"; auto param = abs(op->getParameter().back()); diff --git a/test/hybridmap/test_architecture.cpp b/test/hybridmap/test_architecture.cpp new file mode 100644 index 000000000..2a8f252ef --- /dev/null +++ b/test/hybridmap/test_architecture.cpp @@ -0,0 +1,157 @@ +/* + * 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 + */ + +#include "hybridmap/NeutralAtomArchitecture.hpp" +#include "hybridmap/NeutralAtomDefinitions.hpp" + +#include +#include +#include +#include +#include +#include +#include + +using namespace na; + +TEST(NeutralAtomArchitectureMethods, GetNNTest) { + const auto arch = + NeutralAtomArchitecture("architectures/rubidium_shuttling.json"); + const auto cols = arch.getNcolumns(); + const auto rows = arch.getNrows(); + + // Top-left corner (0): neighbors are right (1) and down (cols) when available + const auto nn0 = arch.getNN(0); + if (cols > 1) { + EXPECT_NE(std::ranges::find(nn0, static_cast(1)), nn0.end()); + } + if (rows > 1) { + EXPECT_NE(std::ranges::find(nn0, cols), nn0.end()); + } + // No negative indices + EXPECT_EQ(std::ranges::find(nn0, static_cast(-1)), nn0.end()); +} + +TEST(NeutralAtomArchitectureMethods, GetIndexRoundTrip) { + const auto arch = + NeutralAtomArchitecture("architectures/rubidium_shuttling.json"); + // Round-trip index -> coordinate -> index + const CoordIndex idx = 3 < arch.getNpositions() ? 3 : 0; + const auto coord = arch.getCoordinate(idx); + const auto idxBack = arch.getIndex(coord); + EXPECT_EQ(idxBack, idx); +} + +TEST(NeutralAtomArchitectureMethods, AnimationAPIsProduceContent) { + const auto arch = + NeutralAtomArchitecture("architectures/rubidium_shuttling.json"); + + // getAnimationMachine should return a non-empty string with known sections + const auto machine = arch.getAnimationMachine(1.0); + EXPECT_FALSE(machine.empty()); + EXPECT_NE(machine.find("movement"), std::string::npos); + EXPECT_NE(machine.find("time"), std::string::npos); + EXPECT_NE(machine.find("zone hybrid"), std::string::npos); + EXPECT_NE(machine.find("trap"), std::string::npos); + + const auto invalidStyle = arch.getAnimationStyle("invalid_file_path.style"); + EXPECT_FALSE(invalidStyle.empty()); + const auto style = arch.getAnimationStyle(""); + EXPECT_GT(style.size(), 0U); + + // Save to temp files + const std::filesystem::path tmpMachine = + std::filesystem::temp_directory_path() / "arch_anim_machine.csv"; + const std::filesystem::path tmpStyle = + std::filesystem::temp_directory_path() / "arch_anim_style.css"; + arch.saveAnimationMachine(tmpMachine.string(), 1.0); + arch.saveAnimationStyle(tmpStyle.string()); + // Verify files exist and are non-empty + ASSERT_TRUE(std::filesystem::exists(tmpMachine)); + ASSERT_TRUE(std::filesystem::exists(tmpStyle)); + EXPECT_GT(std::filesystem::file_size(tmpMachine), 0U); + EXPECT_GT(std::filesystem::file_size(tmpStyle), 0U); + // Cleanup best-effort + std::error_code ec; + std::filesystem::remove(tmpMachine, ec); + std::filesystem::remove(tmpStyle, ec); +} + +TEST(NeutralAtomArchitectureMethods, BasicCountsAndOffsetDistance) { + const auto arch = + NeutralAtomArchitecture("architectures/rubidium_shuttling.json"); + // Simple sanity for counts + EXPECT_GT(arch.getNAods(), 0); + EXPECT_GT(arch.getNAodCoordinates(), 0); + + // offset distance * levels ~= inter-qubit distance + const auto off = arch.getOffsetDistance(); + const auto levels = arch.getNAodIntermediateLevels(); + EXPECT_GT(levels, 0); + EXPECT_NEAR(off * levels, arch.getInterQubitDistance(), 1e-9); +} + +TEST(NeutralAtomArchitectureExceptions, NonexistentFileThrows) { + EXPECT_THROW((void)NeutralAtomArchitecture( + "architectures/this_file_does_not_exist.json"), + std::runtime_error); +} + +TEST(NeutralAtomArchitectureExceptions, ParseInvalidJsonThrows) { + const auto tmp = std::filesystem::temp_directory_path() / "invalid_arch.json"; + { + std::ofstream ofs(tmp); + ofs << "{ invalid json }"; + } + EXPECT_THROW((void)NeutralAtomArchitecture(tmp.string()), std::runtime_error); + std::error_code ec; + std::filesystem::remove(tmp, ec); +} + +TEST(NeutralAtomArchitectureExceptions, TooManyQubitsThrows) { + const auto tmp = + std::filesystem::temp_directory_path() / "too_many_qubits.json"; + // Minimal JSON: 1x1 positions but nQubits = 2 -> should throw + const char* content = R"JSON({ + "name": "test", + "properties": { + "nRows": 1, + "nColumns": 1, + "nAods": 1, + "nAodCoordinates": 1, + "interQubitDistance": 1.0, + "interactionRadius": 1.0, + "blockingFactor": 1.0, + "minimalAodDistance": 1.0 + }, + "parameters": { + "nQubits": 2 + } + })JSON"; + { + std::ofstream ofs(tmp); + ofs << content; + } + EXPECT_THROW((void)NeutralAtomArchitecture(tmp.string()), std::runtime_error); + std::error_code ec; + std::filesystem::remove(tmp, ec); +} + +TEST(NeutralAtomArchitectureExceptions, GetGateTimeFallbackToNone) { + const auto arch = + NeutralAtomArchitecture("architectures/rubidium_shuttling.json"); + const auto fallback = arch.getGateTime("none"); + const auto missing = arch.getGateTime("this_gate_name_should_not_exist"); + EXPECT_DOUBLE_EQ(missing, fallback); + const auto fallbackFid = arch.getGateAverageFidelity("none"); + const auto missingFid = + arch.getGateAverageFidelity("this_gate_name_should_not_exist"); + EXPECT_DOUBLE_EQ(missingFid, fallbackFid); +} From 50301c652227c6982285d99c417c16f4bbc751f2 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 6 Nov 2025 10:27:16 +0100 Subject: [PATCH 215/394] =?UTF-8?q?=F0=9F=94=A5=20removed=20unused=20Layer?= =?UTF-8?q?=20code.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/NeutralAtomLayer.hpp | 11 ----------- src/hybridmap/NeutralAtomLayer.cpp | 16 ---------------- 2 files changed, 27 deletions(-) diff --git a/include/hybridmap/NeutralAtomLayer.hpp b/include/hybridmap/NeutralAtomLayer.hpp index 86779a5c7..4a99849e2 100644 --- a/include/hybridmap/NeutralAtomLayer.hpp +++ b/include/hybridmap/NeutralAtomLayer.hpp @@ -86,11 +86,6 @@ class NeutralAtomLayer { */ GateList getGates() const { return gates; } GateList getNewGates() const { return newGates; } - /** - * @brief Returns a vector of the iterator indices for debugging - * @return A copy of the current iterator indices - */ - std::vector getIteratorOffset(); /** * @brief Initializes the layer by updating all qubits starting */ @@ -102,12 +97,6 @@ class NeutralAtomLayer { * @param commuteWith Gates the new gates should commute with */ void removeGatesAndUpdate(const GateList& gatesToRemove); - - /** - * @brief Returns the mapped single qubit gates - * @return The mapped single qubit gates - */ - GateList getMappedSingleQubitGates() { return mappedSingleQubitGates; } }; // Commutation checks diff --git a/src/hybridmap/NeutralAtomLayer.cpp b/src/hybridmap/NeutralAtomLayer.cpp index 923020255..36fd71de9 100644 --- a/src/hybridmap/NeutralAtomLayer.cpp +++ b/src/hybridmap/NeutralAtomLayer.cpp @@ -29,16 +29,6 @@ void NeutralAtomLayer::updateByQubits( candidatesToGates(qubitsToUpdate); } -std::vector NeutralAtomLayer::getIteratorOffset() { - std::vector offset; - offset.reserve(dag.size()); - for (uint32_t i = 0; i < this->dag.size(); ++i) { - offset.emplace_back(static_cast( - std::distance(this->dag[i].begin(), this->iterators[i]))); - } - return offset; -} - void NeutralAtomLayer::initAllQubits() { std::set allQubits; for (uint32_t i = 0; i < this->dag.size(); ++i) { @@ -182,12 +172,6 @@ bool commuteAtQubit(const qc::Operation* op1, const qc::Operation* op2, return true; } - // Swaps never commute - if (op1->getType() == qc::OpType::SWAP || - op2->getType() == qc::OpType::SWAP) { - return false; - } - // check targets if (std::find(op1->getTargets().begin(), op1->getTargets().end(), qubit) != op1->getTargets().end() && From 2040c37b8d79dad6ceebc274d472d0c0e1d27b54 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 6 Nov 2025 10:28:57 +0100 Subject: [PATCH 216/394] =?UTF-8?q?=F0=9F=94=A5=20removed=20unused=20Defin?= =?UTF-8?q?itions.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/NeutralAtomDefinitions.hpp | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/include/hybridmap/NeutralAtomDefinitions.hpp b/include/hybridmap/NeutralAtomDefinitions.hpp index fc0bb05c1..61e029eb8 100644 --- a/include/hybridmap/NeutralAtomDefinitions.hpp +++ b/include/hybridmap/NeutralAtomDefinitions.hpp @@ -50,11 +50,6 @@ using WeightedSwap = std::pair; using WeightedSwaps = std::vector; // The distance between two hardware qubits using SWAP gates. using SwapDistance = int32_t; -// Bridges -// using Bridge = -// std::tuple; // q_control, q_target, -// Q_between -// using Bridges = std::vector; // Moves are between coordinates (the first is occupied, the second is not). struct AtomMove { CoordIndex c1; @@ -70,13 +65,6 @@ struct AtomMove { bool operator!=(const AtomMove& other) const { return !(*this == other); } }; -// Moves for FlyingAncilla -struct fPoint { - qc::fp x; - qc::fp y; - fPoint(qc::fp x_val, qc::fp y_val) : x(x_val), y(y_val) {} -}; - // Used to represent operations using GateList = std::vector; using GateLists = std::vector; From bef4296e3d6e8be7f8d2845f399ac76683ac3696 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 6 Nov 2025 10:48:38 +0100 Subject: [PATCH 217/394] =?UTF-8?q?=E2=9C=85=20added=20scheduler=20tests.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/NeutralAtomScheduler.hpp | 11 ------ src/hybridmap/NeutralAtomScheduler.cpp | 15 -------- test/hybridmap/test_scheduler.cpp | 41 ++++++++++++++++++++++ 3 files changed, 41 insertions(+), 26 deletions(-) create mode 100644 test/hybridmap/test_scheduler.cpp diff --git a/include/hybridmap/NeutralAtomScheduler.hpp b/include/hybridmap/NeutralAtomScheduler.hpp index ba123413c..fd6d998c4 100644 --- a/include/hybridmap/NeutralAtomScheduler.hpp +++ b/include/hybridmap/NeutralAtomScheduler.hpp @@ -46,13 +46,6 @@ struct SchedulerResults { totalGateFidelities(gateFidelities), totalFidelities(fidelities), nCZs(cZs), nAodActivate(aodActivate), nAodMove(aodMove) {} - [[nodiscard]] std::string toString() const { - std::stringstream ss; - ss << "Total execution time: " << totalExecutionTime; - ss << "\nTotal idle time: " << totalIdleTime - << "\nTotal fidelities: " << totalFidelities; - return ss.str(); - } [[nodiscard]] std::string toCsv() const { std::stringstream ss; ss << totalExecutionTime << ", " << totalIdleTime << "," << totalFidelities; @@ -138,10 +131,6 @@ class NeutralAtomScheduler { qc::fp totalGateFidelities, qc::fp totalFidelities, uint32_t nCZs, uint32_t nAodActivate, uint32_t nAodMove); - static void printTotalExecutionTimes( - const std::vector& totalExecutionTimes, - const std::vector>>& - blockedQubitsTimes); }; } // namespace na diff --git a/src/hybridmap/NeutralAtomScheduler.cpp b/src/hybridmap/NeutralAtomScheduler.cpp index f1e099516..2c11af30a 100644 --- a/src/hybridmap/NeutralAtomScheduler.cpp +++ b/src/hybridmap/NeutralAtomScheduler.cpp @@ -201,18 +201,3 @@ void na::NeutralAtomScheduler::printSchedulerResults( std::cout << "nAodActivate: " << nAodActivate << "\n"; std::cout << "nAodMove: " << nAodMove << "\n"; } - -void na::NeutralAtomScheduler::printTotalExecutionTimes( - const std::vector& totalExecutionTimes, - const std::vector>>& - blockedQubitsTimes) { - std::cout << "ExecutionTime: " - << "\n"; - for (size_t qubit = 0; qubit < totalExecutionTimes.size(); qubit++) { - std::cout << "[" << qubit << "] " << totalExecutionTimes[qubit] << " \t"; - for (const auto& blockedTime : blockedQubitsTimes[qubit]) { - std::cout << blockedTime.first << "-" << blockedTime.second << " \t"; - } - std::cout << "\n"; - } -} diff --git a/test/hybridmap/test_scheduler.cpp b/test/hybridmap/test_scheduler.cpp new file mode 100644 index 000000000..c707d4ca4 --- /dev/null +++ b/test/hybridmap/test_scheduler.cpp @@ -0,0 +1,41 @@ +/* + * 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 + */ + +#include "hybridmap/NeutralAtomScheduler.hpp" + +#include + +TEST(NeutralAtomSchedulerTests, SchedulerResultsToMapForPython) { + // Given some arbitrary scheduler result values + na::SchedulerResults const res(/*executionTime*/ 10.5, + /*idleTime*/ 2.0, + /*gateFidelities*/ 0.9, + /*fidelities*/ 0.85, + /*nCZs*/ 3, + /*nAodActivate*/ 4, + /*nAodMove*/ 5); + + const auto m = res.toMap(); + + // Only the documented keys are exported + ASSERT_EQ(m.size(), 5U); + EXPECT_TRUE(m.count("totalExecutionTime")); + EXPECT_TRUE(m.count("totalIdleTime")); + EXPECT_TRUE(m.count("totalGateFidelities")); + EXPECT_TRUE(m.count("totalFidelities")); + EXPECT_TRUE(m.count("nCZs")); + + // Values preserved exactly + EXPECT_DOUBLE_EQ(m.at("totalExecutionTime"), 10.5); + EXPECT_DOUBLE_EQ(m.at("totalIdleTime"), 2.0); + EXPECT_DOUBLE_EQ(m.at("totalGateFidelities"), 0.9); + EXPECT_DOUBLE_EQ(m.at("totalFidelities"), 0.85); + EXPECT_DOUBLE_EQ(m.at("nCZs"), 3.0); +} From 14da94474a0a3fda91a4a48766ff588d55cb0bd3 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 6 Nov 2025 10:56:47 +0100 Subject: [PATCH 218/394] =?UTF-8?q?=F0=9F=94=A5=20removed=20unused=20move?= =?UTF-8?q?=20group=20post-processing.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/MoveToAodConverter.hpp | 7 ------- src/hybridmap/MoveToAodConverter.cpp | 22 ---------------------- 2 files changed, 29 deletions(-) diff --git a/include/hybridmap/MoveToAodConverter.hpp b/include/hybridmap/MoveToAodConverter.hpp index 0acf54408..854e97451 100644 --- a/include/hybridmap/MoveToAodConverter.hpp +++ b/include/hybridmap/MoveToAodConverter.hpp @@ -343,7 +343,6 @@ class MoveToAodConverter { * is created for the remaining moves. */ void processMoveGroups(); - void postProcessMoveGroups(); std::pair, MoveGroup> processMoves(const std::vector>& moves, @@ -364,12 +363,6 @@ class MoveToAodConverter { hardwareQubits(hardwareQubitsArg) { qcScheduled.addAncillaryRegister(arch.getNpositions()); qcScheduled.addAncillaryRegister(arch.getNpositions(), "fa"); - if (flyingAncillas.getNumQubits() > arch.getNcolumns() || - flyingAncillas.getNumQubits() > arch.getNrows()) { - throw std::invalid_argument( - "Number of flying ancillas must be smaller than the number of " - "columns and rows of the neutral atom architecture."); - } for (auto i = 0; i < flyingAncillas.getInitHwPos().size(); ++i) { const auto coord = flyingAncillas.getInitHwPos().at(i) + (2 * arch.getNpositions()); diff --git a/src/hybridmap/MoveToAodConverter.cpp b/src/hybridmap/MoveToAodConverter.cpp index 2e79880f9..6c794abed 100644 --- a/src/hybridmap/MoveToAodConverter.cpp +++ b/src/hybridmap/MoveToAodConverter.cpp @@ -430,28 +430,6 @@ void MoveToAodConverter::processMoveGroups() { aodActivationHelper, aodDeactivationHelper); } } -void MoveToAodConverter::postProcessMoveGroups() { - for (auto groupIt = moveGroups.begin(); groupIt != moveGroups.end() - 1; - ++groupIt) { - auto nextGroupIt = groupIt + 1; - for (const auto& move : groupIt->moves) { - for (const auto& moveNext : nextGroupIt->moves) { - if (move.first.c2 == moveNext.first.c1 && move.first.load2 && - moveNext.first.load1) { - // moveNext is dependent on move - // moveNext can only be executed - if (groupIt->processedOpsFinal.size() == 2 && - groupIt->processedOpsInit.size() == 2) { - groupIt->processedOpsFinal.pop_back(); - // next remove first move from next group - nextGroupIt->processedOpsInit.erase( - nextGroupIt->processedOpsInit.begin()); - } - } - } - } - } -} std::pair, MoveToAodConverter::MoveGroup> MoveToAodConverter::processMoves( From 6c3b9b8fc912d83c418d5fc461d6431506e0ae80 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 6 Nov 2025 12:13:26 +0100 Subject: [PATCH 219/394] =?UTF-8?q?=F0=9F=A7=AA=20changed=20arch=20to=20ha?= =?UTF-8?q?ve=20more=20aod=20conflicts=20and=20removed=20case=20of=20no=20?= =?UTF-8?q?aod=20movements=20(as=20never=20happens).?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/MoveToAodConverter.cpp | 21 +------------------ .../architectures/rubidium_gate.json | 2 +- 2 files changed, 2 insertions(+), 21 deletions(-) diff --git a/src/hybridmap/MoveToAodConverter.cpp b/src/hybridmap/MoveToAodConverter.cpp index 6c794abed..68a557158 100644 --- a/src/hybridmap/MoveToAodConverter.cpp +++ b/src/hybridmap/MoveToAodConverter.cpp @@ -538,7 +538,7 @@ AodOperation MoveToAodConverter::MoveGroup::connectAodOperations( targetQubits.emplace_back(starts[i]); targetQubits.emplace_back(ends[i]); } else { - // insert before one before the found position + // insert one before the found position const auto newPos = targetQubits.insert(pos - 1, ends[i]); targetQubits.insert(newPos, starts[i]); } @@ -553,9 +553,6 @@ AodOperation MoveToAodConverter::MoveGroup::connectAodOperations( const auto& end = deactivationDim[i]->init * d + deactivationDim[i]->offset * interD; if (std::abs(start - end) > 0.0001) { - if (start > 10000) { - int i = 0; - } aodOperations.emplace_back(dim, start, end); } } @@ -584,9 +581,6 @@ MoveToAodConverter::AodActivationHelper::getAodMovesFromInit( uint32_t MoveToAodConverter::AodActivationHelper::getMaxOffsetAtInit( Dimension dim, uint32_t init, int32_t sign) const { auto aodMoves = getAodMovesFromInit(dim, init); - if (aodMoves.empty()) { - return 0; - } uint32_t maxOffset = 0; for (const auto& aodMove : aodMoves) { auto offset = aodMove->offset; @@ -607,13 +601,6 @@ bool MoveToAodConverter::AodActivationHelper::checkIntermediateSpaceAtInit( } auto aodMoves = getAodMovesFromInit(dim, init); auto aodMovesNeighbor = getAodMovesFromInit(dim, neighborX); - if (aodMoves.empty() && aodMovesNeighbor.empty()) { - return true; - } - if (aodMoves.empty()) { - return getMaxOffsetAtInit(dim, neighborX, sign) < - arch->getNAodIntermediateLevels(); - } if (aodMovesNeighbor.empty()) { return getMaxOffsetAtInit(dim, init, sign) < arch->getNAodIntermediateLevels(); @@ -715,12 +702,6 @@ MoveToAodConverter::AodActivationHelper::getAodOperation( } std::vector aodOperations; - if (initOperations.empty()) { - return {AodOperation(qc::OpType::AodMove, qubitsOffset, offsetOperations)}; - } - if (offsetOperations.empty()) { - return {AodOperation(type, qubitsActivation, initOperations)}; - } return {AodOperation(type, qubitsActivation, initOperations), AodOperation(qc::OpType::AodMove, qubitsOffset, offsetOperations)}; } diff --git a/test/hybridmap/architectures/rubidium_gate.json b/test/hybridmap/architectures/rubidium_gate.json index 4ef84219c..eb33e8dd3 100644 --- a/test/hybridmap/architectures/rubidium_gate.json +++ b/test/hybridmap/architectures/rubidium_gate.json @@ -6,7 +6,7 @@ "nAods": 1, "nAodCoordinates": 5, "interQubitDistance": 3, - "minimalAodDistance": 0.1, + "minimalAodDistance": 1.5, "interactionRadius": 1.5, "blockingFactor": 1 }, From 0886bd8db7b4a70e4553c6f26086ef223a804ef7 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 6 Nov 2025 12:33:01 +0100 Subject: [PATCH 220/394] =?UTF-8?q?=F0=9F=94=A5=20removed=20unused=20code?= =?UTF-8?q?=20in=20mapper=20tests.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridNeutralAtomMapper.hpp | 14 - src/hybridmap/HybridNeutralAtomMapper.cpp | 462 +----------------- test/hybridmap/test_hybridmap.cpp | 5 +- 3 files changed, 4 insertions(+), 477 deletions(-) diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index bc185d7d4..b54e176af 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -304,8 +304,6 @@ class NeutralAtomMapper { const CoordIndices& excludedCoords) const; // Methods for flying ancilla operations mapping - std::vector - findBestFlyingAncillaComb(const qc::Operation* targetOp); [[nodiscard]] MappingMethod compareSwapAndBridge(const Swap& bestSwap, const Bridge& bestBridge); [[nodiscard]] MappingMethod @@ -547,18 +545,6 @@ class NeutralAtomMapper { */ void mapAppend(qc::QuantumComputation& qc, const Mapping& initialMapping); - /** - * @brief Maps the given quantum circuit to the given architecture and - * converts it to the AOD level. - * @param qc The quantum circuit to be mapped - * @param initialMapping The initial mapping of the circuit qubits to the - * hardware qubits - */ - [[maybe_unused]] void mapWithoutReturn(qc::QuantumComputation& qc, - const InitialMapping initialMapping) { - map(qc, initialMapping); - } - /** * @brief Returns the statistics of the mapping. * @return The statistics of the mapping diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 8eac27094..d73f8685a 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -113,8 +113,6 @@ void NeutralAtomMapper::mapAppend(qc::QuantumComputation& qc, std::cout << "nFAncillas: " << stats.nFAncillas << '\n'; std::cout << "nMoves: " << stats.nMoves << '\n'; std::cout << "nPassBy: " << stats.nPassBy << '\n'; - - // mappedQc.print(std::cout); } } @@ -268,9 +266,6 @@ void NeutralAtomMapper::mapGate(const qc::Operation* op) { bool NeutralAtomMapper::isExecutable(const qc::Operation* opPointer) { const auto usedQubits = opPointer->getUsedQubits(); - if (usedQubits.size() == 1) { - return true; - } std::set usedHwQubits; for (const auto qubit : usedQubits) { usedHwQubits.emplace(this->mapping.getHwQubit(qubit)); @@ -415,11 +410,6 @@ void NeutralAtomMapper::applyFlyingAncilla(NeutralAtomLayer& frontLayer, auto allCoords = targetCoords; allCoords.insert(allCoords.end(), controlCoords.begin(), controlCoords.end()); - if (allCoords.size() / 2 != faComb.moves.size()) { - throw std::runtime_error( - "Not enough flying ancilla moves for the given operation"); - } - uint32_t i = 0; const auto nPos = this->arch->getNpositions(); for (const auto& passBy : faComb.moves) { @@ -468,15 +458,7 @@ void NeutralAtomMapper::applyFlyingAncilla(NeutralAtomLayer& frontLayer, mappedQc.h(ancQ1); // update position of flying ancillas - if (this->flyingAncillas.isMapped(passBy.q1) && - passBy.q1 != passBy.origin) { - // move away - const auto& freeCoords = - this->flyingAncillas.getNearbyFreeCoordinatesByCoord(passBy.q1); - const auto& freeCoord = *freeCoords.begin(); - mappedQc.move(passBy.q1 + nPos, freeCoord + nPos); - this->flyingAncillas.move(passBy.q1, freeCoord); - } else if (passBy.q1 != passBy.origin) { + if (passBy.q1 != passBy.origin) { this->flyingAncillas.move(passBy.index, passBy.q1); } @@ -653,10 +635,6 @@ FlyingAncillaComb NeutralAtomMapper::convertMoveCombToFlyingAncillaComb( const auto usedQubits = moveComb.op->getUsedQubits(); const auto hwQubits = this->mapping.getHwQubits(usedQubits); const auto usedCoords = this->hardwareQubits.getCoordIndices(hwQubits); - // not enough qubits for a flying ancilla - if (usedCoords.size() - 1 > mappedQc.getNancillae()) { - return {}; - } // multi-qubit gate -> only one direction std::vector bestFAs; @@ -1049,9 +1027,6 @@ HwQubits NeutralAtomMapper::getBestMultiQubitPositionRec( WeightedSwaps NeutralAtomMapper::getExactSwapsToPosition(const qc::Operation* op, HwQubits position) { - if (position.empty()) { - return {}; - } const auto gateQubits = op->getUsedQubits(); auto gateHwQubits = this->mapping.getHwQubits(gateQubits); WeightedSwaps swapsExact; @@ -1181,26 +1156,6 @@ MoveComb NeutralAtomMapper::findBestAtomMove() { return bestMove->first; } -// std::pair -// NeutralAtomMapper::findBestAtomMoveWithOp() { -// auto moveCombsWithOp = getAllMoveCombinationsWithOp(); -// -// // compute cost for each move combination -// std::vector, qc::fp>> moveCosts; -// moveCosts.reserve(moveCombsWithOp.size()); -// for (const auto& moveCombWithOp : moveCombsWithOp) { -// moveCosts.emplace_back(moveCombWithOp, -// moveCostComb(moveCombWithOp.first)); -// } -// -// std::sort(moveCosts.begin(), moveCosts.end(), -// [](const auto& move1, const auto& move2) { -// return move1.second < move2.second; -// }); -// -// return moveCosts.front().first; -// } - qc::fp NeutralAtomMapper::moveCostComb(const MoveComb& moveComb) const { qc::fp costComb = 0; const auto frontDistReduction = @@ -1396,27 +1351,6 @@ MoveCombs NeutralAtomMapper::getAllMoveCombinations() { return allMoves; } -// std::vector> -// NeutralAtomMapper::getAllMoveCombinationsWithOp() { -// MoveCombs allMoves; -// int i = 1; -// for (const auto& op : this->frontLayer.getGates()) { -// auto usedQubits = op->getUsedQubits(); -// auto usedHwQubits = this->mapping.getHwQubits(usedQubits); -// auto usedCoordsSet = this->hardwareQubits.getCoordIndices(usedHwQubits); -// auto usedCoords = -// std::vector(usedCoordsSet.begin(), usedCoordsSet.end()); -// auto bestPos = getBestMovePos(usedCoords); -// auto moves = getMoveCombinationsToPosition(usedHwQubits, bestPos); -// allMoves.addMoveCombs(moves); -// for (auto move : moves) { -// allMovesWithOp.push_back(std::make_pair(move, MoveInfo{op, bestPos})); -// } -// } -// allMoves.removeLongerMoveCombs(); -// return make_pair(allMoves, allMovesWithOp); -// } - CoordIndices NeutralAtomMapper::getBestMovePos(const CoordIndices& gateCoords) { size_t const maxMoves = gateCoords.size() * 2; size_t const minMoves = gateCoords.size(); @@ -1475,9 +1409,6 @@ CoordIndices NeutralAtomMapper::getBestMovePos(const CoordIndices& gateCoords) { MoveCombs NeutralAtomMapper::getMoveCombinationsToPosition( const HwQubits& gateQubits, const CoordIndices& position) const { - if (position.empty()) { - throw std::invalid_argument("No position given"); - } // compute for each qubit the best position around it based on the cost of // the single move choose best one MoveCombs const moveCombinations; @@ -1561,12 +1492,6 @@ MoveCombs NeutralAtomMapper::getMoveAwayCombinations( } return moveCombinations; } -std::vector -NeutralAtomMapper::findBestFlyingAncillaComb(const qc::Operation* targetOp) { - std::vector const bestFlyingAncillaCombs; - auto usedQubits = targetOp->getUsedQubits(); - return bestFlyingAncillaCombs; -} size_t NeutralAtomMapper::shuttlingBasedMapping( NeutralAtomLayer& frontLayer, NeutralAtomLayer& lookaheadLayer, size_t i) { @@ -1724,31 +1649,6 @@ bool NeutralAtomMapper::swapGateBetter(const qc::Operation* opPointer) { fidMoves * parameters->shuttlingWeight; } -// void NeutralAtomMapper::reassignGatesToLayers(const GateList& frontGates, -// const GateList& lookaheadGates) -// { -// // assign gates to gates or shuttling -// this->frontLayerGate.clear(); -// this->frontLayerShuttling.clear(); -// for (const auto& gate : frontGates) { -// if (swapGateBetter(gate)) { -// this->frontLayerGate.emplace_back(gate); -// } else { -// this->frontLayerShuttling.emplace_back(gate); -// } -// } -// -// this->lookaheadLayerGate.clear(); -// this->lookaheadLayerShuttling.clear(); -// for (const auto& gate : lookaheadGates) { -// if (swapGateBetter(gate)) { -// this->lookaheadLayerGate.emplace_back(gate); -// } else { -// this->lookaheadLayerShuttling.emplace_back(gate); -// } -// } -// } - size_t NeutralAtomMapper::gateBasedMapping(NeutralAtomLayer& frontLayer, NeutralAtomLayer& lookaheadLayer, size_t i) { @@ -1790,328 +1690,6 @@ size_t NeutralAtomMapper::gateBasedMapping(NeutralAtomLayer& frontLayer, return i; } -// -// std::vector> -// NeutralAtomMapper::findAllBridges(qc::QuantumComputation& qc) { -// std::vector> allBridges; -// for (const auto* op : this->frontLayer.getGates()) { -// size_t usedQubitSize = op->getUsedQubits().size(); -// Qubits nearbyQubits; -// if (usedQubitSize == 2) { -// // logical qubits -// qc::Qubit q1 = *(op->getUsedQubits().begin()); -// qc::Qubit q2 = *(std::next(op->getUsedQubits().begin(), 1)); -// // hardware qubits -// HwQubit h1 = this->mapping.getHwQubit(q1); -// HwQubit h2 = this->mapping.getHwQubit(q2); -// qc::fp dist = this->hardwareQubits.getSwapDistance(h1, h2); -// if (dist == 1) { -// // get nearby -// HwQubits h1Near = this->hardwareQubits.getNearbyQubits(h1); -// HwQubits h2Near = this->hardwareQubits.getNearbyQubits(h2); -// for (const auto& h : h1Near) { -// if (h2Near.find(h) != h2Near.end()) { -// qc::Qubit qBtw = this->mapping.getCircQubit(h); -// if (qBtw < qc.getNqubits() && qBtw != -1) { -// nearbyQubits.insert(qBtw); -// } -// } -// } -// } -// allBridges.emplace_back(op, Bridge(q1, q2, nearbyQubits)); -// } -// } -// return allBridges; -// } -// -// void NeutralAtomMapper::updateMappingBridge( -// std::vector> ExecutableBridges, -// NeutralAtomLayer& frontLayer, NeutralAtomLayer& lookaheadLayer) { -// // CX to Bridge -// //[q1] ---c--- = ---------c-----------c--- -// //[qb] | = ---c---H-Z-H---c---H-Z-H- -// //[q2] -H-Z-H- = -H-Z-H-------H-Z-H------- -// -// //-> CZ to Bridge w/ QCO -// //[q1] -c- = -------c-----------c--- -// //[qb] | = -c---H-Z-H---c---H-Z-H- -// //[q2] -Z- = -Z-----------Z--------- -// -// GateList removeGates; -// for (auto bridgePair : ExecutableBridges) { -// nBridges++; -// auto op = bridgePair.first; -// auto bridge = bridgePair.second; -// qc::Qubit q1 = std::get<0>(bridge); -// qc::Qubit q2 = std::get<1>(bridge); -// Qubits Qb = std::get<2>(bridge); -// auto it = Qb.begin(); -// qc::Qubit qb = *it; -// if (this->parameters.verbose) { -// std::cout << "bridged " << q1 << " " << q2 << " by using " << qb; -// std::cout << " physical qubits: "; -// std::cout << this->mapping.getHwQubit(q1); -// std::cout << " "; -// std::cout << this->mapping.getHwQubit(q2); -// std::cout << " "; -// std::cout << this->mapping.getHwQubit(qb); -// std::cout << '\n'; -// } -// // add BR to mappedQc -// mappedQc.cz(qb, q2); -// mappedQc.h(qb); -// mappedQc.cz(q1, qb); -// mappedQc.h(qb); -// mappedQc.cz(qb, q2); -// mappedQc.h(qb); -// mappedQc.cz(q1, qb); -// mappedQc.h(qb); -// -// // remove original gate -// removeGates.push_back(op); -// } -// // remove original gate -// frontLayer.removeGatesAndUpdate(removeGates); -// lookaheadLayer.removeGatesAndUpdate(removeGates); -// } -// -// std::vector> -// NeutralAtomMapper::bridgeCostCompareWithSwap( -// std::vector> allBridges, -// Swap bestSwap, const qc::DAG& dag, NeutralAtomLayer& frontLayer) { -// std::vector> ExecutableBridges; -// -// for (auto bridgePair : allBridges) { -// auto op = bridgePair.first; -// auto bridge = bridgePair.second; -// qc::Qubit q1 = std::get<0>(bridge); -// qc::Qubit q2 = std::get<1>(bridge); -// Qubits Qb = std::get<2>(bridge); -// auto it = Qb.begin(); -// qc::Qubit qb = *it; -// -// // cost for front layer -// qc::fp distbefore = 0; -// qc::fp distswap = 0; -// HwQubit p1 = this->mapping.getHwQubit(q1); -// HwQubit p2 = this->mapping.getHwQubit(q2); -// distbefore += this->hardwareQubits.getSwapDistance(p1, p2); -// if (p1 == bestSwap.first) { -// distswap += this->hardwareQubits.getSwapDistance(bestSwap.second, p2); -// } else if (p1 == bestSwap.second) { -// distswap += this->hardwareQubits.getSwapDistance(bestSwap.first, p2); -// } else if (p2 == bestSwap.first) { -// distswap += this->hardwareQubits.getSwapDistance(p1, bestSwap.second); -// } else if (p2 == bestSwap.second) { -// distswap += this->hardwareQubits.getSwapDistance(p1, bestSwap.first); -// } else { -// distswap += this->hardwareQubits.getSwapDistance(p1, p2); -// } -// if (distbefore - distswap > 0) -// return ExecutableBridges; -// -// // cost for look-ahead window -// qc::fp costBridge = 0; -// qc::fp costBestSwap = 0; -// for (auto& q : {q1, q2}) { -// HwQubit p = this->mapping.getHwQubit(q); -// auto tempIter = dag[q].begin() + frontLayer.getIteratorOffset()[q] + 1; -// qc::fp discountFactor = 0.9; -// while (tempIter < dag[q].end() && discountFactor > 0.1) { -// auto* dagOp = (*tempIter)->get(); -// if (dagOp->getUsedQubits().size() != 1) { -// Qubits usedQubits = dagOp->getUsedQubits(); -// qc::fp distbefore = 0; -// qc::fp distswap = 0; -// for (auto it1 = usedQubits.begin(); it1 != usedQubits.end(); ++it1) -// { -// for (auto it2 = std::next(it1); it2 != usedQubits.end(); ++it2) { -// qc::Qubit qi = *it1; -// qc::Qubit qj = *it2; -// HwQubit pi = this->mapping.getHwQubit(qi); -// HwQubit pj = this->mapping.getHwQubit(qj); -// -// distbefore += this->hardwareQubits.getSwapDistance(pi, pj); -// if (pi == bestSwap.first) { -// distswap += -// this->hardwareQubits.getSwapDistance(bestSwap.second, -// pj); -// } else if (pi == bestSwap.second) { -// distswap += -// this->hardwareQubits.getSwapDistance(bestSwap.first, pj); -// } else if (pj == bestSwap.first) { -// distswap += -// this->hardwareQubits.getSwapDistance(pi, -// bestSwap.second); -// } else if (pj == bestSwap.second) { -// distswap += -// this->hardwareQubits.getSwapDistance(pi, bestSwap.first); -// } else { -// distswap += this->hardwareQubits.getSwapDistance(pi, pj); -// } -// } -// } -// costBridge += distbefore * discountFactor; -// costBestSwap += distswap * discountFactor; -// discountFactor *= 0.9; -// } -// tempIter++; -// } -// } -// -// if (costBridge <= costBestSwap && costBridge != 0) { -// ExecutableBridges.emplace_back(op, Bridge(q1, q2, Qb)); -// } -// } -// return ExecutableBridges; -// } - -// std::pair -// NeutralAtomMapper::findBestFlyingAncilla(qc::QuantumComputation& qc, -// const qc::Operation* targetOp) { -// // information of operation -// qc::QuantumComputation bestAddedQc; -// uint32_t bestNumPassby = 0; -// auto usedQubits = targetOp->getUsedQubits(); -// auto QtargetSet = findQtargetSet(usedQubits); -// if (!QtargetSet.empty()) { -// int idx = 0; -// int bestNumMoves = std::numeric_limits::max(); -// -// int bestIdx; -// std::set bestQtarget; -// std::vector bestQsource; -// // #F.A. => (Q_source, Q_target) iteration -// for (auto Qtarget : QtargetSet) { -// int numFA = usedQubits.size() - Qtarget.size(); -// uint32_t NumPassby = 0; -// std::set Qsource; -// std::set_difference(usedQubits.begin(), usedQubits.end(), -// Qtarget.begin(), -// Qtarget.end(), std::inserter(Qsource, -// Qsource.end())); -// -// // permutate Qtarget & Qsource -// std::vector QsourceVec(Qsource.begin(), Qsource.end()); -// do { -// qc::QuantumComputation addedQc; -// addedQc = qc::QuantumComputation(arch.getNpositions()); -// qc::fp r_int = arch.getInteractionRadius(); -// -// // hardware qubits & coord inices of Qtarget -// auto Htarget = this->mapping.getHwQubits(Qtarget); -// auto Ctarget = this->hardwareQubits.getCoordIndices(Htarget); -// -// std::vector Qancilla; -// std::vector Hsource, Hancilla; -// std::vector Csource, Cancilla; -// for (auto qs : QsourceVec) { -// // hardware qubits & coord inices of Qsource -// auto hs = this->mapping.getHwQubit(qs); -// Hsource.push_back(hs); -// auto cs = this->hardwareQubits.getCoordIndex(hs); -// Csource.push_back(cs); -// } -// std::vector excludeCoords; -// std::copy(Ctarget.begin(), Ctarget.end(), -// std::back_inserter(excludeCoords)); -// std::copy(Csource.begin(), Csource.end(), -// std::back_inserter(excludeCoords)); -// -// std::vector passbyQtarget; -// std::copy(Qtarget.begin(), Qtarget.end(), -// std::back_inserter(passbyQtarget)); -// -// std::vector needPassby(QsourceVec.size(), false); -// for (uint32_t i = 0; i < QsourceVec.size(); i++) { -// // find ancillaQubit of Qsource -// auto qi = QsourceVec[i]; -// auto ci = Csource[i]; -// auto cA = returnClosestAncillaCoord( -// ci, excludeCoords, -// qc); // excludeCoord: Ctarget, Csource, Cancilla -// auto hA = this->hardwareQubits.getHwQubit(cA); -// auto qA = this->mapping.getCircQubit(hA); -// Cancilla.push_back(cA); -// Hancilla.push_back(hA); -// Qancilla.push_back(qA); -// excludeCoords.push_back(cA); -// -// // 1. compare qs, qA -// if (arch.getEuclideanDistance(this->arch.getCoordinate(ci), -// this->arch.getCoordinate(cA)) > -// r_int) { -// // -> 1-1. passby (qA -> qi) -// addedQc.passby(qA, {qi}); -// needPassby[i] = true; -// NumPassby++; -// } -// -// //-> cx (qi, qA) -// addedQc.cx(qi, qA); -// -// // 2. compare qA, {Qtarget, previous qAs} -// // -> 2-1. passby (cA -> Ct) -// addedQc.passby(qA, passbyQtarget); -// NumPassby++; -// passbyQtarget.push_back(qA); -// } -// -// // 2-2. mcz(Qtarget, Qancilla) -// qc::Controls mczControl; -// mczControl.insert(Qtarget.begin(), Qtarget.end()); -// mczControl.insert(Qancilla.begin(), Qancilla.end() - 1); -// qc::Qubit mczTarget = Qancilla.back(); -// addedQc.mcz(mczControl, mczTarget); -// -// // 3. passby -> cx -// for (uint32_t i = 0; i < QsourceVec.size(); i++) { -// if (needPassby[i]) { -// auto qA = Qancilla[i]; -// auto qi = QsourceVec[i]; -// auto cA = Cancilla[i]; -// auto ci = Csource[i]; -// addedQc.passby(qA, {qi}); -// NumPassby++; -// } -// // else{ //TODO: where q_ancilla is moved? -// // addedQc.move( Qancilla[i], Cancilla[i] ); -// // } -// addedQc.cx(QsourceVec[i], Qancilla[i]); -// } -// -// // find bestAddedQc -// qc::CircuitOptimizer::replaceMCXWithMCZ(addedQc); -// if (addedQc.size() < bestNumMoves) { -// bestNumMoves = addedQc.size(); -// bestAddedQc = addedQc; -// // for debugging -// bestIdx = idx; -// bestQtarget = Qtarget; -// bestQsource = QsourceVec; -// bestNumPassby = NumPassby; -// } -// // TODO: how to find the best addedQc? -// // else if(addedQc.size() == bestNumMoves){ -// // } -// } while (std::next_permutation(QsourceVec.begin(), QsourceVec.end())); -// } -// // return the best result -// if (this->parameters.verbose) { -// std::cout << "best FA: " << bestIdx << "th) Qtarget: {"; -// for (auto i : bestQtarget) { -// std::cout << i << ", "; -// } -// std::cout << "} <- bestQsource: {"; -// for (auto i : bestQsource) { -// std::cout << i << ", "; -// } -// std::cout << "} w/ numFA: " << bestQsource.size() << "\n"; -// } -// return std::make_pair(bestAddedQc, bestNumPassby); -// } -// } - MappingMethod NeutralAtomMapper::compareSwapAndBridge(const Swap& bestSwap, const Bridge& bestBridge) { @@ -2268,42 +1846,4 @@ MappingMethod NeutralAtomMapper::compareShuttlingAndFlyingAncilla( } return MappingMethod::PassByMethod; } - -// void NeutralAtomMapper::updateMappingFlyingAncilla( -// qc::QuantumComputation& bestFA, const qc::Operation* targetOp, -// uint32_t numPassby, NeutralAtomLayer& frontLayer, -// NeutralAtomLayer& lookaheadLayer) { -// // add bestFA to mappedQc -// // TODO: solve the error (gate type pass_by could not be converted to -// // OpenQASM) -// -// for (const auto& opPtr : bestFA) { -// const auto* op = opPtr.get(); -// if (op->getType() == qc::OpType::H) { -// mappedQc.h(*op->getUsedQubits().begin()); -// } -// if (op->getType() == qc::OpType::Z) { -// if (op->getUsedQubits().size() > 1) { -// mappedQc.mcz(op->getControls(), op->getTargets()[0]); -// } else { -// mappedQc.z(*op->getUsedQubits().begin()); -// } -// } -// if (op->getType() == qc::OpType::PassBy) { -// mappedQc.passby(*op->getControls().begin(), op->getTargets()); -// } -// if (op->getType() == qc::OpType::Move) { -// mappedQc.move(op->getTargets()[0], op->getTargets()[1]); -// } -// } -// -// // remove original gate -// GateList removeGates; -// removeGates.push_back(targetOp); -// frontLayer.removeGatesAndUpdate(removeGates); -// lookaheadLayer.removeGatesAndUpdate(removeGates); -// -// nFAncillas += numPassby; -// } - } // namespace na diff --git a/test/hybridmap/test_hybridmap.cpp b/test/hybridmap/test_hybridmap.cpp index 36c87330f..42a6a36bb 100644 --- a/test/hybridmap/test_hybridmap.cpp +++ b/test/hybridmap/test_hybridmap.cpp @@ -107,6 +107,8 @@ TEST_P(NeutralAtomMapperTestParams, MapCircuitsIdentity) { mapperParameters.verbose = true; mapperParameters.maxBridgeDistance = 2; mapperParameters.numFlyingAncillas = 1; + mapperParameters.usePassBy = false; + mapperParameters.limitShuttlingLayer = 1; mapper.setParameters(mapperParameters); auto qc = qasm3::Importer::importf(testQcPath); @@ -156,9 +158,8 @@ class NeutralAtomMapperTest : public ::testing::Test { mapperParameters.shuttlingWeight = 0; mapperParameters.seed = 43; mapperParameters.verbose = false; - mapperParameters.numFlyingAncillas = 2; + mapperParameters.numFlyingAncillas = 1; mapperParameters.limitShuttlingLayer = 1; - // mapperParameters.useBridge = true; mapperParameters.usePassBy = true; mapper.setParameters(mapperParameters); qc = qasm3::Importer::importf( From 9804c9c99772eaad82bf34c147424ef466e0d8cb Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 6 Nov 2025 13:06:08 +0100 Subject: [PATCH 221/394] =?UTF-8?q?=E2=9C=85=20added=20mapper=20tests=20to?= =?UTF-8?q?=20test=20exceptions.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridNeutralAtomMapper.hpp | 12 ----- src/hybridmap/NeutralAtomArchitecture.cpp | 13 +++++ .../hybridmap/architectures/arch_minimal.json | 38 +++++++++++++ test/hybridmap/test_hybridmap.cpp | 53 ++++++++++++++++++- 4 files changed, 103 insertions(+), 13 deletions(-) create mode 100644 test/hybridmap/architectures/arch_minimal.json diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index b54e176af..824c7394f 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -253,16 +253,6 @@ class NeutralAtomMapper { [[nodiscard]] PassByComb convertMoveCombToPassByComb(const MoveComb& moveComb) const; - // std::vector> - // findAllBridges(qc::QuantumComputation& qc); - // std::vector> - // bridgeCostCompareWithSwap( - // std::vector> allBridges, - // Swap bestSwap, const qc::DAG& dag, NeutralAtomLayer& frontLayer); - // void updateMappingBridge( - // std::vector> ExecutableBridges, - // NeutralAtomLayer& frontLayer, NeutralAtomLayer& lookaheadLayer); - /** * @brief Returns the next best shuttling move operation for the front layer. * @return The next best shuttling move operation for the front layer @@ -285,8 +275,6 @@ class NeutralAtomMapper { * @return Vector of possible move combinations for the front layer */ MoveCombs getAllMoveCombinations(); - // std::vector> - // getAllMoveCombinationsWithOp(); /** * @brief Returns all possible move away combinations for a move from start to * target. diff --git a/src/hybridmap/NeutralAtomArchitecture.cpp b/src/hybridmap/NeutralAtomArchitecture.cpp index dc014b9b6..30c021d4f 100644 --- a/src/hybridmap/NeutralAtomArchitecture.cpp +++ b/src/hybridmap/NeutralAtomArchitecture.cpp @@ -72,12 +72,25 @@ void NeutralAtomArchitecture::loadJson(const std::string& filename) { for (const auto& [key, value] : jsonDataParameters["gateTimes"].items()) { gateTimes.emplace(key, value); } + // check if cz and h gates are present + if (gateTimes.find("cz") == gateTimes.end()) { + gateTimes["cz"] = gateTimes["none"]; + } + if (gateTimes.find("h") == gateTimes.end()) { + gateTimes["h"] = gateTimes["none"]; + } this->parameters.gateTimes = gateTimes; std::map gateAverageFidelities; for (const auto& [key, value] : jsonDataParameters["gateAverageFidelities"].items()) { gateAverageFidelities.emplace(key, value); } + if (gateAverageFidelities.find("cz") == gateAverageFidelities.end()) { + gateAverageFidelities["cz"] = gateAverageFidelities["none"]; + } + if (gateAverageFidelities.find("h") == gateAverageFidelities.end()) { + gateAverageFidelities["h"] = gateAverageFidelities["none"]; + } this->parameters.gateAverageFidelities = gateAverageFidelities; std::map shuttlingTimes; diff --git a/test/hybridmap/architectures/arch_minimal.json b/test/hybridmap/architectures/arch_minimal.json new file mode 100644 index 000000000..33a47ff4f --- /dev/null +++ b/test/hybridmap/architectures/arch_minimal.json @@ -0,0 +1,38 @@ +{ + "name": "minimal", + "properties": { + "nRows": 1, + "nColumns": 1, + "nAods": 1, + "nAodCoordinates": 1, + "interQubitDistance": 3, + "minimalAodDistance": 0.1, + "interactionRadius": 1, + "blockingFactor": 1 + }, + "parameters": { + "nQubits": 1, + "gateTimes": { + "none": 0.5 + }, + "gateAverageFidelities": { + "none": 0.999 + }, + "decoherenceTimes": { + "t1": 100000000, + "t2": 1500000 + }, + "shuttlingTimes": { + "move": 0.55, + "aod_move": 0.55, + "aod_activate": 20, + "aod_deactivate": 20 + }, + "shuttlingAverageFidelities": { + "move": 1, + "aod_move": 1, + "aod_activate": 1, + "aod_deactivate": 1 + } + } +} diff --git a/test/hybridmap/test_hybridmap.cpp b/test/hybridmap/test_hybridmap.cpp index 42a6a36bb..1fad9c61e 100644 --- a/test/hybridmap/test_hybridmap.cpp +++ b/test/hybridmap/test_hybridmap.cpp @@ -57,7 +57,7 @@ TEST_P(NeutralAtomArchitectureTest, LoadArchitectures) { INSTANTIATE_TEST_SUITE_P(NeutralAtomArchitectureTestSuite, NeutralAtomArchitectureTest, ::testing::Values("rubidium_gate", "rubidium_hybrid", - "rubidium_shuttling")); + "arch_minimal")); class NeutralAtomMapperTestParams // parameters are architecture, circuit, gateWeight, shuttlingWeight, // lookAheadWeight, dynamicMappingWeight, initialCoordinateMapping @@ -189,3 +189,54 @@ TEST_F(NeutralAtomMapperTest, Output) { ASSERT_GT(scheduleResults.totalFidelities, 0); } + +// Exception tests for HybridNeutralAtomMapper + +TEST(NeutralAtomMapperExceptions, NotEnoughQubitsForCircuitAndAncillas) { + const auto arch = + na::NeutralAtomArchitecture("architectures/rubidium_hybrid.json"); + na::MapperParameters p; + p.initialCoordMapping = na::InitialCoordinateMapping::Trivial; + p.shuttlingWeight = 0; // avoid free-coords check interference + p.numFlyingAncillas = 1; // allowed by ctor + na::NeutralAtomMapper mapper(arch, p); + + // Circuit uses exactly all hardware qubits; +1 ancilla should trigger + qc::QuantumComputation qc1(static_cast(arch.getNqubits())); + EXPECT_THROW((void)mapper.map(qc1, na::InitialMapping::Identity), + std::runtime_error); + + // Circuit bigger than architecture should throw + qc::QuantumComputation qc2(static_cast(arch.getNqubits() + 1)); + EXPECT_THROW((void)mapper.map(qc2, na::InitialMapping::Identity), + std::runtime_error); +} + +// for now, only one flying ancilla is supported +TEST(NeutralAtomMapperExceptions, OnlyOneFlyingAncillaSupported) { + const auto arch = + na::NeutralAtomArchitecture("architectures/rubidium_hybrid.json"); + na::MapperParameters p; + p.initialCoordMapping = na::InitialCoordinateMapping::Trivial; + p.shuttlingWeight = 0; // avoid free-coords check interference + p.numFlyingAncillas = 2; // should be rejected + EXPECT_THROW((void)na::NeutralAtomMapper(arch, p), std::runtime_error); +} + +TEST(NeutralAtomMapperExceptions, NoFreeCoordsForShuttlingConstructor) { + // Create minimal arch JSON: 1x1 positions, nQubits = 1 => no free coords + const auto arch = + na::NeutralAtomArchitecture("architectures/arch_minimal.json"); + + na::MapperParameters p; + p.initialCoordMapping = na::InitialCoordinateMapping::Trivial; + p.shuttlingWeight = 1.0; // triggers constructor check + EXPECT_THROW((void)na::NeutralAtomMapper(arch, p), std::runtime_error); + + na::MapperParameters p1 = p; + ; + p1.shuttlingWeight = 0.0; // construct ok + na::NeutralAtomMapper mapper(arch, p1); + p1.shuttlingWeight = 0.5; // triggers setParameters check + EXPECT_THROW(mapper.setParameters(p1), std::runtime_error); +} From baa5cf60adbb60919f6a699ee466c3a39e0efe2a Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 6 Nov 2025 13:17:57 +0100 Subject: [PATCH 222/394] =?UTF-8?q?=E2=9C=85=20improved=20mapper=20tests?= =?UTF-8?q?=20to=20include=20more=20output=20options.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridNeutralAtomMapper.hpp | 7 ----- test/hybridmap/test_hybridmap.cpp | 27 ++++++++++++------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index 824c7394f..96be51d85 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -575,13 +575,6 @@ class NeutralAtomMapper { this->mappedQc.dumpOpenQASM(ofs, false); } - /** - * @brief Returns the mapped quantum circuit with AOD operations. - */ - [[nodiscard]] qc::QuantumComputation getMappedQcAod() const { - return mappedQcAOD; - } - /** * @brief Prints the mapped circuit with AOD operations as an extended * OpenQASM diff --git a/test/hybridmap/test_hybridmap.cpp b/test/hybridmap/test_hybridmap.cpp index 1fad9c61e..63374a7bc 100644 --- a/test/hybridmap/test_hybridmap.cpp +++ b/test/hybridmap/test_hybridmap.cpp @@ -170,19 +170,26 @@ class NeutralAtomMapperTest : public ::testing::Test { TEST_F(NeutralAtomMapperTest, Output) { setvbuf(stdout, NULL, _IONBF, 0); auto qcMapped = mapper.map(qc, initialMapping); - qcMapped.dumpOpenQASM(std::cout, false); // write to file - std::ofstream ofs("test.qasm"); - qcMapped.dumpOpenQASM(ofs, false); - ofs.close(); - - auto qcAodMapped = mapper.convertToAod(); - qcAodMapped.dumpOpenQASM(std::cout, false); - std::ofstream ofsAod("test_aod.qasm"); - qcAodMapped.dumpOpenQASM(ofsAod, false); - ofsAod.close(); + mapper.saveMappedQcQasm("test.qasm"); + const auto qcMappedFromFile = mapper.getMappedQcQasm(); + mapper.saveMappedQcAodQasm("test_aod.qasm"); + const auto qcMappedAod = mapper.getMappedQcAodQasm(); + + const auto MapperStats = mapper.getStats(); + EXPECT_GE(MapperStats.nSwaps + MapperStats.nBridges + MapperStats.nFAncillas + + MapperStats.nMoves + MapperStats.nPassBy, + 0); + const auto MapperStatsMap = mapper.getStatsMap(); + EXPECT_GE(MapperStatsMap.at("nSwaps") + MapperStatsMap.at("nBridges") + + MapperStatsMap.at("nFAncillas") + MapperStatsMap.at("nMoves") + + MapperStatsMap.at("nPassBy"), + 0); + const auto initHwPos = mapper.getInitHwPos(); + EXPECT_EQ(initHwPos.size(), arch.getNqubits() - 1 /* flying ancilla */); const auto scheduleResults = mapper.schedule(true, true); + const auto animationViz = mapper.getAnimationViz(); mapper.saveAnimationFiles("test"); std::cout << scheduleResults.toCsv(); From b82afcd9ea4c42ce5cf4249023327a1fd7fc1304 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 6 Nov 2025 13:57:20 +0100 Subject: [PATCH 223/394] =?UTF-8?q?=E2=9C=85=20improved=20mapper=20tests?= =?UTF-8?q?=20to=20include=20catch=20more=20cases=20and=20removed=20unused?= =?UTF-8?q?=20code.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridNeutralAtomMapper.hpp | 2 +- src/hybridmap/HybridNeutralAtomMapper.cpp | 35 ++----------------- test/hybridmap/test_hybridmap.cpp | 25 +++++++++++++ 3 files changed, 28 insertions(+), 34 deletions(-) diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index 96be51d85..dc4464a6f 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -46,7 +46,7 @@ struct MapperParameters { qc::fp dynamicMappingWeight = 2; qc::fp gateWeight = 1; qc::fp shuttlingWeight = 1; - uint32_t seed = 0; + uint32_t seed = 42; uint32_t numFlyingAncillas = 0; uint32_t limitShuttlingLayer = 10; uint32_t maxBridgeDistance = 1; diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index d73f8685a..d66dc0c70 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -42,11 +42,6 @@ namespace na { void NeutralAtomMapper::mapAppend(qc::QuantumComputation& qc, const Mapping& initialMapping) { - if (qc.getNqubits() + this->parameters->numFlyingAncillas > - arch->getNqubits()) { - throw std::runtime_error( - "Not enough qubits in architecture for circuit and flying ancillas"); - } // check if multi-qubit gates are present for (const auto& op : qc) { if (op->getUsedQubits().size() > 2) { @@ -1047,25 +1042,6 @@ NeutralAtomMapper::getExactSwapsToPosition(const qc::Operation* op, minimalDistancePosQubit.emplace(posQubit); } } - if (minimalDistance == std::numeric_limits::max()) { - // not possible to move to position - // move gate to shuttling layer - const auto idxFrontGate = std::find(this->frontLayerGate.begin(), - this->frontLayerGate.end(), op); - if (idxFrontGate != this->frontLayerGate.end()) { - this->frontLayerGate.erase(idxFrontGate); - this->frontLayerShuttling.emplace_back(op); - } - // remove from lookahead layer if there - const auto idxLookaheadGate = - std::find(this->lookaheadLayerGate.begin(), - this->lookaheadLayerGate.end(), op); - if (idxLookaheadGate != this->lookaheadLayerGate.end()) { - this->lookaheadLayerGate.erase(idxLookaheadGate); - this->lookaheadLayerShuttling.emplace_back(op); - } - return {}; - } minimalDistances.emplace_back(gateQubit, minimalDistancePosQubit, minimalDistance); } @@ -1394,15 +1370,8 @@ CoordIndices NeutralAtomMapper::getBestMovePos(const CoordIndices& gateCoords) { } } if (finalBestPos.coords.empty()) { - // check if interaction radius too small - if (std::sqrt(gateCoords.size()) > this->arch->getInteractionRadius()) { - throw std::runtime_error( - "Interaction radius too small for the given gate size of " + - std::to_string(gateCoords.size())); - } else { - throw std::runtime_error( - "No move position found (check if enough free coords are available)"); - } + throw std::runtime_error( + "No move position found (check if enough free coords are available)"); } return finalBestPos.coords; } diff --git a/test/hybridmap/test_hybridmap.cpp b/test/hybridmap/test_hybridmap.cpp index 63374a7bc..247b17de1 100644 --- a/test/hybridmap/test_hybridmap.cpp +++ b/test/hybridmap/test_hybridmap.cpp @@ -247,3 +247,28 @@ TEST(NeutralAtomMapperExceptions, NoFreeCoordsForShuttlingConstructor) { p1.shuttlingWeight = 0.5; // triggers setParameters check EXPECT_THROW(mapper.setParameters(p1), std::runtime_error); } +TEST(NeutralAtomMapperExceptions, NoMultiQubitSpace) { + // Create minimal arch JSON: 1x1 positions, nQubits = 1 => no free coords + const auto arch = + na::NeutralAtomArchitecture("architectures/rubidium_gate.json"); + na::MapperParameters p; + na::NeutralAtomMapper mapper(arch, p); + qc::QuantumComputation qc = + qasm3::Importer::importf("circuits/multi_qubit.qasm"); + EXPECT_THROW(auto circ = mapper.map(qc, na::InitialMapping::Identity), + std::runtime_error); +} + +TEST(NeutralAtomMapperExceptions, ImpossibleSwaps) { + // Create minimal arch JSON: 1x1 positions, nQubits = 1 => no free coords + const auto arch = + na::NeutralAtomArchitecture("architectures/arch_sparse.json"); + na::MapperParameters p; + p.shuttlingWeight = 0.0; + p.initialCoordMapping = na::InitialCoordinateMapping::Random; + p.verbose = true; + na::NeutralAtomMapper mapper(arch, p); + qc::QuantumComputation qc = + qasm3::Importer::importf("circuits/modulo_2.qasm"); + auto circ = mapper.map(qc, na::InitialMapping::Identity); +} From aad1e7c3732328838b62de00364247071ba88ea1 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 6 Nov 2025 14:15:25 +0100 Subject: [PATCH 224/394] =?UTF-8?q?=E2=9C=85=20tested=20missing=20methods?= =?UTF-8?q?=20of=20hyrbid=20synthesis=20mapper.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridSynthesisMapper.hpp | 5 +---- test/hybridmap/test_hybrid_synthesis_map.cpp | 13 +++++++++++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/include/hybridmap/HybridSynthesisMapper.hpp b/include/hybridmap/HybridSynthesisMapper.hpp index 790b83cc4..f48f52714 100644 --- a/include/hybridmap/HybridSynthesisMapper.hpp +++ b/include/hybridmap/HybridSynthesisMapper.hpp @@ -83,11 +83,8 @@ class HybridSynthesisMapper : public NeutralAtomMapper { * @brief Returns the mapped QuantumComputation. * @return The mapped QuantumComputation. */ - void - completeRemap(InitialMapping initMapping = InitialMapping::Identity, - InitialCoordinateMapping initialCoordinateMapping = Trivial) { + void completeRemap(InitialMapping initMapping = InitialMapping::Identity) { this->map(synthesizedQc, initMapping); - this->convertToAod(); } /** diff --git a/test/hybridmap/test_hybrid_synthesis_map.cpp b/test/hybridmap/test_hybrid_synthesis_map.cpp index 30070b15e..6d55fc757 100644 --- a/test/hybridmap/test_hybrid_synthesis_map.cpp +++ b/test/hybridmap/test_hybrid_synthesis_map.cpp @@ -100,10 +100,12 @@ TEST_F(TestHybridSynthesisMapper, completelyRemap) { mapper.appendWithoutMapping(qc); mapper.appendWithoutMapping(qc); auto mappedQc = mapper.getMappedQc(); - EXPECT_EQ(mappedQc.getNqubits(), arch.getNpositions()); + EXPECT_EQ(mappedQc.getNqubitsWithoutAncillae(), arch.getNpositions()); EXPECT_GE(mappedQc.getNops(), 3); + + mapper.completeRemap(InitialMapping::Identity); auto mappedQcRemapped = mapper.getMappedQc(); - EXPECT_EQ(mappedQcRemapped.getNqubits(), arch.getNpositions()); + EXPECT_EQ(mappedQcRemapped.getNqubitsWithoutAncillae(), arch.getNpositions()); EXPECT_GE(mappedQcRemapped.getNops(), 3); } @@ -114,4 +116,11 @@ TEST_F(TestHybridSynthesisMapper, MapAppend) { EXPECT_GE(synthesizedQc.getNops(), 3); } +TEST_F(TestHybridSynthesisMapper, Output) { + mapper.appendWithMapping(qc); + const auto qasm = mapper.getSynthesizedQcQASM(); + EXPECT_FALSE(qasm.empty()); + mapper.saveSynthesizedQc("test_output.qasm"); +} + } // namespace na From 3ea7b6e4a227e726e8f05afe60d47e300e5a4d84 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 6 Nov 2025 14:16:04 +0100 Subject: [PATCH 225/394] =?UTF-8?q?=E2=9C=85=20fixed=20failing=20test=20on?= =?UTF-8?q?=20sparse=20arch.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/hybridmap/test_hybridmap.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/hybridmap/test_hybridmap.cpp b/test/hybridmap/test_hybridmap.cpp index 247b17de1..5472c12c2 100644 --- a/test/hybridmap/test_hybridmap.cpp +++ b/test/hybridmap/test_hybridmap.cpp @@ -270,5 +270,6 @@ TEST(NeutralAtomMapperExceptions, ImpossibleSwaps) { na::NeutralAtomMapper mapper(arch, p); qc::QuantumComputation qc = qasm3::Importer::importf("circuits/modulo_2.qasm"); - auto circ = mapper.map(qc, na::InitialMapping::Identity); + EXPECT_THROW(auto circ = mapper.map(qc, na::InitialMapping::Identity), + std::runtime_error); } From a3f945c1adc39abb3a509e3613c1cb1356c6f56a Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 6 Nov 2025 14:48:29 +0100 Subject: [PATCH 226/394] =?UTF-8?q?=E2=9C=85=20added=20one=20long=20shuttl?= =?UTF-8?q?ing=20test=20to=20trigger=20more=20shuttling=20cases.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/hybridmap/circuits/long_random.qasm | 224 +++++++++++++++++++++++ test/hybridmap/circuits/multi_qubit.qasm | 4 + test/hybridmap/test_hybridmap.cpp | 13 ++ 3 files changed, 241 insertions(+) create mode 100644 test/hybridmap/circuits/long_random.qasm create mode 100644 test/hybridmap/circuits/multi_qubit.qasm diff --git a/test/hybridmap/circuits/long_random.qasm b/test/hybridmap/circuits/long_random.qasm new file mode 100644 index 000000000..50471a55c --- /dev/null +++ b/test/hybridmap/circuits/long_random.qasm @@ -0,0 +1,224 @@ +OPENQASM 2.0; +include "qelib1.inc"; +qreg q[12]; + +cx q[7],q[3]; +cx q[1],q[8]; +cx q[4],q[10]; +cx q[11],q[2]; +cx q[9],q[6]; +cx q[0],q[5]; +cx q[10],q[1]; +cx q[8],q[11]; +cx q[2],q[0]; +cx q[6],q[4]; +cx q[3],q[9]; +cx q[5],q[7]; +cx q[11],q[10]; +cx q[8],q[9]; +cx q[0],q[3]; +cx q[4],q[2]; +cx q[1],q[7]; +cx q[6],q[5]; +cx q[9],q[0]; +cx q[10],q[8]; +cx q[7],q[3]; +cx q[1],q[8]; +cx q[4],q[10]; +cx q[11],q[2]; +cx q[9],q[6]; +cx q[0],q[5]; +cx q[10],q[1]; +cx q[8],q[11]; +cx q[2],q[0]; +cx q[6],q[4]; +cx q[3],q[9]; +cx q[5],q[7]; +cx q[11],q[10]; +cx q[8],q[9]; +cx q[0],q[3]; +cx q[4],q[2]; +cx q[1],q[7]; +cx q[6],q[5]; +cx q[9],q[0]; +cx q[10],q[8]; +cx q[7],q[3]; +cx q[1],q[8]; +cx q[4],q[10]; +cx q[11],q[2]; +cx q[9],q[6]; +cx q[0],q[5]; +cx q[10],q[1]; +cx q[8],q[11]; +cx q[2],q[0]; +cx q[6],q[4]; +cx q[3],q[9]; +cx q[5],q[7]; +cx q[11],q[10]; +cx q[8],q[9]; +cx q[0],q[3]; +cx q[4],q[2]; +cx q[1],q[7]; +cx q[6],q[5]; +cx q[9],q[0]; +cx q[10],q[8]; +cx q[7],q[3]; +cx q[1],q[8]; +cx q[4],q[10]; +cx q[11],q[2]; +cx q[9],q[6]; +cx q[0],q[5]; +cx q[10],q[1]; +cx q[8],q[11]; +cx q[2],q[0]; +cx q[6],q[4]; +cx q[3],q[9]; +cx q[5],q[7]; +cx q[11],q[10]; +cx q[8],q[9]; +cx q[0],q[3]; +cx q[4],q[2]; +cx q[1],q[7]; +cx q[6],q[5]; +cx q[9],q[0]; +cx q[10],q[8]; +cx q[7],q[3]; +cx q[1],q[8]; +cx q[4],q[10]; +cx q[11],q[2]; +cx q[9],q[6]; +cx q[0],q[5]; +cx q[10],q[1]; +cx q[8],q[11]; +cx q[2],q[0]; +cx q[6],q[4]; +cx q[3],q[9]; +cx q[5],q[7]; +cx q[11],q[10]; +cx q[8],q[9]; +cx q[0],q[3]; +cx q[4],q[2]; +cx q[1],q[7]; +cx q[6],q[5]; +cx q[9],q[0]; +cx q[10],q[8]; +cx q[7],q[3]; +cx q[1],q[8]; +cx q[4],q[10]; +cx q[11],q[2]; +cx q[9],q[6]; +cx q[0],q[5]; +cx q[10],q[1]; +cx q[8],q[11]; +cx q[2],q[0]; +cx q[6],q[4]; +cx q[3],q[9]; +cx q[5],q[7]; +cx q[11],q[10]; +cx q[8],q[9]; +cx q[0],q[3]; +cx q[4],q[2]; +cx q[1],q[7]; +cx q[6],q[5]; +cx q[9],q[0]; +cx q[10],q[8]; +cx q[7],q[3]; +cx q[1],q[8]; +cx q[4],q[10]; +cx q[11],q[2]; +cx q[9],q[6]; +cx q[0],q[5]; +cx q[10],q[1]; +cx q[8],q[11]; +cx q[2],q[0]; +cx q[6],q[4]; +cx q[3],q[9]; +cx q[5],q[7]; +cx q[11],q[10]; +cx q[8],q[9]; +cx q[0],q[3]; +cx q[4],q[2]; +cx q[1],q[7]; +cx q[6],q[5]; +cx q[9],q[0]; +cx q[10],q[8]; +cx q[7],q[3]; +cx q[1],q[8]; +cx q[4],q[10]; +cx q[11],q[2]; +cx q[9],q[6]; +cx q[0],q[5]; +cx q[10],q[1]; +cx q[8],q[11]; +cx q[2],q[0]; +cx q[6],q[4]; +cx q[3],q[9]; +cx q[5],q[7]; +cx q[11],q[10]; +cx q[8],q[9]; +cx q[0],q[3]; +cx q[4],q[2]; +cx q[1],q[7]; +cx q[6],q[5]; +cx q[9],q[0]; +cx q[10],q[8]; +cx q[7],q[3]; +cx q[1],q[8]; +cx q[4],q[10]; +cx q[11],q[2]; +cx q[9],q[6]; +cx q[0],q[5]; +cx q[10],q[1]; +cx q[8],q[11]; +cx q[2],q[0]; +cx q[6],q[4]; +cx q[3],q[9]; +cx q[5],q[7]; +cx q[11],q[10]; +cx q[8],q[9]; +cx q[0],q[3]; +cx q[4],q[2]; +cx q[1],q[7]; +cx q[6],q[5]; +cx q[9],q[0]; +cx q[10],q[8]; +cx q[7],q[3]; +cx q[1],q[8]; +cx q[4],q[10]; +cx q[11],q[2]; +cx q[9],q[6]; +cx q[0],q[5]; +cx q[10],q[1]; +cx q[8],q[11]; +cx q[2],q[0]; +cx q[6],q[4]; +cx q[3],q[9]; +cx q[5],q[7]; +cx q[11],q[10]; +cx q[8],q[9]; +cx q[0],q[3]; +cx q[4],q[2]; +cx q[1],q[7]; +cx q[6],q[5]; +cx q[9],q[0]; +cx q[10],q[8]; +cx q[7],q[3]; +cx q[1],q[8]; +cx q[4],q[10]; +cx q[11],q[2]; +cx q[9],q[6]; +cx q[0],q[5]; +cx q[10],q[1]; +cx q[8],q[11]; +cx q[2],q[0]; +cx q[6],q[4]; +cx q[3],q[9]; +cx q[5],q[7]; +cx q[11],q[10]; +cx q[8],q[9]; +cx q[0],q[3]; +cx q[4],q[2]; +cx q[1],q[7]; +cx q[6],q[5]; +cx q[9],q[0]; +cx q[10],q[8]; diff --git a/test/hybridmap/circuits/multi_qubit.qasm b/test/hybridmap/circuits/multi_qubit.qasm new file mode 100644 index 000000000..27510d869 --- /dev/null +++ b/test/hybridmap/circuits/multi_qubit.qasm @@ -0,0 +1,4 @@ +OPENQASM 2.0; +include "qelib1.inc"; +qreg q[10]; +ccccx q[0], q[1], q[2], q[3], q[4]; diff --git a/test/hybridmap/test_hybridmap.cpp b/test/hybridmap/test_hybridmap.cpp index 5472c12c2..df950450c 100644 --- a/test/hybridmap/test_hybridmap.cpp +++ b/test/hybridmap/test_hybridmap.cpp @@ -273,3 +273,16 @@ TEST(NeutralAtomMapperExceptions, ImpossibleSwaps) { EXPECT_THROW(auto circ = mapper.map(qc, na::InitialMapping::Identity), std::runtime_error); } + +TEST(NeutralAtomMapperExceptions, LongShuttling) { + const auto arch = + na::NeutralAtomArchitecture("architectures/rubidium_shuttling.json"); + na::MapperParameters p; + p.gateWeight = 0.0; + p.verbose = true; + na::NeutralAtomMapper mapper(arch, p); + qc::QuantumComputation qc = + qasm3::Importer::importf("circuits/long_random.qasm"); + const auto circ = mapper.map(qc, na::InitialMapping::Graph); + mapper.convertToAod(); +} From 5e0edc1295e4498cfb383587d0e18d2aa60461b2 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 6 Nov 2025 16:14:55 +0100 Subject: [PATCH 227/394] =?UTF-8?q?=F0=9F=8E=A8=20reworked=20some=20clang?= =?UTF-8?q?=20tidy=20errors.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HardwareQubits.cpp | 49 +++++++++++------------ src/hybridmap/HybridAnimation.cpp | 13 +++--- src/hybridmap/HybridNeutralAtomMapper.cpp | 19 ++++----- src/hybridmap/Mapping.cpp | 34 ++++++++-------- src/hybridmap/MoveToAodConverter.cpp | 29 ++++++-------- src/hybridmap/NeutralAtomArchitecture.cpp | 29 ++++++++------ 6 files changed, 85 insertions(+), 88 deletions(-) diff --git a/src/hybridmap/HardwareQubits.cpp b/src/hybridmap/HardwareQubits.cpp index ae0775fd3..4811064c6 100644 --- a/src/hybridmap/HardwareQubits.cpp +++ b/src/hybridmap/HardwareQubits.cpp @@ -21,7 +21,6 @@ #include #include #include -#include #include #include #include @@ -93,35 +92,31 @@ HardwareQubits::computeAllShortestPaths(const HwQubit q1, const HwQubit q2) const { std::vector allPaths; std::queue pathsQueue; - size_t shortestPathLength = -1; + auto shortestPathLength = std::numeric_limits::max(); - // Initialize the queue with the starting qubit pathsQueue.push(HwQubitsVector{q1}); while (!pathsQueue.empty()) { auto currentPath = pathsQueue.front(); pathsQueue.pop(); + if (currentPath.size() > shortestPathLength) { + continue; + } + HwQubit const currentQubit = currentPath.back(); - // Check if the destination is reached if (currentQubit == q2) { - if (shortestPathLength == -1 || - currentPath.size() == shortestPathLength) { + if (shortestPathLength == std::numeric_limits::max()) { shortestPathLength = currentPath.size(); + } + if (currentPath.size() == shortestPathLength) { allPaths.push_back(currentPath); - break; - } else if (currentPath.size() > shortestPathLength) { - // Since we use BFS, once a path longer than the shortest length is - // found, stop exploring - break; } continue; } - // Get nearby qubits and explore paths for (const auto& neighbor : this->getNearbyQubits(currentQubit)) { - // Avoid cycles by ensuring the neighbor isn't already in the current path if (std::find(currentPath.begin(), currentPath.end(), neighbor) == currentPath.end()) { auto newPath = currentPath; @@ -151,18 +146,24 @@ void HardwareQubits::move(HwQubit hwQubit, const CoordIndex newCoord) { } const auto oldCoord = hwToCoordIdx.at(hwQubit); - occupiedCoordinates.erase(std::find(occupiedCoordinates.begin(), - occupiedCoordinates.end(), oldCoord)); + if (auto it = std::ranges::find(occupiedCoordinates, oldCoord); + it != occupiedCoordinates.end()) { + occupiedCoordinates.erase(it); + } occupiedCoordinates.emplace_back(newCoord); freeCoordinates.emplace_back(oldCoord); - freeCoordinates.erase( - std::find(freeCoordinates.begin(), freeCoordinates.end(), newCoord)); + if (auto it2 = std::ranges::find(freeCoordinates, newCoord); + it2 != freeCoordinates.end()) { + freeCoordinates.erase(it2); + } // remove qubit from old nearby qubits const auto prevNearbyQubits = nearbyQubits.at(hwQubit); for (const auto& qubit : prevNearbyQubits) { - nearbyQubits.at(qubit).erase(std::find( - nearbyQubits.at(qubit).begin(), nearbyQubits.at(qubit).end(), hwQubit)); + auto& neigh = nearbyQubits.at(qubit); + if (auto it3 = std::ranges::find(neigh, hwQubit); it3 != neigh.end()) { + neigh.erase(it3); + } } // move qubit and compute new nearby qubits hwToCoordIdx.at(hwQubit) = newCoord; @@ -261,12 +262,9 @@ std::vector HardwareQubits::findClosestFreeCoord(CoordIndex coord, const Direction direction, const CoordIndices& excludedCoords) const { - // return the closest free coord in general - // and the closest free coord in the given direction std::vector freeCoordsInDirection; for (const auto& freeCoord : freeCoordinates) { - if (std::find(excludedCoords.begin(), excludedCoords.end(), freeCoord) != - excludedCoords.end()) { + if (std::ranges::find(excludedCoords, freeCoord) != excludedCoords.end()) { continue; } if (direction == arch->getVector(coord, freeCoord).direction) { @@ -277,8 +275,7 @@ HardwareQubits::findClosestFreeCoord(CoordIndex coord, // return all free coords except excluded auto allFreeCoords = freeCoordinates; for (const auto& excludedCoord : excludedCoords) { - if (const auto pos = std::find(allFreeCoords.begin(), allFreeCoords.end(), - excludedCoord); + if (const auto pos = std::ranges::find(allFreeCoords, excludedCoord); pos != allFreeCoords.end()) { allFreeCoords.erase(pos); } @@ -302,7 +299,7 @@ HwQubit HardwareQubits::getClosestQubit(const CoordIndex coord, HwQubit closestQubit = 0; auto minDistance = std::numeric_limits::max(); for (auto const& [qubit, idx] : hwToCoordIdx) { - if (ignored.find(qubit) != ignored.end()) { + if (ignored.contains(qubit)) { continue; } if (const auto distance = arch->getEuclideanDistance(coord, idx); diff --git a/src/hybridmap/HybridAnimation.cpp b/src/hybridmap/HybridAnimation.cpp index d8856ab2d..3691e66da 100644 --- a/src/hybridmap/HybridAnimation.cpp +++ b/src/hybridmap/HybridAnimation.cpp @@ -19,6 +19,7 @@ #include #include #include +#include // added #include #include #include @@ -90,11 +91,11 @@ std::string AnimationAtoms::opToNaViz(const std::unique_ptr& op, dynamic_cast(op.get())->getStarts(Dimension::Y); const auto endsY = dynamic_cast(op.get())->getEnds(Dimension::Y); - const auto CoordIndices = op->getTargets(); + const auto coordIndices = op->getTargets(); // renamed // use that coord indices are pairs of origin and target indices - for (size_t i = 0; i < CoordIndices.size(); i++) { + for (size_t i = 0; i < coordIndices.size(); i++) { if (i % 2 == 0) { - const auto coordIdx = CoordIndices[i]; + const auto coordIdx = coordIndices[i]; const auto id = coordIdxToId.at(coordIdx); bool foundX = false; auto newX = std::numeric_limits::max(); @@ -129,9 +130,9 @@ std::string AnimationAtoms::opToNaViz(const std::unique_ptr& op, idToCoord.at(id) = {newX, newY}; } else { // this is the target index -> update coordIdxToId - const auto coordIdx = CoordIndices[i]; - const auto id = coordIdxToId.at(CoordIndices[i - 1]); - coordIdxToId.erase(CoordIndices[i - 1]); + const auto coordIdx = coordIndices[i]; + const auto id = coordIdxToId.at(coordIndices[i - 1]); + coordIdxToId.erase(coordIndices[i - 1]); coordIdxToId[coordIdx] = id; } } diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index d66dc0c70..3a1d98a29 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -28,6 +28,7 @@ #include #include #include +#include // added #include #include #include @@ -183,11 +184,11 @@ void NeutralAtomMapper::applyPassBy(NeutralAtomLayer& frontLayer, if (this->parameters->verbose) { std::cout << "passby " << passBy.c1 << " " << passBy.c2 << '\n'; } - auto itT = std::find(targetCoords.begin(), targetCoords.end(), passBy.c1); + auto itT = std::ranges::find(targetCoords, passBy.c1); if (itT != targetCoords.end()) { *itT = passBy.c2 + arch->getNpositions(); } - auto itC = std::find(controlCoords.begin(), controlCoords.end(), passBy.c1); + auto itC = std::ranges::find(controlCoords, passBy.c1); if (itC != controlCoords.end()) { *itC = passBy.c2 + arch->getNpositions(); } @@ -416,13 +417,11 @@ void NeutralAtomMapper::applyFlyingAncilla(NeutralAtomLayer& frontLayer, mappedQc.h(ancQ1); mappedQc.move(ancQ1, ancQ2); - auto itT = - std::find(targetCoords.begin(), targetCoords.end(), allCoords[i]); + auto itT = std::ranges::find(targetCoords, allCoords[i]); if (itT != targetCoords.end()) { *itT = ancQ2; } - auto itC = - std::find(controlCoords.begin(), controlCoords.end(), allCoords[i]); + auto itC = std::ranges::find(controlCoords, allCoords[i]); if (itC != controlCoords.end()) { *itC = ancQ2; } @@ -681,10 +680,11 @@ NeutralAtomMapper::convertMoveCombToPassByComb(const MoveComb& moveComb) const { std::vector bestPbs; for (const auto move : moveComb.moves) { if (usedCoords.find(move.c1) != usedCoords.end()) { - bestPbs.emplace_back(AtomMove{move.c1, move.c2, true, false}); + bestPbs.emplace_back(AtomMove{ + .c1 = move.c1, .c2 = move.c2, .load1 = true, .load2 = false}); } } - return {bestPbs, moveComb.op}; + return PassByComb{.moves = bestPbs, .op = moveComb.op}; } qc::fp NeutralAtomMapper::swapCost( @@ -1676,7 +1676,8 @@ NeutralAtomMapper::compareSwapAndBridge(const Swap& bestSwap, this->parameters->lookaheadDepth); // bridge distance reduction - qc::fp const bridgeDistReduction = bestBridge.second.size() - 2; + qc::fp const bridgeDistReduction = + static_cast(bestBridge.second.size()) - 2; // fidelity comparison qc::fp const swapFidelity = this->arch->getGateAverageFidelity("swap") * diff --git a/src/hybridmap/Mapping.cpp b/src/hybridmap/Mapping.cpp index a54674fb8..bcc167e7f 100644 --- a/src/hybridmap/Mapping.cpp +++ b/src/hybridmap/Mapping.cpp @@ -55,11 +55,10 @@ std::vector Mapping::graphMatching() { hwGraph[i] = std::vector(neighbors.begin(), neighbors.end()); } for (auto& [qubit, neighbors] : hwGraph) { - std::sort(neighbors.begin(), neighbors.end(), - [this](const uint32_t a, const uint32_t b) { - return hwQubits.getNearbyQubits(a).size() > - hwQubits.getNearbyQubits(b).size(); - }); + std::ranges::sort(neighbors, [this](const uint32_t a, const uint32_t b) { + return hwQubits.getNearbyQubits(a).size() > + hwQubits.getNearbyQubits(b).size(); + }); } uint32_t hwCenter = std::numeric_limits::max(); @@ -88,11 +87,10 @@ std::vector Mapping::graphMatching() { } std::vector> neighbors(weightMap.begin(), weightMap.end()); - std::sort(neighbors.begin(), neighbors.end(), - [](const std::pair& a, - const std::pair& b) { - return a.second > b.second; - }); + std::ranges::sort(neighbors, [](const std::pair& a, + const std::pair& b) { + return a.second > b.second; + }); circGraph[qubit] = std::move(neighbors); } @@ -106,14 +104,14 @@ std::vector Mapping::graphMatching() { } nodes.emplace_back(i, std::make_pair(degree, weightSum)); } - std::sort(nodes.begin(), nodes.end(), - [](const std::pair>& a, - const std::pair>& b) { - if (a.second.first == b.second.first) { - return a.second.second > b.second.second; - } - return a.second.first > b.second.first; - }); + std::ranges::sort(nodes.begin(), nodes.end(), + [](const std::pair>& a, + const std::pair>& b) { + if (a.second.first == b.second.first) { + return a.second.second > b.second.second; + } + return a.second.first > b.second.first; + }); std::queue circGraphQueue; for (const auto& node : nodes) { circGraphQueue.push(node.first); diff --git a/src/hybridmap/MoveToAodConverter.cpp b/src/hybridmap/MoveToAodConverter.cpp index 68a557158..38c881e31 100644 --- a/src/hybridmap/MoveToAodConverter.cpp +++ b/src/hybridmap/MoveToAodConverter.cpp @@ -20,12 +20,12 @@ #include "na/entities/Location.hpp" #include +#include #include #include #include #include #include -#include #include #include @@ -77,7 +77,7 @@ AtomMove MoveToAodConverter::convertOpToMove(qc::Operation* get) { while (q2 >= arch.getNpositions()) { q2 -= arch.getNpositions(); } - return {q1, q2, load1, load2}; + return {.c1 = q1, .c2 = q2, .load1 = load1, .load2 = load2}; } void MoveToAodConverter::initFlyingAncillas() { std::vector coords; @@ -151,16 +151,11 @@ bool MoveToAodConverter::MoveGroup::canAddMove( return false; } // checks if the op can be executed in parallel - std::vector>* movesToCheck; - if (move.load1 || move.load2) { - movesToCheck = &moves; - } else { - movesToCheck = &movesFa; - } + const auto& movesToCheck = (move.load1 || move.load2) ? moves : movesFa; return std::ranges::all_of( - *movesToCheck, - [&move, &archArg](const std::pair opPair) { - auto moveGroup = opPair.first; + movesToCheck, + [&move, &archArg](const std::pair const& opPair) { + const auto& moveGroup = opPair.first; // check that passby and move are not in same group if (move.load1 != moveGroup.load1 || move.load2 != moveGroup.load2) { return false; @@ -322,7 +317,7 @@ MoveToAodConverter::canAddActivation( static_cast(dim == Dimension::X ? origin.x : origin.y); auto end = static_cast(dim == Dimension::X ? final.x : final.y); - const qc::fp delta = static_cast(end - start); + auto delta = static_cast(end - start); // Get Moves that start/end at the same position as the current move auto aodMovesActivation = activationHelper.getAodMovesFromInit(dim, start); @@ -439,8 +434,8 @@ MoveToAodConverter::processMoves( MoveGroup possibleNewMoveGroup; std::vector movesToRemove; - for (auto& movePair : moves) { - auto& move = movePair.first; + for (const auto& movePair : moves) { + const auto& move = movePair.first; const auto idx = movePair.second; auto origin = arch.getCoordinate(move.c1); auto target = arch.getCoordinate(move.c2); @@ -476,7 +471,6 @@ void MoveToAodConverter::processMovesFa( AodActivationHelper& aodDeactivationHelper) const { for (const auto& moveFaPair : movesFa) { const auto& moveFa = moveFaPair.first; - const auto idx = moveFaPair.second; auto origin = arch.getCoordinate(moveFa.c1); auto target = arch.getCoordinate(moveFa.c2); const auto v = arch.getVector(moveFa.c1, moveFa.c2); @@ -501,7 +495,8 @@ AodOperation MoveToAodConverter::MoveGroup::connectAodOperations( auto interD = aodActivationHelper.arch->getInterQubitDistance() / aodActivationHelper.arch->getNAodIntermediateLevels(); - std::vector dimensions = {na::Dimension::X, na::Dimension::Y}; + constexpr std::array dimensions{na::Dimension::X, + na::Dimension::Y}; // connect move operations for (const auto& activation : aodActivationHelper.allActivations) { @@ -718,7 +713,7 @@ MoveToAodConverter::AodActivationHelper::getAodOperations() const { if (type == qc::OpType::AodActivate) { return aodOperations; } - std::reverse(aodOperations.begin(), aodOperations.end()); + std::ranges::reverse(aodOperations); return aodOperations; } } // namespace na diff --git a/src/hybridmap/NeutralAtomArchitecture.cpp b/src/hybridmap/NeutralAtomArchitecture.cpp index 30c021d4f..33fcd7f40 100644 --- a/src/hybridmap/NeutralAtomArchitecture.cpp +++ b/src/hybridmap/NeutralAtomArchitecture.cpp @@ -28,6 +28,7 @@ #include #include #include +#include // added #include #include #include @@ -73,10 +74,10 @@ void NeutralAtomArchitecture::loadJson(const std::string& filename) { gateTimes.emplace(key, value); } // check if cz and h gates are present - if (gateTimes.find("cz") == gateTimes.end()) { + if (!gateTimes.contains("cz")) { gateTimes["cz"] = gateTimes["none"]; } - if (gateTimes.find("h") == gateTimes.end()) { + if (!gateTimes.contains("h")) { gateTimes["h"] = gateTimes["none"]; } this->parameters.gateTimes = gateTimes; @@ -85,10 +86,10 @@ void NeutralAtomArchitecture::loadJson(const std::string& filename) { jsonDataParameters["gateAverageFidelities"].items()) { gateAverageFidelities.emplace(key, value); } - if (gateAverageFidelities.find("cz") == gateAverageFidelities.end()) { + if (!gateAverageFidelities.contains("cz")) { gateAverageFidelities["cz"] = gateAverageFidelities["none"]; } - if (gateAverageFidelities.find("h") == gateAverageFidelities.end()) { + if (!gateAverageFidelities.contains("h")) { gateAverageFidelities["h"] = gateAverageFidelities["none"]; } this->parameters.gateAverageFidelities = gateAverageFidelities; @@ -295,10 +296,14 @@ std::string NeutralAtomArchitecture::getAnimationMachine( for (size_t colIdx = 0; colIdx < this->getNcolumns(); colIdx++) { for (size_t rowIdx = 0; rowIdx < this->getNrows(); rowIdx++) { const auto coordIdx = colIdx + (rowIdx * this->getNcolumns()); - animationMachine += - "trap trap" + std::to_string(coordIdx) + " {\n\tposition: (" + - std::to_string(colIdx * this->getInterQubitDistance()) + ", " + - std::to_string(rowIdx * this->getInterQubitDistance()) + ")\n}\n"; + animationMachine += "trap trap" + std::to_string(coordIdx) + + " {\n\tposition: (" + + std::to_string(static_cast(colIdx) * + this->getInterQubitDistance()) + + ", " + + std::to_string(static_cast(rowIdx) * + this->getInterQubitDistance()) + + ")\n}\n"; } } @@ -324,12 +329,12 @@ qc::fp NeutralAtomArchitecture::getOpTime(const qc::Operation* op) const { if (op->getType() == qc::OpType::P || op->getType() == qc::OpType::RZ) { // use time of theta = pi and linearly scale opName += "z"; - auto param = abs(op->getParameter().back()); - constexpr auto pi = 3.14159265358979323846; + auto param = std::abs(op->getParameter().back()); + constexpr auto pi = std::numbers::pi_v; while (param > pi) { - param = abs(param - (2 * pi)); + param = std::abs(param - (2 * pi)); } - return getGateTime(opName) * param / 3.14159265358979323846; + return getGateTime(opName) * param / std::numbers::pi_v; } opName += op->getName(); return getGateTime(opName); From 0c2e072c02f495ff2633d2f1bdf7fca98f78f594 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 6 Nov 2025 18:56:13 +0100 Subject: [PATCH 228/394] =?UTF-8?q?=F0=9F=8E=A8=20reworked=20pragma=20once?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HardwareQubits.hpp | 5 ++++- include/hybridmap/HybridAnimation.hpp | 5 ++++- include/hybridmap/HybridNeutralAtomMapper.hpp | 5 ++++- include/hybridmap/HybridSynthesisMapper.hpp | 5 ++++- include/hybridmap/Mapping.hpp | 5 ++++- include/hybridmap/MoveToAodConverter.hpp | 5 ++++- include/hybridmap/NeutralAtomArchitecture.hpp | 5 ++++- include/hybridmap/NeutralAtomDefinitions.hpp | 5 ++++- include/hybridmap/NeutralAtomLayer.hpp | 5 ++++- include/hybridmap/NeutralAtomScheduler.hpp | 5 ++++- include/hybridmap/NeutralAtomUtils.hpp | 5 ++++- 11 files changed, 44 insertions(+), 11 deletions(-) diff --git a/include/hybridmap/HardwareQubits.hpp b/include/hybridmap/HardwareQubits.hpp index 3182aeb1d..c9271554c 100644 --- a/include/hybridmap/HardwareQubits.hpp +++ b/include/hybridmap/HardwareQubits.hpp @@ -8,7 +8,8 @@ * Licensed under the MIT License */ -#pragma once +#ifndef HYBRIDMAP_HARDWARE_QUBITS_HPP +#define HYBRIDMAP_HARDWARE_QUBITS_HPP #include "datastructures/SymmetricMatrix.hpp" #include "hybridmap/NeutralAtomArchitecture.hpp" @@ -339,3 +340,5 @@ class HardwareQubits { } }; } // namespace na + +#endif // HYBRIDMAP_HARDWARE_QUBITS_HPP diff --git a/include/hybridmap/HybridAnimation.hpp b/include/hybridmap/HybridAnimation.hpp index 281de0f1a..ed517fb33 100644 --- a/include/hybridmap/HybridAnimation.hpp +++ b/include/hybridmap/HybridAnimation.hpp @@ -8,7 +8,8 @@ * Licensed under the MIT License */ -#pragma once +#ifndef HYBRIDMAP_HYBRID_ANIMATION_HPP +#define HYBRIDMAP_HYBRID_ANIMATION_HPP #include "NeutralAtomArchitecture.hpp" #include "NeutralAtomDefinitions.hpp" @@ -45,3 +46,5 @@ class AnimationAtoms { }; } // namespace na + +#endif // HYBRIDMAP_HYBRID_ANIMATION_HPP diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index dc4464a6f..5199c3f8e 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -8,7 +8,8 @@ * Licensed under the MIT License */ -#pragma once +#ifndef HYBRIDMAP_HYBRID_NEUTRAL_ATOM_MAPPER_HPP +#define HYBRIDMAP_HYBRID_NEUTRAL_ATOM_MAPPER_HPP #include "circuit_optimizer/CircuitOptimizer.hpp" #include "hybridmap/HardwareQubits.hpp" @@ -658,3 +659,5 @@ class NeutralAtomMapper { }; } // namespace na + +#endif // HYBRIDMAP_HYBRID_NEUTRAL_ATOM_MAPPER_HPP diff --git a/include/hybridmap/HybridSynthesisMapper.hpp b/include/hybridmap/HybridSynthesisMapper.hpp index f48f52714..648dfa9d7 100644 --- a/include/hybridmap/HybridSynthesisMapper.hpp +++ b/include/hybridmap/HybridSynthesisMapper.hpp @@ -13,7 +13,8 @@ // See README.md or go to https://github.com/cda-tum/qmap for more information. // -#pragma once +#ifndef HYBRIDMAP_HYBRID_SYNTHESIS_MAPPER_HPP +#define HYBRIDMAP_HYBRID_SYNTHESIS_MAPPER_HPP #include "HybridNeutralAtomMapper.hpp" #include "NeutralAtomArchitecture.hpp" @@ -147,3 +148,5 @@ class HybridSynthesisMapper : public NeutralAtomMapper { [[nodiscard]] AdjacencyMatrix getCircuitAdjacencyMatrix() const; }; } // namespace na + +#endif // HYBRIDMAP_HYBRID_SYNTHESIS_MAPPER_HPP diff --git a/include/hybridmap/Mapping.hpp b/include/hybridmap/Mapping.hpp index c6af1c715..5871f33e8 100644 --- a/include/hybridmap/Mapping.hpp +++ b/include/hybridmap/Mapping.hpp @@ -8,7 +8,8 @@ * Licensed under the MIT License */ -#pragma once +#ifndef HYBRIDMAP_MAPPING_HPP +#define HYBRIDMAP_MAPPING_HPP #include "HardwareQubits.hpp" #include "NeutralAtomArchitecture.hpp" @@ -175,3 +176,5 @@ class Mapping { }; } // namespace na + +#endif // HYBRIDMAP_MAPPING_HPP diff --git a/include/hybridmap/MoveToAodConverter.hpp b/include/hybridmap/MoveToAodConverter.hpp index 854e97451..8409d04a5 100644 --- a/include/hybridmap/MoveToAodConverter.hpp +++ b/include/hybridmap/MoveToAodConverter.hpp @@ -8,7 +8,8 @@ * Licensed under the MIT License */ -#pragma once +#ifndef HYBRIDMAP_MOVE_TO_AOD_CONVERTER_HPP +#define HYBRIDMAP_MOVE_TO_AOD_CONVERTER_HPP #include "hybridmap/HardwareQubits.hpp" #include "hybridmap/NeutralAtomArchitecture.hpp" @@ -391,3 +392,5 @@ class MoveToAodConverter { }; } // namespace na + +#endif // HYBRIDMAP_MOVE_TO_AOD_CONVERTER_HPP diff --git a/include/hybridmap/NeutralAtomArchitecture.hpp b/include/hybridmap/NeutralAtomArchitecture.hpp index 8b83afe00..902c5160b 100644 --- a/include/hybridmap/NeutralAtomArchitecture.hpp +++ b/include/hybridmap/NeutralAtomArchitecture.hpp @@ -8,7 +8,8 @@ * Licensed under the MIT License */ -#pragma once +#ifndef HYBRIDMAP_NEUTRAL_ATOM_ARCHITECTURE_HPP +#define HYBRIDMAP_NEUTRAL_ATOM_ARCHITECTURE_HPP #include "datastructures/SymmetricMatrix.hpp" #include "hybridmap/NeutralAtomDefinitions.hpp" @@ -582,3 +583,5 @@ class NeutralAtomArchitecture { }; } // namespace na + +#endif // HYBRIDMAP_NEUTRAL_ATOM_ARCHITECTURE_HPP diff --git a/include/hybridmap/NeutralAtomDefinitions.hpp b/include/hybridmap/NeutralAtomDefinitions.hpp index 61e029eb8..7147e49e3 100644 --- a/include/hybridmap/NeutralAtomDefinitions.hpp +++ b/include/hybridmap/NeutralAtomDefinitions.hpp @@ -8,7 +8,8 @@ * Licensed under the MIT License */ -#pragma once +#ifndef HYBRIDMAP_NEUTRAL_ATOM_DEFINITIONS_HPP +#define HYBRIDMAP_NEUTRAL_ATOM_DEFINITIONS_HPP #include "datastructures/SymmetricMatrix.hpp" #include "ir/Definitions.hpp" @@ -70,3 +71,5 @@ using GateList = std::vector; using GateLists = std::vector; } // namespace na + +#endif // HYBRIDMAP_NEUTRAL_ATOM_DEFINITIONS_HPP diff --git a/include/hybridmap/NeutralAtomLayer.hpp b/include/hybridmap/NeutralAtomLayer.hpp index 4a99849e2..04fc4114a 100644 --- a/include/hybridmap/NeutralAtomLayer.hpp +++ b/include/hybridmap/NeutralAtomLayer.hpp @@ -8,7 +8,8 @@ * Licensed under the MIT License */ -#pragma once +#ifndef HYBRIDMAP_NEUTRAL_ATOM_LAYER_HPP +#define HYBRIDMAP_NEUTRAL_ATOM_LAYER_HPP #include "hybridmap/NeutralAtomDefinitions.hpp" #include "ir/Definitions.hpp" @@ -105,3 +106,5 @@ bool commutesWithAtQubit(const GateList& layer, const qc::Operation* opPointer, bool commuteAtQubit(const qc::Operation* opPointer1, const qc::Operation* opPointer2, const qc::Qubit& qubit); } // namespace na + +#endif // HYBRIDMAP_NEUTRAL_ATOM_LAYER_HPP diff --git a/include/hybridmap/NeutralAtomScheduler.hpp b/include/hybridmap/NeutralAtomScheduler.hpp index fd6d998c4..c4786e362 100644 --- a/include/hybridmap/NeutralAtomScheduler.hpp +++ b/include/hybridmap/NeutralAtomScheduler.hpp @@ -8,7 +8,8 @@ * Licensed under the MIT License */ -#pragma once +#ifndef HYBRIDMAP_NEUTRAL_ATOM_SCHEDULER_HPP +#define HYBRIDMAP_NEUTRAL_ATOM_SCHEDULER_HPP #include "hybridmap/NeutralAtomArchitecture.hpp" #include "hybridmap/NeutralAtomDefinitions.hpp" @@ -134,3 +135,5 @@ class NeutralAtomScheduler { }; } // namespace na + +#endif // HYBRIDMAP_NEUTRAL_ATOM_SCHEDULER_HPP diff --git a/include/hybridmap/NeutralAtomUtils.hpp b/include/hybridmap/NeutralAtomUtils.hpp index 2e35f26bd..32fd9b16e 100644 --- a/include/hybridmap/NeutralAtomUtils.hpp +++ b/include/hybridmap/NeutralAtomUtils.hpp @@ -8,7 +8,8 @@ * Licensed under the MIT License */ -#pragma once +#ifndef HYBRIDMAP_NEUTRAL_ATOM_UTILS_HPP +#define HYBRIDMAP_NEUTRAL_ATOM_UTILS_HPP #include "circuit_optimizer/CircuitOptimizer.hpp" #include "hybridmap/NeutralAtomDefinitions.hpp" @@ -270,3 +271,5 @@ class BridgeCircuits { bridgeExpand(const qc::QuantumComputation& qcBridge, size_t qubit); }; } // namespace na + +#endif // HYBRIDMAP_NEUTRAL_ATOM_UTILS_HPP From a060d77e20104164c4be679d868367d5a931942a Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 6 Nov 2025 19:07:06 +0100 Subject: [PATCH 229/394] =?UTF-8?q?=F0=9F=8E=A8=20reworked=20clang=20tidy?= =?UTF-8?q?=20HardwareQubits?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HardwareQubits.hpp | 29 +++++++++++++--------------- src/hybridmap/HardwareQubits.cpp | 14 +++++++------- 2 files changed, 20 insertions(+), 23 deletions(-) diff --git a/include/hybridmap/HardwareQubits.hpp b/include/hybridmap/HardwareQubits.hpp index c9271554c..ae7600a6a 100644 --- a/include/hybridmap/HardwareQubits.hpp +++ b/include/hybridmap/HardwareQubits.hpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -60,7 +61,7 @@ class HardwareQubits { /** * @brief Initializes the nearby qubits for each hardware qubit. * @details Nearby qubits are the qubits that are closer than the interaction - * radius. Therefore they can be swapped with a single swap operation. + * radius. Therefore, they can be swapped with a single swap operation. */ void initNearbyQubits(); /** @@ -93,11 +94,10 @@ class HardwareQubits { HardwareQubits() = default; explicit HardwareQubits( const NeutralAtomArchitecture& architecture, const CoordIndex nQubits = 0, - const InitialCoordinateMapping initialCoordinateMapping = - InitialCoordinateMapping::Trivial, + const InitialCoordinateMapping initialCoordinateMapping = Trivial, uint32_t seed = 0) - : arch(&architecture) { - this->nQubits = nQubits; + : arch(&architecture), nQubits(nQubits) { + swapDistances = qc::SymmetricMatrix(this->nQubits); switch (initialCoordinateMapping) { @@ -115,7 +115,7 @@ class HardwareQubits { seed = std::random_device()(); } std::mt19937 g(seed); - std::shuffle(indices.begin(), indices.end(), g); + std::ranges::shuffle(indices, g); for (uint32_t i = 0; i < this->nQubits; ++i) { hwToCoordIdx.emplace(i, indices[i]); occupiedCoordinates.emplace_back(indices[i]); @@ -126,8 +126,8 @@ class HardwareQubits { initNearbyQubits(); for (uint32_t i = 0; i < architecture.getNpositions(); ++i) { - if (std::find(occupiedCoordinates.begin(), occupiedCoordinates.end(), - i) == occupiedCoordinates.end()) { + if (std::ranges::find(occupiedCoordinates, i) == + occupiedCoordinates.end()) { freeCoordinates.emplace_back(i); } } @@ -145,12 +145,9 @@ class HardwareQubits { * @param idx The coordinate index. * @return Boolean indicating if the hardware qubit is mapped to a coordinate. */ - [[nodiscard]] bool isMapped(CoordIndex idx) const { - if (std::find(occupiedCoordinates.begin(), occupiedCoordinates.end(), - idx) != occupiedCoordinates.end()) { - return true; - } - return false; + [[nodiscard]] bool isMapped(const CoordIndex idx) const { + return std::ranges::find(occupiedCoordinates, idx) != + occupiedCoordinates.end(); } /** * @brief Updates mapping after moving a hardware qubit to a coordinate. @@ -172,7 +169,7 @@ class HardwareQubits { swapDistances(i, hwQubit) = -1; } nearbyQubits.erase(hwQubit); - for (auto& [qubit, nearby] : nearbyQubits) { + for (auto& nearby : nearbyQubits | std::views::values) { nearby.erase(hwQubit); } } @@ -319,7 +316,7 @@ class HardwareQubits { const CoordIndices& excludedCoords = {}) const; [[nodiscard]] HwQubit getClosestQubit(CoordIndex coord, - HwQubits ignored) const; + const HwQubits& ignored) const; // Blocking /** diff --git a/src/hybridmap/HardwareQubits.cpp b/src/hybridmap/HardwareQubits.cpp index 4811064c6..3dcdc1d8f 100644 --- a/src/hybridmap/HardwareQubits.cpp +++ b/src/hybridmap/HardwareQubits.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -117,8 +118,7 @@ HardwareQubits::computeAllShortestPaths(const HwQubit q1, } for (const auto& neighbor : this->getNearbyQubits(currentQubit)) { - if (std::find(currentPath.begin(), currentPath.end(), neighbor) == - currentPath.end()) { + if (std::ranges::find(currentPath, neighbor) == currentPath.end()) { auto newPath = currentPath; newPath.push_back(neighbor); pathsQueue.push(newPath); @@ -139,20 +139,20 @@ void HardwareQubits::move(HwQubit hwQubit, const CoordIndex newCoord) { throw std::runtime_error("Invalid coordinate"); } // check if new coordinate is already occupied - for (const auto& [qubit, coord] : hwToCoordIdx) { + for (const auto& coord : hwToCoordIdx | std::views::values) { if (coord == newCoord) { throw std::runtime_error("Coordinate already occupied"); } } const auto oldCoord = hwToCoordIdx.at(hwQubit); - if (auto it = std::ranges::find(occupiedCoordinates, oldCoord); + if (const auto it = std::ranges::find(occupiedCoordinates, oldCoord); it != occupiedCoordinates.end()) { occupiedCoordinates.erase(it); } occupiedCoordinates.emplace_back(newCoord); freeCoordinates.emplace_back(oldCoord); - if (auto it2 = std::ranges::find(freeCoordinates, newCoord); + if (const auto it2 = std::ranges::find(freeCoordinates, newCoord); it2 != freeCoordinates.end()) { freeCoordinates.erase(it2); } @@ -259,7 +259,7 @@ std::set HardwareQubits::getNearbyOccupiedCoordinatesByCoord( } std::vector -HardwareQubits::findClosestFreeCoord(CoordIndex coord, +HardwareQubits::findClosestFreeCoord(const CoordIndex coord, const Direction direction, const CoordIndices& excludedCoords) const { std::vector freeCoordsInDirection; @@ -295,7 +295,7 @@ HardwareQubits::findClosestFreeCoord(CoordIndex coord, } HwQubit HardwareQubits::getClosestQubit(const CoordIndex coord, - HwQubits ignored) const { + const HwQubits& ignored) const { HwQubit closestQubit = 0; auto minDistance = std::numeric_limits::max(); for (auto const& [qubit, idx] : hwToCoordIdx) { From 0dd8aaa031c22a792c8e99022fce2da38feeb4f8 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 6 Nov 2025 19:18:40 +0100 Subject: [PATCH 230/394] =?UTF-8?q?=F0=9F=8E=A8=20reworked=20clang=20tidy?= =?UTF-8?q?=20Animation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridAnimation.hpp | 7 +++--- src/hybridmap/HybridAnimation.cpp | 35 +++++++++++++++------------ 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/include/hybridmap/HybridAnimation.hpp b/include/hybridmap/HybridAnimation.hpp index ed517fb33..38d32dcb9 100644 --- a/include/hybridmap/HybridAnimation.hpp +++ b/include/hybridmap/HybridAnimation.hpp @@ -16,7 +16,6 @@ #include "ir/Definitions.hpp" #include "ir/operations/Operation.hpp" -#include #include #include #include @@ -27,7 +26,7 @@ class AnimationAtoms { protected: std::map coordIdxToId; std::map> idToCoord; - const NeutralAtomArchitecture& arch; + const NeutralAtomArchitecture* arch; void initPositions(const std::map& initHwPos, const std::map& initFaPos); @@ -35,8 +34,8 @@ class AnimationAtoms { public: AnimationAtoms(const std::map& initHwPos, const std::map& initFaPos, - const NeutralAtomArchitecture& arch) - : arch(arch) { + const NeutralAtomArchitecture& architecture) + : arch(&architecture) { initPositions(initHwPos, initFaPos); } diff --git a/src/hybridmap/HybridAnimation.cpp b/src/hybridmap/HybridAnimation.cpp index 3691e66da..5185ca529 100644 --- a/src/hybridmap/HybridAnimation.cpp +++ b/src/hybridmap/HybridAnimation.cpp @@ -17,40 +17,41 @@ #include "ir/operations/OpType.hpp" #include -#include #include -#include // added +#include #include #include -#include #include namespace na { void AnimationAtoms::initPositions( const std::map& initHwPos, const std::map& initFaPos) { - const auto nCols = arch.getNcolumns(); + const auto nCols = arch->getNcolumns(); for (const auto& [id, coord] : initHwPos) { coordIdxToId[coord] = id; const auto column = coord % nCols; const auto row = coord / nCols; - idToCoord[id] = {column * arch.getInterQubitDistance(), - row * arch.getInterQubitDistance()}; + idToCoord[id] = {column * arch->getInterQubitDistance(), + row * arch->getInterQubitDistance()}; } - auto flyingAncillaidxPlusOne = 0; + auto flyingAncillaIdxPlusOne = 0; + const auto hwCount = static_cast(initHwPos.size()); for (const auto& [id, coord] : initFaPos) { - flyingAncillaidxPlusOne++; - coordIdxToId[coord + (2 * arch.getNpositions())] = id + initHwPos.size(); + flyingAncillaIdxPlusOne++; + coordIdxToId[static_cast( + coord + static_cast(2 * arch->getNpositions()))] = + static_cast(id + hwCount); const auto column = coord % nCols; const auto row = coord / nCols; const auto offset = - arch.getInterQubitDistance() / arch.getNAodIntermediateLevels(); - idToCoord[id + initHwPos.size()] = { - (column * arch.getInterQubitDistance()) + - flyingAncillaidxPlusOne * offset, - (row * arch.getInterQubitDistance()) + - flyingAncillaidxPlusOne * offset}; + arch->getInterQubitDistance() / arch->getNAodIntermediateLevels(); + idToCoord[static_cast(id + hwCount)] = { + (column * arch->getInterQubitDistance()) + + flyingAncillaIdxPlusOne * offset, + (row * arch->getInterQubitDistance()) + + flyingAncillaIdxPlusOne * offset}; } } @@ -127,7 +128,9 @@ std::string AnimationAtoms::opToNaViz(const std::unique_ptr& op, opString += "@" + std::to_string(startTime) + " move (" + std::to_string(newX) + ", " + std::to_string(newY) + ") atom" + std::to_string(id) + "\n"; - idToCoord.at(id) = {newX, newY}; + auto& coords = idToCoord.at(id); + coords.first = newX; + coords.second = newY; } else { // this is the target index -> update coordIdxToId const auto coordIdx = coordIndices[i]; From a299a2927730c8af5c05345398671edac65b247a Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 6 Nov 2025 19:24:33 +0100 Subject: [PATCH 231/394] =?UTF-8?q?=F0=9F=8E=A8=20reworked=20clang=20tidy?= =?UTF-8?q?=20Utils?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/NeutralAtomUtils.hpp | 12 ++++++------ src/hybridmap/NeutralAtomUtils.cpp | 23 ++++++++++------------- 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/include/hybridmap/NeutralAtomUtils.hpp b/include/hybridmap/NeutralAtomUtils.hpp index 32fd9b16e..ab6bc0d35 100644 --- a/include/hybridmap/NeutralAtomUtils.hpp +++ b/include/hybridmap/NeutralAtomUtils.hpp @@ -44,10 +44,10 @@ initialCoordinateMappingFromString( const std::string& initialCoordinateMapping) { if (initialCoordinateMapping == "trivial" || initialCoordinateMapping == "0") { - return InitialCoordinateMapping::Trivial; + return Trivial; } if (initialCoordinateMapping == "random" || initialCoordinateMapping == "1") { - return InitialCoordinateMapping::Random; + return Random; } throw std::invalid_argument("Invalid initial coordinate mapping value: " + initialCoordinateMapping); @@ -56,10 +56,10 @@ initialCoordinateMappingFromString( [[maybe_unused]] static InitialMapping initialMappingFromString(const std::string& initialMapping) { if (initialMapping == "identity" || initialMapping == "0") { - return InitialMapping::Identity; + return Identity; } if (initialMapping == "graph" || initialMapping == "1") { - return InitialMapping::Graph; + return Graph; } throw std::invalid_argument("Invalid initial mapping value: " + initialMapping); @@ -150,11 +150,11 @@ struct MoveComb { : moves(std::move(mov)), cost(c), op(o), bestPos(std::move(pos)) {} MoveComb(AtomMove mov, const qc::fp c, const qc::Operation* o, CoordIndices pos) - : moves({std::move(mov)}), cost(c), op(o), bestPos(std::move(pos)) {} + : moves({mov}), cost(c), op(o), bestPos(std::move(pos)) {} MoveComb() = default; explicit MoveComb(std::vector mov) : moves(std::move(mov)) {} - explicit MoveComb(AtomMove mov) : moves({std::move(mov)}) {} + explicit MoveComb(AtomMove mov) : moves({mov}) {} // implement == operator for AtomMove [[nodiscard]] bool operator==(const MoveComb& other) const { diff --git a/src/hybridmap/NeutralAtomUtils.cpp b/src/hybridmap/NeutralAtomUtils.cpp index 6301453eb..9a0157ba2 100644 --- a/src/hybridmap/NeutralAtomUtils.cpp +++ b/src/hybridmap/NeutralAtomUtils.cpp @@ -56,9 +56,9 @@ bool MoveVector::overlap(const MoveVector& other) const { const auto overlapYSecondEnd = secondEndY >= firstStartY && secondEndY <= firstEndY; - return (overlapXFirstStart || overlapXFirstEnd || overlapXSecondStart || - overlapXSecondEnd || overlapYFirstStart || overlapYFirstEnd || - overlapYSecondStart || overlapYSecondEnd); + return overlapXFirstStart || overlapXFirstEnd || overlapXSecondStart || + overlapXSecondEnd || overlapYFirstStart || overlapYFirstEnd || + overlapYSecondStart || overlapYSecondEnd; } bool MoveVector::include(const MoveVector& other) const { @@ -71,10 +71,8 @@ bool MoveVector::include(const MoveVector& other) const { const auto secondStartY = std::min(other.yStart, other.yEnd); const auto secondEndY = std::max(other.yStart, other.yEnd); - const auto includeX = - (secondStartX < firstStartX) && (firstEndX < secondEndX); - const auto includeY = - (secondStartY < firstStartY) && (firstEndY < secondEndY); + const auto includeX = secondStartX < firstStartX && firstEndX < secondEndX; + const auto includeY = secondStartY < firstStartY && firstEndY < secondEndY; return includeX || includeY; } @@ -124,10 +122,9 @@ void BridgeCircuits::computeGates(const size_t length) { } // find max depth const auto maxHcZ = - std::max_element(hsCzsPerQubit.begin(), hsCzsPerQubit.end(), - [](const auto& a, const auto& b) { - return a.first + a.second < b.first + b.second; - }); + std::ranges::max_element(hsCzsPerQubit, [](const auto& a, const auto& b) { + return a.first + a.second < b.first + b.second; + }); hDepth[length] = maxHcZ->first; czDepth[length] = maxHcZ->second; } @@ -141,7 +138,7 @@ void BridgeCircuits::computeBridgeCircuit(const size_t length) { qcBridge = recursiveBridgeIncrease(qcBridge, length - 3); // convert to CZ on qubit 0 - qcBridge.h(qcBridge.getNqubits() - 1); + qcBridge.h(static_cast(qcBridge.getNqubits() - 1)); qcBridge.insert(qcBridge.begin(), std::make_unique( qcBridge.getNqubits() - 1, qc::H)); @@ -162,7 +159,7 @@ BridgeCircuits::recursiveBridgeIncrease(qc::QuantumComputation qcBridge, gates[*gate->getUsedQubits().begin()]++; } const auto minIndex = - std::min_element(gates.begin(), gates.end()) - gates.begin(); + static_cast(std::ranges::min_element(gates) - gates.begin()); qcBridge = bridgeExpand(qcBridge, minIndex); From 91e70b9e9a21e0c78e28be3d7ac9b49800c04094 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 6 Nov 2025 19:44:29 +0100 Subject: [PATCH 232/394] =?UTF-8?q?=F0=9F=8E=A8=20reworked=20clang=20tidy?= =?UTF-8?q?=20Scheduler?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/NeutralAtomScheduler.hpp | 91 ++++++++++++++++++++-- src/hybridmap/NeutralAtomScheduler.cpp | 11 +-- 2 files changed, 90 insertions(+), 12 deletions(-) diff --git a/include/hybridmap/NeutralAtomScheduler.hpp b/include/hybridmap/NeutralAtomScheduler.hpp index c4786e362..677646c37 100644 --- a/include/hybridmap/NeutralAtomScheduler.hpp +++ b/include/hybridmap/NeutralAtomScheduler.hpp @@ -39,6 +39,18 @@ struct SchedulerResults { uint32_t nAodActivate = 0; uint32_t nAodMove = 0; + /** + * @brief Create a new results object. + * @param executionTime The overall makespan (end time of the last operation). + * @param idleTime The sum of all idling time across qubits: end_time * + * n_qubits - total_gate_time. + * @param gateFidelities Product of native gate fidelities (excludes + * decoherence). + * @param fidelities Overall fidelity including decoherence during idle time. + * @param cZs The number of CZ operations encountered. + * @param aodActivate The number of AOD activation operations encountered. + * @param aodMove The number of AOD shuttling/move operations encountered. + */ SchedulerResults(const qc::fp executionTime, const qc::fp idleTime, const qc::fp gateFidelities, const qc::fp fidelities, const uint32_t cZs, const uint32_t aodActivate, @@ -47,12 +59,25 @@ struct SchedulerResults { totalGateFidelities(gateFidelities), totalFidelities(fidelities), nCZs(cZs), nAodActivate(aodActivate), nAodMove(aodMove) {} + /** + * @brief Export a compact CSV line with execution time, idle time, and + * overall fidelity. + * @return A string in the format "totalExecutionTime, totalIdleTime, + * totalFidelities". + */ [[nodiscard]] std::string toCsv() const { std::stringstream ss; ss << totalExecutionTime << ", " << totalIdleTime << "," << totalFidelities; return ss.str(); } + /** + * @brief Export selected metrics to a key-value map. + * @details Currently includes totalExecutionTime, totalIdleTime, + * totalGateFidelities, totalFidelities, and nCZs. Counts for + * nAodActivate/nAodMove are not included. + * @return An unordered_map from metric names to values. + */ [[maybe_unused]] [[nodiscard]] std::unordered_map toMap() const { std::unordered_map result; @@ -91,9 +116,21 @@ class NeutralAtomScheduler { * the earliest possible time slot for execution. If the gate is a multi qubit * gate, also the blocking of other qubits is taken into consideration. The * execution times are read from the neutral atom architecture. - * @param qc Quantum circuit to schedule - * @param verbose If true, prints additional information - * @return SchedulerResults + * Blocking windows for multi-qubit Rydberg interactions and AOD moves are + * respected; optional animation traces can be produced. + * + * @param qc Quantum circuit to schedule. + * @param initHwPos Initial positions of atoms on the hardware grid (by + * hardware qubit). + * @param initFaPos Initial positions of the addressing focus array (by + * hardware qubit). + * @param verbose If true, prints progress and a summary to std::cout. + * @param createAnimationCsv If true, records animation artifacts for + * visualization. + * @param shuttlingSpeedFactor Scale factor applied to AOD + * move/activate/deactivate durations (e.g., 0.5 for twice as fast shuttling). + * @return Aggregated scheduling results including makespan, idle time, and + * fidelities. */ SchedulerResults schedule(const qc::QuantumComputation& qc, const std::map& initHwPos, @@ -101,10 +138,38 @@ class NeutralAtomScheduler { bool verbose, bool createAnimationCsv = false, qc::fp shuttlingSpeedFactor = 1.0); - std::string getAnimationMachine() const { return animationMachine; } - std::string getAnimationViz() const { return animation; } - std::string getAnimationStyle() const { return animationStyle; } + /** + * @brief Get the machine description for the animation output. + * @note Only populated when schedule(...) was run with + * createAnimationCsv=true. + */ + [[nodiscard]] std::string getAnimationMachine() const { + return animationMachine; + } + /** + * @brief Get the visualization event log in .naviz format. + * @note Only populated when schedule(...) was run with + * createAnimationCsv=true. + */ + [[nodiscard]] std::string getAnimationViz() const { return animation; } + /** + * @brief Get the visualization style sheet for the animation. + * @note Only populated when schedule(...) was run with + * createAnimationCsv=true. + */ + [[nodiscard]] std::string getAnimationStyle() const { return animationStyle; } + /** + * @brief Persist the generated animation artifacts to disk. + * @details Creates three files next to the provided filename (without its + * extension): + * - .naviz (visualization event log) + * - .namachine (machine/layout description) + * - .nastyle (visual style configuration) + * The contents are derived from + * getAnimationViz()/getAnimationMachine()/getAnimationStyle(). + * @param filename Base filename whose stem is reused for the three outputs. + */ void saveAnimationFiles(const std::string& filename) const { const auto filenameWithoutExtension = filename.substr(0, filename.find_last_of('.')); @@ -113,7 +178,7 @@ class NeutralAtomScheduler { const auto filenameStyle = filenameWithoutExtension + ".nastyle"; // save animation - std::ofstream file = std::ofstream(filenameViz); + auto file = std::ofstream(filenameViz); file << getAnimationViz(); file.close(); // save machine @@ -127,6 +192,18 @@ class NeutralAtomScheduler { } // Helper Print functions + /** + * @brief Print a human-readable summary of scheduling results. + * @param totalExecutionTimes Per-qubit accumulated execution/makespan + * timeline. + * @param totalIdleTime Sum of idle time across all qubits. + * @param totalGateFidelities Product of native gate fidelities. + * @param totalFidelities Overall fidelity including decoherence during idle + * time. + * @param nCZs Number of CZ gates in the circuit. + * @param nAodActivate Number of AOD activation operations. + * @param nAodMove Number of AOD move operations. + */ static void printSchedulerResults(std::vector& totalExecutionTimes, qc::fp totalIdleTime, qc::fp totalGateFidelities, diff --git a/src/hybridmap/NeutralAtomScheduler.cpp b/src/hybridmap/NeutralAtomScheduler.cpp index 2c11af30a..2e39bbc29 100644 --- a/src/hybridmap/NeutralAtomScheduler.cpp +++ b/src/hybridmap/NeutralAtomScheduler.cpp @@ -39,10 +39,11 @@ na::SchedulerResults na::NeutralAtomScheduler::schedule( std::cout << "\n* schedule start!\n"; } - std::vector totalExecutionTimes(3 * arch->getNpositions(), 0); - // saves for each coord the time slots that are blocked by a multi qubit gate - std::vector rydbergBlockedQubitsTimes( - 3 * arch->getNpositions(), std::deque>()); + const auto nPositions = static_cast(arch->getNpositions()); + const std::size_t numCoords = 3ULL * nPositions; + std::vector totalExecutionTimes(numCoords, qc::fp{0}); + std::vector>> rydbergBlockedQubitsTimes( + numCoords); qc::fp aodLastBlockedTime = 0; qc::fp totalGateTime = 0; qc::fp totalGateFidelities = 1; @@ -172,7 +173,7 @@ na::SchedulerResults na::NeutralAtomScheduler::schedule( const auto maxExecutionTime = *std::ranges::max_element(totalExecutionTimes); const auto totalIdleTime = - (maxExecutionTime * arch->getNqubits()) - totalGateTime; + maxExecutionTime * arch->getNqubits() - totalGateTime; const auto totalFidelities = totalGateFidelities * std::exp(-totalIdleTime / arch->getDecoherenceTime()); From 1bfe788c7d5e40a454dc28c780c3a5b7af410d86 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 6 Nov 2025 19:58:35 +0100 Subject: [PATCH 233/394] =?UTF-8?q?=F0=9F=8E=A8=20reworked=20clang=20tidy?= =?UTF-8?q?=20AtomLayer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/NeutralAtomLayer.hpp | 100 +++++++++++++++++++------ src/hybridmap/NeutralAtomLayer.cpp | 56 +++++++------- 2 files changed, 104 insertions(+), 52 deletions(-) diff --git a/include/hybridmap/NeutralAtomLayer.hpp b/include/hybridmap/NeutralAtomLayer.hpp index 04fc4114a..0fe4a4968 100644 --- a/include/hybridmap/NeutralAtomLayer.hpp +++ b/include/hybridmap/NeutralAtomLayer.hpp @@ -41,34 +41,60 @@ class NeutralAtomLayer { DAGIterators ends; GateList gates; GateList newGates; - GateList mappedSingleQubitGates; GateLists candidates; uint32_t lookaheadDepth; bool isFrontLayer; /** - * @brief Updates the gates for the given qubits - * @details The function iterates over the qc::DAG and updates the gates for - * the given qubits as far es possible. - * @param qubitsToUpdate The qubits that have been updated - * @param commuteWith Gates the new gates should commute with + * @brief Update layer state for a set of qubits. + * @details Advances the internal iterators of the qc::DAG for the provided + * qubits, updates the per-qubit candidate lists, and moves operations that + * are ready across all their used qubits into the current layer. + * In front-layer mode, only commuting operations are considered; in + * look-ahead mode, up to @ref lookaheadDepth multi-qubit gates ahead are + * considered for each qubit. + * @param qubitsToUpdate Logical qubits whose frontier should be advanced and + * whose candidates/gates should be refreshed. */ void updateByQubits(const std::set& qubitsToUpdate); /** - * @brief Updates the candidates for the given qubits + * @brief Update the per-qubit candidate queues. + * @details For front-layer construction, keep pulling operations from each + * qubit's DAG column while they commute with both the already selected + * gates and the existing candidates at that qubit. For look-ahead + * construction, pull forward operations until @ref lookaheadDepth + * multi-qubit operations have been encountered (single-qubit operations in + * between are included as well). + * @param qubitsToUpdate Logical qubits whose candidate queues should be + * extended. */ void updateCandidatesByQubits(const std::set& qubitsToUpdate); /** - * @brief Checks the candidates and add them to the gates if possible - * @param qubitsToUpdate The qubits that have been updated + * @brief Promote eligible candidates to the current layer. + * @details For each provided qubit, move an operation from the candidate + * list to the current layer if and only if it appears as a candidate on + * all qubits it uses. Newly added operations are also tracked in + * @ref newGates, and removed from the candidate lists of all their + * constituent qubits. + * @param qubitsToUpdate Logical qubits whose candidate lists should be + * evaluated. */ void candidatesToGates(const std::set& qubitsToUpdate); public: - // Constructor - explicit NeutralAtomLayer(DAG graph, bool isFrontLayer, - uint32_t lookaheadDepth = 1) + /** + * @brief Construct a NeutralAtomLayer helper. + * @param graph The per-qubit DAG representation of the circuit (each entry + * corresponds to one qubit line). + * @param isFrontLayer If true, build the executable front layer (only + * commuting operations are pulled); if false, build a look-ahead layer. + * @param lookaheadDepth For look-ahead mode, the number of multi-qubit + * operations to consider ahead for each qubit (defaults to 1). Ignored in + * front-layer mode. + */ + explicit NeutralAtomLayer(DAG graph, const bool isFrontLayer, + const uint32_t lookaheadDepth = 1) : dag(std::move(graph)), lookaheadDepth(lookaheadDepth), isFrontLayer(isFrontLayer) { iterators.reserve(dag.size()); @@ -85,26 +111,58 @@ class NeutralAtomLayer { * @brief Returns the current layer of gates * @return The current layer of gates */ - GateList getGates() const { return gates; } - GateList getNewGates() const { return newGates; } + [[nodiscard]] GateList getGates() const { return gates; } + /** + * @brief Return the gates that were added the last time the layer was + * updated. + * @details This is populated during the most recent call to + * updateByQubits()/candidatesToGates() and is cleared and repopulated on + * subsequent updates. + * @return The subset of gates newly added to the layer during the last + * update. + */ + [[nodiscard]] GateList getNewGates() const { return newGates; } /** - * @brief Initializes the layer by updating all qubits starting + * @brief Initialize the layer by updating all qubits from their current + * DAG-frontier. */ void initAllQubits(); /** - * @brief Removes the provided gates from the current layer and update the - * the layer depending on the qubits of the gates. - * @param gatesToRemove Gates to remove from the current layer - * @param commuteWith Gates the new gates should commute with + * @brief Remove gates from the current layer and advance affected qubits. + * @details Erases the provided gates from the layer, then advances the DAG + * frontier for all qubits touched by those gates, updating candidates and + * possibly pulling in new gates. + * @param gatesToRemove Gates to remove from the current layer. */ void removeGatesAndUpdate(const GateList& gatesToRemove); }; // Commutation checks +/** + * @brief Check whether an operation commutes at a specific qubit with all + * operations already present in a layer. + * @param layer The current layer (list of operations) to check against. + * @param opPointer The operation to be tested for commutation. + * @param qubit The qubit at which commutation is assessed. + * @return true if @p opPointer commutes with every operation in @p layer at + * @p qubit; false otherwise. + */ bool commutesWithAtQubit(const GateList& layer, const qc::Operation* opPointer, const qc::Qubit& qubit); -bool commuteAtQubit(const qc::Operation* opPointer1, - const qc::Operation* opPointer2, const qc::Qubit& qubit); +/** + * @brief Check whether two operations commute at a specific qubit. + * @details Applies simple syntactic rules: non-unitaries never commute; + * single-qubit gates commute; identities commute; gates that do not act on the + * qubit commute; for two-qubit gates, certain control/target patterns commute + * (both controlled on the qubit, control with Z on the qubit, or equal target + * types on the qubit). + * @param op1 First operation. + * @param op2 Second operation. + * @param qubit The qubit at which commutation is assessed. + * @return true if the operations commute at @p qubit; false otherwise. + */ +bool commuteAtQubit(const qc::Operation* op1, const qc::Operation* op2, + const qc::Qubit& qubit); } // namespace na #endif // HYBRIDMAP_NEUTRAL_ATOM_LAYER_HPP diff --git a/src/hybridmap/NeutralAtomLayer.cpp b/src/hybridmap/NeutralAtomLayer.cpp index 36fd71de9..da9889e0c 100644 --- a/src/hybridmap/NeutralAtomLayer.cpp +++ b/src/hybridmap/NeutralAtomLayer.cpp @@ -16,8 +16,7 @@ #include "ir/operations/Operation.hpp" #include -#include -#include +#include #include #include @@ -31,8 +30,8 @@ void NeutralAtomLayer::updateByQubits( void NeutralAtomLayer::initAllQubits() { std::set allQubits; - for (uint32_t i = 0; i < this->dag.size(); ++i) { - allQubits.emplace(i); + for (std::size_t i = 0; i < this->dag.size(); ++i) { + allQubits.emplace(static_cast(i)); } updateByQubits(allQubits); } @@ -41,14 +40,14 @@ void NeutralAtomLayer::updateCandidatesByQubits( const std::set& qubitsToUpdate) { for (const auto& qubit : qubitsToUpdate) { if (isFrontLayer) { - while (iterators[qubit] < ends[qubit]) { + while (iterators[qubit] != ends[qubit]) { auto* op = (*iterators[qubit])->get(); // check if operation commutes with gates and candidates - auto commutes = commutesWithAtQubit(gates, op, qubit) && - commutesWithAtQubit(candidates[qubit], op, qubit); + const auto commutes = commutesWithAtQubit(gates, op, qubit) && + commutesWithAtQubit(candidates[qubit], op, qubit); if (commutes) { candidates[qubit].emplace_back(op); - iterators[qubit]++; + ++iterators[qubit]; } else { break; } @@ -56,12 +55,12 @@ void NeutralAtomLayer::updateCandidatesByQubits( } // for lookahead layer, take the next k multi-qubit gates else { - size_t multiQubitGatesFound = 0; - while (iterators[qubit] < ends[qubit] && + std::size_t multiQubitGatesFound = 0; + while (iterators[qubit] != ends[qubit] && multiQubitGatesFound < lookaheadDepth) { auto* op = (*iterators[qubit])->get(); candidates[qubit].emplace_back(op); - iterators[qubit]++; + ++iterators[qubit]; if (op->getUsedQubits().size() > 1) { multiQubitGatesFound++; } @@ -83,8 +82,8 @@ void NeutralAtomLayer::candidatesToGates( if (qubit == opQubit) { continue; } - if (std::find(candidates[opQubit].begin(), candidates[opQubit].end(), - opPointer) == candidates[opQubit].end()) { + if (std::ranges::find(candidates[opQubit], opPointer) == + candidates[opQubit].end()) { inFrontLayer = false; break; } @@ -97,9 +96,8 @@ void NeutralAtomLayer::candidatesToGates( if (qubit == opQubit) { continue; } - candidates[opQubit].erase(std::find(candidates[opQubit].begin(), - candidates[opQubit].end(), - opPointer)); + candidates[opQubit].erase( + std::ranges::find(candidates[opQubit], opPointer)); } toRemove.emplace_back(opPointer); @@ -108,8 +106,7 @@ void NeutralAtomLayer::candidatesToGates( // remove from candidacy of this qubit // has to be done now to not change iterating list for (const auto* opPointer : toRemove) { - candidates[qubit].erase(std::find(candidates[qubit].begin(), - candidates[qubit].end(), opPointer)); + candidates[qubit].erase(std::ranges::find(candidates[qubit], opPointer)); } } } @@ -117,8 +114,9 @@ void NeutralAtomLayer::candidatesToGates( void NeutralAtomLayer::removeGatesAndUpdate(const GateList& gatesToRemove) { std::set qubitsToUpdate; for (const auto& gate : gatesToRemove) { - if (std::ranges::find(gates, gate) != gates.end()) { - gates.erase(std::ranges::find(gates, gate)); + const auto it = std::ranges::find(gates, gate); + if (it != gates.end()) { + gates.erase(it); auto usedQubits = gate->getUsedQubits(); qubitsToUpdate.insert(usedQubits.begin(), usedQubits.end()); } @@ -151,8 +149,8 @@ bool commuteAtQubit(const qc::Operation* op1, const qc::Operation* op2, } // commutes at qubit if at least one of the two gates does not use qubit - auto usedQubits1 = op1->getUsedQubits(); - auto usedQubits2 = op2->getUsedQubits(); + const auto usedQubits1 = op1->getUsedQubits(); + const auto usedQubits2 = op2->getUsedQubits(); if (!usedQubits1.contains(qubit) || !usedQubits2.contains(qubit)) { return true; } @@ -165,19 +163,15 @@ bool commuteAtQubit(const qc::Operation* op1, const qc::Operation* op2, return true; } // control and Z also commute - if ((op1->getControls().find(qubit) != op1->getControls().end() && - op2->getType() == qc::OpType::Z) || - (op2->getControls().find(qubit) != op2->getControls().end() && - op1->getType() == qc::OpType::Z)) { + if ((op1->getControls().contains(qubit) && op2->getType() == qc::OpType::Z) || + (op2->getControls().contains(qubit) && op1->getType() == qc::OpType::Z)) { return true; } // check targets - if (std::find(op1->getTargets().begin(), op1->getTargets().end(), qubit) != - op1->getTargets().end() && - (std::find(op2->getTargets().begin(), op2->getTargets().end(), qubit) != - op2->getTargets().end()) && - (op1->getType() == op2->getType())) { + if (std::ranges::find(op1->getTargets(), qubit) != op1->getTargets().end() && + std::ranges::find(op2->getTargets(), qubit) != op2->getTargets().end() && + op1->getType() == op2->getType()) { return true; } return false; From 6ea07af9ad45a24e80c94d6137b022a1f2d410d3 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 6 Nov 2025 20:09:10 +0100 Subject: [PATCH 234/394] =?UTF-8?q?=F0=9F=93=9D=20added=20descriptions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HardwareQubits.hpp | 94 ++++++++++++++++-------- include/hybridmap/HybridAnimation.hpp | 48 +++++++++++++ include/hybridmap/NeutralAtomUtils.hpp | 99 +++++++++++++++++++++++--- 3 files changed, 204 insertions(+), 37 deletions(-) diff --git a/include/hybridmap/HardwareQubits.hpp b/include/hybridmap/HardwareQubits.hpp index ae7600a6a..a7ea4ece0 100644 --- a/include/hybridmap/HardwareQubits.hpp +++ b/include/hybridmap/HardwareQubits.hpp @@ -34,11 +34,10 @@ namespace na { /** * @brief Class that represents the hardware qubits of a neutral atom quantum - * @details Class that represents the hardware qubits of a neutral atom quantum - * computer. It stores the mapping from the circuit qubits to the hardware - * qubits and the mapping from the hardware qubits to the coordinates of the - * neutral atoms. It also stores the swap distances between the hardware - * qubits. + * computer. + * @details Stores the mapping from circuit qubits to hardware qubits and from + * hardware qubits to atom coordinates. Maintains cached swap distances and + * nearby-qubit relations derived from the architecture's interaction radius. */ class HardwareQubits { protected: @@ -66,9 +65,8 @@ class HardwareQubits { void initNearbyQubits(); /** * @brief Computes the nearby qubits for a single hardware qubit. - * @details Computes the nearby qubits for a single hardware qubit. This - * function is called by initNearbyQubits(). It uses the nearby coordinates - * of the neutral atom architecture to compute the nearby qubits. + * @details Determines nearby qubits by comparing Euclidean distance to the + * architecture's interaction radius. Called by initNearbyQubits(). * @param qubit The hardware qubit for which the nearby qubits are computed. */ void computeNearbyQubits(HwQubit qubit); @@ -85,13 +83,27 @@ class HardwareQubits { /** * @brief Resets the swap distances between the hardware qubits. - * @details Used after each shuttling operation to reset the swap distances. + * @details Used after each shuttling operation to invalidate all cached swap + * distances (set to -1), forcing recomputation on demand. */ void resetSwapDistances(); public: // Constructors HardwareQubits() = default; + /** + * @brief Construct hardware qubit layout and caches. + * @param architecture Reference to the neutral atom architecture. + * @param nQubits Number of hardware qubits managed in the mapping. + * @param initialCoordinateMapping Strategy for initial coordinate assignment: + * Trivial assigns coordinates 0..nQubits-1 in order; Random shuffles over + * all positions. + * @param seed Random seed used for Random initial mapping. If 0, a + * std::random_device() seeds the RNG. + * @details Initializes nearby-qubit relations and occupied/free coordinate + * lists. For Trivial mapping, swap distances are precomputed; for Random, + * swap distances are left invalid (-1) to be computed lazily. + */ explicit HardwareQubits( const NeutralAtomArchitecture& architecture, const CoordIndex nQubits = 0, const InitialCoordinateMapping initialCoordinateMapping = Trivial, @@ -135,9 +147,16 @@ class HardwareQubits { initialHwPos = hwToCoordIdx; } + /** + * @brief Compute all shortest paths between two hardware qubits. + * @details Performs a breadth-first exploration over the nearby-qubit graph + * (edges exist between qubits within the interaction radius) and returns all + * minimal-length paths from q1 to q2 as sequences of hardware qubits. + */ [[nodiscard]] std::vector computeAllShortestPaths(HwQubit q1, HwQubit q2) const; + /** Get number of hardware qubits tracked by this instance. */ [[nodiscard]] CoordIndex getNumQubits() const { return nQubits; } /** @@ -151,13 +170,19 @@ class HardwareQubits { } /** * @brief Updates mapping after moving a hardware qubit to a coordinate. - * @details Checks if the coordinate is valid and free. If yes, the mapping is - * updated. + * @details Verifies that the coordinate exists and is unoccupied, updates the + * mapping, refreshes nearby-qubit relations for the moved qubit and its + * neighbors, and invalidates cached swap distances. * @param hwQubit The hardware qubit to be moved. * @param newCoord The new coordinate of the hardware qubit. */ void move(HwQubit hwQubit, CoordIndex newCoord); + /** + * @brief Remove a hardware qubit from the mapping and caches. + * @details Erases the qubit from the coordinate mapping and nearby lists and + * invalidates swap distances involving that qubit. + */ void removeHwQubit(const HwQubit hwQubit) { hwToCoordIdx.erase(hwQubit); freeCoordinates.emplace_back(initialHwPos.at(hwQubit)); @@ -175,8 +200,8 @@ class HardwareQubits { } /** - * @brief Converts gate qubits from hardware qubits to coordinate indices. - * @param op The operation. + * @brief Convert operation's qubits from hardware indices to coordinates. + * @param op The operation to be updated in-place. */ void mapToCoordIdx(qc::Operation* op) const { op->setTargets(hwToCoordIdx.apply(op->getTargets())); @@ -218,9 +243,8 @@ class HardwareQubits { } /** - * @brief Returns the hardware qubit at a coordinate. - * @details Returns the hardware qubit at a coordinate. Throws an exception if - * there is no hardware qubit at the coordinate. + * @brief Return the hardware qubit at a given coordinate. + * @details Throws std::runtime_error if no hardware qubit is mapped there. * @param coordIndex The coordinate index. * @return The hardware qubit at the coordinate. */ @@ -238,14 +262,15 @@ class HardwareQubits { /** * @brief Returns the swap distance between two hardware qubits. - * @details Returns the swap distance between two hardware qubits. If the - * swap distance is not yet computed, it is computed using a breadth-first - * search. + * @details If not computed yet, uses a breadth-first search over nearby + * qubits to compute the minimal number of intermediate swaps. When closeBy + * is false, one additional step is allowed to stop in the vicinity of q2. * @param q1 The first hardware qubit. * @param q2 The second hardware qubit. * @param closeBy If the swap should be performed to the exact position of q2 * or just to its vicinity. - * @return The swap distance between the two hardware qubits. + * @return The swap distance between the two hardware qubits (0 if equal). If + * closeBy==false, returns the distance plus one. */ [[nodiscard]] SwapDistance getSwapDistance(const HwQubit q1, const HwQubit q2, const bool closeBy = true) { @@ -297,24 +322,33 @@ class HardwareQubits { * @brief Computes the summed swap distance between all hardware qubits in a * set. * @param qubits The set of hardware qubits. - * @return The summed swap distance between all hardware qubits in the set. + * @return The summed pairwise swap distance among all qubits in the set. + * For two qubits, this reduces to their swap distance. */ qc::fp getAllToAllSwapDistance(std::set& qubits); /** - * @brief Computes the closest free coordinate in a given direction. - * @details Uses a breadth-first search to find the closest free coordinate in - * a given direction. - * @param coord The hardware qubit to start the search from. + * @brief Find free coordinates in a given direction from a coordinate. + * @details Returns the nearest free coordinate along the specified direction + * (as a single-element vector). If no free coordinate exists in that + * direction, returns all currently free coordinates excluding the provided + * exclusions. + * @param coord The starting coordinate index. * @param direction The direction in which the search is performed - * (Left/Right, Down/Up) + * (Left/Right, Down/Up). * @param excludedCoords Coordinates to be ignored in the search. - * @return The closest free coordinate in the given direction. + * @return Either a singleton containing the closest free coordinate in the + * given direction, or a list of all free coordinates if none exist in that + * direction. */ [[nodiscard]] std::vector - findClosestFreeCoord(HwQubit coord, Direction direction, + findClosestFreeCoord(CoordIndex coord, Direction direction, const CoordIndices& excludedCoords = {}) const; + /** + * @brief Find the hardware qubit closest (by Euclidean distance) to a + * coordinate, ignoring a set of qubits. + */ [[nodiscard]] HwQubit getClosestQubit(CoordIndex coord, const HwQubits& ignored) const; @@ -328,6 +362,10 @@ class HardwareQubits { [[nodiscard]] std::set getBlockedQubits(const std::set& qubits) const; + /** + * @brief Get the initial hardware-to-coordinate mapping (at construction). + * @return A map from hardware qubit to its initial coordinate index. + */ [[nodiscard]] std::map getInitHwPos() const { std::map initialHwPosMap; for (auto const& pair : initialHwPos) { diff --git a/include/hybridmap/HybridAnimation.hpp b/include/hybridmap/HybridAnimation.hpp index 38d32dcb9..ad1ba3682 100644 --- a/include/hybridmap/HybridAnimation.hpp +++ b/include/hybridmap/HybridAnimation.hpp @@ -22,16 +22,44 @@ #include namespace na { +/** + * @brief Helper to generate NaViz-style animation strings for neutral-atom + * layouts. + * @details Maintains bidirectional mappings between coordinate indices and + * atom identifiers, as well as continuous coordinates derived from the + * architecture's grid geometry. Provides utilities to emit initial placement + * lines and per-operation animation snippets. + */ class AnimationAtoms { protected: + /** Map from discrete coordinate index to atom id. */ std::map coordIdxToId; + /** Map from atom id to continuous (x,y) coordinates. */ std::map> idToCoord; const NeutralAtomArchitecture* arch; + /** + * @brief Initialize coordinate/id maps from initial hardware and ancilla + * positions. + * @details For hardware qubits, places atoms at grid points based on + * architecture columns and inter-qubit distance. Flying ancilla qubits are + * offset from the grid using a small per-index displacement that depends on + * the architecture's AOD intermediate levels, and their id space follows the + * hardware ids. + * @param initHwPos Initial mapping of hardware qubit id -> coordinate index. + * @param initFaPos Initial mapping of flying-ancilla id -> coordinate index. + */ void initPositions(const std::map& initHwPos, const std::map& initFaPos); public: + /** + * @brief Construct animation helper with initial positions. + * @param initHwPos Initial hardware id -> coordinate index mapping. + * @param initFaPos Initial flying-ancilla id -> coordinate index mapping. + * @param architecture Reference to the neutral atom architecture providing + * grid geometry and distances. + */ AnimationAtoms(const std::map& initHwPos, const std::map& initFaPos, const NeutralAtomArchitecture& architecture) @@ -39,7 +67,27 @@ class AnimationAtoms { initPositions(initHwPos, initFaPos); } + /** + * @brief Emit NaViz lines that place all initial atoms. + * @details One line per atom with absolute coordinates and an atom label + * (e.g., "atom (x, y) atom"). + * @return Concatenated lines suitable for a NaViz animation file. + */ std::string placeInitAtoms(); + /** + * @brief Convert a quantum operation into NaViz animation commands. + * @details Supports AOD load/store/move operations and standard gates. + * - AodActivate: emits a load block with listed atoms. + * - AodDeactivate: emits a store block with listed atoms. + * - AodMove: updates internal coordinates by matching AOD start/end lists and + * emits move commands for affected atoms; also updates coordIdxToId for + * origin/target coordinate-index pairs. + * - Multi-qubit gates: emits a grouped cz with all involved atoms. + * - Single-qubit gates: emits a simple rz 1 for the target atom. + * @param op The operation to translate (uses coordinate indices as qubits). + * @param startTime The animation timestamp to annotate the command with. + * @return NaViz command string for the operation at the given time. + */ std::string opToNaViz(const std::unique_ptr& op, qc::fp startTime); }; diff --git a/include/hybridmap/NeutralAtomUtils.hpp b/include/hybridmap/NeutralAtomUtils.hpp index ab6bc0d35..b821d1dcb 100644 --- a/include/hybridmap/NeutralAtomUtils.hpp +++ b/include/hybridmap/NeutralAtomUtils.hpp @@ -28,9 +28,19 @@ namespace na { -// Enums for the different initial mappings strategies +// Enums for the different initial mapping strategies +/** + * @brief Strategy for assigning initial physical coordinates to atoms. + */ enum InitialCoordinateMapping : uint8_t { Trivial, Random }; +/** + * @brief Strategy for initializing the logical-to-physical qubit mapping. + */ enum InitialMapping : uint8_t { Identity, Graph }; +/** + * @brief Method used to realize two-qubit interactions that are not adjacent + * under the current mapping. + */ enum MappingMethod : uint8_t { SwapMethod, BridgeMethod, @@ -39,6 +49,13 @@ enum MappingMethod : uint8_t { PassByMethod }; +/** + * @brief Parse an InitialCoordinateMapping from string. + * @param initialCoordinateMapping Accepted values: "trivial"/"0" or + * "random"/"1" (case-sensitive). + * @return Corresponding InitialCoordinateMapping enum. + * @throws std::invalid_argument if the value is not recognized. + */ [[maybe_unused]] static InitialCoordinateMapping initialCoordinateMappingFromString( const std::string& initialCoordinateMapping) { @@ -53,6 +70,13 @@ initialCoordinateMappingFromString( initialCoordinateMapping); } +/** + * @brief Parse an InitialMapping from string. + * @param initialMapping Accepted values: "identity"/"0" or "graph"/"1" + * (case-sensitive). + * @return Corresponding InitialMapping enum. + * @throws std::invalid_argument if the value is not recognized. + */ [[maybe_unused]] static InitialMapping initialMappingFromString(const std::string& initialMapping) { if (initialMapping == "identity" || initialMapping == "0") { @@ -67,7 +91,9 @@ initialMappingFromString(const std::string& initialMapping) { /** * @brief Helper class to represent a direction in x and y coordinates. - * @details The boolean value corresponds to right/left and down/up. + * @details Booleans encode the sign: x=true means right (>=0), x=false means + * left (<0); y=true means down (>=0) or up depending on the chosen + * coordinate convention. */ struct Direction { bool x; @@ -92,7 +118,8 @@ struct Direction { /** * @brief Helper class to represent a move of an atom from one position to * another. - * @details Each move consists in a start and end coordinate and the direction. + * @details A move is defined by start/end coordinates and an implied + * direction. Utility predicates support spatial reasoning on moves. */ struct MoveVector { qc::fp xStart; @@ -113,36 +140,59 @@ struct MoveVector { [[nodiscard]] qc::fp getLength() const { return std::hypot(xEnd - xStart, yEnd - yStart); } + /** + * @brief Check whether the axis-aligned projections of two moves overlap. + * @details Ignores direction; returns true if the x-intervals overlap or the + * y-intervals overlap, considering inclusive bounds. + */ [[nodiscard]] bool overlap(const MoveVector& other) const; + /** + * @brief Check whether this move is strictly included within another move in + * at least one dimension. + * @details Returns true if the other move's x-interval strictly contains this + * move's x-interval OR the other move's y-interval strictly contains this + * move's y-interval. + */ [[nodiscard]] bool include(const MoveVector& other) const; }; struct FlyingAncilla { + /** Origin coordinate index of the flying ancilla. */ CoordIndex origin; + /** First data atom coordinate involved in interaction. */ CoordIndex q1; + /** Second data atom coordinate involved in interaction. */ CoordIndex q2; + /** Optional bookkeeping index to order/identify moves. */ size_t index; }; struct FlyingAncillaComb { + /** Sequence of flying ancilla moves realizing an interaction. */ std::vector moves; + /** Operation this combination implements or is associated with. */ const qc::Operation* op; }; struct PassByComb { + /** Sequence of atom moves realizing a pass-by maneuver. */ std::vector moves; + /** Operation this combination implements or is associated with. */ const qc::Operation* op; }; /** * @brief Helper class to manage multiple atom moves which belong together. * @details E.g. a move-away combined with the actual move. These are combined - * in a MoveComb to facilitate the cost calculation. + * in a MoveComb to facilitate the cost calculation and selection. */ struct MoveComb { std::vector moves; + /** Aggregated cost heuristic; defaults to +inf until computed. */ qc::fp cost = std::numeric_limits::max(); + /** Operation this move combination aims to realize (optional). */ const qc::Operation* op = nullptr; + /** Best known positions for the operation after applying the moves. */ CoordIndices bestPos; MoveComb(std::vector mov, const qc::fp c, const qc::Operation* o, @@ -166,7 +216,8 @@ struct MoveComb { /** * @brief Append a single move to the end of the combination. - * @param addMove The move to append + * @param addMove The move to append. + * @note Resets cost to +inf to signal it needs recomputation. */ void append(AtomMove addMove) { moves.emplace_back(addMove); @@ -174,7 +225,8 @@ struct MoveComb { } /** * @brief Append all moves of another combination to the end of this one. - * @param addMoveComb The other combination to append + * @param addMoveComb The other combination to append. + * @note Resets cost to +inf to signal it needs recomputation. */ void append(const MoveComb& addMoveComb) { moves.insert(moves.end(), addMoveComb.moves.begin(), @@ -186,7 +238,7 @@ struct MoveComb { }; /** - * @brief Helper class to manage multiple move combinations. + * @brief Container to manage and deduplicate multiple move combinations. */ struct MoveCombs { std::vector moveCombs; @@ -215,6 +267,9 @@ struct MoveCombs { /** * @brief Add a move combination to the list of move combinations. + * @details If an equal combination (same sequence of moves) already exists, + * it will not be duplicated; instead, its cost is invalidated to force + * recomputation. * @param moveComb The move combination to add. */ void addMoveComb(const MoveComb& moveComb); @@ -225,8 +280,9 @@ struct MoveCombs { */ void addMoveCombs(const MoveCombs& otherMoveCombs); /** - * @brief Remove all move combinations that are longer than the shortest move - * combination. + * @brief Keep only the shortest move combinations. + * @details Computes the minimum number of moves among all combinations and + * erases any combination with a larger length. */ void removeLongerMoveCombs(); }; @@ -240,12 +296,23 @@ struct MultiQubitMovePos { size_t nMoves{0}; }; +/** + * @brief Precomputed bridge circuits and their simple gate metrics. + * @details For linear chains of increasing length, stores a CZ-based bridge + * circuit and counts of H and Z gates and their per-qubit maxima. The + * constructor precomputes entries for lengths in [3, maxLength). + */ class BridgeCircuits { public: + /** Bridge circuits indexed by chain length (number of qubits). */ std::vector bridgeCircuits; + /** Total number of H gates per length. */ std::vector hs; + /** Total number of CZ gates per length (counted via Z after MCX->MCZ). */ std::vector czs; + /** Maximum number of H gates on any single qubit for a given length. */ std::vector hDepth; + /** Maximum number of CZ involvements on any single qubit for a length. */ std::vector czDepth; explicit BridgeCircuits(const size_t maxLength) { @@ -261,12 +328,26 @@ class BridgeCircuits { } protected: + /** + * @brief Compute aggregate H/CZ counts and per-qubit maxima for a length. + */ void computeGates(size_t length); + /** + * @brief Build the base bridge circuit for a given linear chain length. + * @details Starts from a 3-qubit pattern and recursively expands. + */ void computeBridgeCircuit(size_t length); + /** + * @brief Recursively expand a bridge circuit by inserting new qubits where + * the current gate load is minimal. + */ static qc::QuantumComputation recursiveBridgeIncrease(qc::QuantumComputation qcBridge, size_t length); + /** + * @brief Expand the circuit by one qubit between positions qubit and qubit+1. + */ static qc::QuantumComputation bridgeExpand(const qc::QuantumComputation& qcBridge, size_t qubit); }; From 989a08decdae457285dcc4d30d5b619e40937cb1 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 7 Nov 2025 10:20:56 +0100 Subject: [PATCH 235/394] =?UTF-8?q?=F0=9F=93=9D=20added=20descriptions=20f?= =?UTF-8?q?or=20Utils?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/NeutralAtomUtils.hpp | 224 ++++++++++++++++++------- 1 file changed, 168 insertions(+), 56 deletions(-) diff --git a/include/hybridmap/NeutralAtomUtils.hpp b/include/hybridmap/NeutralAtomUtils.hpp index b821d1dcb..9f9ec6068 100644 --- a/include/hybridmap/NeutralAtomUtils.hpp +++ b/include/hybridmap/NeutralAtomUtils.hpp @@ -31,15 +31,24 @@ namespace na { // Enums for the different initial mapping strategies /** * @brief Strategy for assigning initial physical coordinates to atoms. + * @details "Trivial" assigns coordinates in order without randomness; "Random" + * samples coordinates uniformly from the available lattice/set. */ enum InitialCoordinateMapping : uint8_t { Trivial, Random }; /** * @brief Strategy for initializing the logical-to-physical qubit mapping. + * @details Identity keeps logical indices matched to physical indices; Graph + * performs a topology-aware assignment (e.g., based on interaction graph + * heuristics). */ enum InitialMapping : uint8_t { Identity, Graph }; /** * @brief Method used to realize two-qubit interactions that are not adjacent * under the current mapping. + * @details Different strategies trade off added gates vs. atom motion: Swap + * introduces SWAP chains, Bridge builds entangling bridges, Move performs + * physical repositioning, FlyingAncilla uses a mobile ancilla, PassBy leverages + * relative motion without stopping. */ enum MappingMethod : uint8_t { SwapMethod, @@ -50,11 +59,11 @@ enum MappingMethod : uint8_t { }; /** - * @brief Parse an InitialCoordinateMapping from string. + * @brief Parse an InitialCoordinateMapping from a string token. * @param initialCoordinateMapping Accepted values: "trivial"/"0" or * "random"/"1" (case-sensitive). - * @return Corresponding InitialCoordinateMapping enum. - * @throws std::invalid_argument if the value is not recognized. + * @return Corresponding InitialCoordinateMapping enum value. + * @throw std::invalid_argument If the value is not recognized. */ [[maybe_unused]] static InitialCoordinateMapping initialCoordinateMappingFromString( @@ -71,11 +80,11 @@ initialCoordinateMappingFromString( } /** - * @brief Parse an InitialMapping from string. + * @brief Parse an InitialMapping from a string token. * @param initialMapping Accepted values: "identity"/"0" or "graph"/"1" * (case-sensitive). - * @return Corresponding InitialMapping enum. - * @throws std::invalid_argument if the value is not recognized. + * @return Corresponding InitialMapping enum value. + * @throw std::invalid_argument If the value is not recognized. */ [[maybe_unused]] static InitialMapping initialMappingFromString(const std::string& initialMapping) { @@ -90,36 +99,63 @@ initialMappingFromString(const std::string& initialMapping) { } /** - * @brief Helper class to represent a direction in x and y coordinates. - * @details Booleans encode the sign: x=true means right (>=0), x=false means - * left (<0); y=true means down (>=0) or up depending on the chosen - * coordinate convention. + * @brief Helper struct representing a 2D direction via sign bits. + * @details Booleans encode the sign of deltas: x==true implies non-negative x + * movement; y==true implies non-negative y movement per the chosen coordinate + * convention. */ struct Direction { bool x; bool y; + /** + * @brief Construct a direction from deltas. + * @param deltaX Signed x delta. + * @param deltaY Signed y delta. + */ Direction(const qc::fp deltaX, const qc::fp deltaY) : x(deltaX >= 0), y(deltaY >= 0) {} + /** + * @brief Equality comparison. + * @param other Direction to compare against. + * @return True if both sign bits are identical. + */ [[nodiscard]] bool operator==(const Direction& other) const { return x == other.x && y == other.y; } + /** + * @brief Inequality comparison. + * @param other Direction to compare against. + * @return True if any sign bit differs. + */ [[nodiscard]] bool operator!=(const Direction& other) const { return !(*this == other); } + /** + * @brief Get signed unit for x. + * @return +1 if x>=0 else -1. + */ [[nodiscard]] int32_t getSignX() const { return x ? 1 : -1; } + /** + * @brief Get signed unit for y. + * @return +1 if y>=0 else -1. + */ [[nodiscard]] int32_t getSignY() const { return y ? 1 : -1; } + /** + * @brief Get signed unit for a given dimension. + * @param dim Dimension (X or Y). + * @return +1 if non-negative movement else -1. + */ [[nodiscard]] int32_t getSign(const Dimension dim) const { return dim == Dimension::X ? getSignX() : getSignY(); } }; /** - * @brief Helper class to represent a move of an atom from one position to - * another. - * @details A move is defined by start/end coordinates and an implied - * direction. Utility predicates support spatial reasoning on moves. + * @brief Represents a single atom move from a start to an end position. + * @details Encapsulates start/end coordinates, derived direction, and spatial + * predicates (direction, length, overlap, inclusion). */ struct MoveVector { qc::fp xStart; @@ -128,34 +164,55 @@ struct MoveVector { qc::fp yEnd; Direction direction; + /** + * @brief Construct a move vector. + * @param xStart Starting x coordinate. + * @param yStart Starting y coordinate. + * @param xEnd Ending x coordinate. + * @param yEnd Ending y coordinate. + */ MoveVector(const qc::fp xStart, const qc::fp yStart, const qc::fp xEnd, const qc::fp yEnd) : xStart(xStart), yStart(yStart), xEnd(xEnd), yEnd(yEnd), direction(xEnd - xStart, yEnd - yStart) {} + /** + * @brief Check if two moves share identical direction signs. + * @param other Other move. + * @return True if both x and y direction signs match. + */ [[nodiscard]] [[maybe_unused]] bool sameDirection(const MoveVector& other) const { return direction == other.direction; } + /** + * @brief Euclidean length of the move. + * @return Hypotenuse of (dx, dy). + */ [[nodiscard]] qc::fp getLength() const { return std::hypot(xEnd - xStart, yEnd - yStart); } /** - * @brief Check whether the axis-aligned projections of two moves overlap. - * @details Ignores direction; returns true if the x-intervals overlap or the - * y-intervals overlap, considering inclusive bounds. + * @brief Check if axis-aligned projections overlap in at least one dimension. + * @param other Other move. + * @return True if x intervals overlap OR y intervals overlap (inclusive + * bounds). */ [[nodiscard]] bool overlap(const MoveVector& other) const; /** - * @brief Check whether this move is strictly included within another move in - * at least one dimension. - * @details Returns true if the other move's x-interval strictly contains this - * move's x-interval OR the other move's y-interval strictly contains this - * move's y-interval. + * @brief Check if this move's interval is strictly included by another's in + * any dimension. + * @param other Other move that may include this. + * @return True if other strictly contains this in x OR in y. */ [[nodiscard]] bool include(const MoveVector& other) const; }; +/** + * @brief Metadata for a flying ancilla interaction step. + * @details Stores origin and two target data atom coordinates plus an index for + * ordering. + */ struct FlyingAncilla { /** Origin coordinate index of the flying ancilla. */ CoordIndex origin; @@ -167,6 +224,11 @@ struct FlyingAncilla { size_t index; }; +/** + * @brief Combination of sequential flying ancilla moves for one operation. + * @param moves Sequence realizing the interaction. + * @param op Operation implemented (non-owning pointer). + */ struct FlyingAncillaComb { /** Sequence of flying ancilla moves realizing an interaction. */ std::vector moves; @@ -174,6 +236,11 @@ struct FlyingAncillaComb { const qc::Operation* op; }; +/** + * @brief Pass-by maneuver representation for an operation. + * @details Aggregates physical atom moves needed to realize a pass-by style + * interaction. + */ struct PassByComb { /** Sequence of atom moves realizing a pass-by maneuver. */ std::vector moves; @@ -182,9 +249,9 @@ struct PassByComb { }; /** - * @brief Helper class to manage multiple atom moves which belong together. - * @details E.g. a move-away combined with the actual move. These are combined - * in a MoveComb to facilitate the cost calculation and selection. + * @brief Aggregates related atom moves forming a candidate realization. + * @details Examples include preparatory clearance moves plus the main move. + * Collectively evaluated for cost heuristics and selection. */ struct MoveComb { std::vector moves; @@ -206,39 +273,58 @@ struct MoveComb { explicit MoveComb(std::vector mov) : moves(std::move(mov)) {} explicit MoveComb(AtomMove mov) : moves({mov}) {} - // implement == operator for AtomMove + /** + * @brief Equality comparison (ignores cost/op/bestPos). + * @param other Other combination. + * @return True if underlying move sequence is identical. + */ [[nodiscard]] bool operator==(const MoveComb& other) const { return moves == other.moves; } + /** + * @brief Inequality comparison. + * @param other Other combination. + * @return True if move sequence differs. + */ [[nodiscard]] bool operator!=(const MoveComb& other) const { return !(*this == other); } /** - * @brief Append a single move to the end of the combination. - * @param addMove The move to append. - * @note Resets cost to +inf to signal it needs recomputation. + * @brief Append a single move. + * @param addMove Move to append. + * @note Resets cost to +inf for later recomputation. */ void append(AtomMove addMove) { moves.emplace_back(addMove); cost = std::numeric_limits::max(); } /** - * @brief Append all moves of another combination to the end of this one. - * @param addMoveComb The other combination to append. - * @note Resets cost to +inf to signal it needs recomputation. + * @brief Concatenate moves from another combination. + * @param addMoveComb Source combination. + * @note Resets cost to +inf for later recomputation. */ void append(const MoveComb& addMoveComb) { moves.insert(moves.end(), addMoveComb.moves.begin(), addMoveComb.moves.end()); cost = std::numeric_limits::max(); } + /** + * @brief Number of moves contained. + * @return Size of move sequence. + */ [[nodiscard]] size_t size() const { return moves.size(); } + /** + * @brief Check emptiness. + * @return True if no moves stored. + */ [[nodiscard]] bool empty() const { return moves.empty(); } }; /** - * @brief Container to manage and deduplicate multiple move combinations. + * @brief Container managing a set of unique move combinations. + * @detail Prevents duplicate sequences, propagates operation metadata, and + * offers pruning utilities. */ struct MoveCombs { std::vector moveCombs; @@ -247,7 +333,15 @@ struct MoveCombs { explicit MoveCombs(std::vector combs) : moveCombs(std::move(combs)) {} + /** + * @brief Check if container is empty. + * @return True if no combinations stored. + */ [[nodiscard]] bool empty() const { return moveCombs.empty(); } + /** + * @brief Number of stored combinations. + * @return Count of combinations. + */ [[nodiscard]] size_t size() const { return moveCombs.size(); } // define iterators that iterate over the moveCombs vector @@ -258,6 +352,11 @@ struct MoveCombs { [[nodiscard]] const_iterator begin() const { return moveCombs.cbegin(); } [[nodiscard]] const_iterator end() const { return moveCombs.cend(); } + /** + * @brief Set associated operation and best positions for all combinations. + * @param op Operation pointer (non-owning). + * @param pos Best coordinate indices for op. + */ void setOperation(const qc::Operation* op, const CoordIndices& pos) { for (auto& moveComb : moveCombs) { moveComb.op = op; @@ -266,30 +365,29 @@ struct MoveCombs { } /** - * @brief Add a move combination to the list of move combinations. - * @details If an equal combination (same sequence of moves) already exists, - * it will not be duplicated; instead, its cost is invalidated to force - * recomputation. - * @param moveComb The move combination to add. + * @brief Insert a combination, deduplicating existing sequences. + * @details If an equal sequence exists, that existing entry's cost is + * invalidated (set to +inf) instead of inserting a duplicate. + * @param moveComb Combination to add. */ void addMoveComb(const MoveComb& moveComb); /** - * @brief Add all move combinations of another MoveCombs object to the list of - * move combinations. - * @param otherMoveCombs The other MoveCombs object to add. + * @brief Bulk insert all combinations from another container. + * @param otherMoveCombs Source container. */ void addMoveCombs(const MoveCombs& otherMoveCombs); /** - * @brief Keep only the shortest move combinations. - * @details Computes the minimum number of moves among all combinations and - * erases any combination with a larger length. + * @brief Prune combinations longer than the current minimum length. + * @details Computes minimal number of moves and removes any with a greater + * count. */ void removeLongerMoveCombs(); }; /** - * @brief Helper struct to store the position of a multi qubit gate and the - * number of moves needed to execute it. + * @brief Position and move count summary for a multi-qubit gate. + * @param coords Coordinate indices involved. + * @param nMoves Number of atom moves required. */ struct MultiQubitMovePos { CoordIndices coords; @@ -297,10 +395,10 @@ struct MultiQubitMovePos { }; /** - * @brief Precomputed bridge circuits and their simple gate metrics. - * @details For linear chains of increasing length, stores a CZ-based bridge - * circuit and counts of H and Z gates and their per-qubit maxima. The - * constructor precomputes entries for lengths in [3, maxLength). + * @brief Precomputes CZ-based bridge circuits and gate metrics for linear + * chains. + * @details For lengths in [3, maxLength) stores circuits plus aggregate H/CZ + * counts and per-qubit depth maxima to support cost estimation. */ class BridgeCircuits { public: @@ -315,6 +413,10 @@ class BridgeCircuits { /** Maximum number of CZ involvements on any single qubit for a length. */ std::vector czDepth; + /** + * @brief Construct and precompute bridge data up to given length. + * @param maxLength Exclusive upper bound on chain length table size. + */ explicit BridgeCircuits(const size_t maxLength) { bridgeCircuits.resize(maxLength, qc::QuantumComputation()); hs.resize(maxLength, 0); @@ -329,24 +431,34 @@ class BridgeCircuits { protected: /** - * @brief Compute aggregate H/CZ counts and per-qubit maxima for a length. + * @brief Compute aggregate gate counts and per-qubit depth metrics. + * @param length Chain length whose circuit has been generated. */ void computeGates(size_t length); /** - * @brief Build the base bridge circuit for a given linear chain length. - * @details Starts from a 3-qubit pattern and recursively expands. + * @brief Build base bridge circuit for a chain length. + * @param length Target chain length. + * @details Starts from length 3 pattern and recurses via minimal-load + * expansion. */ void computeBridgeCircuit(size_t length); /** - * @brief Recursively expand a bridge circuit by inserting new qubits where - * the current gate load is minimal. + * @brief Recursively expand circuit choosing insertion minimizing current + * load. + * @param qcBridge Existing bridge circuit. + * @param length Desired final length. + * @return Expanded circuit of requested length. */ static qc::QuantumComputation recursiveBridgeIncrease(qc::QuantumComputation qcBridge, size_t length); /** - * @brief Expand the circuit by one qubit between positions qubit and qubit+1. + * @brief Expand circuit by inserting a qubit between positions qubit and + * qubit+1. + * @param qcBridge Circuit to expand. + * @param qubit Insertion position. + * @return New circuit with inserted qubit. */ static qc::QuantumComputation bridgeExpand(const qc::QuantumComputation& qcBridge, size_t qubit); From 540dade444ee78d9f1fe45d09a841bbed2320847 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 7 Nov 2025 10:26:43 +0100 Subject: [PATCH 236/394] =?UTF-8?q?=F0=9F=93=9D=20added=20descriptions=20f?= =?UTF-8?q?or=20Scheduler?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/NeutralAtomScheduler.hpp | 133 ++++++++++----------- 1 file changed, 65 insertions(+), 68 deletions(-) diff --git a/include/hybridmap/NeutralAtomScheduler.hpp b/include/hybridmap/NeutralAtomScheduler.hpp index 677646c37..519e479ba 100644 --- a/include/hybridmap/NeutralAtomScheduler.hpp +++ b/include/hybridmap/NeutralAtomScheduler.hpp @@ -28,7 +28,11 @@ namespace na { /** - * @brief Struct to store the results of the scheduler + * @brief Aggregated metrics produced by the neutral atom scheduler. + * @details Captures timing and fidelity information over the entire scheduled + * circuit: total execution (makespan), accumulated idle time, raw gate fidelity + * product (excluding decoherence), overall fidelity (including idle + * decoherence), and counts of selected operation types. */ struct SchedulerResults { qc::fp totalExecutionTime; @@ -40,16 +44,16 @@ struct SchedulerResults { uint32_t nAodMove = 0; /** - * @brief Create a new results object. - * @param executionTime The overall makespan (end time of the last operation). - * @param idleTime The sum of all idling time across qubits: end_time * - * n_qubits - total_gate_time. - * @param gateFidelities Product of native gate fidelities (excludes + * @brief Construct and initialize scheduler result metrics. + * @param executionTime Overall makespan (end time of last operation). + * @param idleTime Sum of idle time across qubits: end_time * n_qubits - + * total_gate_time. + * @param gateFidelities Product of native gate fidelities (excluding * decoherence). - * @param fidelities Overall fidelity including decoherence during idle time. - * @param cZs The number of CZ operations encountered. - * @param aodActivate The number of AOD activation operations encountered. - * @param aodMove The number of AOD shuttling/move operations encountered. + * @param fidelities Overall fidelity including idle-time decoherence. + * @param cZs Number of CZ operations. + * @param aodActivate Number of AOD activation operations. + * @param aodMove Number of AOD shuttling/move operations. */ SchedulerResults(const qc::fp executionTime, const qc::fp idleTime, const qc::fp gateFidelities, const qc::fp fidelities, @@ -60,10 +64,9 @@ struct SchedulerResults { nCZs(cZs), nAodActivate(aodActivate), nAodMove(aodMove) {} /** - * @brief Export a compact CSV line with execution time, idle time, and - * overall fidelity. - * @return A string in the format "totalExecutionTime, totalIdleTime, - * totalFidelities". + * @brief Export a compact CSV line with execution time, idle time, fidelity. + * @return String formatted as: totalExecutionTime, totalIdleTime, + * totalFidelities */ [[nodiscard]] std::string toCsv() const { std::stringstream ss; @@ -73,10 +76,9 @@ struct SchedulerResults { /** * @brief Export selected metrics to a key-value map. - * @details Currently includes totalExecutionTime, totalIdleTime, - * totalGateFidelities, totalFidelities, and nCZs. Counts for - * nAodActivate/nAodMove are not included. - * @return An unordered_map from metric names to values. + * @details Includes totalExecutionTime, totalIdleTime, totalGateFidelities, + * totalFidelities, and nCZs (omits AOD counts for brevity). + * @return Unordered map from metric names to numeric values. */ [[maybe_unused]] [[nodiscard]] std::unordered_map toMap() const { @@ -91,11 +93,11 @@ struct SchedulerResults { }; /** - * @brief Class to schedule a quantum circuit on a neutral atom architecture - * @details For each gate/operation in the input circuit, the scheduler checks - * the earliest possible time slot for execution. If the gate is a multi qubit - * gate, also the blocking of other qubits is taken into consideration. The - * execution times are read from the neutral atom architecture. + * @brief Schedules quantum circuits on a neutral atom architecture. + * @details Iterates operations chronologically assigning earliest feasible + * start times respecting per-gate durations, multi-qubit blocking windows (e.g. + * Rydberg interaction zones), and AOD move/activation timing. Optionally + * records visualization artifacts for animation. */ class NeutralAtomScheduler { protected: @@ -105,32 +107,33 @@ class NeutralAtomScheduler { std::string animationStyle; public: - // Constructor + /** + * @brief Default constructor (no associated architecture yet). + */ NeutralAtomScheduler() = default; + /** + * @brief Construct with a given neutral atom architecture. + * @param architecture Architecture reference whose timing data is used. + */ explicit NeutralAtomScheduler(const NeutralAtomArchitecture& architecture) : arch(&architecture) {} /** - * @brief Schedules the given quantum circuit on the neutral atom architecture - * @details For each gate/operation in the input circuit, the scheduler checks - * the earliest possible time slot for execution. If the gate is a multi qubit - * gate, also the blocking of other qubits is taken into consideration. The - * execution times are read from the neutral atom architecture. - * Blocking windows for multi-qubit Rydberg interactions and AOD moves are - * respected; optional animation traces can be produced. - * + * @brief Schedule a quantum circuit on the architecture. + * @details Greedily assigns earliest feasible start times to each operation + * while tracking per-qubit availability and multi-qubit blocking intervals. + * Generates optional animation traces. * @param qc Quantum circuit to schedule. - * @param initHwPos Initial positions of atoms on the hardware grid (by - * hardware qubit). - * @param initFaPos Initial positions of the addressing focus array (by - * hardware qubit). - * @param verbose If true, prints progress and a summary to std::cout. - * @param createAnimationCsv If true, records animation artifacts for - * visualization. - * @param shuttlingSpeedFactor Scale factor applied to AOD - * move/activate/deactivate durations (e.g., 0.5 for twice as fast shuttling). - * @return Aggregated scheduling results including makespan, idle time, and - * fidelities. + * @param initHwPos Initial atom positions indexed by hardware qubit. + * @param initFaPos Initial AOD focus array positions indexed by hardware + * qubit. + * @param verbose If true, prints progress and summary to stdout. + * @param createAnimationCsv If true, records animation artifacts + * (.naviz/.namachine/.nastyle). + * @param shuttlingSpeedFactor Factor scaling AOD move/activation durations + * (1.0 = unchanged). + * @return SchedulerResults containing makespan, idle time, fidelity metrics, + * and operation counts. */ SchedulerResults schedule(const qc::QuantumComputation& qc, const std::map& initHwPos, @@ -139,36 +142,32 @@ class NeutralAtomScheduler { qc::fp shuttlingSpeedFactor = 1.0); /** - * @brief Get the machine description for the animation output. - * @note Only populated when schedule(...) was run with - * createAnimationCsv=true. + * @brief Retrieve machine/layout description (.namachine content). + * @return Machine description string. + * @note Populated only if schedule() ran with createAnimationCsv=true. */ [[nodiscard]] std::string getAnimationMachine() const { return animationMachine; } /** - * @brief Get the visualization event log in .naviz format. - * @note Only populated when schedule(...) was run with - * createAnimationCsv=true. + * @brief Retrieve visualization event log (.naviz content). + * @return Event log string. + * @note Populated only if schedule() ran with createAnimationCsv=true. */ [[nodiscard]] std::string getAnimationViz() const { return animation; } /** - * @brief Get the visualization style sheet for the animation. - * @note Only populated when schedule(...) was run with - * createAnimationCsv=true. + * @brief Retrieve visualization style sheet (.nastyle content). + * @return Style sheet string. + * @note Populated only if schedule() ran with createAnimationCsv=true. */ [[nodiscard]] std::string getAnimationStyle() const { return animationStyle; } /** - * @brief Persist the generated animation artifacts to disk. - * @details Creates three files next to the provided filename (without its - * extension): - * - .naviz (visualization event log) - * - .namachine (machine/layout description) - * - .nastyle (visual style configuration) - * The contents are derived from - * getAnimationViz()/getAnimationMachine()/getAnimationStyle(). - * @param filename Base filename whose stem is reused for the three outputs. + * @brief Write animation artifacts (.naviz/.namachine/.nastyle) to disk. + * @details Uses the stem of the provided filename to derive target paths for + * each artifact. + * @param filename Base filename (its extension is stripped before appending + * artifact extensions). */ void saveAnimationFiles(const std::string& filename) const { const auto filenameWithoutExtension = @@ -194,15 +193,13 @@ class NeutralAtomScheduler { // Helper Print functions /** * @brief Print a human-readable summary of scheduling results. - * @param totalExecutionTimes Per-qubit accumulated execution/makespan - * timeline. + * @param totalExecutionTimes Per-qubit cumulative execution times. * @param totalIdleTime Sum of idle time across all qubits. * @param totalGateFidelities Product of native gate fidelities. - * @param totalFidelities Overall fidelity including decoherence during idle - * time. - * @param nCZs Number of CZ gates in the circuit. - * @param nAodActivate Number of AOD activation operations. - * @param nAodMove Number of AOD move operations. + * @param totalFidelities Overall fidelity including idle-time decoherence. + * @param nCZs Count of CZ gates. + * @param nAodActivate Count of AOD activation operations. + * @param nAodMove Count of AOD move operations. */ static void printSchedulerResults(std::vector& totalExecutionTimes, qc::fp totalIdleTime, From 7412ab5ee7edc42337cf1426020d506a00c0ca1b Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 7 Nov 2025 10:29:47 +0100 Subject: [PATCH 237/394] =?UTF-8?q?=F0=9F=93=9D=20added=20descriptions=20f?= =?UTF-8?q?or=20Layer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/NeutralAtomLayer.hpp | 123 +++++++++++-------------- 1 file changed, 55 insertions(+), 68 deletions(-) diff --git a/include/hybridmap/NeutralAtomLayer.hpp b/include/hybridmap/NeutralAtomLayer.hpp index 0fe4a4968..2e8efa580 100644 --- a/include/hybridmap/NeutralAtomLayer.hpp +++ b/include/hybridmap/NeutralAtomLayer.hpp @@ -23,11 +23,12 @@ namespace na { /** - * @brief Class to manage the creation of layers when traversing a quantum - * circuit. - * @details The class uses the qc::DAG of the circuit to create layers of gates - * that can be executed at the same time. It can be used to create the front or - * look ahead layer. + * @brief Helper for constructing executable or look-ahead layers from a circuit + * DAG. + * @details Consumes a per-qubit DAG representation and maintains frontier + * iterators to build either (a) the front layer of mutually commuting gates + * (ready to execute) or (b) look-ahead layers containing a bounded depth of + * forthcoming multi-qubit gates per qubit for heuristic evaluation. */ class NeutralAtomLayer { @@ -46,52 +47,43 @@ class NeutralAtomLayer { bool isFrontLayer; /** - * @brief Update layer state for a set of qubits. - * @details Advances the internal iterators of the qc::DAG for the provided - * qubits, updates the per-qubit candidate lists, and moves operations that - * are ready across all their used qubits into the current layer. - * In front-layer mode, only commuting operations are considered; in - * look-ahead mode, up to @ref lookaheadDepth multi-qubit gates ahead are - * considered for each qubit. - * @param qubitsToUpdate Logical qubits whose frontier should be advanced and - * whose candidates/gates should be refreshed. + * @brief Advance frontier and refresh candidates/gates for specified qubits. + * @details Moves DAG iterators forward for each qubit, replenishes candidate + * queues and promotes ready operations into the current layer (all involved + * qubits agree). Front-layer mode restricts to commuting operations; + * look-ahead mode gathers up to lookaheadDepth multi-qubit gates. + * @param qubitsToUpdate Logical qubits whose DAG frontier and candidate sets + * are updated. */ void updateByQubits(const std::set& qubitsToUpdate); /** - * @brief Update the per-qubit candidate queues. - * @details For front-layer construction, keep pulling operations from each - * qubit's DAG column while they commute with both the already selected - * gates and the existing candidates at that qubit. For look-ahead - * construction, pull forward operations until @ref lookaheadDepth - * multi-qubit operations have been encountered (single-qubit operations in - * between are included as well). + * @brief Extend per-qubit candidate queues from DAG columns. + * @details Front-layer: continue pulling while new ops commute with existing + * layer and per-qubit candidates. Look-ahead: pull until lookaheadDepth + * multi-qubit ops encountered (including intervening single-qubit ops). * @param qubitsToUpdate Logical qubits whose candidate queues should be * extended. */ void updateCandidatesByQubits(const std::set& qubitsToUpdate); /** - * @brief Promote eligible candidates to the current layer. - * @details For each provided qubit, move an operation from the candidate - * list to the current layer if and only if it appears as a candidate on - * all qubits it uses. Newly added operations are also tracked in - * @ref newGates, and removed from the candidate lists of all their - * constituent qubits. - * @param qubitsToUpdate Logical qubits whose candidate lists should be - * evaluated. + * @brief Promote multi-qubit candidates that are ready on all their qubits. + * @details Checks for each qubit whether the head candidate appears across + * candidate lists of all its operand qubits; if so, moves it to the layer and + * records it in newGates, removing from all candidate lists. + * @param qubitsToUpdate Logical qubits whose candidate lists are evaluated + * for promotion. */ void candidatesToGates(const std::set& qubitsToUpdate); public: /** - * @brief Construct a NeutralAtomLayer helper. - * @param graph The per-qubit DAG representation of the circuit (each entry - * corresponds to one qubit line). - * @param isFrontLayer If true, build the executable front layer (only - * commuting operations are pulled); if false, build a look-ahead layer. - * @param lookaheadDepth For look-ahead mode, the number of multi-qubit - * operations to consider ahead for each qubit (defaults to 1). Ignored in - * front-layer mode. + * @brief Construct a layer builder over a per-qubit DAG. + * @param graph Per-qubit DAG columns (each deque owns operation pointers). + * @param isFrontLayer True for executable front layer mode; false for + * look-ahead mode. + * @param lookaheadDepth Max number of multi-qubit gates to look ahead per + * qubit. */ explicit NeutralAtomLayer(DAG graph, const bool isFrontLayer, const uint32_t lookaheadDepth = 1) @@ -108,58 +100,53 @@ class NeutralAtomLayer { } /** - * @brief Returns the current layer of gates - * @return The current layer of gates + * @brief Get the current executable/look-ahead layer gate list. + * @return Copy of current layer gates. */ [[nodiscard]] GateList getGates() const { return gates; } /** - * @brief Return the gates that were added the last time the layer was - * updated. - * @details This is populated during the most recent call to - * updateByQubits()/candidatesToGates() and is cleared and repopulated on - * subsequent updates. - * @return The subset of gates newly added to the layer during the last - * update. + * @brief Get gates newly added during the latest update. + * @details Populated by the most recent update invocation then refreshed each + * subsequent update. + * @return Copy of gates added since prior update. */ [[nodiscard]] GateList getNewGates() const { return newGates; } /** - * @brief Initialize the layer by updating all qubits from their current - * DAG-frontier. + * @brief Initialize internal frontiers and populate initial candidates/gates. + * @details Advances all qubit DAG iterators, builds candidate queues and + * promotes ready operations. */ void initAllQubits(); /** - * @brief Remove gates from the current layer and advance affected qubits. - * @details Erases the provided gates from the layer, then advances the DAG - * frontier for all qubits touched by those gates, updating candidates and - * possibly pulling in new gates. - * @param gatesToRemove Gates to remove from the current layer. + * @brief Remove specified gates then advance affected qubit frontiers. + * @details After erasing gates, updates candidates/gates for all qubits they + * touched, potentially adding new ready operations. + * @param gatesToRemove Gates to erase from current layer. */ void removeGatesAndUpdate(const GateList& gatesToRemove); }; // Commutation checks /** - * @brief Check whether an operation commutes at a specific qubit with all - * operations already present in a layer. - * @param layer The current layer (list of operations) to check against. - * @param opPointer The operation to be tested for commutation. - * @param qubit The qubit at which commutation is assessed. - * @return true if @p opPointer commutes with every operation in @p layer at - * @p qubit; false otherwise. + * @brief Determine if an operation commutes with all layer operations at a + * qubit. + * @param layer Current layer gate list. + * @param opPointer Operation to test. + * @param qubit Qubit index for commutation assessment. + * @return True if opPointer commutes with every gate in layer at qubit; false + * otherwise. */ bool commutesWithAtQubit(const GateList& layer, const qc::Operation* opPointer, const qc::Qubit& qubit); /** - * @brief Check whether two operations commute at a specific qubit. - * @details Applies simple syntactic rules: non-unitaries never commute; - * single-qubit gates commute; identities commute; gates that do not act on the - * qubit commute; for two-qubit gates, certain control/target patterns commute - * (both controlled on the qubit, control with Z on the qubit, or equal target - * types on the qubit). + * @brief Check pairwise commutation of two operations at a qubit. + * @details Simple syntactic rules: non-unitaries never commute; identities and + * single-qubit ops commute; ops not acting on the qubit commute; specific + * two-qubit control/target patterns are considered commuting. * @param op1 First operation. * @param op2 Second operation. - * @param qubit The qubit at which commutation is assessed. - * @return true if the operations commute at @p qubit; false otherwise. + * @param qubit Qubit index for commutation assessment. + * @return True if operations commute at qubit; false otherwise. */ bool commuteAtQubit(const qc::Operation* op1, const qc::Operation* op2, const qc::Qubit& qubit); From ccaaf06891c6b9d4d2844e9f9f1aa2dcd66105d2 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 7 Nov 2025 10:32:31 +0100 Subject: [PATCH 238/394] =?UTF-8?q?=F0=9F=93=9D=20added=20descriptions=20f?= =?UTF-8?q?or=20Definitions.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/NeutralAtomDefinitions.hpp | 62 ++++++++++++++++---- 1 file changed, 50 insertions(+), 12 deletions(-) diff --git a/include/hybridmap/NeutralAtomDefinitions.hpp b/include/hybridmap/NeutralAtomDefinitions.hpp index 7147e49e3..35f6b6f9f 100644 --- a/include/hybridmap/NeutralAtomDefinitions.hpp +++ b/include/hybridmap/NeutralAtomDefinitions.hpp @@ -26,48 +26,86 @@ class Operation; } // namespace qc namespace na { -// A CoordIndex corresponds to node in the SLM grid, where an atom can be placed -// (or not). +/** + * @brief Index of a site in the SLM grid where an atom may reside. + * @details Refers to a physical coordinate slot; occupancy varies during + * mapping/shuttling. + */ using CoordIndex = std::uint32_t; using CoordIndices = std::vector; using AdjacencyMatrix = qc::SymmetricMatrix; -// A HwQubit corresponds to an atom in the neutral atom architecture. It can be -// used as qubit or not and occupies a certain position in the architecture. +/** + * @brief Identifier of an atom (hardware qubit) in the architecture. + * @details May or may not currently host a logical qubit; linked to a + * coordinate index. + */ using HwQubit = uint32_t; using HwQubits = std::set; using HwQubitsVector = std::vector; +/** + * @brief Bridge operation and the sequence of hardware qubits it spans. + * @details The vector lists qubits involved in mediating an interaction (e.g., + * for a CZ bridge chain). + */ using Bridge = std::pair>; using Bridges = std::vector; -using HwPositions [[maybe_unused]] = std::vector; -// A qc::Qubit corresponds to a qubit in the quantum circuit. It can be mapped -// to a hardware qubit. +using HwPositions [[maybe_unused]] = + std::vector; // Deprecated alias for historical layout snapshots. +/** + * @brief Set of logical circuit qubits (indices in the quantum computation). + */ using Qubits = std::set; -// Swaps are between hardware qc::Qubits (one of them can be unmapped). +/** + * @brief Hardware-level SWAP operand pair (one endpoint can be unmapped). + */ using Swap = std::pair; using Swaps = std::vector; +/** + * @brief SWAP annotated with a weight (e.g., cost or heuristic score). + */ using WeightedSwap = std::pair; using WeightedSwaps = std::vector; -// The distance between two hardware qubits using SWAP gates. +/** + * @brief Distance (# of SWAPs) between hardware qubits under current mapping. + */ using SwapDistance = int32_t; -// Moves are between coordinates (the first is occupied, the second is not). +/** + * @brief Atom shuttle between two coordinate indices. + * @details c1: source (expected occupied), c2: destination (expected free). + * load1/load2 specify load/unload actions (e.g., addressing focus). + */ struct AtomMove { CoordIndex c1; CoordIndex c2; bool load1; bool load2; - // implement operator== + /** + * @brief Equality comparison. + * @param other Move to compare. + * @return True if all fields match. + */ bool operator==(const AtomMove& other) const { return c1 == other.c1 && c2 == other.c2 && load1 == other.load1 && load2 == other.load2; } + /** + * @brief Inequality comparison. + * @param other Move to compare. + * @return True if any field differs. + */ bool operator!=(const AtomMove& other) const { return !(*this == other); } }; -// Used to represent operations +/** + * @brief List of quantum operations (gate pointers), e.g., a layer. + */ using GateList = std::vector; +/** + * @brief Collection of gate lists (e.g., per-qubit candidate sets). + */ using GateLists = std::vector; } // namespace na From 277922da1f34ca1e4130b9fc793a72095db5f4a9 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 7 Nov 2025 10:35:41 +0100 Subject: [PATCH 239/394] =?UTF-8?q?=F0=9F=93=9D=20added=20descriptions=20f?= =?UTF-8?q?or=20Architecture.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/NeutralAtomArchitecture.hpp | 389 ++++++++++-------- 1 file changed, 224 insertions(+), 165 deletions(-) diff --git a/include/hybridmap/NeutralAtomArchitecture.hpp b/include/hybridmap/NeutralAtomArchitecture.hpp index 902c5160b..1305435b9 100644 --- a/include/hybridmap/NeutralAtomArchitecture.hpp +++ b/include/hybridmap/NeutralAtomArchitecture.hpp @@ -37,37 +37,18 @@ namespace na { /** - * @brief Class to store the properties of a neutral atom architecture - * @details - * The properties of a neutral atom architecture are: - * - number of rows - * - number of columns - * - number of AODs - * - number of AOD coordinates - * - inter-qubit distance - * - interaction radius - * - blocking factor - * - minimal AOD distance - * The properties are loaded from a JSON file. - * - * The class also provides functions to compute the swap distances between - * qubits and the nearby qubits for each qubit. + * @brief Device model for a neutral atom architecture. + * @details Holds fixed device properties and run-time parameters loaded from + * JSON, derives coordinate layout, connectivity (swap distances), and proximity + * lists. Provides timing and fidelity queries, distance helpers, and optional + * animation export. */ class NeutralAtomArchitecture { /** - * @brief Class to store the properties of a neutral atom architecture - * @details - * The properties of a neutral atom architecture are: - * - number of rows - * - number of columns - * - number of AODs - * - number of AOD coordinates - * - inter-qubit distance - * - interaction radius - * - blocking factor - * - minimal AOD distance - * The properties are loaded from a JSON file and are fixed for each - * architecture. + * @brief Fixed, immutable properties of a device. + * @details Encodes grid layout and geometry: rows/columns, number of AODs and + * coordinates, inter-qubit distance, interaction radius, blocking factor, and + * derived AOD intermediate levels. */ class Properties { protected: @@ -81,7 +62,22 @@ class NeutralAtomArchitecture { qc::fp blockingFactor; public: + /** + * @brief Default-construct with zeroed properties. + */ Properties() = default; + /** + * @brief Construct with explicit device properties. + * @param rows Grid rows. + * @param columns Grid columns. + * @param aods Number of AODs. + * @param aodCoordinates Number of AOD coordinates. + * @param qubitDistance Inter-qubit spacing (grid unit). + * @param radius Interaction radius (in grid units or scaled distance). + * @param blockingFac Blocking factor for concurrent operations. + * @param aodDist Minimum AOD step distance used to derive intermediate + * levels. + */ Properties(const std::uint16_t rows, const std::uint16_t columns, const std::uint16_t aods, const std::uint16_t aodCoordinates, const qc::fp qubitDistance, const qc::fp radius, @@ -91,55 +87,74 @@ class NeutralAtomArchitecture { static_cast(qubitDistance / aodDist)), nAodCoordinates(aodCoordinates), interQubitDistance(qubitDistance), interactionRadius(radius), blockingFactor(blockingFac) {} + /** + * @brief Total grid sites (rows*columns). + * @return Number of positions. + */ [[nodiscard]] std::uint16_t getNpositions() const { return nRows * nColumns; } + /** + * @brief Grid rows. + */ [[nodiscard]] std::uint16_t getNrows() const { return nRows; } + /** + * @brief Grid columns. + */ [[nodiscard]] std::uint16_t getNcolumns() const { return nColumns; } + /** + * @brief Number of AODs. + */ [[nodiscard]] std::uint16_t getNAods() const { return nAods; } + /** + * @brief Number of AOD coordinates. + */ [[nodiscard]] std::uint16_t getNAodCoordinates() const { return nAodCoordinates; } + /** + * @brief Number of intermediate AOD steps between neighboring sites. + */ [[nodiscard]] std::uint16_t getNAodIntermediateLevels() const { return nAodIntermediateLevels; } + /** + * @brief Inter-qubit spacing. + */ [[nodiscard]] qc::fp getInterQubitDistance() const { return interQubitDistance; } + /** + * @brief Interaction radius. + */ [[nodiscard]] qc::fp getInteractionRadius() const { return interactionRadius; } + /** + * @brief Blocking factor. + */ [[nodiscard]] qc::fp getBlockingFactor() const { return blockingFactor; } }; /** - * @brief Class to store the parameters of a neutral atom architecture - * @details - * The parameters of a neutral atom architecture are: - * - number of qubits - * - gate times - * - gate average fidelities - * - shuttling times - * - shuttling average fidelities - * - decoherence times - * The parameters are loaded from a JSON file. - * The difference to the properties is that the parameters can change - * from run to run. + * @brief Run-time parameters of a device (may vary per run). + * @details Includes number of active qubits, gate and shuttling + * times/fidelities, and decoherence times loaded from JSON. */ struct Parameters { /** - * @brief Struct to store the decoherence times of a neutral atom - * architecture - * @details - * The decoherence times of a neutral atom architecture are: - * - T1 [µs] - * - T2 [µs] - * - effective decoherence time [µs] + * @brief Longitudinal and transverse decoherence times. + * @details Provides an effective time tEff = (T1*T2)/(T1+T2) when both are + * non-zero. */ struct DecoherenceTimes { qc::fp t1 = 0; qc::fp t2 = 0; + /** + * @brief Effective decoherence time. + * @return 0 if both T1 and T2 are zero; otherwise (T1*T2)/(T1+T2). + */ [[nodiscard]] qc::fp tEff() const { if (t1 == 0 && t2 == 0) { return 0; @@ -166,24 +181,20 @@ class NeutralAtomArchitecture { BridgeCircuits bridgeCircuits = BridgeCircuits(10); /** - * @brief Create the coordinates. + * @brief Create grid coordinates for each position on the device. */ void createCoordinates(); /** - * @brief Compute the swap distances between the coordinates - * @details - * The swap distances are computed using the coordinates of the qubits. - * The swap distance is the distance between the qubits in terms of - * edges in the resulting connectivity graph. This can be computed - * beforehand. + * @brief Precompute swap distances between coordinates. + * @details Build connectivity graph based on interaction radius and compute + * shortest-path distances in number of edges. + * @param interactionRadius Interaction radius used to define connectivity. */ void computeSwapDistances(qc::fp interactionRadius); /** - * @brief Compute the nearby coordinates for each coordinate - * @details - * The nearby qubits are the qubits that are close enough to be connected - * by an edge in the resulting connectivity graph. This can be be computed - * beforehand. + * @brief Precompute per-site lists of nearby coordinates. + * @details Nearby coordinates are those within interaction radius forming + * edges in the connectivity graph. */ void computeNearbyCoordinates(); @@ -191,99 +202,106 @@ class NeutralAtomArchitecture { std::string name; /** - * @brief Construct a new Neutral Atom Architecture object - * @details - * The properties of the architecture are loaded from a JSON file. - * @param filename The name of the JSON file + * @brief Construct an architecture by loading its JSON description. + * @param filename Path to the JSON file. + * @details Loads properties and parameters, then derives coordinates, + * connectivity, and proximity tables. + * @throw std::runtime_error If the file cannot be opened or parsed (depending + * on JSON loader implementation). */ explicit NeutralAtomArchitecture(const std::string& filename); /** - * @brief Load the properties of the architecture from a JSON file - * @param filename The name of the JSON file + * @brief Load (or reload) properties and parameters from JSON. + * @param filename Path to the JSON file. + * @throw std::runtime_error If the file cannot be opened or parsed (depending + * on JSON loader implementation). */ void loadJson(const std::string& filename); // Getters /** - * @brief Get the number of rows - * @return The number of rows + * @brief Get the number of rows. + * @return Row count. */ [[nodiscard]] std::uint16_t getNrows() const { return properties.getNrows(); } /** - * @brief Get the number of columns - * @return The number of columns + * @brief Get the number of columns. + * @return Column count. */ [[nodiscard]] std::uint16_t getNcolumns() const { return properties.getNcolumns(); } /** - * @brief Get the number of positions - * @return The number of positions + * @brief Get the number of grid positions. + * @return Positions (rows*columns). */ [[nodiscard]] std::uint16_t getNpositions() const { return properties.getNpositions(); } /** - * @brief Get the number of AODs - * @return The number of AODs + * @brief Get the number of AODs. + * @return AOD count. */ [[maybe_unused]] [[nodiscard]] std::uint16_t getNAods() const { return properties.getNAods(); } /** - * @brief Get the number of AOD coordinates - * @return The number of AOD coordinates + * @brief Get the number of AOD coordinates. + * @return AOD coordinate count. */ [[nodiscard]] [[maybe_unused]] std::uint16_t getNAodCoordinates() const { return properties.getNAodCoordinates(); } /** - * @brief Get the number of qubits - * @return The number of qubits + * @brief Get the number of qubits. + * @return Active qubit count. */ [[nodiscard]] CoordIndex getNqubits() const { return parameters.nQubits; } /** - * @brief Get the inter-qubit distance - * @return The inter-qubit distance + * @brief Get the inter-qubit distance. + * @return Spacing between neighboring sites. */ [[nodiscard]] qc::fp getInterQubitDistance() const { return properties.getInterQubitDistance(); } - + /** + * @brief Distance represented by one AOD intermediate level. + * @return Inter-qubit distance divided by number of AOD intermediate levels. + */ [[nodiscard]] qc::fp getOffsetDistance() const { return getInterQubitDistance() / getNAodIntermediateLevels(); } /** - * @brief Get the interaction radius - * @return The interaction radius + * @brief Get the interaction radius. + * @return Interaction radius. */ [[nodiscard]] qc::fp getInteractionRadius() const { return properties.getInteractionRadius(); } /** - * @brief Get the blocking factor - * @return The blocking factor + * @brief Get the blocking factor. + * @return Blocking factor. */ [[nodiscard]] qc::fp getBlockingFactor() const { return properties.getBlockingFactor(); } /** - * @brief Get precomputed swap distance between two coordinates - * @param idx1 The index of the first coordinate - * @param idx2 The index of the second coordinate - * @return The swap distance between the two coordinates + * @brief Get precomputed swap distance between two coordinates. + * @param idx1 First coordinate index. + * @param idx2 Second coordinate index. + * @return Swap distance (#edges) between the coordinates. */ [[nodiscard]] SwapDistance getSwapDistance(const CoordIndex idx1, const CoordIndex idx2) const { return swapDistances(idx1, idx2); } /** - * @brief Get precomputed swap distance between two coordinates - * @param c1 The first coordinate - * @param c2 The second coordinate - * @return The swap distance between the two coordinates + * @brief Get precomputed swap distance between two coordinates. + * @param c1 First coordinate. + * @param c2 Second coordinate. + * @return Swap distance (#edges) between the coordinates. */ [[nodiscard]] SwapDistance getSwapDistance(const Location& c1, const Location& c2) const { @@ -293,36 +311,41 @@ class NeutralAtomArchitecture { } /** - * @brief Get the number of AOD intermediate levels, i.e. the number of - * possible positions between two coordinates. - * @return The number of AOD intermediate levels + * @brief Number of AOD intermediate levels (positions between two neighbors). + * @return AOD intermediate levels. */ [[nodiscard]] uint16_t getNAodIntermediateLevels() const { return properties.getNAodIntermediateLevels(); } /** - * @brief Get the execution time of an operation - * @param op The operation - * @return The execution time of the operation + * @brief Get the execution time of an operation. + * @param op Operation pointer. + * @return Duration of the operation on this device. */ [[nodiscard]] qc::fp getOpTime(const qc::Operation* op) const; /** - * @brief Get the fidelity of an operation - * @param op The operation - * @return The fidelity of the operation + * @brief Get the average fidelity of an operation. + * @param op Operation pointer. + * @return Average fidelity for the operation on this device. */ [[nodiscard]] qc::fp getOpFidelity(const qc::Operation* op) const; /** - * @brief Get indices of the nearby coordinates that are blocked by an - * operation - * @param op The operation - * @return The indices of the nearby coordinates that are blocked by the - * operation + * @brief Get indices of nearby coordinates blocked by an operation. + * @param op Operation pointer. + * @return Set of coordinate indices blocked while executing op. */ [[nodiscard]] std::set getBlockedCoordIndices(const qc::Operation* op) const; // Getters for the parameters + /** + * @brief Retrieve the base time for a named gate. + * @param s Gate name. + * @return Gate time if present; otherwise falls back to the time of "none" + * and prints a message. + * @note If the fallback entry "none" is not present, an exception from + * std::map::at may be thrown. + */ [[nodiscard]] qc::fp getGateTime(const std::string& s) const { if (parameters.gateTimes.find(s) == parameters.gateTimes.end()) { std::cout << "Gate time for " << s << " not found\n" @@ -332,16 +355,12 @@ class NeutralAtomArchitecture { return parameters.gateTimes.at(s); } /** - * @brief Retrieves the average fidelity of a gate. - * - * This function is responsible for fetching the average fidelity of a gate - * specified by its name. If the gate is not found in the parameters, it will - * print a message to the console and return the average fidelity of a default - * gate. - * - * @param s The name of the gate. - * @return The average fidelity of the specified gate or the default gate if - * the specified gate is not found. + * @brief Retrieve the average fidelity for a named gate. + * @param s Gate name. + * @return Gate average fidelity if present; otherwise falls back to the + * fidelity of "none" and prints a message. + * @note If the fallback entry "none" is not present, an exception from + * std::map::at may be thrown. */ [[nodiscard]] qc::fp getGateAverageFidelity(const std::string& s) const { if (parameters.gateAverageFidelities.find(s) == @@ -353,25 +372,27 @@ class NeutralAtomArchitecture { return parameters.gateAverageFidelities.at(s); } /** - * @brief Get the shuttling time of a shuttling operation - * @param shuttlingType The type of the shuttling operation - * @return The shuttling time of the shuttling operation + * @brief Get the shuttling time of an operation type. + * @param shuttlingType Shuttling operation type (OpType). + * @return Shuttling time for the given type. + * @throw std::out_of_range If the operation type is unknown. */ [[nodiscard]] qc::fp getShuttlingTime(const qc::OpType shuttlingType) const { return parameters.shuttlingTimes.at(shuttlingType); } /** - * @brief Get the average fidelity of a shuttling operation - * @param shuttlingType The type of the shuttling operation - * @return The average fidelity of the shuttling operation + * @brief Get the average fidelity of a shuttling operation type. + * @param shuttlingType Shuttling operation type (OpType). + * @return Average shuttling fidelity for the given type. + * @throw std::out_of_range If the operation type is unknown. */ [[nodiscard]] qc::fp getShuttlingAverageFidelity(const qc::OpType shuttlingType) const { return parameters.shuttlingAverageFidelities.at(shuttlingType); } /** - * @brief Get the decoherence time - * @return The decoherence time + * @brief Get the effective decoherence time of the device. + * @return Effective T (computed from T1 and T2). */ [[nodiscard]] qc::fp getDecoherenceTime() const { return parameters.decoherenceTimes.tEff(); @@ -379,22 +400,27 @@ class NeutralAtomArchitecture { // Converters between indices and coordinates /** - * @brief Get a coordinate corresponding to an index - * @param idx The index - * @return The coordinate corresponding to the index + * @brief Convert an index to a grid coordinate. + * @param idx Coordinate index. + * @return Location at the given index. */ [[nodiscard]] Location getCoordinate(const CoordIndex idx) const { return coordinates[idx]; } /** - * @brief Get the index corresponding to a coordinate - * @param c The coordinate - * @return The index corresponding to the coordinate + * @brief Convert a grid coordinate to its index. + * @param c Location. + * @return Linearized index for the coordinate. */ [[nodiscard]] [[maybe_unused]] CoordIndex getIndex(const Location& c) const { return static_cast(c.x + (c.y * properties.getNcolumns())); } + /** + * @brief Retrieve a precomputed bridge circuit of a given chain length. + * @param length Linear chain length. + * @return QuantumComputation holding the bridge circuit. + */ [[nodiscard]] [[maybe_unused]] qc::QuantumComputation getBridgeCircuit(const size_t length) const { return bridgeCircuits.bridgeCircuits[length]; @@ -402,16 +428,21 @@ class NeutralAtomArchitecture { // Distance functions /** - * @brief Get the Euclidean distance between two coordinate indices - * @param idx1 The index of the first coordinate - * @param idx2 The index of the second coordinate - * @return The Euclidean distance between the two coordinate indices + * @brief Euclidean distance between two coordinate indices. + * @param idx1 First coordinate index. + * @param idx2 Second coordinate index. + * @return Euclidean distance. */ [[nodiscard]] qc::fp getEuclideanDistance(const CoordIndex idx1, const CoordIndex idx2) const { return this->coordinates.at(idx1).getEuclideanDistance( this->coordinates.at(idx2)); } + /** + * @brief Sum of pairwise Euclidean distances among a set of indices. + * @param coords Set of coordinate indices. + * @return Sum of distances across ordered pairs (i!=j). + */ [[nodiscard]] qc::fp getAllToAllEuclideanDistance(const std::set& coords) const { qc::fp dist = 0; @@ -425,6 +456,11 @@ class NeutralAtomArchitecture { } return dist; } + /** + * @brief Total Euclidean distance covered by an aggregate move combination. + * @param moveComb Combination of atom moves. + * @return Sum of Euclidean distances for each move. + */ [[nodiscard]] qc::fp getMoveCombEuclideanDistance(const MoveComb& moveComb) const { qc::fp dist = 0; @@ -434,6 +470,11 @@ class NeutralAtomArchitecture { return dist; } + /** + * @brief Estimated path length for a flying-ancilla combination. + * @param faComb Flying ancilla move combination. + * @return Sum of distances along origin->q1 and twice q1->q2 per step. + */ [[nodiscard]] qc::fp getFaEuclideanDistance(const FlyingAncillaComb& faComb) const { qc::fp dist = 0; @@ -444,6 +485,11 @@ class NeutralAtomArchitecture { return dist; } + /** + * @brief Estimated path length for a pass-by combination. + * @param pbComb Pass-by move combination. + * @return Twice the sum of distances of its constituent moves. + */ [[nodiscard]] qc::fp getPassByEuclideanDistance(const PassByComb& pbComb) const { qc::fp dist = 0; @@ -454,20 +500,20 @@ class NeutralAtomArchitecture { } /** - * @brief Get the Euclidean distance between two coordinates - * @param c1 The first coordinate - * @param c2 The second coordinate - * @return The Euclidean distance between the two coordinates + * @brief Euclidean distance between two coordinates. + * @param c1 First coordinate. + * @param c2 Second coordinate. + * @return Euclidean distance. */ [[nodiscard]] static qc::fp getEuclideanDistance(const Location& c1, const Location& c2) { return c1.getEuclideanDistance(c2); } /** - * @brief Get the Manhattan distance between two coordinate indices - * @param idx1 The index of the first coordinate - * @param idx2 The index of the second coordinate - * @return The Manhattan distance between the two coordinate indices + * @brief Manhattan distance in X between two indices. + * @param idx1 First index. + * @param idx2 Second index. + * @return |x1-x2|. */ [[nodiscard]] CoordIndex getManhattanDistanceX(const CoordIndex idx1, const CoordIndex idx2) const { @@ -476,10 +522,10 @@ class NeutralAtomArchitecture { this->coordinates.at(idx2))); } /** - * @brief Get the Manhattan distance between two coordinate indices - * @param idx1 The index of the first coordinate - * @param idx2 The index of the second coordinate - * @return The Manhattan distance between the two coordinate indices + * @brief Manhattan distance in Y between two indices. + * @param idx1 First index. + * @param idx2 Second index. + * @return |y1-y2|. */ [[nodiscard]] CoordIndex getManhattanDistanceY(const CoordIndex idx1, const CoordIndex idx2) const { @@ -489,29 +535,28 @@ class NeutralAtomArchitecture { // Nearby coordinates /** - * @brief Get the precomputed nearby coordinates for a coordinate index - * @param idx The index of the coordinate - * @return The precomputed nearby coordinates for the coordinate index + * @brief Get precomputed nearby coordinates for an index. + * @param idx Coordinate index. + * @return Set of indices within interaction radius of idx. */ [[nodiscard]] std::set getNearbyCoordinates(const CoordIndex idx) const { return nearbyCoordinates[idx]; } /** - * @brief Get the coordinates which are exactly one step away from a - * coordinate index, i.e. the ones above, below, left and right. - * @param idx The index of the coordinate - * @return The coordinates which are exactly one step away from the - * coordinate index + * @brief Get coordinates exactly one grid step away (von Neumann + * neighborhood). + * @param idx Coordinate index. + * @return Indices of neighbors above, below, left, and right. */ [[nodiscard]] std::vector getNN(CoordIndex idx) const; // MoveVector functions /** - * @brief Get the MoveVector between two coordinate indices - * @param idx1 The index of the first coordinate - * @param idx2 The index of the second coordinate - * @return The MoveVector between the two coordinate indices + * @brief Construct a MoveVector between two coordinate indices. + * @param idx1 Start index. + * @param idx2 End index. + * @return MoveVector from start to end. */ [[nodiscard]] MoveVector getVector(const CoordIndex idx1, const CoordIndex idx2) const { @@ -519,9 +564,9 @@ class NeutralAtomArchitecture { this->coordinates[idx2].x, this->coordinates[idx2].y}; } /** - * @brief Computes the time it takes to move a qubit along a MoveVector - * @param v The MoveVector - * @return The time it takes to move a qubit along the MoveVector + * @brief Estimate time to move along a MoveVector. + * @param v MoveVector path. + * @return Shuttling time proportional to Euclidean length and device speed. */ [[nodiscard]] qc::fp getVectorShuttlingTime(const MoveVector& v) const { return v.getLength() * this->getInterQubitDistance() / @@ -529,12 +574,19 @@ class NeutralAtomArchitecture { } /** - * @brief Returns a csv string for the animation of the architecture - * @return The csv string for the animation of the architecture + * @brief Generate CSV content describing the device for animation. + * @param shuttlingSpeedFactor Scaling factor applied to shuttling speeds. + * @return CSV string describing layout and timing parameters. */ [[nodiscard]] std::string getAnimationMachine(qc::fp shuttlingSpeedFactor) const; + /** + * @brief Generate the animation style content. + * @param stylePath Optional path to a style file overriding defaults. + * @return Text content for the style (.nastyle). + * @note Falls back to compiled-in defaults if the file cannot be opened. + */ [[nodiscard]] std::string getAnimationStyle(const std::string& stylePath) const { std::string style(defaultStyle); @@ -564,8 +616,9 @@ class NeutralAtomArchitecture { } /** - * @brief Save the animation of the architecture to a csv file - * @param filename The name of the csv file + * @brief Save the device animation CSV to a file. + * @param filename Output CSV filename. + * @param shuttlingSpeedFactor Scaling factor applied to shuttling speeds. */ [[maybe_unused]] void saveAnimationMachine(const std::string& filename, @@ -574,6 +627,12 @@ class NeutralAtomArchitecture { file << getAnimationMachine(shuttlingSpeedFactor); } + /** + * @brief Save the animation style content to a file. + * @param filename Output style filename. + * @param stylePath Optional path to a base style to load before applying + * defaults. + */ [[maybe_unused]] void saveAnimationStyle(const std::string& filename, const std::string& stylePath = "") const { From 08c6ea3f263428690a7dcce23618ca6602734138 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 7 Nov 2025 10:38:04 +0100 Subject: [PATCH 240/394] =?UTF-8?q?=F0=9F=93=9D=20added=20descriptions=20f?= =?UTF-8?q?or=20AodConverter.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/MoveToAodConverter.hpp | 186 +++++++++++------------ 1 file changed, 89 insertions(+), 97 deletions(-) diff --git a/include/hybridmap/MoveToAodConverter.hpp b/include/hybridmap/MoveToAodConverter.hpp index 8409d04a5..876902d85 100644 --- a/include/hybridmap/MoveToAodConverter.hpp +++ b/include/hybridmap/MoveToAodConverter.hpp @@ -29,18 +29,23 @@ #include namespace na { -// Possible types two Move combination can be combined to +/** + * @brief Result type for merging two move-derived activations. + * @details Indicates whether merging is impossible, trivial, a full merge, or + * requires appending. + */ enum class ActivationMergeType : uint8_t { Impossible, Trivial, Merge, Append }; -// Used to indicate how AodOperations can be merged +/** + * @brief Pair of merge types for X and Y dimensions. + */ using MergeTypeXY = std::pair; /** - * @brief Class to convert abstract move operations to AOD movements on a - * neutral atom architecture - * @details The scheduler takes a quantum circuit containing abstract move - * operations and tries to merge them into parallel AO movements. It also - * manages the small offset movements required while loading or unloading of - * AODs. + * @brief Converts abstract atom moves into concrete AOD activation/move + * sequences. + * @details Groups parallelizable moves, computes safe offset maneuvers for + * loading/unloading AODs, and emits AOD operations (activate, move, deactivate) + * respecting device constraints. */ class MoveToAodConverter { struct AncillaAtom { @@ -62,18 +67,15 @@ class MoveToAodConverter { protected: /** - * @brief Struct to store information about specific AOD activations. - * @details It contains: - * - the offset moves in x and y direction - * - the actual moves + * @brief Helper for constructing and merging AOD activations/moves. + * @details Tracks per-dimension AOD moves with offsets and associated atom + * moves; produces AodOperation sequences for activation/move/deactivation. */ struct AodActivationHelper { /** - * @brief Describes a single AOD movement in either x or y direction - * @details It contains: - * - the initial position of the AOD - * - the delta of the AOD movement - * - the size of the offset of the AOD movement + * @brief Single AOD movement in one dimension (x or y). + * @details Stores initial position, movement delta, required offset to + * avoid crossing, and whether a load/unload is needed. */ struct AodMove { // start of the move @@ -93,9 +95,10 @@ class MoveToAodConverter { load(loadMove) {} }; /** - * @brief Manages the activation of an atom using an AOD. - * @details The same struct is used also to deactivate the AOD, just - * reversed. + * @brief Aggregate of per-dimension activation moves plus logical atom + * move. + * @details Represents either activation or deactivation depending on + * context. Holds x- and y- dimension AOD moves and the associated AtomMove. */ struct AodActivation { // first: x, second: delta x, third: offset x @@ -128,7 +131,6 @@ class MoveToAodConverter { } }; - // AODScheduler // NeutralAtomArchitecture to call necessary hardware information const NeutralAtomArchitecture* arch; std::vector allActivations; @@ -147,25 +149,27 @@ class MoveToAodConverter { // Methods /** - * @brief Returns all AOD moves in the given dimension/direction which start - * at the given initial position - * @param dim The dimension/direction to check - * @param init The initial position to check - * @return A vector of AOD moves + * @brief Return all AOD moves along a dimension that start at a given + * position. + * @param dim Dimension (X or Y). + * @param init Initial position index. + * @return Vector of matching AOD move descriptors. */ [[nodiscard]] std::vector> getAodMovesFromInit(Dimension dim, uint32_t init) const; // Activation management /** - * @brief Adds the move to the current activations - * @details The move is merged into the current activations depending on the - * given merge types - * @param merge The merge types in x and y direction - * @param origin The origin of the move - * @param move The move to add - * @param v The move vector of the move - * @param needLoad + * @brief Merge an atom move into current activations according to merge + * policy. + * @details Uses per-dimension merge types to either merge, append, or + * reject combining with in-flight activations; records offsets and + * load/unload handling. + * @param merge Merge policy for X and Y. + * @param origin Origin location. + * @param move Atom move descriptor. + * @param v Geometric move vector. + * @param needLoad Whether an AOD load is required. */ void addActivation(std::pair merge, @@ -175,42 +179,38 @@ class MoveToAodConverter { void addActivationFa(const Location& origin, const AtomMove& move, MoveVector v, bool needLoad); /** - * @brief Merges the given activation into the current activations - * @param dim The dimension/direction of the activation - * @param activationDim The activation to merge in the given - * dimension/direction - * @param activationOtherDim The activation to merge/add in the other - * dimension/direction + * @brief Merge an activation into the aggregate along a specific dimension. + * @param dim Dimension of the activation. + * @param activationDim Activation to merge for the specified dimension. + * @param activationOtherDim Complementary activation for the other + * dimension. */ void mergeActivationDim(Dimension dim, const AodActivation& activationDim, const AodActivation& activationOtherDim); /** - * @brief Orders the aod offset moves such that they will not cross each - * other - * @param aodMoves The aod offset moves to order - * @param sign The direction of the offset moves (right/left or down/up) + * @brief Reorder offset moves to avoid crossing. + * @param aodMoves Collection of offset moves to reorder. + * @param sign Direction of offsets (+1/-1 for right/left or down/up). */ static void reAssignOffsets(std::vector>& aodMoves, int32_t sign); /** - * @brief Returns the maximum offset in the given dimension/direction from - * the given initial position - * @param dim The dimension/direction to check - * @param init The initial position to check - * @param sign The direction of the offset moves (right/left or down/up) - * @return The maximum offset + * @brief Maximum absolute offset at a position along a dimension. + * @param dim Dimension. + * @param init Initial position. + * @param sign Direction (+1/-1). + * @return Maximum offset value. */ [[nodiscard]] uint32_t getMaxOffsetAtInit(Dimension dim, uint32_t init, int32_t sign) const; /** - * @brief Checks if there is still space at the given initial position and - * the given direction - * @param dim The dimension/direction to check - * @param init The initial position to check - * @param sign The direction of the offset moves (right/left or down/up) - * @return True if there is still space, false otherwise + * @brief Check whether additional offset space is available at a position. + * @param dim Dimension. + * @param init Initial position. + * @param sign Direction (+1/-1). + * @return True if more offset steps fit; false otherwise. */ [[nodiscard]] bool checkIntermediateSpaceAtInit(Dimension dim, uint32_t init, @@ -222,19 +222,16 @@ class MoveToAodConverter { std::vector& offsetOperations) const; // Convert activation to AOD operations /** - * @brief Converts activation into AOD operation (activate, move, - * deactivate) - * @param activation The activation to convert - * @param arch The neutral atom architecture to call necessary hardware - * information - * @param type The type of the activation (loading or unloading) - * @return The activation as AOD operation + * @brief Convert a single activation aggregate into AOD operations. + * @details Emission order: activate, move, deactivate. + * @param activation Activation aggregate to convert. + * @return Vector of emitted AOD operations. */ [[nodiscard]] std::vector getAodOperation(const AodActivation& activation) const; /** - * @brief Converts all activations into AOD operations - * @return All activations of the AOD activation helper as AOD operations + * @brief Convert all stored activations into AOD operations. + * @return Concatenated vector of emitted AOD operations. */ [[nodiscard]] std::vector getAodOperations() const; @@ -271,21 +268,22 @@ class MoveToAodConverter { // Methods /** - * @brief Checks if the given move can be added to the move group - * @param move Move to check - * @return True if the move can be added, false otherwise + * @brief Check if a move can be added to the current group. + * @param move Move to check. + * @param archArg Architecture for geometric/constraint checks. + * @return True if compatible with group; false otherwise. */ bool canAddMove(const AtomMove& move, const NeutralAtomArchitecture& archArg); /** - * @brief Adds the given move to the move group - * @param move Move to add - * @param idx Index of the move in the original quantum circuit + * @brief Add a move to the group. + * @param move Move to add. + * @param idx Circuit index of the move. */ void addMove(const AtomMove& move, uint32_t idx); /** - * @brief Returns the circuit index of the first move in the move group - * @return Circuit index of the first move in the move group + * @brief Circuit index of the earliest move in the group. + * @return Minimum circuit index across stored moves. */ [[nodiscard]] uint32_t getFirstIdx() const { @@ -298,21 +296,18 @@ class MoveToAodConverter { return std::min(moves.front().second, movesFa.front().second); } /** - * @brief Checks if the two moves can be executed in parallel - * @param v1 The first move - * @param v2 The second move - * @return True if the moves can be executed in parallel, false otherwise + * @brief Check if two moves are parallelizable. + * @param v1 First move vector. + * @param v2 Second move vector. + * @return True if they can execute in parallel; false otherwise. */ static bool parallelCheck(const MoveVector& v1, const MoveVector& v2); /** - * @brief Helper function to create the actual shuttling operation between - * the loading at the initial position and the unloading at the final - * position - * @param opsInit Loading operations - * @param opsFinal Unloading operations - * @return The shuttling operation between the loading and unloading - * operations + * @brief Build the shuttling operation connecting load and unload phases. + * @param aodActivationHelper Activation helper (loading phase info). + * @param aodDeactivationHelper Deactivation helper (unloading phase info). + * @return Constructed AOD shuttling operation. */ static AodOperation connectAodOperations(const AodActivationHelper& aodActivationHelper, @@ -330,18 +325,15 @@ class MoveToAodConverter { void initFlyingAncillas(); /** - * @brief Assigns move operations into groups that can be executed in parallel - * @param qc Quantum circuit to schedule + * @brief Partition moves into groups that can execute in parallel. + * @param qc Quantum circuit to schedule. */ void initMoveGroups( qc::QuantumComputation& qc); //, qc::Permutation& hwToCoordIdx); /** - * @brief Converts the move groups into the actual AOD operations - * @details For this the following steps are performed: - * - ActivationHelper to manage the loading - * - ActivationHelper to manage the unloading - * If not the whole move group can be executed in parallel, a new move group - * is created for the remaining moves. + * @brief Convert move groups into concrete AOD operations. + * @details Uses activation/deactivation helpers to emit load/move/unload + * sequences; splits groups when parallelism constraints require it. */ void processMoveGroups(); @@ -377,16 +369,16 @@ class MoveToAodConverter { } /** - * @brief Schedules the given quantum circuit using AODs - * @param qc Quantum circuit to schedule - * @return Scheduled quantum circuit, containing AOD operations + * @brief Schedule a circuit: replace abstract moves by AOD load/move/unload. + * @param qc Quantum circuit to schedule. + * @return New circuit containing AOD operations. */ qc::QuantumComputation schedule(qc::QuantumComputation& qc); //, qc::Permutation hwToCoordIdx); /** - * @brief Returns the number of move groups - * @return Number of move groups + * @brief Get number of constructed move groups. + * @return Count of move groups. */ [[nodiscard]] auto getNMoveGroups() const { return moveGroups.size(); } }; From 4bab3f6918fb05e88454730ffe18fd4482890891 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 7 Nov 2025 10:40:25 +0100 Subject: [PATCH 241/394] =?UTF-8?q?=F0=9F=93=9D=20added=20descriptions=20f?= =?UTF-8?q?or=20Mapping.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/Mapping.hpp | 73 ++++++++++++++++++++++++++--------- 1 file changed, 54 insertions(+), 19 deletions(-) diff --git a/include/hybridmap/Mapping.hpp b/include/hybridmap/Mapping.hpp index 5871f33e8..b8e9123d2 100644 --- a/include/hybridmap/Mapping.hpp +++ b/include/hybridmap/Mapping.hpp @@ -31,8 +31,11 @@ namespace na { /** - * @brief Class to manage the mapping between circuit qubits and hardware qubits - * in a bijective manner. + * @brief Maintains a bijective mapping between circuit (logical) and hardware + * qubits. + * @details Supports different initialization strategies, queries in both + * directions, and in-place rewriting of operation qubit indices. The mapping is + * kept one-to-one; swaps can update the mapping as the circuit is transformed. */ class Mapping { protected: @@ -43,18 +46,39 @@ class Mapping { DAG dag; /** - * @brief GraphMatching for initCoordMapping + * @brief Compute an initial mapping via (heuristic) graph matching. + * @details Matches circuit interaction structure to device structure to + * reduce expected routing overhead. + * @return Vector mapping logical qubit index i -> chosen hardware index for + * i. */ [[nodiscard]] std::vector graphMatching(); public: + /** + * @brief Default-construct an empty mapping. + */ Mapping() = default; + /** + * @brief Initialize with identity mapping for the first nQubits. + * @param nQubits Number of logical qubits; maps i -> i for i in [0,nQubits). + */ explicit Mapping(const size_t nQubits) { for (size_t i = 0; i < nQubits; ++i) { circToHw.emplace(i, i); } } + /** + * @brief Construct a mapping using a chosen initialization strategy. + * @param nQubits Number of logical qubits to map. + * @param initialMapping Initialization strategy (Identity or Graph). + * @param qc Circuit used to derive structure for graph-based initialization. + * @param hwQubits Target hardware description (capacity/topology + * constraints). + * @throw std::runtime_error If the circuit has more qubits than available + * hardware qubits. + */ Mapping(const size_t nQubits, const InitialMapping initialMapping, qc::QuantumComputation qc, HardwareQubits hwQubits) : hwQubits(std::move(hwQubits)), @@ -80,8 +104,8 @@ class Mapping { } /** * @brief Assigns a circuit qubit to a hardware qubit. - * @param qubit The circuit qubit to be assigned - * @param hwQubit The hardware qubit to be assigned + * @param qubit Circuit qubit to assign. + * @param hwQubit Hardware qubit index. */ void setCircuitQubit(const qc::Qubit qubit, const HwQubit hwQubit) { circToHw[qubit] = hwQubit; @@ -89,8 +113,10 @@ class Mapping { /** * @brief Returns the hardware qubit assigned to the given circuit qubit. - * @param qubit The circuit qubit to be queried - * @return The hardware qubit assigned to the given circuit qubit + * @param qubit Circuit qubit to query. + * @return Hardware qubit assigned to the given circuit qubit. + * @throw std::out_of_range If the circuit qubit is not present in the + * mapping. */ [[nodiscard]] HwQubit getHwQubit(const qc::Qubit qubit) const { return circToHw.at(qubit); @@ -98,8 +124,10 @@ class Mapping { /** * @brief Returns the hardware qubits assigned to the given circuit qubits. - * @param qubits The circuit qubits to be queried - * @return The hardware qubits assigned to the given circuit qubits + * @param qubits Set of circuit qubits to query. + * @return Set of corresponding hardware qubits. + * @throw std::out_of_range If any circuit qubit is not present in the + * mapping. */ [[nodiscard]] std::set getHwQubits(const std::set& qubits) const { @@ -112,8 +140,10 @@ class Mapping { /** * @brief Returns the hardware qubits assigned to the given circuit qubits. - * @param qubits The circuit qubits to be queried - * @return The hardware qubits assigned to the given circuit qubits + * @param qubits Ordered list of circuit qubits to query. + * @return Vector of corresponding hardware qubits (same order as input). + * @throw std::out_of_range If any circuit qubit is not present in the + * mapping. */ [[nodiscard]] std::vector getHwQubits(const std::vector& qubits) const { @@ -128,8 +158,10 @@ class Mapping { * @brief Returns the circuit qubit assigned to the given hardware qubit. * @details Throws an exception if the hardware qubit is not assigned to any * circuit qubit. - * @param qubit The hardware qubit to be queried - * @return The circuit qubit assigned to the given hardware qubit + * @param qubit Hardware qubit to query. + * @return Circuit qubit assigned to the given hardware qubit. + * @throw std::runtime_error If the hardware qubit is not found in the + * mapping. */ [[nodiscard]] qc::Qubit getCircQubit(const HwQubit qubit) const { for (const auto& [circQubit, hwQubit] : circToHw) { @@ -144,9 +176,9 @@ class Mapping { /** * @brief Indicates if any circuit qubit is assigned to the given hardware * qubit. - * @param qubit The hardware qubit to be queried - * @return True if any circuit qubit is assigned to the given hardware qubit, - * false otherwise + * @param qubit Hardware qubit to query. + * @return True if any circuit qubit currently maps to this hardware qubit; + * false otherwise. */ [[nodiscard]] bool isMapped(HwQubit qubit) const { return std::any_of( @@ -157,7 +189,9 @@ class Mapping { /** * @brief Converts the qubits of an operation from circuit qubits to hardware * qubits. - * @param op The operation to be converted + * @details Rewrites targets (and controls, if present) in-place using the + * current mapping. + * @param op Operation to be converted (modified in place). */ void mapToHwQubits(qc::Operation* op) const { op->setTargets(circToHw.apply(op->getTargets())); @@ -169,8 +203,9 @@ class Mapping { /** * @brief Interchanges the mapping of two hardware qubits. At least one of it * must be mapped to a circuit qubit. - * @param swap The two circuit qubits to be swapped - * @throws std::runtime_error if hardware qubits are not mapped + * @param swap Pair of hardware qubits whose mapped circuit qubits shall be + * swapped. + * @throw std::runtime_error If neither hardware qubit is currently mapped. */ void applySwap(const Swap& swap); }; From 98905970d904d057e864ac679b22b14d86c22fe1 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 7 Nov 2025 10:42:13 +0100 Subject: [PATCH 242/394] =?UTF-8?q?=F0=9F=93=9D=20added=20descriptions=20f?= =?UTF-8?q?or=20SynthesisMapper.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridSynthesisMapper.hpp | 80 ++++++++++----------- 1 file changed, 38 insertions(+), 42 deletions(-) diff --git a/include/hybridmap/HybridSynthesisMapper.hpp b/include/hybridmap/HybridSynthesisMapper.hpp index 648dfa9d7..61f67f004 100644 --- a/include/hybridmap/HybridSynthesisMapper.hpp +++ b/include/hybridmap/HybridSynthesisMapper.hpp @@ -32,16 +32,13 @@ namespace na { /** - * @brief Class to manage information exchange between the neutral atom mapper - * and the ZX extraction. - * @deteils This class is derived from the HybridNeutralAtomMapper and stores - * all the information about the neutral atom hardware and the current status of - * the mapping. The ZX (or another synthesis algorithm) can propose different - * possible next synthesis steps, which are then evaluated by the - * HybridNeutralAtomMapper regarding the "effort" to map this synthesis step. It - * also provides additional functionality to exchange information between the ZX - * and the HybridNeutralAtomMapper. - * + * @brief Bridges circuit synthesis (e.g., ZX extraction) with neutral-atom + * mapping. + * @details Derived from NeutralAtomMapper, this class maintains device state + * and current mapping while accepting proposed synthesis steps. It evaluates + * steps by mapping effort (e.g., required swaps/shuttling and timing), can + * append steps with or without mapping, and exposes utilities to exchange + * information with the synthesis algorithm. */ class HybridSynthesisMapper : public NeutralAtomMapper { using qcs = std::vector; @@ -49,18 +46,22 @@ class HybridSynthesisMapper : public NeutralAtomMapper { qc::QuantumComputation synthesizedQc; /** - * @brief Evaluates a single synthesis step proposed by the ZX extraction. - * @details The effort is calculated by the NeutralAtomMapper, taking into - * account the number of SWAP gates or shuttling moves and the time needed to - * execute the mapped synthesis step. - * @param qc The synthesis step to be evaluated. - * @return The cost/effort to map the synthesis step. + * @brief Evaluate a single proposed synthesis step. + * @details Effort considers swaps/shuttling and execution time estimated by + * the mapper. + * @param qc Proposed synthesis subcircuit. + * @return Scalar cost/effort score for mapping qc. */ qc::fp evaluateSynthesisStep(qc::QuantumComputation& qc); public: // Constructors HybridSynthesisMapper() = delete; + /** + * @brief Construct with device and optional mapper parameters. + * @param arch Neutral atom architecture. + * @param params Optional mapper configuration parameters. + */ explicit HybridSynthesisMapper( const NeutralAtomArchitecture& arch, const MapperParameters& params = MapperParameters()) @@ -69,10 +70,8 @@ class HybridSynthesisMapper : public NeutralAtomMapper { // Functions /** - * @brief Initializes the mapping with the given number of qubits and the - * initial mapping. - * @param nQubits The number of qubits to be mapped. - * @param initialMapping The initial mapping to be used. + * @brief Initialize synthesized and mapped circuits and mapping structures. + * @param nQubits Number of logical qubits to synthesize. */ void initMapping(size_t nQubits) { mappedQc = qc::QuantumComputation(arch->getNpositions()); @@ -81,25 +80,24 @@ class HybridSynthesisMapper : public NeutralAtomMapper { } /** - * @brief Returns the mapped QuantumComputation. - * @return The mapped QuantumComputation. + * @brief Complete a (re-)mapping of the synthesized circuit to hardware. + * @param initMapping Initial mapping heuristic (defaults to Identity). */ void completeRemap(InitialMapping initMapping = InitialMapping::Identity) { this->map(synthesizedQc, initMapping); } /** - * @brief Returns the synthesized QuantumComputation with all gates but not - * mapped to the hardware. - * @return The synthesized QuantumComputation. + * @brief Get the currently synthesized (unmapped) circuit. + * @return Synthesized QuantumComputation. */ [[nodiscard]] qc::QuantumComputation getSynthesizedQc() const { return this->synthesizedQc; } /** - * @brief Returns the synthesized QuantumComputation as a string. - * @return The synthesized QuantumComputation as a string. + * @brief Export synthesized circuit as OpenQASM string. + * @return QASM representation of the synthesized circuit. */ [[maybe_unused]] std::string getSynthesizedQcQASM() { std::stringstream ss; @@ -108,8 +106,9 @@ class HybridSynthesisMapper : public NeutralAtomMapper { } /** - * @brief Saves the synthesized QuantumComputation to the given file. - * @param filename The file to save the synthesized QuantumComputation to. + * @brief Save synthesized circuit as OpenQASM to a file. + * @param filename Output filename. + * @throw std::ios_base::failure If the file cannot be opened for writing. */ [[maybe_unused]] void saveSynthesizedQc(const std::string& filename) { std::ofstream ofs(filename); @@ -118,32 +117,29 @@ class HybridSynthesisMapper : public NeutralAtomMapper { } /** - * @brief Evaluates the synthesis steps proposed by the ZX extraction. - * @param synthesisSteps The synthesis steps proposed by the ZX extraction. - * @param alsoMap If true, the best synthesis step is directly mapped to the - * hardware. - * @return Returns a list of fidelities of the mapped synthesis steps. + * @brief Evaluate candidate synthesis steps and optionally map the best. + * @param synthesisSteps Vector of candidate subcircuits. + * @param alsoMap If true, append and map the best candidate. + * @return List of fidelity scores for mapped steps (order matches input). */ std::vector evaluateSynthesisSteps(qcs& synthesisSteps, bool alsoMap = false); /** - * @brief Directly maps the given QuantumComputation to the hardware NOT - * inserting SWAP gates or shuttling move operations. - * @param qc The gates (QuantumComputation) to be mapped. + * @brief Append gates without mapping (no SWAPs/shuttling inserted). + * @param qc Subcircuit to append as-is. */ void appendWithoutMapping(const qc::QuantumComputation& qc); /** - * @brief Appends the given QuantumComputation to the synthesized - * QuantumComputation and maps the gates to the hardware. - * @param qc The gates (QuantumComputation) to be appended and mapped. + * @brief Append and map a subcircuit to hardware (may insert moves/SWAPs). + * @param qc Subcircuit to append and map. */ void appendWithMapping(qc::QuantumComputation& qc); /** - * @brief Returns the current adjacency matrix of the neutral atom hardware. - * @return The current adjacency matrix of the neutral atom hardware. + * @brief Get the current device adjacency (connectivity) matrix. + * @return Symmetric adjacency matrix for the neutral atom hardware. */ [[nodiscard]] AdjacencyMatrix getCircuitAdjacencyMatrix() const; }; From 56c3a3281374490146fe3626a27c427c60be1c6a Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 7 Nov 2025 10:47:19 +0100 Subject: [PATCH 243/394] =?UTF-8?q?=F0=9F=93=9D=20added=20descriptions=20f?= =?UTF-8?q?or=20HardwareQubits.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HardwareQubits.hpp | 212 +++++++++++++-------------- 1 file changed, 101 insertions(+), 111 deletions(-) diff --git a/include/hybridmap/HardwareQubits.hpp b/include/hybridmap/HardwareQubits.hpp index a7ea4ece0..6b1260605 100644 --- a/include/hybridmap/HardwareQubits.hpp +++ b/include/hybridmap/HardwareQubits.hpp @@ -33,11 +33,12 @@ namespace na { /** - * @brief Class that represents the hardware qubits of a neutral atom quantum - * computer. - * @details Stores the mapping from circuit qubits to hardware qubits and from - * hardware qubits to atom coordinates. Maintains cached swap distances and - * nearby-qubit relations derived from the architecture's interaction radius. + * @brief Represents hardware qubits and their placement on a neutral atom + * device. + * @details Manages mapping (hardware qubit -> coordinate), maintains cached + * swap distances and nearby-qubit sets derived from the architecture + * interaction radius, and provides utilities for movement, neighborhood + * queries, blocking analysis, and coordinate-based path heuristics. */ class HardwareQubits { protected: @@ -51,40 +52,34 @@ class HardwareQubits { qc::Permutation initialHwPos; /** - * @brief Initializes the swap distances between the hardware qubits for the - * trivial initial layout. - * @details Initializes the swap distances between the hardware qubits. This - * is only valid for the trivial initial layout. + * @brief Precompute swap distances for trivial initial layout. + * @details Fills symmetric matrix with shortest-path distances (in edges) + * when coordinates align with hardware indices. */ void initTrivialSwapDistances(); /** - * @brief Initializes the nearby qubits for each hardware qubit. - * @details Nearby qubits are the qubits that are closer than the interaction - * radius. Therefore, they can be swapped with a single swap operation. + * @brief Initialize per-qubit nearby sets (within interaction radius). + * @details Nearby qubits are those reachable by a single + * entangling/interaction edge and hence by one swap. */ void initNearbyQubits(); /** - * @brief Computes the nearby qubits for a single hardware qubit. - * @details Determines nearby qubits by comparing Euclidean distance to the - * architecture's interaction radius. Called by initNearbyQubits(). - * @param qubit The hardware qubit for which the nearby qubits are computed. + * @brief Populate nearby set for one qubit. + * @param qubit Hardware qubit whose neighbors are computed. */ void computeNearbyQubits(HwQubit qubit); /** - * @brief Computes the swap distance between two hardware qubits. - * @details Computes the swap distance between two hardware qubits. This - * function is called by getSwapDistance(). It uses a breadth-first search - * to find the shortest path between the two qubits. - * @param q1 The first hardware qubit. - * @param q2 The second hardware qubit. + * @brief Compute swap distance (BFS) between two hardware qubits and cache + * it. + * @param q1 First hardware qubit. + * @param q2 Second hardware qubit. */ void computeSwapDistance(HwQubit q1, HwQubit q2); /** - * @brief Resets the swap distances between the hardware qubits. - * @details Used after each shuttling operation to invalidate all cached swap - * distances (set to -1), forcing recomputation on demand. + * @brief Invalidate all cached swap distances (set entries to -1). + * @note Call after shuttling alters physical adjacency relevance. */ void resetSwapDistances(); @@ -93,16 +88,14 @@ class HardwareQubits { HardwareQubits() = default; /** * @brief Construct hardware qubit layout and caches. - * @param architecture Reference to the neutral atom architecture. - * @param nQubits Number of hardware qubits managed in the mapping. - * @param initialCoordinateMapping Strategy for initial coordinate assignment: - * Trivial assigns coordinates 0..nQubits-1 in order; Random shuffles over - * all positions. - * @param seed Random seed used for Random initial mapping. If 0, a - * std::random_device() seeds the RNG. - * @details Initializes nearby-qubit relations and occupied/free coordinate - * lists. For Trivial mapping, swap distances are precomputed; for Random, - * swap distances are left invalid (-1) to be computed lazily. + * @param architecture Device architecture reference. + * @param nQubits Number of hardware qubits to track. + * @param initialCoordinateMapping Initial placement strategy + * (Trivial/Random). + * @param seed RNG seed for Random; if 0 uses std::random_device(). + * @details Populates hw->coord mapping, nearby sets, occupied/free coordinate + * lists; precomputes swap distances for trivial mapping, leaves them lazy + * (-1) for random mapping. */ explicit HardwareQubits( const NeutralAtomArchitecture& architecture, const CoordIndex nQubits = 0, @@ -148,40 +141,46 @@ class HardwareQubits { } /** - * @brief Compute all shortest paths between two hardware qubits. - * @details Performs a breadth-first exploration over the nearby-qubit graph - * (edges exist between qubits within the interaction radius) and returns all - * minimal-length paths from q1 to q2 as sequences of hardware qubits. + * @brief Enumerate all minimal-length paths between two qubits. + * @details BFS builds predecessor layers; all shortest sequences q1->...->q2 + * are returned. + * @param q1 Source hardware qubit. + * @param q2 Target hardware qubit. + * @return Vector of shortest paths, each path a vector of hardware qubits. */ [[nodiscard]] std::vector computeAllShortestPaths(HwQubit q1, HwQubit q2) const; - /** Get number of hardware qubits tracked by this instance. */ + /** + * @brief Number of hardware qubits tracked. + * @return Hardware qubit count. + */ [[nodiscard]] CoordIndex getNumQubits() const { return nQubits; } /** - * @brief Checks if a hardware qubit is mapped to a coordinate. - * @param idx The coordinate index. - * @return Boolean indicating if the hardware qubit is mapped to a coordinate. + * @brief Check whether a coordinate is currently occupied. + * @param idx Coordinate index. + * @return True if occupied by some hardware qubit; false otherwise. */ [[nodiscard]] bool isMapped(const CoordIndex idx) const { return std::ranges::find(occupiedCoordinates, idx) != occupiedCoordinates.end(); } /** - * @brief Updates mapping after moving a hardware qubit to a coordinate. - * @details Verifies that the coordinate exists and is unoccupied, updates the - * mapping, refreshes nearby-qubit relations for the moved qubit and its - * neighbors, and invalidates cached swap distances. - * @param hwQubit The hardware qubit to be moved. - * @param newCoord The new coordinate of the hardware qubit. + * @brief Move a hardware qubit to a new coordinate. + * @details Validates destination free, updates mapping and occupancy sets, + * recalculates affected nearby sets, invalidates swap distance cache. + * @param hwQubit Hardware qubit identifier. + * @param newCoord Destination coordinate index. + * @throw std::runtime_error If newCoord invalid or already occupied. */ void move(HwQubit hwQubit, CoordIndex newCoord); /** - * @brief Remove a hardware qubit from the mapping and caches. - * @details Erases the qubit from the coordinate mapping and nearby lists and - * invalidates swap distances involving that qubit. + * @brief Remove a hardware qubit from mapping, occupancy and nearby caches. + * @details Invalidates all swap distances involving the qubit and erases it + * from neighbor sets. + * @param hwQubit Hardware qubit to remove. */ void removeHwQubit(const HwQubit hwQubit) { hwToCoordIdx.erase(hwQubit); @@ -200,8 +199,8 @@ class HardwareQubits { } /** - * @brief Convert operation's qubits from hardware indices to coordinates. - * @param op The operation to be updated in-place. + * @brief Rewrite operation qubit indices from hardware IDs to coordinates. + * @param op Operation pointer (modified in place). */ void mapToCoordIdx(qc::Operation* op) const { op->setTargets(hwToCoordIdx.apply(op->getTargets())); @@ -211,17 +210,18 @@ class HardwareQubits { } /** - * @brief Returns the coordinate index of a hardware qubit. - * @param qubit The hardware qubit. - * @return The coordinate index of the hardware qubit. + * @brief Coordinate index mapped to a hardware qubit. + * @param qubit Hardware qubit. + * @return Coordinate index. + * @throw std::out_of_range If qubit not in mapping. */ [[nodiscard]] CoordIndex getCoordIndex(const HwQubit qubit) const { return hwToCoordIdx.at(qubit); } /** - * @brief Returns the coordinate indices of a set of hardware qubits. - * @param hwQubits The set of hardware qubits. - * @return The coordinate indices of the hardware qubits. + * @brief Coordinate indices for a set of hardware qubits. + * @param hwQubits Set of hardware qubits. + * @return Set of coordinate indices. */ [[nodiscard]] std::set getCoordIndices(const std::set& hwQubits) const { @@ -243,10 +243,10 @@ class HardwareQubits { } /** - * @brief Return the hardware qubit at a given coordinate. - * @details Throws std::runtime_error if no hardware qubit is mapped there. - * @param coordIndex The coordinate index. - * @return The hardware qubit at the coordinate. + * @brief Hardware qubit occupying a coordinate. + * @param coordIndex Coordinate index. + * @return Hardware qubit ID. + * @throw std::runtime_error If no qubit occupies the coordinate. */ [[nodiscard]] HwQubit getHwQubit(const CoordIndex coordIndex) const { for (auto const& [hwQubit, index] : hwToCoordIdx) { @@ -261,16 +261,12 @@ class HardwareQubits { // Swap Distances and Nearby qc::Qubits /** - * @brief Returns the swap distance between two hardware qubits. - * @details If not computed yet, uses a breadth-first search over nearby - * qubits to compute the minimal number of intermediate swaps. When closeBy - * is false, one additional step is allowed to stop in the vicinity of q2. - * @param q1 The first hardware qubit. - * @param q2 The second hardware qubit. - * @param closeBy If the swap should be performed to the exact position of q2 - * or just to its vicinity. - * @return The swap distance between the two hardware qubits (0 if equal). If - * closeBy==false, returns the distance plus one. + * @brief Swap distance between two hardware qubits (lazy cached). + * @param q1 First qubit. + * @param q2 Second qubit. + * @param closeBy If false, allow stopping adjacent to q2 (adds 1 to + * distance). + * @return Distance in number of swaps (0 if identical). */ [[nodiscard]] SwapDistance getSwapDistance(const HwQubit q1, const HwQubit q2, const bool closeBy = true) { @@ -287,84 +283,78 @@ class HardwareQubits { } /** - * @brief Returns the nearby hardware qubits of a hardware qubit. - * @param q The hardware qubit. - * @return The nearby hardware qubits of the hardware qubit. + * @brief Nearby hardware qubits (within interaction radius). + * @param q Hardware qubit. + * @return Set of nearby qubits. */ [[nodiscard]] HwQubits getNearbyQubits(const HwQubit q) const { return nearbyQubits.at(q); } /** - * @brief Returns vector of all possible swaps for a hardware qubit. - * @param q The hardware qubit. - * @return The vector of all possible swaps for the hardware qubit. + * @brief All possible immediate swaps (pairs with each nearby qubit). + * @param q Hardware qubit. + * @return Vector of swap pairs. */ [[nodiscard]] std::vector getNearbySwaps(HwQubit q) const; /** - * @brief Returns the unoccupied coordinates in the vicinity of a coordinate. - * @param idx The coordinate index. - * @return The unoccupied coordinates in the vicinity of the coordinate. + * @brief Unoccupied coordinates within interaction radius of a coordinate. + * @param idx Coordinate index. + * @return Set of free coordinate indices nearby. */ [[nodiscard]] std::set getNearbyFreeCoordinatesByCoord(CoordIndex idx) const; /** - * @brief Returns the occupied coordinates in the vicinity of a coordinate. - * @param idx The coordinate index. - * @return The occupied coordinates in the vicinity of the coordinate. + * @brief Occupied coordinates within interaction radius of a coordinate. + * @param idx Coordinate index. + * @return Set of occupied coordinate indices nearby. */ [[nodiscard]] std::set getNearbyOccupiedCoordinatesByCoord(CoordIndex idx) const; /** - * @brief Computes the summed swap distance between all hardware qubits in a - * set. - * @param qubits The set of hardware qubits. - * @return The summed pairwise swap distance among all qubits in the set. - * For two qubits, this reduces to their swap distance. + * @brief Sum of pairwise swap distances among a set of qubits. + * @param qubits Set of hardware qubits (modified if needed for caching). + * @return Summed distances; for two qubits equals their distance. */ qc::fp getAllToAllSwapDistance(std::set& qubits); /** - * @brief Find free coordinates in a given direction from a coordinate. - * @details Returns the nearest free coordinate along the specified direction - * (as a single-element vector). If no free coordinate exists in that - * direction, returns all currently free coordinates excluding the provided - * exclusions. - * @param coord The starting coordinate index. - * @param direction The direction in which the search is performed - * (Left/Right, Down/Up). - * @param excludedCoords Coordinates to be ignored in the search. - * @return Either a singleton containing the closest free coordinate in the - * given direction, or a list of all free coordinates if none exist in that - * direction. + * @brief Find closest free coordinate in a direction, else return all free. + * @param coord Starting coordinate. + * @param direction Direction sign (per dimension) encapsulated in Direction. + * @param excludedCoords Coordinates to ignore. + * @return Singleton vector with nearest directional free coordinate or all + * free coordinates if none in direction. */ [[nodiscard]] std::vector findClosestFreeCoord(CoordIndex coord, Direction direction, const CoordIndices& excludedCoords = {}) const; /** - * @brief Find the hardware qubit closest (by Euclidean distance) to a - * coordinate, ignoring a set of qubits. + * @brief Hardware qubit closest by Euclidean distance to a coordinate, + * excluding some. + * @param coord Target coordinate index. + * @param ignored Set of qubits to ignore. + * @return Closest hardware qubit ID. */ [[nodiscard]] HwQubit getClosestQubit(CoordIndex coord, const HwQubits& ignored) const; // Blocking /** - * @brief Computes all hardware qubits that are blocked by a set of hardware - * qubits. - * @param qubits The input hardware qubits. - * @return The blocked hardware qubits. + * @brief Hardware qubits blocked by interactions of given qubits. + * @param qubits Active hardware qubits. + * @return Set of hardware qubits considered blocked. */ [[nodiscard]] std::set getBlockedQubits(const std::set& qubits) const; /** - * @brief Get the initial hardware-to-coordinate mapping (at construction). - * @return A map from hardware qubit to its initial coordinate index. + * @brief Initial hardware->coordinate mapping from construction time. + * @return Map of hardware qubit to initial coordinate index. */ [[nodiscard]] std::map getInitHwPos() const { std::map initialHwPosMap; From 7523b1b75b4c15e9b162f33b95bb2db8f55023d6 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 7 Nov 2025 10:51:11 +0100 Subject: [PATCH 244/394] =?UTF-8?q?=F0=9F=93=9D=20added=20descriptions=20f?= =?UTF-8?q?or=20Mapper.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridNeutralAtomMapper.hpp | 353 ++++++++---------- 1 file changed, 166 insertions(+), 187 deletions(-) diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index 5199c3f8e..d57116347 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -36,7 +36,9 @@ namespace na { /** - * @brief Struct to store the runtime parameters of the mapper. + * @brief Runtime configuration parameters for the neutral atom mapper. + * @details Tunable weights and limits guiding swap vs. shuttling vs. ancilla + * decisions, lookahead, and stochastic initialization. */ struct MapperParameters { uint32_t lookaheadDepth = 1; @@ -57,39 +59,41 @@ struct MapperParameters { InitialCoordinateMapping::Trivial; }; +/** + * @brief Aggregated counters collected during mapping. + */ struct MapperStats { - uint32_t nSwaps = 0; - uint32_t nBridges = 0; - uint32_t nFAncillas = 0; - uint32_t nMoves = 0; - uint32_t nPassBy = 0; + uint32_t nSwaps = 0; ///< Number of executed SWAP gates. + uint32_t nBridges = 0; ///< Number of bridge operations. + uint32_t nFAncillas = 0; ///< Number of flying ancilla usages. + uint32_t nMoves = 0; ///< Number of MOVE operations. + uint32_t nPassBy = 0; ///< Number of pass-by combinations. }; +/** + * @brief Enumeration of supported routing primitives. + */ enum RoutingType : uint8_t { - SwapType, - BridgeType, - MoveType, - PassByType, - FlyingAncillaType + SwapType, ///< Conventional SWAP gate routing. + BridgeType, ///< Bridge circuit using intermediate ancillas. + MoveType, ///< Physical MOVE (shuttling) of atoms. + PassByType, ///< Pass-by movement combination. + FlyingAncillaType ///< Flying ancilla mediated interaction. }; /** - * @brief Class to map a quantum circuit to a neutral atom architecture. - * @details The mapping has following important parts: - * - initial mapping: The initial mapping of the circuit qubits to the hardware - * qubits. - * - layer creation: The creation of the front and lookahead layers, done one - * the fly and taking into account basic commutation rules. - * - estimation: The estimation of the number of swap gates and moves needed to - * execute a given gate and the decision which technique is better. - * - gate based mapping: SABRE based algorithm to choose the bast swap for the - * given layers. - * - shuttling based mapping: Computing and evaluation of possible moves and - * choosing best. - * - multi-qubit-gates: Additional steps and checks to bring multiple qubits - * together. - * -> Final circuit contains abstract SWAP gates and MOVE operations, which need - * to be decomposed using AODScheduler. + * @brief Maps a quantum circuit onto a neutral atom architecture using hybrid + * routing. + * @details Combines gate-based (swap/bridge) and shuttling-based + * (move/pass-by/flying ancilla) strategies. Workflow: + * 1. Initialize mapping and hardware placement. + * 2. Build front/lookahead layers using commutation rules. + * 3. Estimate routing costs (swap vs. move vs. bridge/flying ancilla). + * 4. Select best primitive via weighted cost functions (SABRE-inspired + * heuristic). + * 5. Handle multi-qubit gate positioning. + * 6. Produce circuit with abstract SWAP and MOVE operations (later decomposed + * to AOD level). */ class NeutralAtomMapper { protected: @@ -134,29 +138,28 @@ class NeutralAtomMapper { // Methods for mapping /** - * @brief Maps the gate to the mapped quantum circuit. - * @param op The gate to map + * @brief Append a single operation to the mapped circuit applying current + * qubit mapping. + * @param op Operation (from original circuit) to map. */ void mapGate(const qc::Operation* op); /** - * @brief Maps all currently possible gates and updated until no more gates - * can be mapped. - * @param frontLayer The front layer to map all possible gates for - * @param lookaheadLayer The lookahead layer to map all possible gates for + * @brief Iteratively map all executable gates until front layer stalls. + * @param frontLayer Current front layer container. + * @param lookaheadLayer Lookahead layer container. */ void mapAllPossibleGates(NeutralAtomLayer& frontLayer, NeutralAtomLayer& lookaheadLayer); /** - * @brief Returns all gates that can be executed now - * @param gates The gates to be checked - * @return All gates that can be executed now + * @brief Filter a list to gates executable under current mapping. + * @param gates Candidate gates. + * @return Subset of executable gates. */ GateList getExecutableGates(const GateList& gates); /** - * @brief Checks if the given gate can be executed for the given mapping and - * hardware arrangement. - * @param opPointer The gate to check - * @return True if the gate can be executed, false otherwise + * @brief Check gate executability given current mapping and placement. + * @param opPointer Gate to test. + * @return True if gate can be applied now; false otherwise. */ bool isExecutable(const qc::Operation* opPointer); @@ -167,13 +170,13 @@ class NeutralAtomMapper { void updateBlockedQubits(const HwQubits& qubits); /** - * @brief Update the mapping for the given swap gate. - * @param swap The swap gate to update the mapping for + * @brief Apply a SWAP to update logical↔hardware mapping state. + * @param swap Hardware qubit pair. */ void applySwap(const Swap& swap); /** - * @brief Update the mapping for the given move operation. - * @param move The move operation to update the mapping for + * @brief Apply a MOVE (shuttling) operation updating placement & mapping. + * @param move Move descriptor. */ void applyMove(AtomMove move); @@ -184,18 +187,17 @@ class NeutralAtomMapper { // Methods for gate vs. shuttling /** - * @brief Assigns the given gates to the gate or shuttling layers. - * @param frontGates The gates to be assigned to the front layers - * @param lookaheadGates The gates to be assigned to the lookahead layers + * @brief Partition gates into gate-routing vs. shuttling lists for each + * layer. + * @param frontGates Gates in current front layer. + * @param lookaheadGates Gates in lookahead layer. */ void reassignGatesToLayers(const GateList& frontGates, const GateList& lookaheadGates); /** - * @brief Estimates the minimal number of swap gates and time needed to - * execute the given gate. - * @param opPointer The gate to estimate the number of swap gates and time for - * @return The minimal number of swap gates and time needed to execute the - * given gate + * @brief Estimate swaps and time required to execute a gate via swapping. + * @param opPointer Gate under consideration. + * @return Pair (#swaps, estimated time cost). */ size_t gateBasedMapping(NeutralAtomLayer& frontLayer, @@ -206,38 +208,30 @@ class NeutralAtomMapper { std::pair estimateNumSwapGates(const qc::Operation* opPointer); /** - * @brief Estimates the minimal number of move operations and time needed to - * execute the given gate. - * @param opPointer The gate to estimate the number of move operations and - * time for - * @return The minimal number of move operations and time needed to execute - * the given gate + * @brief Estimate MOVE count and time for executing a gate via shuttling. + * @param opPointer Gate under consideration. + * @return Pair (#moves, estimated time cost). */ std::pair estimateNumMove(const qc::Operation* opPointer) const; /** - * @brief Uses estimateNumSwapGates and estimateNumMove to decide if a swap - * gate or move operation is better. - * @param opPointer The gate to estimate the number of swap gates and move - * operations for - * @return True if a swap gate is better, false if a move operation is better + * @brief Compare swap vs. move estimates to choose routing primitive. + * @param opPointer Target gate. + * @return True if swap-based routing chosen; false for move-based. */ bool swapGateBetter(const qc::Operation* opPointer); // Methods for swap gates mapping /** - * @brief Finds the best swap gate for the front layer. - * @details The best swap gate is the one that minimizes the cost function. - * This takes into account close by swaps from two-qubit gates and exact moves - * from multi-qubit gates. - * @return The best swap gate for the front layer + * @brief Select best swap minimizing composite cost (distance + decay). + * @param lastSwap Previously applied swap (for decay context). + * @return Best swap candidate. */ Swap findBestSwap(const Swap& lastSwap); /** - * @brief Returns all possible swap gates for the front layer. - * @details The possible swap gates are all swaps starting from qubits in the - * front layer. - * @return All possible swap gates for the front layer + * @brief Enumerate candidate swaps derived from front layer proximity. + * @param swapsFront Pair of (close-by swaps, weighted exact swaps). + * @return Set of swap candidates. */ [[nodiscard]] std::set getAllPossibleSwaps(const std::pair& swapsFront) const; @@ -261,32 +255,23 @@ class NeutralAtomMapper { // Methods for shuttling operations mapping /** - * @brief Finds the current best move operation based on the cost function. - * @details Uses getAllMoveCombinations to find all possible move combinations - * (direct move, move away, multi-qubit moves) and then chooses the best one - * based on the cost function. - * @return The current best move operation + * @brief Select best MOVE combination using cost (distance reduction + + * parallelization). + * @return Best move combination descriptor. */ MoveComb findBestAtomMove(); // std::pair findBestAtomMoveWithOp(); /** - * @brief Returns all possible move combinations for the front layer. - * @details This includes direct moves, move away and multi-qubit moves. - * Only move combinations with minimal number of moves are kept. - * @return Vector of possible move combinations for the front layer + * @brief Generate all minimal MOVE combinations (direct/away/multi-qubit). + * @return List of viable combinations. */ MoveCombs getAllMoveCombinations(); /** - * @brief Returns all possible move away combinations for a move from start to - * target. - * @details The possible move away combinations are all combinations of move - * operations that move qubits away and then the performs the actual move - * operation. The move away is chosen such that it is in the same direction as - * the second move operation. - * @param startCoord The start position of the actual move operation - * @param targetCoord The target position of the actual move operation - * @param excludedCoords Coordinates the qubits should not be moved to - * @return All possible move away combinations for a move from start to target + * @brief Enumerate staged move-away then move-to-target combinations. + * @param startCoord Origin coordinate of final move. + * @param targetCoord Destination coordinate. + * @param excludedCoords Coordinates disallowed for interim moves. + * @return Candidate move-away sequences. */ [[nodiscard]] MoveCombs getMoveAwayCombinations(CoordIndex startCoord, CoordIndex targetCoord, @@ -302,39 +287,33 @@ class NeutralAtomMapper { // Helper methods /** - * @brief Distinguishes between two-qubit swaps and multi-qubit swaps. - * @details Two-qubit swaps only need to swap next to each other, while - * multi-qubit swaps need to swap exactly to the multi-qubit gate position. - * The multi-qubit swaps are weighted depending on their importance to finish - * the multi-qubit gate. - * @param layer The layer to distinguish the swaps for (front or lookahead) - * @return The two-qubit swaps and multi-qubit swaps for the given layer + * @brief Classify swaps into close-by (2-qubit) vs. exact (multi-qubit + * positioning). + * @param layer Gate list (front or lookahead). + * @return Pair (close-by swaps, weighted exact swaps). */ std::pair initSwaps(const GateList& layer); /** - * @brief Helper function to set the two-qubit swap weight to the minimal - * weight of all multi-qubit gates, or 1. - * @param swapExact The exact moves from multi-qubit gates + * @brief Set base two-qubit swap weight to min multi-qubit exact weight or 1. + * @param swapExact Weighted exact swaps. */ void setTwoQubitSwapWeight(const WeightedSwaps& swapExact); /** - * @brief Returns the best position for the given gate coordinates. - * @details Recursively calls getMovePositionRec - * @param gateCoords The coordinates of the gate to find the best position for - * @return The best position for the given gate coordinates + * @brief Compute best coordinate aggregation for gate qubits. + * @param gateCoords Current coordinate indices of gate's qubits. + * @return Selected position indices candidate set. */ CoordIndices getBestMovePos(const CoordIndices& gateCoords); MultiQubitMovePos getMovePositionRec(MultiQubitMovePos currentPos, const CoordIndices& gateCoords, const size_t& maxNMoves); /** - * @brief Returns possible move combinations to move the gate qubits to the - * given position. - * @param gateQubits The gate qubit to be moved - * @param position The target position of the gate qubits - * @return Possible move combinations to move the gate qubits to the given - * position + * @brief Enumerate move combinations relocating gate qubits to target + * coordinates. + * @param gateQubits Hardware qubits participating in gate. + * @param position Target coordinate set. + * @return List of move combination candidates. */ [[nodiscard]] MoveCombs getMoveCombinationsToPosition(const HwQubits& gateQubits, @@ -342,48 +321,42 @@ class NeutralAtomMapper { // Multi-qubit gate based methods /** - * @brief Returns the best position for the given multi-qubit gate. - * @details Calls getBestMultiQubitPositionRec to find the best position by - * performing a recursive search in a breadth-first manner. - * @param opPointer The multi-qubit gate to find the best position for - * @return The best position for the given multi-qubit gate + * @brief Determine optimal convergence position for a multi-qubit gate. + * @param opPointer Multi-qubit operation. + * @return Hardware qubit set representing chosen position. */ HwQubits getBestMultiQubitPosition(const qc::Operation* opPointer); HwQubits getBestMultiQubitPositionRec(HwQubits remainingGateQubits, std::vector selectedQubits, HwQubits remainingNearbyQubits); /** - * @brief Returns the swaps needed to move the given qubits to the given - * multi-qubit gate position. - * @param op The multi-qubit gate to find the best position for - * @param position The target position of the multi-qubit gate - * @return The swaps needed to move the given qubits to the given multi-qubit + * @brief Compute exact swaps required to align qubits to target multi-qubit + * position. + * @param op Multi-qubit operation. + * @param position Target hardware qubit set. + * @return Weighted swap list for alignment. */ WeightedSwaps getExactSwapsToPosition(const qc::Operation* op, HwQubits position); // Cost function calculation /** - * @brief Calculates the distance reduction for a swap gate given the - * necessary close by swaps and exact moves. - * @details Close by swaps are from two qubit gates, which only require to - * swap close by. The exact moves are from multi-qubit gates, that require - * swapping exactly to the multi-qubit gate position. - * @param swap The swap gate to compute the distance reduction for - * @param swapCloseBy The close by swaps from two-qubit gates - * @param moveExact The exact moves from multi-qubit gates - * @return The distance reduction cost + * @brief Compute distance reduction contribution for a swap. + * @param swap Candidate swap. + * @param swapCloseBy Close-by (2-qubit) swaps. + * @param moveExact Weighted exact swaps (multi-qubit alignment). + * @return Reduction metric (higher means better improvement). */ qc::fp swapCostPerLayer(const Swap& swap, const Swaps& swapCloseBy, const WeightedSwaps& swapExact); /** - * @brief Calculates the cost of a swap gate. - * @details The cost of a swap gate is computed with the following terms: - * - distance reduction for front + lookahead layers using swapCostPerLayer - * - decay term for blocked qubit from last swaps - * The cost is negative. - * @param swap The swap gate to compute the cost for - * @return The cost of the swap gate + * @brief Aggregate total cost for a swap (distance reduction + decay + * penalties). + * @param swap Candidate swap. + * @param swapsFront Pair (close-by, exact) for front layer. + * @param swapsLookahead Pair (close-by, exact) for lookahead layer. + * @return Total cost (lower preferred if negative convention, or compared + * relatively). */ qc::fp swapCost(const Swap& swap, const std::pair& swapsFront, @@ -393,22 +366,21 @@ class NeutralAtomMapper { qc::fp swapDistanceReduction(const Swap& swap, const GateList& layer); /** - * @brief Calculates a parallelization cost if the move operation can be - * parallelized with the last moves. - * @param move The move operation to compute the cost for - * @return The parallelization cost + * @brief Compute bonus/penalty for parallelizing a move with recent moves. + * @param moveComb Move combination candidate. + * @return Parallelization cost component. */ [[nodiscard]] qc::fp parallelMoveCost(const MoveComb& moveComb) const; /** - * @brief Calculates the cost of a series of move operations by summing up the - * cost of each move. - * @param moveComb The series of move operations to compute the cost for - * @return The total cost of the series of move operations + * @brief Total cost for a move combination (distance reduction + + * parallelization). + * @param moveComb Candidate combination. + * @return Aggregate cost value. */ [[nodiscard]] qc::fp moveCostComb(const MoveComb& moveComb) const; /** - * @brief Print the current layers for debugging. + * @brief Debug print of current front/lookahead layers. */ void printLayers() const; @@ -443,8 +415,10 @@ class NeutralAtomMapper { : NeutralAtomMapper(&architecture, &p) {} /** - * @brief Sets the runtime parameters of the mapper. - * @param p The runtime parameters of the mapper + * @brief Set/replace runtime parameters and reset internal state. + * @param p New parameter set. + * @throw std::runtime_error If shuttling weight >0 but no free coordinates or + * unsupported number of flying ancillas. */ void setParameters(const MapperParameters& p) { this->parameters = &p; @@ -458,8 +432,9 @@ class NeutralAtomMapper { } /** - * @brief Copies the state from the given mapper. - * @param mapper The mapper to copy the state from + * @brief Shallow copy of architecture, parameters, mapping, placement and + * scheduler state. + * @param mapper Source mapper. */ void copyStateFrom(const NeutralAtomMapper& mapper) { this->arch = mapper.arch; @@ -473,7 +448,8 @@ class NeutralAtomMapper { } /** - * @brief Resets the mapper and the hardware qubits. + * @brief Reset mapping and hardware placement (reinitialize qubits & + * ancillas). */ void reset() { hardwareQubits = HardwareQubits( @@ -527,16 +503,16 @@ class NeutralAtomMapper { } /** - * @brief Appends the given quantum circuit to the mapped quantum circuit. - * @param qc The quantum circuit to be mapped - * @param initialMapping The initial mapping of the circuit qubits to the - * hardware qubits + * @brief Append and map additional circuit portion using given initial + * mapping. + * @param qc Circuit to extend mapping with. + * @param initialMapping Initial mapping state. */ void mapAppend(qc::QuantumComputation& qc, const Mapping& initialMapping); /** - * @brief Returns the statistics of the mapping. - * @return The statistics of the mapping + * @brief Retrieve accumulated mapping statistics. + * @return Stats struct. */ [[nodiscard]] MapperStats getStats() const { return stats; } @@ -552,14 +528,14 @@ class NeutralAtomMapper { } /** - * @brief Returns the mapped quantum circuit. - * @return The mapped quantum circuit + * @brief Get current mapped circuit (abstract operations form). + * @return Mapped circuit object. */ [[nodiscard]] qc::QuantumComputation getMappedQc() const { return mappedQc; } /** - * @brief Prints the mapped circuits as an extended OpenQASM string. - * @return The mapped quantum circuit with abstract SWAP gates and MOVE + * @brief Serialize mapped circuit (abstract operations) to extended OpenQASM. + * @return OpenQASM string. */ [[nodiscard]] [[maybe_unused]] std::string getMappedQcQasm() const { std::stringstream ss; @@ -568,8 +544,9 @@ class NeutralAtomMapper { } /** - * @brief Saves the mapped quantum circuit to a file. - * @param filename The name of the file to save the mapped quantum circuit to + * @brief Save mapped abstract circuit (SWAP/MOVE) to file in OpenQASM. + * @param filename Output file path. + * @throw std::runtime_error On file I/O failure. */ [[maybe_unused]] void saveMappedQcQasm(const std::string& filename) const { std::ofstream ofs(filename); @@ -577,9 +554,8 @@ class NeutralAtomMapper { } /** - * @brief Prints the mapped circuit with AOD operations as an extended - * OpenQASM - * @return The mapped quantum circuit with native AOD operations + * @brief Get mapped circuit serialized at native AOD (movement + CZ) level. + * @return OpenQASM string (AOD-native). */ [[maybe_unused]] std::string getMappedQcAodQasm() { if (this->mappedQcAOD.empty()) { @@ -591,9 +567,9 @@ class NeutralAtomMapper { } /** - * @brief Saves the mapped quantum circuit with AOD operations to a file. - * @param filename The name of the file to save the mapped quantum circuit - * with AOD operations to + * @brief Save AOD-native mapped circuit to file. + * @param filename Output file path. + * @throw std::runtime_error On file I/O failure. */ [[maybe_unused]] void saveMappedQcAodQasm(const std::string& filename) { if (this->mappedQcAOD.empty()) { @@ -604,16 +580,13 @@ class NeutralAtomMapper { } /** - * @brief Schedules the mapped quantum circuit on the neutral atom - * architecture. - * @details For each gate/operation in the input circuit, the scheduler checks - * the earliest possible time slot for execution. If the gate is a multi qubit - * gate, also the blocking of other qubits is taken into consideration. The - * execution times are read from the neutral atom architecture. - * @param verboseArg If true, prints additional information - * @param createAnimationCsv If true, creates a csv file for the animation - * @param shuttlingSpeedFactor The factor to speed up the shuttling time - * @return The results of the scheduler + * @brief Schedule mapped circuit (AOD level) on architecture timeline. + * @details Lazily converts to AOD if needed, then computes start times with + * blocking, timing and optional animation. + * @param verboseArg Enable verbose scheduler output. + * @param createAnimationCsv Generate animation CSV artifacts. + * @param shuttlingSpeedFactor Factor to scale shuttling durations. + * @return Scheduler results (timings, animation, metrics). */ [[maybe_unused]] SchedulerResults schedule(const bool verboseArg = false, const bool createAnimationCsv = false, @@ -627,16 +600,17 @@ class NeutralAtomMapper { } /** - * @brief Saves the animation csv file of the scheduled quantum circuit. - * @return The animation csv string + * @brief Retrieve animation CSV content produced by scheduler. + * @return CSV string. */ [[maybe_unused]] std::string getAnimationViz() const { return scheduler.getAnimationViz(); } /** - * @brief Saves the animation csv file of the scheduled quantum circuit. - * @param filename The name of the file to save the animation csv file to + * @brief Persist animation CSV assets to disk. + * @param filename Base filename for output. + * @throw std::runtime_error On file I/O failure. */ [[maybe_unused]] void saveAnimationFiles(const std::string& filename) const { scheduler.saveAnimationFiles(filename); @@ -645,13 +619,18 @@ class NeutralAtomMapper { void decomposeBridgeGates(qc::QuantumComputation& qc) const; /** - * @brief Converts a mapped circuit down to the AOD level and CZ level. - * @details SWAP gates are decomposed into CX gates. Then CnX gates are - * decomposed into CnZ gates. Move operations are combined if possible and - * then converted into native AOD operations. + * @brief Convert abstract mapped circuit to native AOD (movement & CZ) level. + * @details Decompose SWAP to CX sequence, multi-controlled X to CZ form, + * merge consecutive MOVE operations, and translate to AOD-native + * instructions. + * @return Converted quantum computation object. */ qc::QuantumComputation convertToAod(); + /** + * @brief Initial hardware placement map (delegated from HardwareQubits). + * @return Map: hardware qubit -> initial coordinate index. + */ [[maybe_unused]] [[nodiscard]] std::map getInitHwPos() const { return hardwareQubits.getInitHwPos(); From 8fb4df2d6ba8b3f7a08c734386d094c14feb7557 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 7 Nov 2025 12:17:22 +0100 Subject: [PATCH 245/394] =?UTF-8?q?=F0=9F=8E=A8=20reworked=20clang=20tidy?= =?UTF-8?q?=20Architecture?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/NeutralAtomArchitecture.hpp | 7 +++---- src/hybridmap/NeutralAtomArchitecture.cpp | 13 ++++++------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/include/hybridmap/NeutralAtomArchitecture.hpp b/include/hybridmap/NeutralAtomArchitecture.hpp index 1305435b9..fbd700ed0 100644 --- a/include/hybridmap/NeutralAtomArchitecture.hpp +++ b/include/hybridmap/NeutralAtomArchitecture.hpp @@ -162,7 +162,7 @@ class NeutralAtomArchitecture { return t1 * t2 / (t1 + t2); } }; - CoordIndex nQubits; + CoordIndex nQubits = 0; std::map gateTimes; std::map gateAverageFidelities; std::map shuttlingTimes; @@ -347,7 +347,7 @@ class NeutralAtomArchitecture { * std::map::at may be thrown. */ [[nodiscard]] qc::fp getGateTime(const std::string& s) const { - if (parameters.gateTimes.find(s) == parameters.gateTimes.end()) { + if (!parameters.gateTimes.contains(s)) { std::cout << "Gate time for " << s << " not found\n" << "Returning default value\n"; return parameters.gateTimes.at("none"); @@ -363,8 +363,7 @@ class NeutralAtomArchitecture { * std::map::at may be thrown. */ [[nodiscard]] qc::fp getGateAverageFidelity(const std::string& s) const { - if (parameters.gateAverageFidelities.find(s) == - parameters.gateAverageFidelities.end()) { + if (!parameters.gateAverageFidelities.contains(s)) { std::cout << "Gate average fidelity for " << s << " not found\n" << "Returning default value\n"; return parameters.gateAverageFidelities.at("none"); diff --git a/src/hybridmap/NeutralAtomArchitecture.cpp b/src/hybridmap/NeutralAtomArchitecture.cpp index 33fcd7f40..2d87177ba 100644 --- a/src/hybridmap/NeutralAtomArchitecture.cpp +++ b/src/hybridmap/NeutralAtomArchitecture.cpp @@ -132,10 +132,9 @@ void NeutralAtomArchitecture::loadJson(const std::string& filename) { } this->parameters.shuttlingAverageFidelities = shuttlingAverageFidelities; - this->parameters.decoherenceTimes = - NeutralAtomArchitecture::Parameters::DecoherenceTimes{ - .t1 = jsonDataParameters["decoherenceTimes"]["t1"], - .t2 = jsonDataParameters["decoherenceTimes"]["t2"]}; + this->parameters.decoherenceTimes = Parameters::DecoherenceTimes{ + .t1 = jsonDataParameters["decoherenceTimes"]["t1"], + .t2 = jsonDataParameters["decoherenceTimes"]["t2"]}; } catch (std::exception& e) { throw std::runtime_error("Could not parse JSON file " + filename + ": " + @@ -174,7 +173,7 @@ void NeutralAtomArchitecture::computeSwapDistances( for (uint32_t i = 0; i < this->getNcolumns() && i < interactionRadius; i++) { for (uint32_t j = i; j < this->getNrows(); j++) { - const auto dist = NeutralAtomArchitecture::getEuclideanDistance( + const auto dist = getEuclideanDistance( Location{.x = 0.0, .y = 0.0}, Location{.x = static_cast(i), .y = static_cast(j)}); if (dist <= interactionRadius) { @@ -210,7 +209,7 @@ void NeutralAtomArchitecture::computeSwapDistances( // check if one can go diagonal to reduce the swap distance int32_t swapDistance = 0; - for (auto& diagonalDistance : + for (const auto& diagonalDistance : std::ranges::reverse_view(diagonalDistances)) { while (deltaX >= diagonalDistance.x && deltaY >= diagonalDistance.y) { swapDistance += 1; @@ -259,7 +258,7 @@ NeutralAtomArchitecture::getNN(const CoordIndex idx) const { } std::string NeutralAtomArchitecture::getAnimationMachine( const qc::fp shuttlingSpeedFactor) const { - std::string animationMachine = "name: \"Hyrbid_" + this->name + "\"\n"; + std::string animationMachine = "name: \"Hybrid_" + this->name + "\"\n"; animationMachine += "movement {\n\tmax_speed: " + From 806f43dd7de19c71ab0c6bad66c7246950bdfcd3 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 7 Nov 2025 12:32:09 +0100 Subject: [PATCH 246/394] =?UTF-8?q?=F0=9F=8E=A8=20reworked=20clang=20tidy?= =?UTF-8?q?=20AodConverter.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/MoveToAodConverter.hpp | 68 ++++++++-------- src/hybridmap/MoveToAodConverter.cpp | 99 +++++++++++++----------- 2 files changed, 84 insertions(+), 83 deletions(-) diff --git a/include/hybridmap/MoveToAodConverter.hpp b/include/hybridmap/MoveToAodConverter.hpp index 876902d85..628814c61 100644 --- a/include/hybridmap/MoveToAodConverter.hpp +++ b/include/hybridmap/MoveToAodConverter.hpp @@ -21,10 +21,12 @@ #include "ir/operations/OpType.hpp" #include "na/entities/Location.hpp" +#include #include #include #include #include +#include #include #include @@ -49,18 +51,18 @@ using MergeTypeXY = std::pair; */ class MoveToAodConverter { struct AncillaAtom { - struct xAndY { - int x; - int y; - xAndY(std::uint32_t x, std::uint32_t y) : x(x), y(y) {} + struct XAndY { + uint x; + uint y; + XAndY(const uint x, const uint y) : x(x), y(y) {} }; - xAndY coord; - xAndY coordDodged; - xAndY offset; - xAndY offsetDodged; + XAndY coord; + XAndY coordDodged; + XAndY offset; + XAndY offsetDodged; AncillaAtom() = delete; - AncillaAtom(xAndY c, xAndY o) + AncillaAtom(const XAndY c, const XAndY o) : coord(c), coordDodged(c), offset(o), offsetDodged(o) {} }; using AncillaAtoms = std::vector; @@ -89,10 +91,10 @@ class MoveToAodConverter { AodMove() = default; - AodMove(uint32_t initMove, qc::fp deltaMove, int32_t offsetMove, - bool loadMove) - : init(initMove), offset(offsetMove), delta(deltaMove), - load(loadMove) {} + AodMove(const uint32_t initMove, const qc::fp deltaMove, + const int32_t offsetMove, const bool loadMove) + : init(initMove), load(loadMove), offset(offsetMove), + delta(deltaMove) {} }; /** * @brief Aggregate of per-dimension activation moves plus logical atom @@ -123,7 +125,7 @@ class MoveToAodConverter { } [[nodiscard]] std::vector> - getActivates(Dimension dim) const { + getActivates(const Dimension dim) const { if (dim == Dimension::X) { return activateXs; } @@ -143,7 +145,7 @@ class MoveToAodConverter { AodActivationHelper(const AodActivationHelper&) = delete; AodActivationHelper(AodActivationHelper&&) = delete; AodActivationHelper(const NeutralAtomArchitecture& architecture, - qc::OpType opType, AncillaAtoms* ancillas) + const qc::OpType opType, AncillaAtoms* ancillas) : arch(&architecture), type(opType), ancillas(ancillas) {} // Methods @@ -171,13 +173,13 @@ class MoveToAodConverter { * @param v Geometric move vector. * @param needLoad Whether an AOD load is required. */ - void - addActivation(std::pair merge, - const Location& origin, const AtomMove& move, MoveVector v, - bool needLoad); + void addActivation( + const std::pair& merge, + const Location& origin, const AtomMove& move, const MoveVector& v, + bool needLoad); void addActivationFa(const Location& origin, const AtomMove& move, - MoveVector v, bool needLoad); + const MoveVector& v, bool needLoad); /** * @brief Merge an activation into the aggregate along a specific dimension. * @param dim Dimension of the activation. @@ -217,7 +219,7 @@ class MoveToAodConverter { int32_t sign) const; void computeInitAndOffsetOperations( - Dimension dimension, const std::shared_ptr& move, + Dimension dimension, const std::shared_ptr& aodMove, std::vector& initOperations, std::vector& offsetOperations) const; // Convert activation to AOD operations @@ -234,9 +236,6 @@ class MoveToAodConverter { * @return Concatenated vector of emitted AOD operations. */ [[nodiscard]] std::vector getAodOperations() const; - - [[nodiscard]] static std::vector - reverseActivations(const std::vector& ops); }; [[nodiscard]] static std::pair @@ -287,10 +286,10 @@ class MoveToAodConverter { */ [[nodiscard]] uint32_t getFirstIdx() const { - if (moves.size() == 0) { + if (moves.empty()) { return movesFa.front().second; } - if (movesFa.size() == 0) { + if (movesFa.empty()) { return moves.front().second; } return std::min(moves.front().second, movesFa.front().second); @@ -317,10 +316,10 @@ class MoveToAodConverter { const NeutralAtomArchitecture& arch; qc::QuantumComputation qcScheduled; std::vector moveGroups; - const na::HardwareQubits& hardwareQubits; + const HardwareQubits& hardwareQubits; AncillaAtoms ancillas; - AtomMove convertOpToMove(qc::Operation* get); + AtomMove convertOpToMove(qc::Operation* get) const; void initFlyingAncillas(); @@ -340,7 +339,7 @@ class MoveToAodConverter { std::pair, MoveGroup> processMoves(const std::vector>& moves, AodActivationHelper& aodActivationHelper, - AodActivationHelper& aodDeactivationHelper); + AodActivationHelper& aodDeactivationHelper) const; void processMovesFa(const std::vector>& movesFa, AodActivationHelper& aodActivationHelper, AodActivationHelper& aodDeactivationHelper) const; @@ -350,20 +349,18 @@ class MoveToAodConverter { MoveToAodConverter(const MoveToAodConverter&) = delete; MoveToAodConverter(MoveToAodConverter&&) = delete; explicit MoveToAodConverter(const NeutralAtomArchitecture& archArg, - const na::HardwareQubits& hardwareQubitsArg, + const HardwareQubits& hardwareQubitsArg, const HardwareQubits& flyingAncillas) : arch(archArg), qcScheduled(arch.getNpositions()), hardwareQubits(hardwareQubitsArg) { qcScheduled.addAncillaryRegister(arch.getNpositions()); qcScheduled.addAncillaryRegister(arch.getNpositions(), "fa"); - for (auto i = 0; i < flyingAncillas.getInitHwPos().size(); ++i) { + for (uint 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}, - {static_cast(i + 1), - static_cast(i + 1)}); + const AncillaAtom ancillaAtom({col, row}, {i + 1, i + 1}); ancillas.emplace_back(ancillaAtom); } } @@ -373,8 +370,7 @@ class MoveToAodConverter { * @param qc Quantum circuit to schedule. * @return New circuit containing AOD operations. */ - qc::QuantumComputation - schedule(qc::QuantumComputation& qc); //, qc::Permutation hwToCoordIdx); + qc::QuantumComputation schedule(qc::QuantumComputation& qc); /** * @brief Get number of constructed move groups. diff --git a/src/hybridmap/MoveToAodConverter.cpp b/src/hybridmap/MoveToAodConverter.cpp index 38c881e31..72d0d0e6c 100644 --- a/src/hybridmap/MoveToAodConverter.cpp +++ b/src/hybridmap/MoveToAodConverter.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -56,7 +57,7 @@ MoveToAodConverter::schedule(qc::QuantumComputation& qc) { for (auto& aodOp : groupIt->processedOpsFinal) { qcScheduled.emplace_back(std::make_unique(aodOp)); } - groupIt++; + ++groupIt; } else if (op->getType() != qc::OpType::Move) { qcScheduled.emplace_back(op->clone()); } @@ -66,7 +67,7 @@ MoveToAodConverter::schedule(qc::QuantumComputation& qc) { return qcScheduled; } -AtomMove MoveToAodConverter::convertOpToMove(qc::Operation* get) { +AtomMove MoveToAodConverter::convertOpToMove(qc::Operation* get) const { auto q1 = get->getTargets().front(); auto q2 = get->getTargets().back(); const auto load1 = q1 < arch.getNpositions(); @@ -87,7 +88,7 @@ void MoveToAodConverter::initFlyingAncillas() { std::set rowsActivated; std::set columnsActivated; for (const auto& ancilla : ancillas) { - auto coord = ancilla.coord.x + (ancilla.coord.y * arch.getNcolumns()); + auto coord = ancilla.coord.x + ancilla.coord.y * arch.getNcolumns(); const auto offsets = ancilla.offset; coords.emplace_back(coord); coord -= 2 * arch.getNpositions(); @@ -154,7 +155,7 @@ bool MoveToAodConverter::MoveGroup::canAddMove( const auto& movesToCheck = (move.load1 || move.load2) ? moves : movesFa; return std::ranges::all_of( movesToCheck, - [&move, &archArg](const std::pair const& opPair) { + [&move, &archArg](const std::pair& opPair) { const auto& moveGroup = opPair.first; // check that passby and move are not in same group if (move.load1 != moveGroup.load1 || move.load2 != moveGroup.load2) { @@ -165,8 +166,8 @@ bool MoveToAodConverter::MoveGroup::canAddMove( return false; } // check if parallel executable - auto moveVector = archArg.getVector(move.c1, move.c2); - auto opVector = archArg.getVector(moveGroup.c1, moveGroup.c2); + const auto moveVector = archArg.getVector(move.c1, move.c2); + const auto opVector = archArg.getVector(moveGroup.c1, moveGroup.c2); return parallelCheck(moveVector, opVector); }); } @@ -198,8 +199,9 @@ void MoveToAodConverter::MoveGroup::addMove(const AtomMove& move, } void MoveToAodConverter::AodActivationHelper::addActivation( - std::pair merge, - const Location& origin, const AtomMove& move, MoveVector v, bool needLoad) { + const std::pair& merge, + const Location& origin, const AtomMove& move, const MoveVector& v, + bool needLoad) { const auto x = static_cast(origin.x); const auto y = static_cast(origin.y); const auto signX = v.direction.getSignX(); @@ -295,7 +297,8 @@ void MoveToAodConverter::AodActivationHelper::addActivation( } } void MoveToAodConverter::AodActivationHelper::addActivationFa( - const Location& origin, const AtomMove& move, MoveVector v, bool needLoad) { + const Location& origin, const AtomMove& move, const MoveVector& v, + bool needLoad) { const auto x = static_cast(origin.x); const auto y = static_cast(origin.y); const auto signX = v.direction.getSignX(); @@ -312,16 +315,18 @@ MoveToAodConverter::canAddActivation( const AodActivationHelper& activationHelper, const AodActivationHelper& deactivationHelper, const Location& origin, const MoveVector& v, const Location& final, const MoveVector& vReverse, - Dimension dim) { - auto start = + const Dimension dim) { + const auto start = static_cast(dim == Dimension::X ? origin.x : origin.y); - auto end = + const auto end = static_cast(dim == Dimension::X ? final.x : final.y); - auto delta = static_cast(end - start); + const auto delta = static_cast(end - start); // Get Moves that start/end at the same position as the current move - auto aodMovesActivation = activationHelper.getAodMovesFromInit(dim, start); - auto aodMovesDeactivation = deactivationHelper.getAodMovesFromInit(dim, end); + const auto aodMovesActivation = + activationHelper.getAodMovesFromInit(dim, start); + const auto aodMovesDeactivation = + deactivationHelper.getAodMovesFromInit(dim, end); // both empty if (aodMovesActivation.empty() && aodMovesDeactivation.empty()) { @@ -372,13 +377,13 @@ MoveToAodConverter::canAddActivation( } void MoveToAodConverter::AodActivationHelper::reAssignOffsets( - std::vector>& aodMoves, int32_t sign) { + std::vector>& aodMoves, const int32_t sign) { std::ranges::sort(aodMoves, [](const std::shared_ptr& a, const std::shared_ptr& b) { return std::abs(a->delta) < std::abs(b->delta); }); int32_t offset = sign; - for (auto& aodMove : aodMoves) { + for (const auto& aodMove : aodMoves) { // same sign if (aodMove->delta * sign >= 0) { aodMove->offset = offset; @@ -407,17 +412,18 @@ void MoveToAodConverter::processMoveGroups() { // remove from current move group for (const auto& moveToRemove : movesToRemove) { groupIt->moves.erase( - std::remove_if(groupIt->moves.begin(), groupIt->moves.end(), - [&moveToRemove](const auto& movePair) { - return movePair.first == moveToRemove; - }), + std::ranges::remove_if(groupIt->moves, + [&moveToRemove](const auto& movePair) { + return movePair.first == moveToRemove; + }) + .begin(), groupIt->moves.end()); } if (!possibleNewMoveGroup.moves.empty()) { groupIt = moveGroups.emplace(groupIt + 1, std::move(possibleNewMoveGroup)); possibleNewMoveGroup = MoveGroup(); - groupIt--; + --groupIt; } groupIt->processedOpsInit = aodActivationHelper.getAodOperations(); groupIt->processedOpsFinal = aodDeactivationHelper.getAodOperations(); @@ -430,7 +436,7 @@ std::pair, MoveToAodConverter::MoveGroup> MoveToAodConverter::processMoves( const std::vector>& moves, AodActivationHelper& aodActivationHelper, - AodActivationHelper& aodDeactivationHelper) { + AodActivationHelper& aodDeactivationHelper) const { MoveGroup possibleNewMoveGroup; std::vector movesToRemove; @@ -445,8 +451,10 @@ MoveToAodConverter::processMoves( origin, v, target, vReverse, Dimension::X); auto canAddY = canAddActivation(aodActivationHelper, aodDeactivationHelper, origin, v, target, vReverse, Dimension::Y); - auto activationCanAddXY = std::make_pair(canAddX.first, canAddY.first); - auto deactivationCanAddXY = std::make_pair(canAddX.second, canAddY.second); + const auto activationCanAddXY = + std::make_pair(canAddX.first, canAddY.first); + const auto deactivationCanAddXY = + std::make_pair(canAddX.second, canAddY.second); if (activationCanAddXY.first == ActivationMergeType::Impossible || activationCanAddXY.second == ActivationMergeType::Impossible || deactivationCanAddXY.first == ActivationMergeType::Impossible || @@ -469,8 +477,8 @@ void MoveToAodConverter::processMovesFa( const std::vector>& movesFa, AodActivationHelper& aodActivationHelper, AodActivationHelper& aodDeactivationHelper) const { - for (const auto& moveFaPair : movesFa) { - const auto& moveFa = moveFaPair.first; + for (const auto& key : movesFa | std::views::keys) { + const auto& moveFa = key; auto origin = arch.getCoordinate(moveFa.c1); auto target = arch.getCoordinate(moveFa.c2); const auto v = arch.getVector(moveFa.c1, moveFa.c2); @@ -491,12 +499,11 @@ AodOperation MoveToAodConverter::MoveGroup::connectAodOperations( std::vector aodOperations; std::vector targetQubits; - auto d = aodActivationHelper.arch->getInterQubitDistance(); - auto interD = aodActivationHelper.arch->getInterQubitDistance() / - aodActivationHelper.arch->getNAodIntermediateLevels(); + const auto d = aodActivationHelper.arch->getInterQubitDistance(); + const auto interD = aodActivationHelper.arch->getInterQubitDistance() / + aodActivationHelper.arch->getNAodIntermediateLevels(); - constexpr std::array dimensions{na::Dimension::X, - na::Dimension::Y}; + constexpr std::array dimensions{Dimension::X, Dimension::Y}; // connect move operations for (const auto& activation : aodActivationHelper.allActivations) { @@ -526,8 +533,7 @@ AodOperation MoveToAodConverter::MoveGroup::connectAodOperations( // Ensure that the ordering of the target qubits such that atoms are // moved away before used as a target for (size_t i = 0; i < starts.size(); i++) { - const auto pos = - std::find(targetQubits.begin(), targetQubits.end(), starts[i]); + const auto pos = std::ranges::find(targetQubits, starts[i]); if (pos == targetQubits.end()) { // if the start qubit is not already in the target qubits targetQubits.emplace_back(starts[i]); @@ -561,7 +567,7 @@ AodOperation MoveToAodConverter::MoveGroup::connectAodOperations( std::vector> MoveToAodConverter::AodActivationHelper::getAodMovesFromInit( - Dimension dim, uint32_t init) const { + const Dimension dim, const uint32_t init) const { std::vector> aodMoves; for (const auto& activation : allActivations) { for (auto& aodMove : activation.getActivates(dim)) { @@ -574,11 +580,11 @@ MoveToAodConverter::AodActivationHelper::getAodMovesFromInit( } uint32_t MoveToAodConverter::AodActivationHelper::getMaxOffsetAtInit( - Dimension dim, uint32_t init, int32_t sign) const { - auto aodMoves = getAodMovesFromInit(dim, init); + const Dimension dim, const uint32_t init, const int32_t sign) const { + const auto aodMoves = getAodMovesFromInit(dim, init); uint32_t maxOffset = 0; for (const auto& aodMove : aodMoves) { - auto offset = aodMove->offset; + const auto offset = aodMove->offset; if (offset * sign >= 0) { maxOffset = std::max(maxOffset, static_cast(std::abs(offset))); } @@ -587,7 +593,7 @@ uint32_t MoveToAodConverter::AodActivationHelper::getMaxOffsetAtInit( } bool MoveToAodConverter::AodActivationHelper::checkIntermediateSpaceAtInit( - Dimension dim, uint32_t init, int32_t sign) const { + const Dimension dim, const uint32_t init, const int32_t sign) const { uint32_t neighborX = init; if (sign > 0) { neighborX += 1; @@ -595,7 +601,7 @@ bool MoveToAodConverter::AodActivationHelper::checkIntermediateSpaceAtInit( neighborX -= 1; } auto aodMoves = getAodMovesFromInit(dim, init); - auto aodMovesNeighbor = getAodMovesFromInit(dim, neighborX); + const auto aodMovesNeighbor = getAodMovesFromInit(dim, neighborX); if (aodMovesNeighbor.empty()) { return getMaxOffsetAtInit(dim, init, sign) < arch->getNAodIntermediateLevels(); @@ -609,9 +615,9 @@ void MoveToAodConverter::AodActivationHelper::computeInitAndOffsetOperations( std::vector& initOperations, std::vector& offsetOperations) const { - auto d = this->arch->getInterQubitDistance(); - auto interD = this->arch->getInterQubitDistance() / - this->arch->getNAodIntermediateLevels(); + const auto d = this->arch->getInterQubitDistance(); + const auto interD = this->arch->getInterQubitDistance() / + this->arch->getNAodIntermediateLevels(); initOperations.emplace_back(dimension, static_cast(aodMove->init) * d, static_cast(aodMove->init) * d); @@ -630,12 +636,12 @@ void MoveToAodConverter::AodActivationHelper::computeInitAndOffsetOperations( } void MoveToAodConverter::AodActivationHelper::mergeActivationDim( - Dimension dim, const AodActivation& activationDim, + const Dimension dim, const AodActivation& activationDim, const AodActivation& activationOtherDim) { // merge activations for (auto& activationCurrent : allActivations) { auto activates = activationCurrent.getActivates(dim); - for (auto& aodMove : activates) { + for (const auto& aodMove : activates) { if (aodMove->init == activationDim.getActivates(dim)[0]->init && aodMove->delta == activationDim.getActivates(dim)[0]->delta) { // append move @@ -656,7 +662,7 @@ void MoveToAodConverter::AodActivationHelper::mergeActivationDim( std::vector MoveToAodConverter::AodActivationHelper::getAodOperation( - const AodActivationHelper::AodActivation& activation) const { + const AodActivation& activation) const { CoordIndices qubitsActivation; qubitsActivation.reserve(activation.moves.size()); for (const auto& move : activation.moves) { @@ -695,7 +701,6 @@ MoveToAodConverter::AodActivationHelper::getAodOperation( if (initOperations.empty() && offsetOperations.empty()) { return {}; } - std::vector aodOperations; return {AodOperation(type, qubitsActivation, initOperations), AodOperation(qc::OpType::AodMove, qubitsOffset, offsetOperations)}; From 6f2516322b4258c6ea18ba1688214f060d8fdf7b Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 7 Nov 2025 12:46:47 +0100 Subject: [PATCH 247/394] =?UTF-8?q?=F0=9F=8E=A8=20reworked=20clang=20tidy?= =?UTF-8?q?=20Mapping.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/Mapping.hpp | 26 +++++++++++++------------- src/hybridmap/Mapping.cpp | 26 +++++++++++++------------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/include/hybridmap/Mapping.hpp b/include/hybridmap/Mapping.hpp index b8e9123d2..0a771170f 100644 --- a/include/hybridmap/Mapping.hpp +++ b/include/hybridmap/Mapping.hpp @@ -50,7 +50,7 @@ class Mapping { * @details Matches circuit interaction structure to device structure to * reduce expected routing overhead. * @return Vector mapping logical qubit index i -> chosen hardware index for - * i. + * qubit index i. */ [[nodiscard]] std::vector graphMatching(); @@ -74,14 +74,14 @@ class Mapping { * @param nQubits Number of logical qubits to map. * @param initialMapping Initialization strategy (Identity or Graph). * @param qc Circuit used to derive structure for graph-based initialization. - * @param hwQubits Target hardware description (capacity/topology + * @param hwQubitsArg Target hardware description (capacity/topology * constraints). * @throw std::runtime_error If the circuit has more qubits than available * hardware qubits. */ Mapping(const size_t nQubits, const InitialMapping initialMapping, - qc::QuantumComputation qc, HardwareQubits hwQubits) - : hwQubits(std::move(hwQubits)), + qc::QuantumComputation qc, HardwareQubits hwQubitsArg) + : hwQubits(std::move(hwQubitsArg)), dag(qc::CircuitOptimizer::constructDAG(qc)) { if (qc.getNqubits() > hwQubits.getNumQubits()) { @@ -131,11 +131,11 @@ class Mapping { */ [[nodiscard]] std::set getHwQubits(const std::set& qubits) const { - std::set hwQubits; + std::set hw; for (const auto& qubit : qubits) { - hwQubits.emplace(this->getHwQubit(qubit)); + hw.emplace(this->getHwQubit(qubit)); } - return hwQubits; + return hw; } /** @@ -147,11 +147,12 @@ class Mapping { */ [[nodiscard]] std::vector getHwQubits(const std::vector& qubits) const { - std::vector hwQubits; + std::vector hw; + hw.reserve(qubits.size()); for (const auto& qubit : qubits) { - hwQubits.emplace_back(this->getHwQubit(qubit)); + hw.emplace_back(this->getHwQubit(qubit)); } - return hwQubits; + return hw; } /** @@ -181,9 +182,8 @@ class Mapping { * false otherwise. */ [[nodiscard]] bool isMapped(HwQubit qubit) const { - return std::any_of( - circToHw.begin(), circToHw.end(), - [qubit](const auto& pair) { return pair.second == qubit; }); + return std::ranges::any_of( + circToHw, [qubit](const auto& pair) { return pair.second == qubit; }); } /** diff --git a/src/hybridmap/Mapping.cpp b/src/hybridmap/Mapping.cpp index bcc167e7f..5232ae357 100644 --- a/src/hybridmap/Mapping.cpp +++ b/src/hybridmap/Mapping.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -43,10 +44,9 @@ void Mapping::applySwap(const Swap& swap) { std::vector Mapping::graphMatching() { - std::vector qubitIndices(dag.size(), - std::numeric_limits::max()); + std::vector qubitIndices(dag.size(), std::numeric_limits::max()); std::vector hwIndices(hwQubits.getNumQubits(), - std::numeric_limits::max()); + std::numeric_limits::max()); // make hardware graph std::unordered_map> hwGraph; @@ -54,7 +54,7 @@ std::vector Mapping::graphMatching() { auto neighbors = hwQubits.getNearbyQubits(i); hwGraph[i] = std::vector(neighbors.begin(), neighbors.end()); } - for (auto& [qubit, neighbors] : hwGraph) { + for (auto& neighbors : hwGraph | std::views::values) { std::ranges::sort(neighbors, [this](const uint32_t a, const uint32_t b) { return hwQubits.getNearbyQubits(a).size() > hwQubits.getNearbyQubits(b).size(); @@ -95,12 +95,12 @@ std::vector Mapping::graphMatching() { } // circuit queue for graph matching - std::vector>> nodes; + std::vector>> nodes; for (size_t i = 0; i < circGraph.size(); ++i) { const auto degree = circGraph[i].size(); double weightSum = 0; - for (const auto& neighbor : circGraph[i]) { - weightSum += neighbor.second; + for (const auto& val : circGraph[i] | std::views::values) { + weightSum += val; } nodes.emplace_back(i, std::make_pair(degree, weightSum)); } @@ -112,9 +112,9 @@ std::vector Mapping::graphMatching() { } return a.second.first > b.second.first; }); - std::queue circGraphQueue; - for (const auto& node : nodes) { - circGraphQueue.push(node.first); + std::queue circGraphQueue; + for (const auto& key : nodes | std::views::keys) { + circGraphQueue.push(key); } // graph matching -> return qubit Indices @@ -134,7 +134,7 @@ std::vector Mapping::graphMatching() { else { auto minDistance = std::numeric_limits::max(); for (HwQubit qCandi = 0; qCandi < hwQubits.getNumQubits(); ++qCandi) { - if (hwIndices[qCandi] != std::numeric_limits::max()) { + if (hwIndices[qCandi] != std::numeric_limits::max()) { continue; } auto weightDistance = 0.0; @@ -161,8 +161,8 @@ std::vector Mapping::graphMatching() { qI = qubitIndices[qi]; } // neighbor mapping - for (auto& qnPair : circGraph[qi]) { - auto const qn = qnPair.first; + for (auto& key : circGraph[qi] | std::views::keys) { + auto const qn = key; if (qubitIndices[qn] != std::numeric_limits::max()) { continue; } From 06f63cafdf887c394e47d58caa6f43454eae00c4 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 7 Nov 2025 13:13:37 +0100 Subject: [PATCH 248/394] =?UTF-8?q?=F0=9F=8E=A8=20reworked=20clang=20tidy?= =?UTF-8?q?=20HybridSynthesisMapper.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridSynthesisMapper.hpp | 10 +++++----- src/hybridmap/HybridSynthesisMapper.cpp | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/include/hybridmap/HybridSynthesisMapper.hpp b/include/hybridmap/HybridSynthesisMapper.hpp index 61f67f004..d2edea805 100644 --- a/include/hybridmap/HybridSynthesisMapper.hpp +++ b/include/hybridmap/HybridSynthesisMapper.hpp @@ -52,7 +52,7 @@ class HybridSynthesisMapper : public NeutralAtomMapper { * @param qc Proposed synthesis subcircuit. * @return Scalar cost/effort score for mapping qc. */ - qc::fp evaluateSynthesisStep(qc::QuantumComputation& qc); + qc::fp evaluateSynthesisStep(qc::QuantumComputation& qc) const; public: // Constructors @@ -73,7 +73,7 @@ class HybridSynthesisMapper : public NeutralAtomMapper { * @brief Initialize synthesized and mapped circuits and mapping structures. * @param nQubits Number of logical qubits to synthesize. */ - void initMapping(size_t nQubits) { + void initMapping(const size_t nQubits) { mappedQc = qc::QuantumComputation(arch->getNpositions()); synthesizedQc = qc::QuantumComputation(nQubits); mapping = Mapping(nQubits); @@ -83,7 +83,7 @@ class HybridSynthesisMapper : public NeutralAtomMapper { * @brief Complete a (re-)mapping of the synthesized circuit to hardware. * @param initMapping Initial mapping heuristic (defaults to Identity). */ - void completeRemap(InitialMapping initMapping = InitialMapping::Identity) { + void completeRemap(const InitialMapping initMapping = Identity) { this->map(synthesizedQc, initMapping); } @@ -99,7 +99,7 @@ class HybridSynthesisMapper : public NeutralAtomMapper { * @brief Export synthesized circuit as OpenQASM string. * @return QASM representation of the synthesized circuit. */ - [[maybe_unused]] std::string getSynthesizedQcQASM() { + [[nodiscard]] [[maybe_unused]] std::string getSynthesizedQcQASM() const { std::stringstream ss; synthesizedQc.dumpOpenQASM(ss, false); return ss.str(); @@ -110,7 +110,7 @@ class HybridSynthesisMapper : public NeutralAtomMapper { * @param filename Output filename. * @throw std::ios_base::failure If the file cannot be opened for writing. */ - [[maybe_unused]] void saveSynthesizedQc(const std::string& filename) { + [[maybe_unused]] void saveSynthesizedQc(const std::string& filename) const { std::ofstream ofs(filename); synthesizedQc.dumpOpenQASM(ofs, false); ofs.close(); diff --git a/src/hybridmap/HybridSynthesisMapper.cpp b/src/hybridmap/HybridSynthesisMapper.cpp index d4e437047..3a530a530 100644 --- a/src/hybridmap/HybridSynthesisMapper.cpp +++ b/src/hybridmap/HybridSynthesisMapper.cpp @@ -29,7 +29,7 @@ namespace na { std::vector HybridSynthesisMapper::evaluateSynthesisSteps(qcs& synthesisSteps, - bool alsoMap) { + const bool alsoMap) { std::vector> candidates; size_t qcIndex = 0; for (auto& qc : synthesisSteps) { @@ -59,7 +59,7 @@ HybridSynthesisMapper::evaluateSynthesisSteps(qcs& synthesisSteps, } qc::fp -HybridSynthesisMapper::evaluateSynthesisStep(qc::QuantumComputation& qc) { +HybridSynthesisMapper::evaluateSynthesisStep(qc::QuantumComputation& qc) const { NeutralAtomMapper tempMapper; tempMapper.copyStateFrom(*this); auto mappedQc = tempMapper.map(qc, mapping); @@ -87,13 +87,13 @@ void HybridSynthesisMapper::appendWithMapping(qc::QuantumComputation& qc) { } AdjacencyMatrix HybridSynthesisMapper::getCircuitAdjacencyMatrix() const { - auto numCircQubits = synthesizedQc.getNqubits(); + const auto numCircQubits = synthesizedQc.getNqubits(); AdjacencyMatrix adjMatrix(numCircQubits); for (uint32_t i = 0; i < numCircQubits; ++i) { for (uint32_t j = 0; j < i; ++j) { - auto mappedI = this->mapping.getHwQubit(i); - auto mappedJ = this->mapping.getHwQubit(j); + const auto mappedI = this->mapping.getHwQubit(i); + const auto mappedJ = this->mapping.getHwQubit(j); if (this->arch->getSwapDistance(mappedI, mappedJ) == 0) { adjMatrix(i, j) = 1; } else { From 78d22780186622b6c31a71a6ab09215bdfbbea35 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 7 Nov 2025 13:14:46 +0100 Subject: [PATCH 249/394] =?UTF-8?q?=F0=9F=8E=A8=20reworked=20clang=20tidy?= =?UTF-8?q?=20Animation.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridAnimation.cpp | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/hybridmap/HybridAnimation.cpp b/src/hybridmap/HybridAnimation.cpp index 5185ca529..c38ce3dcf 100644 --- a/src/hybridmap/HybridAnimation.cpp +++ b/src/hybridmap/HybridAnimation.cpp @@ -40,18 +40,16 @@ void AnimationAtoms::initPositions( const auto hwCount = static_cast(initHwPos.size()); for (const auto& [id, coord] : initFaPos) { flyingAncillaIdxPlusOne++; - coordIdxToId[static_cast( - coord + static_cast(2 * arch->getNpositions()))] = - static_cast(id + hwCount); + coordIdxToId[(coord + static_cast(2 * arch->getNpositions()))] = + id + hwCount; const auto column = coord % nCols; const auto row = coord / nCols; const auto offset = arch->getInterQubitDistance() / arch->getNAodIntermediateLevels(); - idToCoord[static_cast(id + hwCount)] = { - (column * arch->getInterQubitDistance()) + - flyingAncillaIdxPlusOne * offset, - (row * arch->getInterQubitDistance()) + - flyingAncillaIdxPlusOne * offset}; + idToCoord[(id + hwCount)] = {(column * arch->getInterQubitDistance()) + + flyingAncillaIdxPlusOne * offset, + (row * arch->getInterQubitDistance()) + + flyingAncillaIdxPlusOne * offset}; } } From b38d45cab377322bfe42165928cfface1074064b Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 7 Nov 2025 13:17:20 +0100 Subject: [PATCH 250/394] =?UTF-8?q?=F0=9F=8E=A8=20reworked=20clang=20tidy?= =?UTF-8?q?=20Mapper.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridNeutralAtomMapper.hpp | 4 +- src/hybridmap/HybridNeutralAtomMapper.cpp | 105 +++++++++--------- 2 files changed, 54 insertions(+), 55 deletions(-) diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index d57116347..c9060cad2 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -224,10 +224,10 @@ class NeutralAtomMapper { // Methods for swap gates mapping /** * @brief Select best swap minimizing composite cost (distance + decay). - * @param lastSwap Previously applied swap (for decay context). + * @param lastSwapUsed Previously applied swap (for decay context). * @return Best swap candidate. */ - Swap findBestSwap(const Swap& lastSwap); + Swap findBestSwap(const Swap& lastSwapUsed); /** * @brief Enumerate candidate swaps derived from front layer proximity. * @param swapsFront Pair of (close-by swaps, weighted exact swaps). diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 3a1d98a29..287e67e0f 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -34,10 +34,12 @@ #include #include #include +#include #include #include #include #include +#include #include namespace na { @@ -168,15 +170,15 @@ qc::QuantumComputation NeutralAtomMapper::convertToAod() { void NeutralAtomMapper::applyPassBy(NeutralAtomLayer& frontLayer, const PassByComb& pbComb) { - auto opTargets = pbComb.op->getTargets(); - auto targetHwQubits = mapping.getHwQubits(opTargets); + const auto opTargets = pbComb.op->getTargets(); + const auto targetHwQubits = mapping.getHwQubits(opTargets); auto targetCoords = hardwareQubits.getCoordIndices(targetHwQubits); - auto opControls = pbComb.op->getControls(); + const auto opControls = pbComb.op->getControls(); HwQubitsVector controlQubits; for (const auto& control : opControls) { controlQubits.emplace_back(control.qubit); } - auto controlHwQubits = mapping.getHwQubits(controlQubits); + const auto controlHwQubits = mapping.getHwQubits(controlQubits); auto controlCoords = hardwareQubits.getCoordIndices(controlHwQubits); for (const auto& passBy : pbComb.moves) { @@ -193,7 +195,7 @@ void NeutralAtomMapper::applyPassBy(NeutralAtomLayer& frontLayer, *itC = passBy.c2 + arch->getNpositions(); } } - auto opCopy = pbComb.op->clone(); + const auto opCopy = pbComb.op->clone(); opCopy->setTargets(targetCoords); qc::Controls controls; for (const auto& control : controlCoords) { @@ -395,7 +397,7 @@ void NeutralAtomMapper::applyFlyingAncilla(NeutralAtomLayer& frontLayer, auto targetCoords = hardwareQubits.getCoordIndices( mapping.getHwQubits(faComb.op->getTargets())); // get control vector - auto opControls = faComb.op->getControls(); + const auto opControls = faComb.op->getControls(); HwQubitsVector controlQubits; for (const auto& control : opControls) { controlQubits.emplace_back(control.qubit); @@ -466,7 +468,7 @@ void NeutralAtomMapper::applyFlyingAncilla(NeutralAtomLayer& frontLayer, stats.nFAncillas += faComb.moves.size(); } -Swap NeutralAtomMapper::findBestSwap(const Swap& lastSwap) { +Swap NeutralAtomMapper::findBestSwap(const Swap& lastSwapUsed) { // compute necessary movements const auto swapsFront = initSwaps(this->frontLayerGate); const auto swapsLookahead = initSwaps(this->lookaheadLayerGate); @@ -475,8 +477,8 @@ Swap NeutralAtomMapper::findBestSwap(const Swap& lastSwap) { // evaluate swaps based on cost function auto swaps = getAllPossibleSwaps(swapsFront); // remove last swap to prevent immediate swap back - swaps.erase(lastSwap); - swaps.erase({lastSwap.second, lastSwap.first}); + swaps.erase(lastSwapUsed); + swaps.erase({lastSwapUsed.second, lastSwapUsed.first}); // no swap possible if (swaps.empty()) { @@ -492,16 +494,15 @@ Swap NeutralAtomMapper::findBestSwap(const Swap& lastSwap) { return swap1.second < swap2.second; }); // get swap of minimal cost - const auto bestSwap = - std::min_element(swapCosts.begin(), swapCosts.end(), - [](const auto& swap1, const auto& swap2) { - return swap1.second < swap2.second; - }); + const auto bestSwap = std::ranges::min_element( + swapCosts, [](const auto& swap1, const auto& swap2) { + return swap1.second < swap2.second; + }); return bestSwap->first; } void NeutralAtomMapper::setTwoQubitSwapWeight(const WeightedSwaps& swapExact) { - for (const auto& [swap, weight] : swapExact) { + for (const auto& weight : swapExact | std::views::values) { this->twoQubitSwapWeight = std::min(weight, this->twoQubitSwapWeight); } } @@ -522,7 +523,7 @@ std::set NeutralAtomMapper::getAllPossibleSwaps( swaps.emplace(swapSecond); } } - for (const auto& [swap, weight] : swapExactFront) { + for (const auto& swap : swapExactFront | std::views::keys) { const auto nearbySwapsFirst = this->hardwareQubits.getNearbySwaps(swap.first); for (const auto& swapFirst : nearbySwapsFirst) { @@ -635,7 +636,7 @@ FlyingAncillaComb NeutralAtomMapper::convertMoveCombToFlyingAncillaComb( FlyingAncilla bestFA{}; HwQubits usedFA; for (const auto move : moveComb.moves) { - if (usedCoords.find(move.c1) != usedCoords.end()) { + if (usedCoords.contains(move.c1)) { const auto nearFirstIdx = this->flyingAncillas.getClosestQubit(move.c1, usedFA); const auto nearFirst = this->flyingAncillas.getCoordIndex(nearFirstIdx); @@ -679,7 +680,7 @@ NeutralAtomMapper::convertMoveCombToPassByComb(const MoveComb& moveComb) const { std::vector bestPbs; for (const auto move : moveComb.moves) { - if (usedCoords.find(move.c1) != usedCoords.end()) { + if (usedCoords.contains(move.c1)) { bestPbs.emplace_back(AtomMove{ .c1 = move.c1, .c2 = move.c2, .load1 = true, .load2 = false}); } @@ -756,7 +757,7 @@ NeutralAtomMapper::moveCombDistanceReduction(const MoveComb& moveComb, auto hwQubits = this->mapping.getHwQubits(usedQubits); auto coordIndices = this->hardwareQubits.getCoordIndices(hwQubits); for (const auto& move : moveComb.moves) { - if (coordIndices.find(move.c1) != coordIndices.end()) { + if (coordIndices.contains(move.c1)) { const auto& distBefore = this->arch->getAllToAllEuclideanDistance(coordIndices); coordIndices.erase(move.c1); @@ -932,16 +933,14 @@ NeutralAtomMapper::getBestMultiQubitPosition(const qc::Operation* opPointer) { } } // find gate and move it to the shuttling layer - const auto idxFrontGate = std::find(this->frontLayerGate.begin(), - this->frontLayerGate.end(), opPointer); + const auto idxFrontGate = std::ranges::find(this->frontLayerGate, opPointer); if (idxFrontGate != this->frontLayerGate.end()) { this->frontLayerGate.erase(idxFrontGate); this->frontLayerShuttling.emplace_back(opPointer); } // remove from lookahead layer if there const auto idxLookaheadGate = - std::find(this->lookaheadLayerGate.begin(), - this->lookaheadLayerGate.end(), opPointer); + std::ranges::find(this->lookaheadLayerGate, opPointer); if (idxLookaheadGate != this->lookaheadLayerGate.end()) { this->lookaheadLayerGate.erase(idxLookaheadGate); this->lookaheadLayerShuttling.emplace_back(opPointer); @@ -994,11 +993,10 @@ HwQubits NeutralAtomMapper::getBestMultiQubitPositionRec( summedDistances.emplace_back(hwQubit, distance); } // select next qubit as the one with minimal distance - const auto nextQubitDist = - std::min_element(summedDistances.begin(), summedDistances.end(), - [](const auto& qubit1, const auto& qubit2) { - return qubit1.second < qubit2.second; - }); + const auto nextQubitDist = std::ranges::min_element( + summedDistances, [](const auto& qubit1, const auto& qubit2) { + return qubit1.second < qubit2.second; + }); auto nextQubit = nextQubitDist->first; selectedQubits.emplace_back(nextQubit); // remove from remaining gate qubits the one that is closest to the next @@ -1093,7 +1091,7 @@ NeutralAtomMapper::getExactSwapsToPosition(const qc::Operation* op, // compute total distance of all moves SwapDistance totalDistance = 0; - for (const auto& [swap, weight] : swapsExact) { + for (const auto& swap : swapsExact | std::views::keys) { auto [q1, q2] = swap; totalDistance += this->hardwareQubits.getSwapDistance(q1, q2, false); } @@ -1102,8 +1100,8 @@ NeutralAtomMapper::getExactSwapsToPosition(const qc::Operation* op, const auto nQubits = op->getUsedQubits().size(); const auto multiQubitFactor = (static_cast(nQubits) * static_cast(nQubits - 1)) / 2; - for (auto& move : swapsExact) { - move.second = multiQubitFactor / static_cast(totalDistance); + for (auto& val : swapsExact | std::views::values) { + val = multiQubitFactor / static_cast(totalDistance); } return swapsExact; @@ -1124,11 +1122,10 @@ MoveComb NeutralAtomMapper::findBestAtomMove() { }); // get move of minimal cost - const auto bestMove = - std::min_element(moveCosts.begin(), moveCosts.end(), - [](const auto& move1, const auto& move2) { - return move1.second < move2.second; - }); + const auto bestMove = std::ranges::min_element( + moveCosts, [](const auto& move1, const auto& move2) { + return move1.second < move2.second; + }); return bestMove->first; } @@ -1157,7 +1154,7 @@ qc::fp NeutralAtomMapper::moveCostComb(const MoveComb& moveComb) const { qc::fp NeutralAtomMapper::parallelMoveCost(const MoveComb& moveComb) const { qc::fp parallelCost = 0; // only first move matters for parallelization - auto move = moveComb.moves.front(); + const auto move = moveComb.moves.front(); const auto moveVector = this->arch->getVector(move.c1, move.c2); parallelCost += arch->getVectorShuttlingTime(moveVector); bool canBeDoneInParallel = true; @@ -1309,10 +1306,10 @@ MoveCombs NeutralAtomMapper::getAllMoveCombinations() { std::set bestPositions; // iterate over all possible permutations of the usedCoords // to find different best positions - std::sort(usedCoords.begin(), usedCoords.end()); + std::ranges::sort(usedCoords); do { bestPositions.insert(getBestMovePos(usedCoords)); - } while (std::next_permutation(usedCoords.begin(), usedCoords.end())); + } while (std::ranges::next_permutation(usedCoords).found); for (const auto& bestPos : bestPositions) { auto moves = getMoveCombinationsToPosition(usedHwQubits, bestPos); moves.setOperation(op, bestPos); @@ -1423,8 +1420,8 @@ MoveCombs NeutralAtomMapper::getMoveCombinationsToPosition( } } // find minimal cost - const auto bestCost = std::min_element( - costs.begin(), costs.end(), [](const auto& cost1, const auto& cost2) { + const auto bestCost = std::ranges::min_element( + costs, [](const auto& cost1, const auto& cost2) { return cost1.second < cost2.second; }); auto bestCoord = bestCost->first; @@ -1443,7 +1440,7 @@ MoveCombs NeutralAtomMapper::getMoveCombinationsToPosition( } MoveCombs NeutralAtomMapper::getMoveAwayCombinations( - CoordIndex startCoord, CoordIndex targetCoord, + const CoordIndex startCoord, const CoordIndex targetCoord, const CoordIndices& excludedCoords) const { MoveCombs moveCombinations; auto const originalVector = this->arch->getVector(startCoord, targetCoord); @@ -1452,8 +1449,10 @@ MoveCombs NeutralAtomMapper::getMoveAwayCombinations( const auto moveAwayTargets = this->hardwareQubits.findClosestFreeCoord( targetCoord, originalDirection, excludedCoords); for (const auto& moveAwayTarget : moveAwayTargets) { - const AtomMove move = {startCoord, targetCoord}; - const AtomMove moveAway = {targetCoord, moveAwayTarget}; + const AtomMove move = { + .c1 = startCoord, .c2 = targetCoord, .load1 = true, .load2 = true}; + const AtomMove moveAway = { + .c1 = targetCoord, .c2 = moveAwayTarget, .load1 = true, .load2 = true}; moveCombinations.addMoveComb(MoveComb({moveAway, move})); } if (moveCombinations.empty()) { @@ -1520,7 +1519,7 @@ NeutralAtomMapper::estimateNumSwapGates(const qc::Operation* opPointer) { return {std::numeric_limits::max(), std::numeric_limits::max()}; } - for (const auto& [swap, weight] : exactSwaps) { + for (const auto& swap : exactSwaps | std::views::keys) { auto [q1, q2] = swap; minNumSwaps += this->hardwareQubits.getSwapDistance(q1, q2, false); } @@ -1708,16 +1707,16 @@ MappingMethod NeutralAtomMapper::compareShuttlingAndFlyingAncilla( } // move distance reduction - auto moveDistReductionf = + const auto moveDistReductionFront = moveCombDistanceReduction(bestMoveComb, this->frontLayerShuttling); - auto const moveDistReductionl = 0.0; - if (this->lookaheadLayerShuttling.size() != 0) { + constexpr auto moveDistReductionLookAhead = 0.0; + if (!this->lookaheadLayerShuttling.empty()) { (this->parameters->lookaheadWeightMoves * moveCombDistanceReduction(bestMoveComb, this->lookaheadLayerShuttling) / - this->lookaheadLayerShuttling.size()); + static_cast(this->lookaheadLayerShuttling.size())); } - auto moveDistReduction = moveDistReductionf + moveDistReductionl; + auto moveDistReduction = moveDistReductionFront + moveDistReductionLookAhead; // move auto const moveDist = this->arch->getMoveCombEuclideanDistance(bestMoveComb); auto const moveCombSize = bestMoveComb.size(); @@ -1790,7 +1789,7 @@ MappingMethod NeutralAtomMapper::compareShuttlingAndFlyingAncilla( auto const minDistanceReduction = std::min({moveDistReduction, faDistReduction, pbDistReduction}); - qc::fp const constant = 1; + constexpr qc::fp constant = 1; if (minDistanceReduction < 0) { moveDistReduction -= minDistanceReduction - constant; faDistReduction -= minDistanceReduction - constant; @@ -1802,8 +1801,8 @@ MappingMethod NeutralAtomMapper::compareShuttlingAndFlyingAncilla( } // higher is better - auto a = parameters->dynamicMappingWeight; - const auto move = std::log(moveFidelity) / moveDistReduction / a; + const auto move = std::log(moveFidelity) / moveDistReduction / + parameters->dynamicMappingWeight; const auto fa = std::log(faFidelity) / faDistReduction; const auto passBy = std::log(passByFidelity) / pbDistReduction; From c8832c82f85d6a65a5d9d40cadb79d8f182c56f4 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 7 Nov 2025 13:44:41 +0100 Subject: [PATCH 251/394] =?UTF-8?q?=F0=9F=8E=A8=20reworked=20clang=20tidy?= =?UTF-8?q?=20Mapper.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridNeutralAtomMapper.hpp | 111 ++++++++++++++---- include/hybridmap/NeutralAtomDefinitions.hpp | 4 +- src/hybridmap/HybridNeutralAtomMapper.cpp | 76 ++++++------ 3 files changed, 128 insertions(+), 63 deletions(-) diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index c9060cad2..86a686482 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -22,6 +22,7 @@ #include "ir/Definitions.hpp" #include "ir/QuantumComputation.hpp" +#include #include #include #include @@ -55,8 +56,7 @@ struct MapperParameters { uint32_t maxBridgeDistance = 1; bool usePassBy = true; bool verbose = false; - InitialCoordinateMapping initialCoordMapping = - InitialCoordinateMapping::Trivial; + InitialCoordinateMapping initialCoordMapping = Trivial; }; /** @@ -194,17 +194,32 @@ class NeutralAtomMapper { */ void reassignGatesToLayers(const GateList& frontGates, const GateList& lookaheadGates); + /** - * @brief Estimate swaps and time required to execute a gate via swapping. - * @param opPointer Gate under consideration. - * @return Pair (#swaps, estimated time cost). + * @brief Advance one step using gate-based routing (swaps/bridges). + * @param frontLayer Front layer container. + * @param lookaheadLayer Lookahead layer container. + * @param i Index of the considered operation in the front layer. + * @return Number of mapped operations performed. */ - size_t gateBasedMapping(NeutralAtomLayer& frontLayer, NeutralAtomLayer& lookaheadLayer, size_t i); + /** + * @brief Advance one step using shuttling primitives (moves/pass-by/flying + * ancilla). + * @param frontLayer Front layer container. + * @param lookaheadLayer Lookahead layer container. + * @param i Index of the considered operation in the front layer. + * @return Number of mapped operations performed. + */ size_t shuttlingBasedMapping(NeutralAtomLayer& frontLayer, NeutralAtomLayer& lookaheadLayer, size_t i); + /** + * @brief Estimate swaps and time required to execute a gate via swapping. + * @param opPointer Gate under consideration. + * @return Pair (#swaps, estimated time cost). + */ std::pair estimateNumSwapGates(const qc::Operation* opPointer); /** @@ -238,20 +253,40 @@ class NeutralAtomMapper { // Methods for bridge operations mapping + /** + * @brief Choose best bridge operation given current best swap. + * @param bestSwap Candidate swap used for context. + * @return Selected bridge descriptor. + */ [[nodiscard]] Bridge findBestBridge(const Swap& bestSwap); + /** + * @brief Compute shortest bridge circuits compatible with a swap candidate. + * @param bestSwap Swap context. + * @return List of shortest bridges. + */ [[nodiscard]] Bridges getShortestBridges(const Swap& bestSwap); + /** + * @brief Current coordinate usage (occupied indices) snapshot. + * @return Set of occupied coordinate indices. + */ [[nodiscard]] CoordIndices computeCurrentCoordUsages() const; + /** + * @brief Convert a MOVE combination to a flying ancilla combination if + * suitable. + * @param moveComb MOVE combination candidate. + * @return Equivalent flying-ancilla combination. + */ [[nodiscard]] FlyingAncillaComb convertMoveCombToFlyingAncillaComb(const MoveComb& moveComb) const; - [[nodiscard]] PassByComb - convertMoveCombToPassByComb(const MoveComb& moveComb) const; - /** - * @brief Returns the next best shuttling move operation for the front layer. - * @return The next best shuttling move operation for the front layer + * @brief Convert a MOVE combination to a pass-by combination if suitable. + * @param moveComb MOVE combination candidate. + * @return Equivalent pass-by combination. */ + [[nodiscard]] PassByComb + convertMoveCombToPassByComb(const MoveComb& moveComb) const; // Methods for shuttling operations mapping /** @@ -278,8 +313,22 @@ class NeutralAtomMapper { const CoordIndices& excludedCoords) const; // Methods for flying ancilla operations mapping + /** + * @brief Compare swap vs. bridge and select routing method. + * @param bestSwap Swap candidate. + * @param bestBridge Bridge candidate. + * @return Chosen mapping method. + */ [[nodiscard]] MappingMethod compareSwapAndBridge(const Swap& bestSwap, const Bridge& bestBridge); + /** + * @brief Compare shuttling vs. flying ancilla vs. pass-by for a move + * candidate. + * @param bestMoveComb Best move combination. + * @param bestFaComb Best flying ancilla combination. + * @param bestPbComb Best pass-by combination. + * @return Chosen mapping method. + */ [[nodiscard]] MappingMethod compareShuttlingAndFlyingAncilla(const MoveComb& bestMoveComb, const FlyingAncillaComb& bestFaComb, @@ -326,6 +375,14 @@ class NeutralAtomMapper { * @return Hardware qubit set representing chosen position. */ HwQubits getBestMultiQubitPosition(const qc::Operation* opPointer); + /** + * @brief Recursive helper to search for optimal multi-qubit convergence + * position. + * @param remainingGateQubits Remaining gate participants. + * @param selectedQubits Already selected qubits (path state). + * @param remainingNearbyQubits Candidate nearby qubits. + * @return Selected hardware qubit set. + */ HwQubits getBestMultiQubitPositionRec(HwQubits remainingGateQubits, std::vector selectedQubits, HwQubits remainingNearbyQubits); @@ -344,7 +401,7 @@ class NeutralAtomMapper { * @brief Compute distance reduction contribution for a swap. * @param swap Candidate swap. * @param swapCloseBy Close-by (2-qubit) swaps. - * @param moveExact Weighted exact swaps (multi-qubit alignment). + * @param swapExact Weighted exact swaps (multi-qubit alignment). * @return Reduction metric (higher means better improvement). */ qc::fp swapCostPerLayer(const Swap& swap, const Swaps& swapCloseBy, @@ -361,8 +418,20 @@ class NeutralAtomMapper { qc::fp swapCost(const Swap& swap, const std::pair& swapsFront, const std::pair& swapsLookahead); - qc::fp moveCombDistanceReduction(const MoveComb& moveComb, - const GateList& layer) const; + /** + * @brief Distance reduction from a move combination for a given layer. + * @param moveComb Move combo. + * @param layer Target layer. + * @return Distance reduction score. + */ + [[nodiscard]] qc::fp moveCombDistanceReduction(const MoveComb& moveComb, + const GateList& layer) const; + /** + * @brief Distance reduction from a swap for a given layer. + * @param swap Swap candidate. + * @param layer Target layer. + * @return Distance reduction score. + */ qc::fp swapDistanceReduction(const Swap& swap, const GateList& layer); /** @@ -392,9 +461,7 @@ class NeutralAtomMapper { : arch(architecture), scheduler(*architecture), parameters(p), hardwareQubits(*arch, arch->getNqubits() - p->numFlyingAncillas, p->initialCoordMapping, p->seed), - flyingAncillas(*arch, p->numFlyingAncillas, - InitialCoordinateMapping::Trivial, p->seed), - mapping() { + flyingAncillas(*arch, p->numFlyingAncillas, Trivial, p->seed) { if (arch->getNpositions() - arch->getNqubits() < 1 && p->shuttlingWeight > 0) { throw std::runtime_error( @@ -409,7 +476,7 @@ class NeutralAtomMapper { for (uint32_t i = this->arch->getNcolumns(); i > 0; --i) { this->decayWeights.emplace_back(std::exp(-this->parameters->decay * i)); } - }; + } explicit NeutralAtomMapper(const NeutralAtomArchitecture& architecture, const MapperParameters& p = MapperParameters()) : NeutralAtomMapper(&architecture, &p) {} @@ -455,9 +522,8 @@ class NeutralAtomMapper { hardwareQubits = HardwareQubits( *arch, arch->getNqubits() - parameters->numFlyingAncillas, parameters->initialCoordMapping, parameters->seed); - flyingAncillas = - HardwareQubits(*arch, parameters->numFlyingAncillas, - InitialCoordinateMapping::Trivial, parameters->seed); + flyingAncillas = HardwareQubits(*arch, parameters->numFlyingAncillas, + Trivial, parameters->seed); } // Methods @@ -482,7 +548,6 @@ class NeutralAtomMapper { * @param qc The quantum circuit to be mapped * @param initialMapping The initial mapping of the circuit qubits to the * hardware qubits - * @param verbose If true, prints additional information * @return The mapped quantum circuit with abstract SWAP gates and MOVE * operations */ @@ -603,7 +668,7 @@ class NeutralAtomMapper { * @brief Retrieve animation CSV content produced by scheduler. * @return CSV string. */ - [[maybe_unused]] std::string getAnimationViz() const { + [[maybe_unused]] [[nodiscard]] std::string getAnimationViz() const { return scheduler.getAnimationViz(); } diff --git a/include/hybridmap/NeutralAtomDefinitions.hpp b/include/hybridmap/NeutralAtomDefinitions.hpp index 35f6b6f9f..2b2300be4 100644 --- a/include/hybridmap/NeutralAtomDefinitions.hpp +++ b/include/hybridmap/NeutralAtomDefinitions.hpp @@ -79,8 +79,8 @@ using SwapDistance = int32_t; struct AtomMove { CoordIndex c1; CoordIndex c2; - bool load1; - bool load2; + bool load1 = true; + bool load2 = true; /** * @brief Equality comparison. diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 287e67e0f..c38fa32fd 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -455,7 +455,7 @@ void NeutralAtomMapper::applyFlyingAncilla(NeutralAtomLayer& frontLayer, // update position of flying ancillas if (passBy.q1 != passBy.origin) { - this->flyingAncillas.move(passBy.index, passBy.q1); + this->flyingAncillas.move(static_cast(passBy.index), passBy.q1); } if (this->parameters->verbose) { @@ -616,8 +616,8 @@ CoordIndices NeutralAtomMapper::computeCurrentCoordUsages() const { if (this->lastBlockedQubits.empty()) { return coordUsages; } - const auto lastBlockedQubits = this->lastBlockedQubits.back(); - for (const auto qubit : lastBlockedQubits) { + const auto lastBlockedQubitSet = this->lastBlockedQubits.back(); + for (const auto qubit : lastBlockedQubitSet) { coordUsages[hardwareQubits.getCoordIndex(qubit)]++; } return coordUsages; @@ -666,7 +666,7 @@ FlyingAncillaComb NeutralAtomMapper::convertMoveCombToFlyingAncillaComb( bestFAs.emplace_back(bestFA); } } - return {bestFAs, moveComb.op}; + return {.moves = bestFAs, .op = moveComb.op}; } PassByComb @@ -1143,10 +1143,10 @@ qc::fp NeutralAtomMapper::moveCostComb(const MoveComb& moveComb) const { static_cast(this->parameters->lookaheadDepth); } if (!this->lastMoves.empty()) { - const auto parallelMovecost = + const auto parallelMovecCost = parameters->shuttlingTimeWeight * parallelMoveCost(moveComb) / static_cast(this->frontLayerShuttling.size()); - costComb += parallelMovecost; + costComb += parallelMovecCost; } return costComb; } @@ -1297,7 +1297,6 @@ NeutralAtomMapper::getMovePositionRec(MultiQubitMovePos currentPos, MoveCombs NeutralAtomMapper::getAllMoveCombinations() { MoveCombs allMoves; - size_t i = 0; for (const auto& op : this->frontLayerShuttling) { auto usedQubits = op->getUsedQubits(); auto usedHwQubits = this->mapping.getHwQubits(usedQubits); @@ -1318,7 +1317,6 @@ MoveCombs NeutralAtomMapper::getAllMoveCombinations() { } allMoves.addMoveCombs(moves); } - ++i; } allMoves.removeLongerMoveCombs(); return allMoves; @@ -1326,7 +1324,6 @@ MoveCombs NeutralAtomMapper::getAllMoveCombinations() { CoordIndices NeutralAtomMapper::getBestMovePos(const CoordIndices& gateCoords) { size_t const maxMoves = gateCoords.size() * 2; - size_t const minMoves = gateCoords.size(); size_t nMovesGate = maxMoves; // do a breadth first search for the best position // start with the used coords @@ -1414,8 +1411,9 @@ MoveCombs NeutralAtomMapper::getMoveCombinationsToPosition( costs.emplace_back(remainingCoord, cost); } } else { - MoveComb moveComb({AtomMove{currentGateQubit, remainingCoord}}); - auto cost = moveCostComb(moveComb); + MoveComb const moveCombNew( + {AtomMove{.c1 = currentGateQubit, .c2 = remainingCoord}}); + auto cost = moveCostComb(moveCombNew); costs.emplace_back(remainingCoord, cost); } } @@ -1424,17 +1422,20 @@ MoveCombs NeutralAtomMapper::getMoveCombinationsToPosition( costs, [](const auto& cost1, const auto& cost2) { return cost1.second < cost2.second; }); - auto bestCoord = bestCost->first; - if (this->hardwareQubits.isMapped(bestCoord)) { - auto moveAwayComb = - getMoveAwayCombinations(currentGateQubit, bestCoord, movedAwayCoords); + auto targetCoord = bestCost->first; + if (this->hardwareQubits.isMapped(targetCoord)) { + auto moveAwayComb = getMoveAwayCombinations(currentGateQubit, targetCoord, + movedAwayCoords); moveComb.append(moveAwayComb.moveCombs[0]); movedAwayCoords.emplace_back(moveAwayComb.moveCombs[0].moves[0].c2); } else { - moveComb.append(AtomMove{currentGateQubit, bestCoord}); + moveComb.append(AtomMove{.c1 = currentGateQubit, + .c2 = targetCoord, + .load1 = true, + .load2 = true}); } remainingGateCoords.erase(currentGateQubit); - remainingCoords.erase(std::ranges::find(remainingCoords, bestCoord)); + remainingCoords.erase(std::ranges::find(remainingCoords, targetCoord)); } return MoveCombs({moveComb}); } @@ -1464,7 +1465,6 @@ MoveCombs NeutralAtomMapper::getMoveAwayCombinations( size_t NeutralAtomMapper::shuttlingBasedMapping( NeutralAtomLayer& frontLayer, NeutralAtomLayer& lookaheadLayer, size_t i) { while (!this->frontLayerShuttling.empty()) { - GateList gatesToExecute; ++i; if (this->parameters->verbose) { std::cout << "iteration " << i << '\n'; @@ -1475,17 +1475,17 @@ size_t NeutralAtomMapper::shuttlingBasedMapping( switch ( compareShuttlingAndFlyingAncilla(bestComb, bestFaComb, bestPbComb)) { - case MappingMethod::MoveMethod: + case MoveMethod: // apply whole move combination at once for (const auto& move : bestComb.moves) { applyMove(move); } // applyMove(bestComb.moves[0]); break; - case MappingMethod::FlyingAncillaMethod: + case FlyingAncillaMethod: applyFlyingAncilla(frontLayer, bestFaComb); break; - case MappingMethod::PassByMethod: + case PassByMethod: applyPassBy(frontLayer, bestPbComb); break; default: @@ -1630,18 +1630,17 @@ size_t NeutralAtomMapper::gateBasedMapping(NeutralAtomLayer& frontLayer, } auto bestSwap = findBestSwap(lastSwap); - MappingMethod bestMethod = MappingMethod::SwapMethod; + MappingMethod bestMethod = SwapMethod; if (parameters->maxBridgeDistance > 0 && !multiQubitGates) { auto bestBridge = findBestBridge(bestSwap); bestMethod = compareSwapAndBridge(bestSwap, bestBridge); - if (bestMethod == MappingMethod::BridgeMethod) { + if (bestMethod == BridgeMethod) { updateBlockedQubits( {bestBridge.second.begin(), bestBridge.second.end()}); applyBridge(frontLayer, bestBridge); - break; } } - if (bestMethod == MappingMethod::SwapMethod) { + if (bestMethod == SwapMethod) { lastSwap = bestSwap; updateBlockedQubits(bestSwap); applySwap(bestSwap); @@ -1662,10 +1661,10 @@ MappingMethod NeutralAtomMapper::compareSwapAndBridge(const Swap& bestSwap, const Bridge& bestBridge) { if (bestBridge == Bridge()) { - return MappingMethod::SwapMethod; + return SwapMethod; } if (this->parameters->dynamicMappingWeight == 0) { - return MappingMethod::BridgeMethod; + return BridgeMethod; } // swap distance reduction qc::fp const swapDistReduction = @@ -1691,30 +1690,31 @@ NeutralAtomMapper::compareSwapAndBridge(const Swap& bestSwap, parameters->dynamicMappingWeight; const auto bridge = std::log(bridgeFidelity) / bridgeDistReduction; if (swap >= bridge) { - return MappingMethod::SwapMethod; + return SwapMethod; } - return MappingMethod::BridgeMethod; + return BridgeMethod; } MappingMethod NeutralAtomMapper::compareShuttlingAndFlyingAncilla( const MoveComb& bestMoveComb, const FlyingAncillaComb& bestFaComb, const PassByComb& bestPbComb) const { if (flyingAncillas.getNumQubits() == 0 && !parameters->usePassBy) { - return MappingMethod::MoveMethod; + return MoveMethod; } if (multiQubitGates) { - return MappingMethod::MoveMethod; + return MoveMethod; } // move distance reduction const auto moveDistReductionFront = moveCombDistanceReduction(bestMoveComb, this->frontLayerShuttling); - constexpr auto moveDistReductionLookAhead = 0.0; + auto moveDistReductionLookAhead = 0.0; if (!this->lookaheadLayerShuttling.empty()) { - (this->parameters->lookaheadWeightMoves * - moveCombDistanceReduction(bestMoveComb, this->lookaheadLayerShuttling) / - static_cast(this->lookaheadLayerShuttling.size())); + moveDistReductionLookAhead = + this->parameters->lookaheadWeightMoves * + moveCombDistanceReduction(bestMoveComb, this->lookaheadLayerShuttling) / + static_cast(this->lookaheadLayerShuttling.size()); } auto moveDistReduction = moveDistReductionFront + moveDistReductionLookAhead; // move @@ -1808,11 +1808,11 @@ MappingMethod NeutralAtomMapper::compareShuttlingAndFlyingAncilla( const auto passBy = std::log(passByFidelity) / pbDistReduction; if (move > fa && move > passBy) { - return MappingMethod::MoveMethod; + return MoveMethod; } if (fa > move && fa > passBy) { - return MappingMethod::FlyingAncillaMethod; + return FlyingAncillaMethod; } - return MappingMethod::PassByMethod; + return PassByMethod; } } // namespace na From 15e3c72964037bb2f85897e30d8af303b7811d7d Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 7 Nov 2025 13:54:21 +0100 Subject: [PATCH 252/394] =?UTF-8?q?=F0=9F=8E=A8=20reworked=20clang=20tidy?= =?UTF-8?q?=20tests.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/hybridmap/test_architecture.cpp | 6 ++-- test/hybridmap/test_hardware_qubits.cpp | 11 ++++-- test/hybridmap/test_hybrid_synthesis_map.cpp | 21 +++++------ test/hybridmap/test_hybridmap.cpp | 37 ++++++++++---------- test/hybridmap/test_mapping.cpp | 32 +++++++++-------- test/hybridmap/test_utils.cpp | 3 +- 6 files changed, 60 insertions(+), 50 deletions(-) diff --git a/test/hybridmap/test_architecture.cpp b/test/hybridmap/test_architecture.cpp index 2a8f252ef..675fae719 100644 --- a/test/hybridmap/test_architecture.cpp +++ b/test/hybridmap/test_architecture.cpp @@ -15,7 +15,7 @@ #include #include #include -#include +#include #include #include @@ -119,7 +119,8 @@ TEST(NeutralAtomArchitectureExceptions, TooManyQubitsThrows) { const auto tmp = std::filesystem::temp_directory_path() / "too_many_qubits.json"; // Minimal JSON: 1x1 positions but nQubits = 2 -> should throw - const char* content = R"JSON({ + { + const auto* content = R"JSON({ "name": "test", "properties": { "nRows": 1, @@ -135,7 +136,6 @@ TEST(NeutralAtomArchitectureExceptions, TooManyQubitsThrows) { "nQubits": 2 } })JSON"; - { std::ofstream ofs(tmp); ofs << content; } diff --git a/test/hybridmap/test_hardware_qubits.cpp b/test/hybridmap/test_hardware_qubits.cpp index 3098efc3a..7d5caee31 100644 --- a/test/hybridmap/test_hardware_qubits.cpp +++ b/test/hybridmap/test_hardware_qubits.cpp @@ -10,8 +10,13 @@ #include "hybridmap/HardwareQubits.hpp" #include "hybridmap/NeutralAtomArchitecture.hpp" +#include "hybridmap/NeutralAtomDefinitions.hpp" +#include "hybridmap/NeutralAtomUtils.hpp" +#include #include +#include +#include namespace { @@ -59,7 +64,7 @@ TEST(HardwareQubitsBehavior, RemoveHwQubitRemovesMappingsAndNeighbors) { EXPECT_THROW((void)hw.getCoordIndex(1), std::out_of_range); // Remaining qubits neighbor lists should not contain the removed qubit - for (na::HwQubit q : {0U, 2U}) { + for (na::HwQubit const q : {0U, 2U}) { const auto neighbors = hw.getNearbyQubits(q); EXPECT_TRUE(!neighbors.contains(1U)); } @@ -70,8 +75,8 @@ TEST(HardwareQubitsBehavior, RandomInitializationIsDeterministicPerSeed) { na::NeutralAtomArchitecture("architectures/rubidium_shuttling.json"); constexpr na::CoordIndex nQ = 4; - na::HardwareQubits hw(arch, nQ, na::InitialCoordinateMapping::Random, - /*seed*/ 0); + na::HardwareQubits const hw(arch, nQ, na::InitialCoordinateMapping::Random, + /*seed*/ 0); // All assigned coordinates are unique and within bounds std::set coords; diff --git a/test/hybridmap/test_hybrid_synthesis_map.cpp b/test/hybridmap/test_hybrid_synthesis_map.cpp index 6d55fc757..da1c432a0 100644 --- a/test/hybridmap/test_hybrid_synthesis_map.cpp +++ b/test/hybridmap/test_hybrid_synthesis_map.cpp @@ -15,6 +15,7 @@ #include "hybridmap/HybridSynthesisMapper.hpp" #include "hybridmap/NeutralAtomArchitecture.hpp" +#include "hybridmap/NeutralAtomUtils.hpp" #include "ir/QuantumComputation.hpp" #include @@ -24,7 +25,7 @@ namespace na { class TestParametrizedHybridSynthesisMapper - : public ::testing::TestWithParam { + : public testing::TestWithParam { protected: std::string testArchitecturePath = "architectures/"; std::vector circuits; @@ -47,7 +48,7 @@ class TestParametrizedHybridSynthesisMapper }; TEST_P(TestParametrizedHybridSynthesisMapper, AdjaencyMatrix) { - auto arch = NeutralAtomArchitecture(testArchitecturePath); + const auto arch = NeutralAtomArchitecture(testArchitecturePath); auto mapper = HybridSynthesisMapper(arch); mapper.initMapping(3); auto adjMatrix = mapper.getCircuitAdjacencyMatrix(); @@ -56,12 +57,12 @@ TEST_P(TestParametrizedHybridSynthesisMapper, AdjaencyMatrix) { } TEST_P(TestParametrizedHybridSynthesisMapper, EvaluateSynthesisStep) { - auto arch = NeutralAtomArchitecture(testArchitecturePath); + const auto arch = NeutralAtomArchitecture(testArchitecturePath); auto params = MapperParameters(); params.verbose = true; auto mapper = HybridSynthesisMapper(arch, params); mapper.initMapping(3); - auto best = mapper.evaluateSynthesisSteps(circuits, true); + const auto best = mapper.evaluateSynthesisSteps(circuits, true); EXPECT_EQ(best.size(), 2); EXPECT_GE(best[0], 0); EXPECT_GE(best[1], 0); @@ -72,7 +73,7 @@ INSTANTIATE_TEST_SUITE_P(HybridSynthesisMapperTestSuite, ::testing::Values("rubidium_gate", "rubidium_hybrid", "rubidium_shuttling")); -class TestHybridSynthesisMapper : public ::testing::Test { +class TestHybridSynthesisMapper : public testing::Test { protected: NeutralAtomArchitecture arch = NeutralAtomArchitecture("architectures/rubidium_gate.json"); @@ -91,7 +92,7 @@ class TestHybridSynthesisMapper : public ::testing::Test { TEST_F(TestHybridSynthesisMapper, DirectlyMap) { mapper.appendWithoutMapping(qc); - auto synthesizedQc = mapper.getSynthesizedQc(); + const auto synthesizedQc = mapper.getSynthesizedQc(); EXPECT_EQ(synthesizedQc.getNqubits(), 3); EXPECT_EQ(synthesizedQc.getNops(), 3); } @@ -99,19 +100,19 @@ TEST_F(TestHybridSynthesisMapper, DirectlyMap) { TEST_F(TestHybridSynthesisMapper, completelyRemap) { mapper.appendWithoutMapping(qc); mapper.appendWithoutMapping(qc); - auto mappedQc = mapper.getMappedQc(); + const auto mappedQc = mapper.getMappedQc(); EXPECT_EQ(mappedQc.getNqubitsWithoutAncillae(), arch.getNpositions()); EXPECT_GE(mappedQc.getNops(), 3); - mapper.completeRemap(InitialMapping::Identity); - auto mappedQcRemapped = mapper.getMappedQc(); + mapper.completeRemap(Identity); + const auto mappedQcRemapped = mapper.getMappedQc(); EXPECT_EQ(mappedQcRemapped.getNqubitsWithoutAncillae(), arch.getNpositions()); EXPECT_GE(mappedQcRemapped.getNops(), 3); } TEST_F(TestHybridSynthesisMapper, MapAppend) { mapper.appendWithMapping(qc); - auto synthesizedQc = mapper.getSynthesizedQc(); + const auto synthesizedQc = mapper.getSynthesizedQc(); EXPECT_EQ(synthesizedQc.getNqubits(), 3); EXPECT_GE(synthesizedQc.getNops(), 3); } diff --git a/test/hybridmap/test_hybridmap.cpp b/test/hybridmap/test_hybridmap.cpp index df950450c..96b0e9c55 100644 --- a/test/hybridmap/test_hybridmap.cpp +++ b/test/hybridmap/test_hybridmap.cpp @@ -19,13 +19,11 @@ #include #include #include +#include #include #include -// additional include for direct Mapping test -#include "hybridmap/Mapping.hpp" -class NeutralAtomArchitectureTest - : public ::testing::TestWithParam { +class NeutralAtomArchitectureTest : public testing::TestWithParam { protected: std::string testArchitecturePath = "architectures/"; @@ -61,7 +59,7 @@ INSTANTIATE_TEST_SUITE_P(NeutralAtomArchitectureTestSuite, class NeutralAtomMapperTestParams // parameters are architecture, circuit, gateWeight, shuttlingWeight, // lookAheadWeight, dynamicMappingWeight, initialCoordinateMapping - : public ::testing::TestWithParam< + : public testing::TestWithParam< std::tuple> { protected: @@ -79,7 +77,7 @@ class NeutralAtomMapperTestParams uint32_t seed = 42; void SetUp() override { - const auto params = GetParam(); + const auto& params = GetParam(); testArchitecturePath += std::get<0>(params) + ".json"; testQcPath += std::get<1>(params) + ".qasm"; gateWeight = std::get<2>(params); @@ -135,7 +133,7 @@ INSTANTIATE_TEST_SUITE_P( ::testing::Values(0.1), ::testing::Values(0), ::testing::Values(na::InitialCoordinateMapping::Trivial))); -class NeutralAtomMapperTest : public ::testing::Test { +class NeutralAtomMapperTest : public testing::Test { protected: std::string testArchitecturePath = "architectures/rubidium_shuttling.json"; const na::NeutralAtomArchitecture arch = @@ -168,28 +166,30 @@ class NeutralAtomMapperTest : public ::testing::Test { }; TEST_F(NeutralAtomMapperTest, Output) { - setvbuf(stdout, NULL, _IONBF, 0); auto qcMapped = mapper.map(qc, initialMapping); // write to file mapper.saveMappedQcQasm("test.qasm"); const auto qcMappedFromFile = mapper.getMappedQcQasm(); + EXPECT_GT(qcMappedFromFile.size(), 0); mapper.saveMappedQcAodQasm("test_aod.qasm"); const auto qcMappedAod = mapper.getMappedQcAodQasm(); + EXPECT_GT(qcMappedAod.size(), 0); - const auto MapperStats = mapper.getStats(); - EXPECT_GE(MapperStats.nSwaps + MapperStats.nBridges + MapperStats.nFAncillas + - MapperStats.nMoves + MapperStats.nPassBy, + const auto mapperStats = mapper.getStats(); + EXPECT_GE(mapperStats.nSwaps + mapperStats.nBridges + mapperStats.nFAncillas + + mapperStats.nMoves + mapperStats.nPassBy, 0); - const auto MapperStatsMap = mapper.getStatsMap(); - EXPECT_GE(MapperStatsMap.at("nSwaps") + MapperStatsMap.at("nBridges") + - MapperStatsMap.at("nFAncillas") + MapperStatsMap.at("nMoves") + - MapperStatsMap.at("nPassBy"), + const auto mapperStatsMap = mapper.getStatsMap(); + EXPECT_GE(mapperStatsMap.at("nSwaps") + mapperStatsMap.at("nBridges") + + mapperStatsMap.at("nFAncillas") + mapperStatsMap.at("nMoves") + + mapperStatsMap.at("nPassBy"), 0); const auto initHwPos = mapper.getInitHwPos(); EXPECT_EQ(initHwPos.size(), arch.getNqubits() - 1 /* flying ancilla */); const auto scheduleResults = mapper.schedule(true, true); const auto animationViz = mapper.getAnimationViz(); + EXPECT_GT(animationViz.size(), 0); mapper.saveAnimationFiles("test"); std::cout << scheduleResults.toCsv(); @@ -209,12 +209,12 @@ TEST(NeutralAtomMapperExceptions, NotEnoughQubitsForCircuitAndAncillas) { na::NeutralAtomMapper mapper(arch, p); // Circuit uses exactly all hardware qubits; +1 ancilla should trigger - qc::QuantumComputation qc1(static_cast(arch.getNqubits())); + qc::QuantumComputation qc1((arch.getNqubits())); EXPECT_THROW((void)mapper.map(qc1, na::InitialMapping::Identity), std::runtime_error); // Circuit bigger than architecture should throw - qc::QuantumComputation qc2(static_cast(arch.getNqubits() + 1)); + qc::QuantumComputation qc2(arch.getNqubits() + 1); EXPECT_THROW((void)mapper.map(qc2, na::InitialMapping::Identity), std::runtime_error); } @@ -241,7 +241,6 @@ TEST(NeutralAtomMapperExceptions, NoFreeCoordsForShuttlingConstructor) { EXPECT_THROW((void)na::NeutralAtomMapper(arch, p), std::runtime_error); na::MapperParameters p1 = p; - ; p1.shuttlingWeight = 0.0; // construct ok na::NeutralAtomMapper mapper(arch, p1); p1.shuttlingWeight = 0.5; // triggers setParameters check @@ -251,7 +250,7 @@ TEST(NeutralAtomMapperExceptions, NoMultiQubitSpace) { // Create minimal arch JSON: 1x1 positions, nQubits = 1 => no free coords const auto arch = na::NeutralAtomArchitecture("architectures/rubidium_gate.json"); - na::MapperParameters p; + constexpr na::MapperParameters p; na::NeutralAtomMapper mapper(arch, p); qc::QuantumComputation qc = qasm3::Importer::importf("circuits/multi_qubit.qasm"); diff --git a/test/hybridmap/test_mapping.cpp b/test/hybridmap/test_mapping.cpp index ffef0161e..1ff7a646c 100644 --- a/test/hybridmap/test_mapping.cpp +++ b/test/hybridmap/test_mapping.cpp @@ -8,10 +8,14 @@ * Licensed under the MIT License */ +#include "hybridmap/HardwareQubits.hpp" #include "hybridmap/Mapping.hpp" #include "hybridmap/NeutralAtomArchitecture.hpp" +#include "hybridmap/NeutralAtomUtils.hpp" +#include "ir/QuantumComputation.hpp" #include +#include // These tests focus only on exception behavior in Mapping (mapping.cpp/.hpp). @@ -19,9 +23,9 @@ TEST(MappingExceptions, CircuitExceedsHardwareThrows) { const auto arch = na::NeutralAtomArchitecture("architectures/rubidium_shuttling.json"); // Hardware has 1 available logical qubit, circuit needs 2 - na::HardwareQubits hw(arch, /*nQubits*/ 1, - na::InitialCoordinateMapping::Trivial, /*seed*/ 0); - qc::QuantumComputation qc(2); + na::HardwareQubits const hw( + arch, /*nQubits*/ 1, na::InitialCoordinateMapping::Trivial, /*seed*/ 0); + qc::QuantumComputation const qc(2); EXPECT_THROW((void)na::Mapping(2, na::InitialMapping::Identity, qc, hw), std::runtime_error); @@ -31,10 +35,10 @@ TEST(MappingExceptions, GetCircQubitThrowsIfHardwareNotMapped) { const auto arch = na::NeutralAtomArchitecture("architectures/rubidium_shuttling.json"); // Hardware has 4 logical spots, but circuit uses only 2 (identity mapping) - na::HardwareQubits hw(arch, /*nQubits*/ 4, - na::InitialCoordinateMapping::Trivial, /*seed*/ 0); - qc::QuantumComputation qc(2); - na::Mapping m(2, na::InitialMapping::Identity, qc, hw); + na::HardwareQubits const hw( + arch, /*nQubits*/ 4, na::InitialCoordinateMapping::Trivial, /*seed*/ 0); + qc::QuantumComputation const qc(2); + na::Mapping const m(2, na::InitialMapping::Identity, qc, hw); // hw qubits 0 and 1 are mapped; 2 and 3 are not -> getCircQubit(2) should // throw @@ -45,9 +49,9 @@ TEST(MappingExceptions, ApplySwapThrowsIfBothHardwareQubitsUnmapped) { const auto arch = na::NeutralAtomArchitecture("architectures/rubidium_shuttling.json"); // Hardware has 4, circuit maps only 2 via identity - na::HardwareQubits hw(arch, /*nQubits*/ 4, - na::InitialCoordinateMapping::Trivial, /*seed*/ 0); - qc::QuantumComputation qc(2); + na::HardwareQubits const hw( + arch, /*nQubits*/ 4, na::InitialCoordinateMapping::Trivial, /*seed*/ 0); + qc::QuantumComputation const qc(2); na::Mapping m(2, na::InitialMapping::Identity, qc, hw); // Swap two unmapped hardware qubits (2 and 3) -> should throw @@ -57,10 +61,10 @@ TEST(MappingExceptions, ApplySwapThrowsIfBothHardwareQubitsUnmapped) { TEST(MappingExceptions, GetHwQubitThrowsOutOfRangeForInvalidCircuitIndex) { const auto arch = na::NeutralAtomArchitecture("architectures/rubidium_shuttling.json"); - na::HardwareQubits hw(arch, /*nQubits*/ 2, - na::InitialCoordinateMapping::Trivial, /*seed*/ 0); - qc::QuantumComputation qc(2); - na::Mapping m(2, na::InitialMapping::Identity, qc, hw); + na::HardwareQubits const hw( + arch, /*nQubits*/ 2, na::InitialCoordinateMapping::Trivial, /*seed*/ 0); + qc::QuantumComputation const qc(2); + na::Mapping const m(2, na::InitialMapping::Identity, qc, hw); // Access circuit qubit index outside [0, nQubits) -> std::out_of_range EXPECT_THROW((void)m.getHwQubit(2), std::out_of_range); diff --git a/test/hybridmap/test_utils.cpp b/test/hybridmap/test_utils.cpp index d74efa65a..e5a4dbba4 100644 --- a/test/hybridmap/test_utils.cpp +++ b/test/hybridmap/test_utils.cpp @@ -12,6 +12,7 @@ #include "hybridmap/NeutralAtomUtils.hpp" #include +#include using namespace na; @@ -50,7 +51,7 @@ TEST(NeutralAtomUtils, MoveCombConstructorsAndEquality) { // vector-based constructor const MoveComb cvec({m1, m2}, /*cost*/ 1.23, /*op*/ nullptr, pos); EXPECT_FALSE(cvec.empty()); - EXPECT_EQ(cvec.moves.size(), 2u); + EXPECT_EQ(cvec.moves.size(), 2U); EXPECT_DOUBLE_EQ(cvec.cost, 1.23); EXPECT_EQ(cvec.op, nullptr); EXPECT_EQ(cvec.bestPos, pos); From 39bef0be68d21d982390f08eb20c6a0a61339f34 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 7 Nov 2025 14:13:24 +0100 Subject: [PATCH 253/394] =?UTF-8?q?=F0=9F=8E=A8=20reworked=20clang=20tidy?= =?UTF-8?q?=20warnings.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/NeutralAtomDefinitions.hpp | 4 ++-- src/hybridmap/NeutralAtomLayer.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/include/hybridmap/NeutralAtomDefinitions.hpp b/include/hybridmap/NeutralAtomDefinitions.hpp index 2b2300be4..d320903dd 100644 --- a/include/hybridmap/NeutralAtomDefinitions.hpp +++ b/include/hybridmap/NeutralAtomDefinitions.hpp @@ -77,8 +77,8 @@ using SwapDistance = int32_t; * load1/load2 specify load/unload actions (e.g., addressing focus). */ struct AtomMove { - CoordIndex c1; - CoordIndex c2; + CoordIndex c1 = 0; + CoordIndex c2 = 0; bool load1 = true; bool load2 = true; diff --git a/src/hybridmap/NeutralAtomLayer.cpp b/src/hybridmap/NeutralAtomLayer.cpp index da9889e0c..82ebb5a6c 100644 --- a/src/hybridmap/NeutralAtomLayer.cpp +++ b/src/hybridmap/NeutralAtomLayer.cpp @@ -158,8 +158,8 @@ bool commuteAtQubit(const qc::Operation* op1, const qc::Operation* op2, // for two-qubit gates, check if they commute at qubit // commute if both are controlled at qubit or const Operation* on qubit is // same check controls - if (op1->getControls().find(qubit) != op1->getControls().end() && - op2->getControls().find(qubit) != op2->getControls().end()) { + if (op1->getControls().contains(qubit) && + op2->getControls().contains(qubit)) { return true; } // control and Z also commute From d047a2ff0842277d5472e3295b56f1c0d5919973 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Tue, 11 Nov 2025 15:51:32 +0100 Subject: [PATCH 254/394] =?UTF-8?q?=F0=9F=90=8D=20Update=20Python=20bindin?= =?UTF-8?q?gs=20and=20tests.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bindings/hybrid_mapper/hybrid_mapper.cpp | 16 ++++++---- python/mqt/qmap/hybrid_mapper.pyi | 3 +- .../hybrid_mapper/test_hybrid_mapper.py | 30 +++++++++++-------- 3 files changed, 29 insertions(+), 20 deletions(-) diff --git a/bindings/hybrid_mapper/hybrid_mapper.cpp b/bindings/hybrid_mapper/hybrid_mapper.cpp index a019e8df8..e98a523c9 100644 --- a/bindings/hybrid_mapper/hybrid_mapper.cpp +++ b/bindings/hybrid_mapper/hybrid_mapper.cpp @@ -176,9 +176,14 @@ PYBIND11_MODULE(MQT_QMAP_MODULE_NAME, m, py::mod_gil_not_used()) { .def( "get_init_hw_pos", &na::NeutralAtomMapper::getInitHwPos, "Get the initial hardware positions, required to create an animation") - .def("map", &na::NeutralAtomMapper::mapWithoutReturn, - "Map a quantum circuit to the neutral atom quantum computer", - "circ"_a, "initial_mapping"_a = na::InitialMapping::Identity) + .def( + "map", + [](na::NeutralAtomMapper& mapper, qc::QuantumComputation& circ, + const na::InitialMapping initial_mapping) { + mapper.map(circ, initial_mapping); + }, + "Map a quantum circuit object to the neutral atom quantum computer", + "qc"_a, "initial_mapping"_a = na::InitialMapping::Identity) .def( "map_qasm_file", [](na::NeutralAtomMapper& mapper, const std::string& filename, @@ -191,11 +196,10 @@ PYBIND11_MODULE(MQT_QMAP_MODULE_NAME, m, py::mod_gil_not_used()) { .def("get_stats", &na::NeutralAtomMapper::getStatsMap, "Returns the statistics of the mapping") .def("get_mapped_qc", &na::NeutralAtomMapper::getMappedQc, - "Returns the mapped circuit as an extended qasm2 string") + "Returns the mapped circuit") .def("get_mapped_qc_qasm", &na::NeutralAtomMapper::getMappedQcQasm, "Returns the mapped circuit as an extended qasm2 string") - .def("save_mapped_qc_aod_qasm", - &na::NeutralAtomMapper::getMappedQcAodQasm, + .def("get_mapped_qc_aod_qasm", &na::NeutralAtomMapper::getMappedQcAodQasm, "Returns the mapped circuit with AOD operations as an extended " "qasm2 string") .def("save_mapped_qc_aod_qasm", diff --git a/python/mqt/qmap/hybrid_mapper.pyi b/python/mqt/qmap/hybrid_mapper.pyi index 9a376eefe..75d6c43f3 100644 --- a/python/mqt/qmap/hybrid_mapper.pyi +++ b/python/mqt/qmap/hybrid_mapper.pyi @@ -84,11 +84,12 @@ class HybridNAMapper: def get_init_hw_pos(self) -> dict[int, int]: ... def get_mapped_qc(self) -> QuantumComputation: ... def get_mapped_qc_qasm(self) -> str: ... + def get_mapped_qc_aod_qasm(self) -> str: ... def get_stats(self) -> dict[str, float]: ... def map(self, circ: QuantumComputation, initial_mapping: InitialCircuitMapping = ...) -> None: ... def map_qasm_file(self, filename: str, initial_mapping: InitialCircuitMapping = ...) -> None: ... def save_animation_files(self, filename: str) -> None: ... - def save_mapped_qc_aod_qasm(self) -> str: ... + def save_mapped_qc_aod_qasm(self, filename: str) -> None: ... def schedule( self, verbose: bool = ..., diff --git a/test/python/hybrid_mapper/test_hybrid_mapper.py b/test/python/hybrid_mapper/test_hybrid_mapper.py index dd598a0a6..55005d40f 100644 --- a/test/python/hybrid_mapper/test_hybrid_mapper.py +++ b/test/python/hybrid_mapper/test_hybrid_mapper.py @@ -15,7 +15,7 @@ import pytest from mqt.core import load -from mqt.qmap.hybrid_mapper import HybridMapperParameters, HybridNAMapper, NeutralAtomHybridArchitecture +from mqt.qmap.hybrid_mapper import HybridNAMapper, MapperParameters, NeutralAtomHybridArchitecture arch_dir = Path(__file__).parent.parent.parent / "hybridmap" / "architectures" circuit_dir = Path(__file__).parent.parent.parent / "hybridmap" / "circuits" @@ -34,7 +34,7 @@ @pytest.mark.parametrize( "arch_filename", [ - "rubidium.json", + "rubidium_gate.json", "rubidium_hybrid.json", "rubidium_shuttling.json", ], @@ -48,18 +48,20 @@ def test_hybrid_na_mapper( """Test the hybrid Neutral Atom mapper.""" arch = NeutralAtomHybridArchitecture(str(arch_dir / arch_filename)) - params = HybridMapperParameters( - lookahead_weight_moves=lookahead_weight, - lookahead_weight_swaps=lookahead_weight, - decay=decay, - gate_weight=gate_shuttling_weight, - ) + params = MapperParameters() + params.lookahead_weight_moves = lookahead_weight + params.lookahead_weight_swaps = lookahead_weight + params.decay = decay + params.gate_weight = gate_shuttling_weight mapper = HybridNAMapper(arch, params=params) - qc = load(circuit_dir / circuit_filename) - - mapper.map(qc) + # Map directly from QASM file using the pybind-exposed convenience method + mapper.map_qasm_file(str(circuit_dir / circuit_filename)) results = mapper.schedule(create_animation_csv=False) + # Validate QASM exports (mapped abstract and AOD-annotated) + mapped_qasm = mapper.get_mapped_qc_qasm() + # AOD QASM retrieval currently not exposed in Python API; just sanity check mapped_qasm + assert mapped_qasm.strip() assert results["totalExecutionTime"] > 0 assert results["totalIdleTime"] > 0 @@ -68,8 +70,8 @@ def test_hybrid_na_mapper( def _nested_mapper_create() -> HybridNAMapper: """Create a nested Neutral Atom hybrid architecture.""" - arch = NeutralAtomHybridArchitecture(str(arch_dir / "rubidium.json")) - params = HybridMapperParameters() + arch = NeutralAtomHybridArchitecture(str(arch_dir / "rubidium_gate.json")) + params = MapperParameters() return HybridNAMapper(arch, params=params) @@ -81,6 +83,8 @@ def test_keep_alive() -> None: mapper.map(qc) results = mapper.schedule(create_animation_csv=False) + mapped_qasm = mapper.get_mapped_qc_qasm() + assert mapped_qasm.strip() assert results["totalExecutionTime"] > 0 assert results["totalIdleTime"] > 0 From 1d39661f4c42c409ed1fe9db217be0077e6b5f40 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 13 Nov 2025 09:55:13 +0100 Subject: [PATCH 255/394] =?UTF-8?q?=F0=9F=90=8D=20Moved=20hybrid=20synthes?= =?UTF-8?q?is=20mapper=20test=20files.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../test_hybrid_synthesis.py | 39 ++++++++++++------- 1 file changed, 25 insertions(+), 14 deletions(-) rename test/python/{ => hybrid_mapper}/test_hybrid_synthesis.py (80%) diff --git a/test/python/test_hybrid_synthesis.py b/test/python/hybrid_mapper/test_hybrid_synthesis.py similarity index 80% rename from test/python/test_hybrid_synthesis.py rename to test/python/hybrid_mapper/test_hybrid_synthesis.py index 488a69eb0..5dbf5b74e 100644 --- a/test/python/test_hybrid_synthesis.py +++ b/test/python/hybrid_mapper/test_hybrid_synthesis.py @@ -1,3 +1,11 @@ +# 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 + """Test the hybrid Neutral Atom synthesis mapping.""" from __future__ import annotations @@ -6,27 +14,30 @@ import numpy as np import pytest +from mqt.core import load from qiskit import QuantumCircuit -from mqt.qmap import HybridSynthesisMapper, NeutralAtomHybridArchitecture +from mqt.qmap.hybrid_mapper import HybridSynthesisMapper, NeutralAtomHybridArchitecture -arch_dir = Path(__file__).parent.parent / "hybridmap" / "architectures" -circuit_dir = Path(__file__).parent.parent / "hybridmap" / "circuits" +arch_dir = Path(__file__).parent.parent.parent / "hybridmap" / "architectures" +circuit_dir = Path(__file__).parent.parent.parent / "hybridmap" / "circuits" -qc1 = QuantumCircuit(3) -qc1.h(0) -qc1.cx(0, 1) -qc1.cx(1, 2) +qc1_qiskit = QuantumCircuit(3) +qc1_qiskit.h(0) +qc1_qiskit.cx(0, 1) +qc1_qiskit.cx(1, 2) +qc1 = load(qc1_qiskit) -qc2 = QuantumCircuit(3) -qc2.cx(0, 2) -qc2.cx(1, 2) +qc2_qiskit = QuantumCircuit(3) +qc2_qiskit.cx(0, 2) +qc2_qiskit.cx(1, 2) +qc2 = load(qc2_qiskit) @pytest.mark.parametrize( "arch_filename", [ - "rubidium.json", + "rubidium_gate.json", "rubidium_hybrid.json", "rubidium_shuttling.json", ], @@ -45,7 +56,7 @@ def test_hybrid_synthesis(arch_filename: str) -> None: @pytest.mark.parametrize( "arch_filename", [ - "rubidium.json", + "rubidium_gate.json", "rubidium_hybrid.json", "rubidium_shuttling.json", ], @@ -81,7 +92,7 @@ def test_hybrid_synthesis_input_output(arch_filename: str) -> None: def test_adjacency_matrix() -> None: """Test the adjacency matrix of the hybrid Neutral Atom synthesis mapper.""" - arch = NeutralAtomHybridArchitecture(str(arch_dir / "rubidium.json")) + arch = NeutralAtomHybridArchitecture(str(arch_dir / "rubidium_gate.json")) synthesis_mapper = HybridSynthesisMapper(arch) circ_size = 3 synthesis_mapper.init_mapping(circ_size) @@ -109,6 +120,6 @@ def help_create_mapper(arch_filename: str) -> HybridSynthesisMapper: def test_keep_alive() -> None: """Test the keep alive functionality of the hybrid Neutral Atom synthesis mapper.""" - synthesis_mapper = help_create_mapper("rubidium.json") + synthesis_mapper = help_create_mapper("rubidium_gate.json") synthesis_mapper.append_with_mapping(qc1) _ = synthesis_mapper.get_circuit_adjacency_matrix() From e38d2093f86d8dfacdbac8a7ab2184d14246c9b2 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 13 Nov 2025 09:59:17 +0100 Subject: [PATCH 256/394] =?UTF-8?q?=F0=9F=90=8D=20update=20Python=20bindin?= =?UTF-8?q?gs=20for=20HybridMapper.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bindings/hybrid_mapper/hybrid_mapper.cpp | 95 ++++++++++++++++++++++++ python/mqt/qmap/hybrid_mapper.pyi | 23 ++++++ 2 files changed, 118 insertions(+) diff --git a/bindings/hybrid_mapper/hybrid_mapper.cpp b/bindings/hybrid_mapper/hybrid_mapper.cpp index e98a523c9..ae4e9698c 100644 --- a/bindings/hybrid_mapper/hybrid_mapper.cpp +++ b/bindings/hybrid_mapper/hybrid_mapper.cpp @@ -9,6 +9,7 @@ */ #include "hybridmap/HybridNeutralAtomMapper.hpp" +#include "hybridmap/HybridSynthesisMapper.hpp" #include "hybridmap/NeutralAtomArchitecture.hpp" #include "hybridmap/NeutralAtomScheduler.hpp" #include "hybridmap/NeutralAtomUtils.hpp" @@ -223,4 +224,98 @@ PYBIND11_MODULE(MQT_QMAP_MODULE_NAME, m, py::mod_gil_not_used()) { "filename"_a) .def("get_animation_viz", &na::NeutralAtomMapper::getAnimationViz, "Returns the animation csv string for the last scheduling"); + + py::class_( + m, "HybridSynthesisMapper", + "Neutral Atom Mapper that can evaluate different synthesis steps " + "to choose the best one.") + .def(py::init(), + "Create Hybrid Synthesis Mapper with mapper parameters", + py::keep_alive<1, 2>(), py::keep_alive<1, 3>(), "arch"_a, + "params"_a = na::MapperParameters()) + .def("set_parameters", &na::HybridSynthesisMapper::setParameters, + "Set the parameters for the Hybrid Synthesis Mapper", "params"_a) + .def("init_mapping", &na::HybridSynthesisMapper::initMapping, + "Initializes the synthesized and mapped circuits and mapping " + "structures for the given number of qubits.", + "n_qubits"_a) + .def("get_mapped_qc", &na::HybridSynthesisMapper::getMappedQcQasm, + "Returns the mapped QuantumComputation") + .def("save_mapped_qc", &na::HybridSynthesisMapper::saveMappedQcQasm, + "Saves the mapped QuantumComputation to a file", "filename"_a) + .def( + "convert_to_aod", &na::HybridSynthesisMapper::convertToAod, + "Converts the mapped QuantumComputation to a QuantumComputation with " + "native AOD movements") + .def("get_mapped_qc_aod", &na::HybridSynthesisMapper::getMappedQcAodQasm, + "Returns the mapped QuantumComputation with native AOD movements") + .def("save_mapped_qc_aod", + &na::HybridSynthesisMapper::saveMappedQcAodQasm, + "Saves the mapped QuantumComputation with native AOD movements to a " + "file", + "filename"_a) + .def("get_synthesized_qc", + &na::HybridSynthesisMapper::getSynthesizedQcQASM, + "Returns the synthesized QuantumComputation with all gates but not " + "mapped to the hardware.") + .def("save_synthesized_qc", &na::HybridSynthesisMapper::saveSynthesizedQc, + "Saves the synthesized QuantumComputation with all gates but not " + "mapped to the hardware to a file", + "filename"_a) + .def( + "append_without_mapping", + [](na::HybridSynthesisMapper& mapper, qc::QuantumComputation& qc) { + mapper.appendWithoutMapping(qc); + }, + "Appends the given QuantumComputation to the synthesized " + "QuantumComputation without mapping it to the hardware.", + "qc"_a) + .def( + "append_with_mapping", + [](na::HybridSynthesisMapper& mapper, qc::QuantumComputation& qc) { + mapper.appendWithMapping(qc); + }, + "Appends the given QuantumComputation to the synthesized " + "QuantumComputation and maps the gates to the hardware.", + "qc"_a) + .def( + "get_circuit_adjacency_matrix", + [](na::HybridSynthesisMapper& mapper) { + const auto symAdjMatrix = mapper.getCircuitAdjacencyMatrix(); + std::vector> adjMatrix = {}; + for (size_t i = 0; i < symAdjMatrix.size(); ++i) { + adjMatrix.emplace_back(); + for (size_t j = 0; j < symAdjMatrix.size(); ++j) { + adjMatrix[i].emplace_back(symAdjMatrix(i, j)); + } + } + return adjMatrix; + }, + "Returns the current adjacency matrix of the neutral atom hardware.") + .def( + "evaluate_synthesis_steps", + [](na::HybridSynthesisMapper& mapper, + std::vector& qcs, bool alsoMap) { + for (const auto& qc : qcs) { + qcs.push_back(qc); + } + return mapper.evaluateSynthesisSteps(qcs, alsoMap); + }, + "Evaluates the synthesis steps proposed by the ZX extraction. " + "Returns a list of fidelities of the mapped synthesis steps.", + "synthesis_steps"_a, "also_map"_a = false) + .def("complete_remap", &na::HybridSynthesisMapper::completeRemap, + "Remaps the synthesized QuantumComputation to the hardware.", + "initial_mapping"_a = na::InitialMapping::Identity) + .def( + "schedule", + [](na::HybridSynthesisMapper& mapper, bool verbose, + bool create_animation_csv, double shuttling_speed_factor) { + auto results = mapper.schedule(verbose, create_animation_csv, + shuttling_speed_factor); + return results.toMap(); + }, + "Schedule the mapped circuit", "verbose"_a = false, + "create_animation_csv"_a = false, "shuttling_speed_factor"_a = 1.0); } diff --git a/python/mqt/qmap/hybrid_mapper.pyi b/python/mqt/qmap/hybrid_mapper.pyi index 75d6c43f3..641b54d91 100644 --- a/python/mqt/qmap/hybrid_mapper.pyi +++ b/python/mqt/qmap/hybrid_mapper.pyi @@ -97,3 +97,26 @@ class HybridNAMapper: shuttling_speed_factor: typing.SupportsFloat = ..., ) -> dict[str, float]: ... def set_parameters(self, params: MapperParameters) -> None: ... + +# noinspection DuplicatedCode +class HybridSynthesisMapper: + def __init__(self, arch: NeutralAtomHybridArchitecture, params: MapperParameters = ...) -> None: ... + def append_with_mapping(self, qc: QuantumComputation) -> None: ... + def append_without_mapping(self, qc: QuantumComputation) -> None: ... + def complete_remap(self, initial_mapping: InitialCircuitMapping = ...) -> None: ... + def convert_to_aod(self) -> None: ... + def evaluate_synthesis_steps( + self, synthesis_steps: list[QuantumComputation], also_map: bool = ... + ) -> list[float]: ... + def get_circuit_adjacency_matrix(self) -> list[list[int]]: ... + def get_mapped_qc(self) -> str: ... + def get_mapped_qc_aod(self) -> str: ... + def get_synthesized_qc(self) -> str: ... + def init_mapping(self, n_qubits: typing.SupportsInt) -> None: ... + def save_mapped_qc(self, filename: str) -> None: ... + def save_mapped_qc_aod(self, filename: str) -> None: ... + def save_synthesized_qc(self, filename: str) -> None: ... + def schedule( + self, verbose: bool = ..., create_animation_csv: bool = ..., shuttling_speed_factor: typing.SupportsFloat = ... + ) -> dict[str, float]: ... + def set_parameters(self, params: MapperParameters) -> None: ... From a9db8ba7818d0cbc359a70bc0992446a61d08edb Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Mon, 17 Nov 2025 18:11:29 +0100 Subject: [PATCH 257/394] =?UTF-8?q?=F0=9F=90=9B=20added=20missing=20reset?= =?UTF-8?q?=20of=20the=20mapper=20stats.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridNeutralAtomMapper.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index 86a686482..e2aef5ee5 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -553,6 +553,7 @@ class NeutralAtomMapper { */ qc::QuantumComputation map(qc::QuantumComputation& qc, const Mapping& initialMapping) { + stats = MapperStats(); mappedQc = qc::QuantumComputation(arch->getNpositions()); mappedQcAOD = qc::QuantumComputation(arch->getNpositions()); mapAppend(qc, initialMapping); From 357e40af86608c2df4db37495a3bc49a2a50ca6b Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Mon, 17 Nov 2025 18:46:35 +0100 Subject: [PATCH 258/394] =?UTF-8?q?=F0=9F=90=9B=20added=20"display"=20to?= =?UTF-8?q?=20default=20naviz=20style?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/default_style.hpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/include/hybridmap/default_style.hpp b/include/hybridmap/default_style.hpp index 3a6f25677..8b56ee92c 100644 --- a/include/hybridmap/default_style.hpp +++ b/include/hybridmap/default_style.hpp @@ -1,3 +1,13 @@ +/* + * 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 inline const char* defaultStyle = "name: \"hybrid\"\n\natom {\n trapped {\n color: " @@ -36,7 +46,8 @@ inline const char* defaultStyle = "20\n y: 20\n color: #999999\n line " "{\n thickness: 0.0\n dash " "{\n length: 0\n " - " duty: 100%\n }\n }\n }\n number " + " duty: 100%\n }\n }\n display: " + "true\n }\n number " "{\n x {\n distance: 40\n " " position: bottom\n }\n y {\n " " distance: 40\n position: left\n " From 633d0edbf4dd97e7c3d8c1cecd5b65cb8ca9ca67 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Wed, 19 Nov 2025 12:49:40 +0100 Subject: [PATCH 259/394] =?UTF-8?q?=F0=9F=90=9B=20avoid=20loading=20if=20n?= =?UTF-8?q?o=20flying=20ancillas=20configured.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/MoveToAodConverter.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/hybridmap/MoveToAodConverter.cpp b/src/hybridmap/MoveToAodConverter.cpp index 72d0d0e6c..e5cb69324 100644 --- a/src/hybridmap/MoveToAodConverter.cpp +++ b/src/hybridmap/MoveToAodConverter.cpp @@ -81,6 +81,9 @@ AtomMove MoveToAodConverter::convertOpToMove(qc::Operation* get) const { return {.c1 = q1, .c2 = q2, .load1 = load1, .load2 = load2}; } void MoveToAodConverter::initFlyingAncillas() { + if (ancillas.empty()) { + return; + } std::vector coords; std::vector dirs; std::vector starts; From 3e539b5610027ba57a7ca075530482fbea3406dc Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Wed, 19 Nov 2025 13:21:41 +0100 Subject: [PATCH 260/394] =?UTF-8?q?=F0=9F=90=9B=20fixed=20aod=20move=20spe?= =?UTF-8?q?edup=20in=20animation.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/NeutralAtomArchitecture.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hybridmap/NeutralAtomArchitecture.cpp b/src/hybridmap/NeutralAtomArchitecture.cpp index 2d87177ba..e3130ce14 100644 --- a/src/hybridmap/NeutralAtomArchitecture.cpp +++ b/src/hybridmap/NeutralAtomArchitecture.cpp @@ -262,7 +262,7 @@ std::string NeutralAtomArchitecture::getAnimationMachine( animationMachine += "movement {\n\tmax_speed: " + - std::to_string(this->getShuttlingTime(qc::OpType::AodMove) * + std::to_string(this->getShuttlingTime(qc::OpType::AodMove) / shuttlingSpeedFactor) + "\n}\n"; From f19cb73642b952aab412560ff4a922a08b1479a5 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Wed, 19 Nov 2025 13:31:47 +0100 Subject: [PATCH 261/394] =?UTF-8?q?=F0=9F=94=A5removed=20hybrid-specific?= =?UTF-8?q?=20animation=20style.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/NeutralAtomArchitecture.hpp | 48 ------------- include/hybridmap/NeutralAtomScheduler.hpp | 12 ---- include/hybridmap/default_style.hpp | 67 ------------------- src/hybridmap/NeutralAtomScheduler.cpp | 1 - test/hybridmap/test_architecture.cpp | 11 --- 5 files changed, 139 deletions(-) delete mode 100644 include/hybridmap/default_style.hpp diff --git a/include/hybridmap/NeutralAtomArchitecture.hpp b/include/hybridmap/NeutralAtomArchitecture.hpp index fbd700ed0..f5eade984 100644 --- a/include/hybridmap/NeutralAtomArchitecture.hpp +++ b/include/hybridmap/NeutralAtomArchitecture.hpp @@ -14,7 +14,6 @@ #include "datastructures/SymmetricMatrix.hpp" #include "hybridmap/NeutralAtomDefinitions.hpp" #include "hybridmap/NeutralAtomUtils.hpp" -#include "hybridmap/default_style.hpp" #include "ir/Definitions.hpp" #include "ir/QuantumComputation.hpp" #include "ir/operations/OpType.hpp" @@ -580,40 +579,6 @@ class NeutralAtomArchitecture { [[nodiscard]] std::string getAnimationMachine(qc::fp shuttlingSpeedFactor) const; - /** - * @brief Generate the animation style content. - * @param stylePath Optional path to a style file overriding defaults. - * @return Text content for the style (.nastyle). - * @note Falls back to compiled-in defaults if the file cannot be opened. - */ - [[nodiscard]] std::string - getAnimationStyle(const std::string& stylePath) const { - std::string style(defaultStyle); - if (!stylePath.empty()) { - std::ifstream file(stylePath); - if (file.is_open()) { - std::string line; - while (std::getline(file, line)) { - style += line + "\n"; - } - file.close(); - } else { - std::cerr << "Could not open file " << stylePath - << "! Using default style.\n"; - } - } - const std::string toReplace = "XXX"; - const std::string replaceWith = - std::to_string(getInteractionRadius() * getInterQubitDistance()); - - size_t pos = 0; - while ((pos = style.find(toReplace, pos)) != std::string::npos) { - style.replace(pos, toReplace.length(), replaceWith); - pos += replaceWith.length(); - } - return style; - } - /** * @brief Save the device animation CSV to a file. * @param filename Output CSV filename. @@ -625,19 +590,6 @@ class NeutralAtomArchitecture { std::ofstream file(filename); file << getAnimationMachine(shuttlingSpeedFactor); } - - /** - * @brief Save the animation style content to a file. - * @param filename Output style filename. - * @param stylePath Optional path to a base style to load before applying - * defaults. - */ - [[maybe_unused]] void - saveAnimationStyle(const std::string& filename, - const std::string& stylePath = "") const { - std::ofstream file(filename); - file << getAnimationStyle(stylePath); - } }; } // namespace na diff --git a/include/hybridmap/NeutralAtomScheduler.hpp b/include/hybridmap/NeutralAtomScheduler.hpp index 519e479ba..6b75cf004 100644 --- a/include/hybridmap/NeutralAtomScheduler.hpp +++ b/include/hybridmap/NeutralAtomScheduler.hpp @@ -104,7 +104,6 @@ class NeutralAtomScheduler { const NeutralAtomArchitecture* arch = nullptr; std::string animation; std::string animationMachine; - std::string animationStyle; public: /** @@ -155,12 +154,6 @@ class NeutralAtomScheduler { * @note Populated only if schedule() ran with createAnimationCsv=true. */ [[nodiscard]] std::string getAnimationViz() const { return animation; } - /** - * @brief Retrieve visualization style sheet (.nastyle content). - * @return Style sheet string. - * @note Populated only if schedule() ran with createAnimationCsv=true. - */ - [[nodiscard]] std::string getAnimationStyle() const { return animationStyle; } /** * @brief Write animation artifacts (.naviz/.namachine/.nastyle) to disk. @@ -174,7 +167,6 @@ class NeutralAtomScheduler { filename.substr(0, filename.find_last_of('.')); const auto filenameViz = filenameWithoutExtension + ".naviz"; const auto filenameMachine = filenameWithoutExtension + ".namachine"; - const auto filenameStyle = filenameWithoutExtension + ".nastyle"; // save animation auto file = std::ofstream(filenameViz); @@ -184,10 +176,6 @@ class NeutralAtomScheduler { file.open(filenameMachine); file << getAnimationMachine(); file.close(); - // save style - file.open(filenameStyle); - file << getAnimationStyle(); - file.close(); } // Helper Print functions diff --git a/include/hybridmap/default_style.hpp b/include/hybridmap/default_style.hpp deleted file mode 100644 index 8b56ee92c..000000000 --- a/include/hybridmap/default_style.hpp +++ /dev/null @@ -1,67 +0,0 @@ -/* - * 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 -inline const char* defaultStyle = - "name: \"hybrid\"\n\natom {\n trapped {\n color: " - "#a2ad00\n }\n shuttling {\n color: #005293\n " - "}\n legend {\n name {\n " - "^atom(.*)$: \"$1\"\n ^.*$: \"$0\"\n " - "}\n font {\n family: \"DejaVu " - "Sans\"\n size: 3\n color: " - "#000000\n }\n }\n radius: 2\n}\n\nzone {\n config " - "^zone_cz.*$ {\n color: #e37222\n line " - "{\n thickness: 1\n dash " - "{\n length: 0\n " - " duty: 100%\n }\n }\n name: " - "\"CZ\"\n }\n config ^zone.*$ {\n color: " - "#64a0c8\n line {\n thickness: 1\n " - " dash {\n length: 0\n " - " duty: 100%\n }\n " - "}\n name: \"\"\n }\n legend {\n " - "display: true\n title: \"Zones\"\n }\n}\n\noperation " - "{\n config {\n ry {\n color: " - "#98c6ea\n name: \"RY\"\n radius: " - "150%\n }\n rz {\n color: " - "#64a0c8\n name: \"RZ\"\n radius: " - "150%\n }\n cz {\n color: " - "#e37222\n name: \"CZ\"\n radius: " - "XXX\n }\n }\n legend {\n display: " - "true\n title: \"Gates\"\n }\n}\n\nmachine {\n trap " - "{\n color: #999999\n radius: 2\n " - " line_width: 0.5\n name: \"SLM\"\n }\n shuttle " - "{\n color: #999999\n line {\n " - " thickness: 0.5\n dash {\n " - " length: 5\n duty: " - "50%\n }\n }\n name: " - "\"AOD\"\n }\n legend {\n display: true\n title: " - "\"Atom States\"\n }\n}\n\ncoordinate {\n tick {\n x: " - "20\n y: 20\n color: #999999\n line " - "{\n thickness: 0.0\n dash " - "{\n length: 0\n " - " duty: 100%\n }\n }\n display: " - "true\n }\n number " - "{\n x {\n distance: 40\n " - " position: bottom\n }\n y {\n " - " distance: 40\n position: left\n " - "}\n display: true\n font {\n " - " family: \"DajaVu Sans\"\n size: 8\n " - " color: #000000\n }\n }\n axis " - "{\n x: \"x\"\n y: \"y\"\n " - "display: true\n font {\n family: " - "\"DajaVu Sans\"\n size: 8\n color: " - "#000000\n }\n }\n margin: 20\n}\n\nsidebar {\n font " - "{\n family: \"DejaVu Sans\"\n size: " - "32\n color: #000000\n }\n margin: 8\n " - "padding {\n color: 16\n heading: 52\n " - " entry: 45\n }\n color_radius: 16\n}\n\ntime {\n display: " - "true\n prefix: \"Time: \"\n precision: 1\n font {\n " - " family: \"DejaVu Sans\"\n size: 12\n color: " - "#000000\n }\n}\n\nviewport {\n margin: 4\n color: #ffffff\n}\n"; diff --git a/src/hybridmap/NeutralAtomScheduler.cpp b/src/hybridmap/NeutralAtomScheduler.cpp index 2e39bbc29..dcb12b32e 100644 --- a/src/hybridmap/NeutralAtomScheduler.cpp +++ b/src/hybridmap/NeutralAtomScheduler.cpp @@ -52,7 +52,6 @@ na::SchedulerResults na::NeutralAtomScheduler::schedule( if (createAnimationCsv) { animation += animationAtoms.placeInitAtoms(); animationMachine = arch->getAnimationMachine(shuttlingSpeedFactor); - animationStyle = arch->getAnimationStyle(""); } int index = 0; diff --git a/test/hybridmap/test_architecture.cpp b/test/hybridmap/test_architecture.cpp index 675fae719..e0c1d36db 100644 --- a/test/hybridmap/test_architecture.cpp +++ b/test/hybridmap/test_architecture.cpp @@ -61,27 +61,16 @@ TEST(NeutralAtomArchitectureMethods, AnimationAPIsProduceContent) { EXPECT_NE(machine.find("zone hybrid"), std::string::npos); EXPECT_NE(machine.find("trap"), std::string::npos); - const auto invalidStyle = arch.getAnimationStyle("invalid_file_path.style"); - EXPECT_FALSE(invalidStyle.empty()); - const auto style = arch.getAnimationStyle(""); - EXPECT_GT(style.size(), 0U); - // Save to temp files const std::filesystem::path tmpMachine = std::filesystem::temp_directory_path() / "arch_anim_machine.csv"; - const std::filesystem::path tmpStyle = - std::filesystem::temp_directory_path() / "arch_anim_style.css"; arch.saveAnimationMachine(tmpMachine.string(), 1.0); - arch.saveAnimationStyle(tmpStyle.string()); // Verify files exist and are non-empty ASSERT_TRUE(std::filesystem::exists(tmpMachine)); - ASSERT_TRUE(std::filesystem::exists(tmpStyle)); EXPECT_GT(std::filesystem::file_size(tmpMachine), 0U); - EXPECT_GT(std::filesystem::file_size(tmpStyle), 0U); // Cleanup best-effort std::error_code ec; std::filesystem::remove(tmpMachine, ec); - std::filesystem::remove(tmpStyle, ec); } TEST(NeutralAtomArchitectureMethods, BasicCountsAndOffsetDistance) { From 8c2f0b80af5b13ea0ea3f05454e2d800e35028e5 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Wed, 19 Nov 2025 15:15:52 +0100 Subject: [PATCH 262/394] =?UTF-8?q?=F0=9F=90=9Bavoid=20duplication=20of=20?= =?UTF-8?q?circuits=20in=20python=20binding=20for=20SynthesisMapper.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bindings/hybrid_mapper/hybrid_mapper.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/bindings/hybrid_mapper/hybrid_mapper.cpp b/bindings/hybrid_mapper/hybrid_mapper.cpp index ae4e9698c..d9c653516 100644 --- a/bindings/hybrid_mapper/hybrid_mapper.cpp +++ b/bindings/hybrid_mapper/hybrid_mapper.cpp @@ -297,9 +297,6 @@ PYBIND11_MODULE(MQT_QMAP_MODULE_NAME, m, py::mod_gil_not_used()) { "evaluate_synthesis_steps", [](na::HybridSynthesisMapper& mapper, std::vector& qcs, bool alsoMap) { - for (const auto& qc : qcs) { - qcs.push_back(qc); - } return mapper.evaluateSynthesisSteps(qcs, alsoMap); }, "Evaluates the synthesis steps proposed by the ZX extraction. " From 873274e84f04e4954ce0c95a8d37248f8d46f051 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Wed, 19 Nov 2025 15:18:57 +0100 Subject: [PATCH 263/394] =?UTF-8?q?=F0=9F=90=9Bavoid=20adding=20ancilla=20?= =?UTF-8?q?register=20multiple=20times=20for=20HybridSynthesisMapper.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridNeutralAtomMapper.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index c38fa32fd..bd0a688e8 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -53,8 +53,11 @@ void NeutralAtomMapper::mapAppend(qc::QuantumComputation& qc, break; } } - mappedQc.addAncillaryRegister(this->arch->getNpositions()); - mappedQc.addAncillaryRegister(this->arch->getNpositions(), "fa"); + // only add flying ancillas if not already present + if (mappedQc.getNancillae() == 0) { + mappedQc.addAncillaryRegister(this->arch->getNpositions()); + mappedQc.addAncillaryRegister(this->arch->getNpositions(), "fa"); + } qc::CircuitOptimizer::replaceMCXWithMCZ(qc); qc::CircuitOptimizer::singleQubitGateFusion(qc); From fcb84fbb9a9bb0147ed6027294925d2e0783d458 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Wed, 19 Nov 2025 15:26:13 +0100 Subject: [PATCH 264/394] =?UTF-8?q?=F0=9F=93=9DAdded=20documentation=20for?= =?UTF-8?q?=20Hybrid=20Neutral=20Atom=20Mapper.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/images/hybrid_shuttling.gif | Bin 0 -> 366024 bytes docs/na_hybrid.md | 283 +++++++++++++++++++++++++++++++ 2 files changed, 283 insertions(+) create mode 100644 docs/images/hybrid_shuttling.gif create mode 100644 docs/na_hybrid.md diff --git a/docs/images/hybrid_shuttling.gif b/docs/images/hybrid_shuttling.gif new file mode 100644 index 0000000000000000000000000000000000000000..2bd43604a0987d758a426d1c2743fe57045f5faa GIT binary patch literal 366024 zcmeFY`8!l^{P=%nF*A%=2q83<7_yc$WEuO;gt8=!R0w5HQiidwL)M6~W-AJz$c%j_ zEmYKG_euzb!hF5oKYgz2dtKju;QM)8=lpd4a?X98&*wS!xt|YXQzL@9s{_~uw9N(% z0fWIX7z~L-^6>B+J$h6`R7Bj!SyECGkC#zYR8q9{Qc;1PK7IPkAv$ORL0wZ*Q%g%r z`w$aA*AURt1L^7M85tQFpW`zzF)=kYH9O=y$lToA!U}3>3AMJiK7S5ma|vX7hy%*@ zewMwx{Uztimu`TZjgZb4crQO}zH&?T$|J^Aw`*7F{nu}xu7})k_l0}j^tyFOA@la_ z+rEd~)$_fF^7r=-h_nd2t`d|KeCHPW&Ye3UwA{N?-q5nHu&}WEhdj7{pAvCDt0cUl zmy*O0bx5*ibSO_;EGF(DGJ&y}$XrfxrZv5Pc*vXS zOfT+C`jM>c?Cf%IcGqG~^Jq@nr`$v6#N5`2M}9ny=*J(metOh4nU|NHcSt!;e*WY9 z8gStu?UVFFs>R$ zm{mikY~!oo#_qRGhrGVj#8iCs==$p}oz~XYwnHXX-n2)wzs_cO^D`I>CX?BDNMB;- z*UipvTisKu-3tfZOYEMvmOZSEo@I9LO?>YmE9}0$-oCz({zHZz5A;7AER7x-8XE3x z8Xiv?9-kQgJUco%{PvKqOXI#0<5O4Py?giZ;FJGpY zyuW!L`?m3y#bSN;7yo|9a^m+r_6%!!W{*7^tus42J2$tmFx#`}C%U+}_}|jZj~_o) zDzB}uN>;X#R@XPy4*7eqzBax7yJGX#(&pyo)@t|f-+vC--rnBLbKKqiy?apo_wU!g zfB*jb_iwK+b8l~N|KGv>-oO1r4!ZXb4i5G=4o~*pE_?qkoBf&1{>^5y0X87cMp_x# z+3M?(3{NR4LqLc5AifLbfF2IX|4(xKPnyL3-z53JN%H^aBtfu&ADkj4b#x{baYW9! zudb*w7At9%Z}PmjJ5liT&8fcUCB3OQ{R9!y7p46V@wQdY{V&P}vz6Tj@=fc@hw}&l z|4sGRKN&3~Qn^IU8YH?6Fy6)rV?_`kT`OjnxyiOJ07T^@Y-^h=ZP?7(C5re~KEH#WXb4>r|0hg0^aH_e;tzSk}A=22y83tW0rjv+Hd zcI)N`bM6dmx+_AXkXYf;FDn=qEoS6=?w>_LF;;J`+hDeT8xE+w({ZDmd)h6W0!%jZV*&rTQQ&Et2^C@6x6I>mo&O|a=sgVSAC^+ET|be z5Pbr%SPQB|1nynN+quQ-2phjAQgytE`Xcub(mPW(L@Yp)VoHEf7oRv{7K619VKI^7 z@=Qy_F+Q6|ky2T4pUxdHBJ{|01V9j;5JF7QImhselsMpim#DlDMJ38?)7@ui1|J)R z&Vr+SJCSGTn~`vt5LPO7B} zFLK@Q+zCz+=>?<{MVdJs7YZrqhEC*4P%y@`vjVJyQCR3p#o(OKqV- zbIl~!(=EeL?!Sr~F=qc&Rw$ks^`)XT(Y`JRE79s|%F(YqJtAsK&94lH!uTkDpY3dnge6ek3|x^7heG}+uaVlFZumuD!rG^KB?_3XPpmAodh zn4|N#m2|QMpY9i}gOAw+-)G+XvDVHrEos#ZHU7n^`EO89pBD zuWo^ZC>#jI8|LkAL$wib=r^ZcUr{_?Gvt4Io3VmXfO(V*g$eEy4|7cLQ6ynuB5&7u zY`t~C5gg5&LGQKw76yCszhC_|73jroOx2#*vuXU`N`87q_V&N}h%>ff;+)te13>Wj zIeFbJUfWiRX}4J7r)^T6E`*Wr@NYd7XinPMb?8wm@n=SoW>Y&3a4wHvy?81ZRhIJ* zxMfhM)vc8^>suSP5b`FJgb&35?FTqIWn++f!dknq^_e$cdrsmrodOGc47Bpz7*Try zO95jMz5ȁ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 z%0ACykTW-r&xfdBI_d$mQ!#No;Mi}8 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 literal 0 HcmV?d00001 diff --git a/docs/na_hybrid.md b/docs/na_hybrid.md new file mode 100644 index 000000000..95c580fa8 --- /dev/null +++ b/docs/na_hybrid.md @@ -0,0 +1,283 @@ +--- +file_format: mystnb +kernelspec: + name: python3 + number_source_lines: true +--- + +```{code-cell} ipython3 +:tags: [remove-cell] +%config InlineBackend.figure_formats = ['svg'] +``` + + + +# Hybrid Neutral Atom Routing and Mapping + +Neutral-atom (NA) processors combine long-range, native multi-qubit interactions with high-fidelity atom transport. +HyRoNA, the hybrid mapper in MQT QMAP, exploits both capabilities: it adaptively mixes gate-based routing (SWAP/BRIDGE) +with atom transport to minimize latency and error. It pairs interaction-aware initial placement with fast, +capability-specific cost models and an ASAP scheduler that respects hardware constraints, and it emits hardware-native +programs plus optional animation files for visualization. + +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. + +## Example: GHZ state on a hybrid NA architecture + +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. + +```{code-cell} ipython3 + +from qiskit import QuantumCircuit +# Build a compact GHZ(8) circuit (tree-like to keep depth small) + +qc = QuantumCircuit(8) +qc.h(0) +qc.cx(0, 4) +qc.cx(0, 2) +qc.cx(4, 6) +qc.cx(0, 1) +qc.cx(2, 3) +qc.cx(4, 5) +qc.cx(6, 7) + +qc.draw(output="mpl") + +``` + +### Load a hybrid NA architecture + +The hybrid mapper expects an architecture specification in JSON. This repository ships several ready-to-use examples + +```{code-cell} ipython3 + +from pathlib import Path +import tempfile +import os +from mqt.qmap.hybrid_mapper import NeutralAtomHybridArchitecture + +# Create a minimal architecture from an in-code JSON string and instantiate +# the NeutralAtomHybridArchitecture using a temporary file. The mapper's +# constructor expects a filename, so we write the JSON to a temp file first. +arch_json = '''{ + "name": "example arch", + "properties": { + "nRows": 5, + "nColumns": 5, + "nAods": 1, + "nAodCoordinates": 1, + "interQubitDistance": 10, + "minimalAodDistance": 0.1, + "interactionRadius": 1, + "blockingFactor": 1 + }, + "parameters": { + "nQubits": 10, + "gateTimes": { + "none": 0.5 + }, + "gateAverageFidelities": { + "none": 0.999 + }, + "decoherenceTimes": { + "t1": 100000000, + "t2": 1500000 + }, + "shuttlingTimes": { + "move": 0.55, + "aod_move": 0.55, + "aod_activate": 20, + "aod_deactivate": 20 + }, + "shuttlingAverageFidelities": { + "move": 1, + "aod_move": 1, + "aod_activate": 1, + "aod_deactivate": 1 + } + } +}''' + +with tempfile.NamedTemporaryFile(mode="w", delete=False, suffix=".json") as _tmp: + _tmp.write(arch_json) + tmp_name = _tmp.name + +arch = NeutralAtomHybridArchitecture(tmp_name) +# Clean up the temporary file +os.unlink(tmp_name) + +print(f"Loaded architecture: {arch.name} with {arch.num_qubits} qubits.") + +``` + +### Map with HyRoNA + +Mapping translates the algorithm to hardware-native operations using a combination of routing with SWAP/BRIDGE gates and +atom moves. What combination is used depends on the architecture capabilities and the mapper parameters. +Here, we show two examples: first, we only use gate-based routing, then we let the mapper decide freely. + +```{code-cell} ipython3 +from mqt.core import load +from mqt.qmap.hybrid_mapper import HybridNAMapper, MapperParameters + +# Optional: tweak parameters (defaults are sensible for most cases) +params_shuttling = MapperParameters() +params_shuttling.gate_weight = 1.0 +params_shuttling.shuttling_weight = 0.0 # prefer atom moves over gates + + +mapper = HybridNAMapper(arch, params=params_shuttling) + +# Convert the Qiskit circuit to an MQT QuantumComputation and map it +circ = load(qc) +mapper.map(circ) # optionally: mapper.map(circ, initial_mapping=InitialCircuitMapping.identity) + +# Retrieve mapping statistics +mapper.get_stats() +``` + +Note, how we set `shuttling_weight` zero to disallow atom moves and only use SWAP gates for routing. + +The idea of the hybrid mapper is to mix both capabilities or to automatically select the best one. +We now re-run the mapping with default parameters that allow both SWAPs and atom moves. + +```{code-cell} ipython3 +params_default = MapperParameters() + +mapper.set_parameters(params_default) + +mapper.map(circ) +mapper.get_stats() +``` + +Now the mapper uses atom moves only as they are the better option on this architecture where moves are unit fidelity and +gates are noisy. + +### Schedule the mapped circuit + +Scheduling orders the mapped operations as-soon-as-possible while respecting hardware constraints. + +```{code-cell} ipython3 +# Schedule; set create_animation_csv=True to generate visualization data +results = mapper.schedule(verbose=False, create_animation_csv=False) + +results +``` + +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. + +```{code-cell} ipython3 + +mapped_qasm = mapper.get_mapped_qc_qasm() +# Print a snippet of the mapped QASM +print("\n... Mapped QASM snippet ...\n" + + "\n".join(mapped_qasm.splitlines()[:])) + +# AOD-annotated variant (hardware-native moves) +mapped_aod_qasm = mapper.get_mapped_qc_aod_qasm() +print("\n... AOD (converted) snippet ...\n" + "\n".join(mapped_aod_qasm.splitlines()[:30]) + "\n...") +``` + +Here, the `q` register corresponds to the 5x5=25 coordinates of the architecture and no longer to the circuit qubits. +The other registers are used for temporary storage and AOD control. + +The second variant shows explicit AOD movements that correspond to the atom moves done on hardware. +Here, the AODs can be activated, moved, and deactivated to shuttle atoms around. +The zero entry corresponds to x-direction, the one entry to y-direction movements, where the two number are start and end coordinates. + +### Export animation files (optional) + +HyRoNA can write animation output files that can be visualized with [MQT NAViz](https://github.com/munich-quantum-toolkit/naviz). +Typically one has to accelerate the shuttling speed for better visualization by setting `shuttling_speed_factor` to a value smaller than one. + +```{code-cell} ipython3 +# Re-run scheduling with animation output enabled +_ = mapper.schedule(verbose=False, create_animation_csv=True, shuttling_speed_factor=0.01) + +# Save the files; the method writes both CSV and HTML next to the given base name +mapper.save_animation_files("ghz8_hyrona_animation") +``` + +This creates a `namachine` and a `.naviz` file which can then be imported into MQT NAViz for visualization. + +![Animation](images/hybrid_shuttling.gif) + +## Tuning the mapper + +HyRoNA exposes a concise set of parameters via `MapperParameters`: + +- lookahead_depth: limited lookahead to peek at near-future layers +- lookahead_weight_swaps / lookahead_weight_moves: balance gate-routing vs. atom motion during lookahead +- decay: decreases the incentive for repeatedly blocking the same qubits +- gate_weight / shuttling_weight / shuttling_time_weight: cost-model weights for gates vs. transport +- dynamic_mapping_weight: bias for enabling dynamic re-mapping (SWAP/MOVE) when beneficial +- max_bridge_distance: limit for BRIDGE operations; use to avoid long-range chains +- initial_coord_mapping: strategy for hardware coordinate initialization + +For more details, please check the source code documentation of `MapperParameters`. + +## Tips + +- Initial mapping: you can explicitly select the initial circuit-to-hardware mapping for `map` or `map_qasm_file` using + `initial_mapping=InitialCircuitMapping.identity` or `InitialCircuitMapping.graph`. + +- QASM input: instead of building a circuit in Qiskit, you can call `mapper.map_qasm_file("path/to/circuit.qasm")` and + then `mapper.schedule(...)` as shown above. + +- Architectures: the examples `rubidium_hybrid.json`, `rubidium_gate.json`, and `rubidium_shuttling.json` in + `architectures/` cover different capability profiles and are a good starting point + +## Hybrid Synthesis Mapper + +The Hybrid Synthesis Mapper helps you compare and stitch together alternative circuit fragments while keeping track of the current mapping on the NA device. Below is a compact example that mirrors the unit test flow and shows how to + +- evaluate multiple candidate fragments and pick the best, +- append fragments, +- and retrieve mapped QASM as well as the AOD-annotated variant. + +First, we create the `HybridSynthesisMapper` and evaluate two candidate fragments (here, both are the same GHZ circuit as above for simplicity). + +```{code-cell} ipython3 +from mqt.qmap.hybrid_mapper import HybridSynthesisMapper + +# Reuse the architecture `arch` and GHZ circuit `qc` defined above. +synth = HybridSynthesisMapper(arch) +synth.init_mapping(qc.num_qubits) + +fidelities = synth.evaluate_synthesis_steps([circ, circ], also_map = False) +print("Fidelities of subcircuits:", fidelities ) +``` + +The fidelity return indicates how well the subcircuit can be appended to the circuit mapped to the hardware. + +You can then build up a larger program by appending fragments. + +```{code-cell} ipython3 +synth.append_with_mapping(circ) +``` + +This appends the circuit and maps it directly. This can be repeated for multiple fragments to always choose the best one. + +Similar to the normal Hybrid Mapper, you can retrieve the mapped circuit and the AOD-annotated variant: + +```{code-cell} ipyth + +# Retrieve mapped circuit (extended QASM2) +qasm_mapped = synth.get_mapped_qc() +print("\n... Mapped QASM snippet ...\n" + + "\n".join(qasm_mapped.splitlines()[:30]) + + "\n...") +``` + +One can also access the circuits which is constructed step-by-step in a unmapped state (e.g. to use a different mapper). + +```{code-cell} ipython3 +qasm_synth = synth.get_synthesized_qc() +print("\n... Synthesized QASM snippet ...\n" + + "\n".join(qasm_synth.splitlines()[:30]) + + "\n...") +``` + +Here this corresponds simply to the input GHZ circuit. + +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. From 82a8daa9be52e9b705dd5c50e0c3a4449070efb0 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 20 Nov 2025 08:44:34 +0100 Subject: [PATCH 265/394] =?UTF-8?q?=F0=9F=93=9DAdded=20documentation=20for?= =?UTF-8?q?=20Hybrid=20Neutral=20to=20README.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/index.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/index.md b/docs/index.md index f76396076..c660878bd 100644 --- a/docs/index.md +++ b/docs/index.md @@ -44,6 +44,7 @@ mapping synthesis na_state_prep na_zoned_compiler +na_hybrid references CHANGELOG UPGRADING From 10f8ca7b07d915bbfdf38dcf533b978c765e78cc Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 20 Nov 2025 09:54:53 +0100 Subject: [PATCH 266/394] =?UTF-8?q?=F0=9F=93=9DAdded=20documentation=20for?= =?UTF-8?q?=20Hybrid=20Neutral=20to=20README.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 52d9902fc..a2d008894 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ It builds upon [MQT Core](https://github.com/munich-quantum-toolkit/core), which - Clifford circuit synthesis and optimization: SAT-based depth/gate-optimal Clifford synthesis with optional destabilizer preservation, plus a fast heuristic splitter for larger circuits. [Guide](https://mqt.readthedocs.io/projects/qmap/en/latest/synthesis.html) - Zoned neutral-atom compilers: routing-agnostic and routing-aware flows that place, route, and schedule atom transfers between storage/entanglement zones. [Guide](https://mqt.readthedocs.io/projects/qmap/en/latest/na_zoned_compiler.html) - Neutral-atom logical state preparation (NASP): SMT-based generator for optimal preparation schedules of logical graph states on zoned architectures. [Guide](https://mqt.readthedocs.io/projects/qmap/en/latest/na_state_prep.html) -- Hybrid circuit mapper for neutral atom quantum computers: a hybrid approach combining superconducting mapping techniques with atom shuttling. +- Hybrid circuit mapper for neutral atom quantum computers: a hybrid approach combining superconducting mapping techniques with atom shuttling. [Guide](https://mqt.readthedocs.io/projects/qmap/en/latest/na_hybrid.html) - Python-first API with Qiskit integration: pass `QuantumCircuit` or OpenQASM; one-call `compile()` or `optimize_clifford()` via plugin wrappers. [API](https://mqt.readthedocs.io/projects/qmap/en/latest/api/mqt/qmap/index.html) - Efficient and portable: C++20 core with Z3-backed solvers, prebuilt wheels for Linux/macOS/Windows via [PyPI](https://pypi.org/project/mqt.qmap/). From 2bf1f48bcc067fcbad89af9d294574065efb9cdc Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 20 Nov 2025 12:55:10 +0100 Subject: [PATCH 267/394] =?UTF-8?q?=E2=9C=85=20removed=20randomness=20from?= =?UTF-8?q?=20ImpossibleSwaps=20test.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/hybridmap/test_hybridmap.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/hybridmap/test_hybridmap.cpp b/test/hybridmap/test_hybridmap.cpp index 96b0e9c55..cd279b75a 100644 --- a/test/hybridmap/test_hybridmap.cpp +++ b/test/hybridmap/test_hybridmap.cpp @@ -264,7 +264,7 @@ TEST(NeutralAtomMapperExceptions, ImpossibleSwaps) { na::NeutralAtomArchitecture("architectures/arch_sparse.json"); na::MapperParameters p; p.shuttlingWeight = 0.0; - p.initialCoordMapping = na::InitialCoordinateMapping::Random; + p.initialCoordMapping = na::InitialCoordinateMapping::Trivial; p.verbose = true; na::NeutralAtomMapper mapper(arch, p); qc::QuantumComputation qc = From a7273bfd0f2065357eea347aac54764fa6aafb77 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 20 Nov 2025 13:07:49 +0100 Subject: [PATCH 268/394] =?UTF-8?q?=F0=9F=9A=A8=20Fix=20clang=20warnings.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bindings/hybrid_mapper/hybrid_mapper.cpp | 13 +++++++------ include/hybridmap/HardwareQubits.hpp | 2 +- test/hybridmap/test_hardware_qubits.cpp | 2 +- test/hybridmap/test_hybrid_synthesis_map.cpp | 1 - 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/bindings/hybrid_mapper/hybrid_mapper.cpp b/bindings/hybrid_mapper/hybrid_mapper.cpp index d9c653516..d6d448be1 100644 --- a/bindings/hybrid_mapper/hybrid_mapper.cpp +++ b/bindings/hybrid_mapper/hybrid_mapper.cpp @@ -15,6 +15,7 @@ #include "hybridmap/NeutralAtomUtils.hpp" #include "qasm3/Importer.hpp" +#include #include #include #include @@ -24,6 +25,7 @@ #include #include // NOLINT(misc-include-cleaner) #include +#include namespace py = pybind11; using namespace pybind11::literals; @@ -180,8 +182,8 @@ PYBIND11_MODULE(MQT_QMAP_MODULE_NAME, m, py::mod_gil_not_used()) { .def( "map", [](na::NeutralAtomMapper& mapper, qc::QuantumComputation& circ, - const na::InitialMapping initial_mapping) { - mapper.map(circ, initial_mapping); + const na::InitialMapping initialMapping) { + mapper.map(circ, initialMapping); }, "Map a quantum circuit object to the neutral atom quantum computer", "qc"_a, "initial_mapping"_a = na::InitialMapping::Identity) @@ -211,10 +213,9 @@ PYBIND11_MODULE(MQT_QMAP_MODULE_NAME, m, py::mod_gil_not_used()) { .def( "schedule", [](na::NeutralAtomMapper& mapper, const bool verbose, - const bool create_animation_csv, - const double shuttling_speed_factor) { - auto results = mapper.schedule(verbose, create_animation_csv, - shuttling_speed_factor); + const bool creatAnimationCsv, const double shuttlingSpeedFactor) { + auto results = mapper.schedule(verbose, creatAnimationCsv, + shuttlingSpeedFactor); return results.toMap(); }, "Schedule the mapped circuit", "verbose"_a = false, diff --git a/include/hybridmap/HardwareQubits.hpp b/include/hybridmap/HardwareQubits.hpp index 6b1260605..f8a3904de 100644 --- a/include/hybridmap/HardwareQubits.hpp +++ b/include/hybridmap/HardwareQubits.hpp @@ -248,7 +248,7 @@ class HardwareQubits { * @return Hardware qubit ID. * @throw std::runtime_error If no qubit occupies the coordinate. */ - [[nodiscard]] HwQubit getHwQubit(const CoordIndex coordIndex) const { + HwQubit getHwQubit(const CoordIndex coordIndex) const { for (auto const& [hwQubit, index] : hwToCoordIdx) { if (index == coordIndex) { return hwQubit; diff --git a/test/hybridmap/test_hardware_qubits.cpp b/test/hybridmap/test_hardware_qubits.cpp index 7d5caee31..f8b3528b6 100644 --- a/test/hybridmap/test_hardware_qubits.cpp +++ b/test/hybridmap/test_hardware_qubits.cpp @@ -27,7 +27,7 @@ TEST(HardwareQubitsExceptions, AccessEmptyCoordThrows) { 0); constexpr auto emptyCoord = static_cast(3); - EXPECT_THROW(auto result = hw.getHwQubit(emptyCoord), std::runtime_error); + EXPECT_THROW(hw.getHwQubit(emptyCoord), std::runtime_error); } TEST(HardwareQubitsExceptions, MoveInvalidCoordinateThrows) { diff --git a/test/hybridmap/test_hybrid_synthesis_map.cpp b/test/hybridmap/test_hybrid_synthesis_map.cpp index da1c432a0..8873fbccb 100644 --- a/test/hybridmap/test_hybrid_synthesis_map.cpp +++ b/test/hybridmap/test_hybrid_synthesis_map.cpp @@ -18,7 +18,6 @@ #include "hybridmap/NeutralAtomUtils.hpp" #include "ir/QuantumComputation.hpp" -#include #include #include #include From 2abb8700b54c80f981ba67cc01a9543143992256 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 20 Nov 2025 13:15:54 +0100 Subject: [PATCH 269/394] =?UTF-8?q?=F0=9F=93=9Dchanged=20docstrings=20to?= =?UTF-8?q?=20make=20clear=20that=20qasm=20is=20returned.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bindings/hybrid_mapper/hybrid_mapper.cpp | 29 +++++++++++++----------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/bindings/hybrid_mapper/hybrid_mapper.cpp b/bindings/hybrid_mapper/hybrid_mapper.cpp index d6d448be1..6f4dea64a 100644 --- a/bindings/hybrid_mapper/hybrid_mapper.cpp +++ b/bindings/hybrid_mapper/hybrid_mapper.cpp @@ -242,27 +242,30 @@ PYBIND11_MODULE(MQT_QMAP_MODULE_NAME, m, py::mod_gil_not_used()) { "structures for the given number of qubits.", "n_qubits"_a) .def("get_mapped_qc", &na::HybridSynthesisMapper::getMappedQcQasm, - "Returns the mapped QuantumComputation") + "Returns the mapped circuit as an extended qasm2 string") .def("save_mapped_qc", &na::HybridSynthesisMapper::saveMappedQcQasm, - "Saves the mapped QuantumComputation to a file", "filename"_a) + "Saves the mapped circuit as an extended qasm2 to a file", + "filename"_a) + .def("convert_to_aod", &na::HybridSynthesisMapper::convertToAod, + "Converts the mapped circuit to " + "native AOD movements") .def( - "convert_to_aod", &na::HybridSynthesisMapper::convertToAod, - "Converts the mapped QuantumComputation to a QuantumComputation with " - "native AOD movements") - .def("get_mapped_qc_aod", &na::HybridSynthesisMapper::getMappedQcAodQasm, - "Returns the mapped QuantumComputation with native AOD movements") + "get_mapped_qc_aod", &na::HybridSynthesisMapper::getMappedQcAodQasm, + "Returns the mapped circuit with native AOD movements as an extended " + "qasm2 string") .def("save_mapped_qc_aod", &na::HybridSynthesisMapper::saveMappedQcAodQasm, - "Saves the mapped QuantumComputation with native AOD movements to a " + "Saves the mapped circuit with native AOD movements as an extended " + "qasm2 to a " "file", "filename"_a) .def("get_synthesized_qc", &na::HybridSynthesisMapper::getSynthesizedQcQASM, - "Returns the synthesized QuantumComputation with all gates but not " - "mapped to the hardware.") + "Returns the synthesized circuit with all gates but not " + "mapped to the hardware as a qasm2 string") .def("save_synthesized_qc", &na::HybridSynthesisMapper::saveSynthesizedQc, - "Saves the synthesized QuantumComputation with all gates but not " - "mapped to the hardware to a file", + "Saves the synthesized circuit with all gates but not " + "mapped to the hardware as qasm2 to a file", "filename"_a) .def( "append_without_mapping", @@ -282,7 +285,7 @@ PYBIND11_MODULE(MQT_QMAP_MODULE_NAME, m, py::mod_gil_not_used()) { "qc"_a) .def( "get_circuit_adjacency_matrix", - [](na::HybridSynthesisMapper& mapper) { + [](const na::HybridSynthesisMapper& mapper) { const auto symAdjMatrix = mapper.getCircuitAdjacencyMatrix(); std::vector> adjMatrix = {}; for (size_t i = 0; i < symAdjMatrix.size(); ++i) { From e39bde322ff0822962fc5be503b7049f23d85908 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 20 Nov 2025 13:21:25 +0100 Subject: [PATCH 270/394] =?UTF-8?q?=F0=9F=93=9D=20fixed=20minor=20errors?= =?UTF-8?q?=20in=20documentation.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/na_hybrid.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/na_hybrid.md b/docs/na_hybrid.md index 95c580fa8..f6717a69e 100644 --- a/docs/na_hybrid.md +++ b/docs/na_hybrid.md @@ -123,7 +123,7 @@ from mqt.qmap.hybrid_mapper import HybridNAMapper, MapperParameters # Optional: tweak parameters (defaults are sensible for most cases) params_shuttling = MapperParameters() params_shuttling.gate_weight = 1.0 -params_shuttling.shuttling_weight = 0.0 # prefer atom moves over gates +params_shuttling.shuttling_weight = 0.0 # disables atom moves mapper = HybridNAMapper(arch, params=params_shuttling) @@ -171,7 +171,7 @@ You can retrieve the mapped scheduled circuit (extended QASM2) and, if desired, mapped_qasm = mapper.get_mapped_qc_qasm() # Print a snippet of the mapped QASM print("\n... Mapped QASM snippet ...\n" + - "\n".join(mapped_qasm.splitlines()[:])) + "\n".join(mapped_qasm.splitlines()[:30])) # AOD-annotated variant (hardware-native moves) mapped_aod_qasm = mapper.get_mapped_qc_aod_qasm() @@ -183,7 +183,7 @@ The other registers are used for temporary storage and AOD control. The second variant shows explicit AOD movements that correspond to the atom moves done on hardware. Here, the AODs can be activated, moved, and deactivated to shuttle atoms around. -The zero entry corresponds to x-direction, the one entry to y-direction movements, where the two number are start and end coordinates. +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. ### Export animation files (optional) @@ -260,7 +260,7 @@ This appends the circuit and maps it directly. This can be repeated for multiple Similar to the normal Hybrid Mapper, you can retrieve the mapped circuit and the AOD-annotated variant: -```{code-cell} ipyth +```{code-cell} ipython3 # Retrieve mapped circuit (extended QASM2) qasm_mapped = synth.get_mapped_qc() From 2f04afe56f5dffe94f00f4589a41d909a7b3c2a0 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 20 Nov 2025 13:27:14 +0100 Subject: [PATCH 271/394] =?UTF-8?q?=F0=9F=93=9D=20clarify=20opToNaViz=20do?= =?UTF-8?q?c=20string.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridAnimation.hpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/include/hybridmap/HybridAnimation.hpp b/include/hybridmap/HybridAnimation.hpp index ad1ba3682..8f390995b 100644 --- a/include/hybridmap/HybridAnimation.hpp +++ b/include/hybridmap/HybridAnimation.hpp @@ -83,7 +83,9 @@ class AnimationAtoms { * emits move commands for affected atoms; also updates coordIdxToId for * origin/target coordinate-index pairs. * - Multi-qubit gates: emits a grouped cz with all involved atoms. - * - Single-qubit gates: emits a simple rz 1 for the target atom. + * - Single-qubit gates: emits a simple rz 1 for the target atom (independent + * of the exact gate -> does not matter for visualization). + * - Other operations are currently not supported for animation output. * @param op The operation to translate (uses coordinate indices as qubits). * @param startTime The animation timestamp to annotate the command with. * @return NaViz command string for the operation at the given time. From b960f6294cca8d350f9c6d53c3037514959c1210 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 20 Nov 2025 13:31:09 +0100 Subject: [PATCH 272/394] =?UTF-8?q?=F0=9F=8E=A8add=20additional=20check=20?= =?UTF-8?q?when=20initializing=20Sythesis=20Mapper.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridSynthesisMapper.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/hybridmap/HybridSynthesisMapper.hpp b/include/hybridmap/HybridSynthesisMapper.hpp index d2edea805..d107b9f01 100644 --- a/include/hybridmap/HybridSynthesisMapper.hpp +++ b/include/hybridmap/HybridSynthesisMapper.hpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -74,6 +75,9 @@ class HybridSynthesisMapper : public NeutralAtomMapper { * @param nQubits Number of logical qubits to synthesize. */ void initMapping(const size_t nQubits) { + if (nQubits > arch->getNpositions()) { + throw std::runtime_error("Not enough qubits in architecture."); + } mappedQc = qc::QuantumComputation(arch->getNpositions()); synthesizedQc = qc::QuantumComputation(nQubits); mapping = Mapping(nQubits); From 9937893a1f0e3d8b7802b0a2469f58d7dc4d987f Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 20 Nov 2025 13:32:04 +0100 Subject: [PATCH 273/394] =?UTF-8?q?=F0=9F=93=9Dfixed=20non-exisiting=20thr?= =?UTF-8?q?ow=20statement=20in=20doc=20string?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridSynthesisMapper.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/include/hybridmap/HybridSynthesisMapper.hpp b/include/hybridmap/HybridSynthesisMapper.hpp index d107b9f01..320bb7007 100644 --- a/include/hybridmap/HybridSynthesisMapper.hpp +++ b/include/hybridmap/HybridSynthesisMapper.hpp @@ -112,7 +112,6 @@ class HybridSynthesisMapper : public NeutralAtomMapper { /** * @brief Save synthesized circuit as OpenQASM to a file. * @param filename Output filename. - * @throw std::ios_base::failure If the file cannot be opened for writing. */ [[maybe_unused]] void saveSynthesizedQc(const std::string& filename) const { std::ofstream ofs(filename); From 9d533423b1997468f0fab72732a45c8b5cf2ba64 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 20 Nov 2025 13:44:49 +0100 Subject: [PATCH 274/394] =?UTF-8?q?=F0=9F=8E=A8=20improved=20checks=20for?= =?UTF-8?q?=20initialization=20of=20Synthesis=20Mapper.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridSynthesisMapper.hpp | 4 +++- src/hybridmap/HybridSynthesisMapper.cpp | 12 ++++++++++++ test/hybridmap/test_hybrid_synthesis_map.cpp | 6 +++++- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/include/hybridmap/HybridSynthesisMapper.hpp b/include/hybridmap/HybridSynthesisMapper.hpp index 320bb7007..3a5266a7b 100644 --- a/include/hybridmap/HybridSynthesisMapper.hpp +++ b/include/hybridmap/HybridSynthesisMapper.hpp @@ -45,6 +45,7 @@ class HybridSynthesisMapper : public NeutralAtomMapper { using qcs = std::vector; qc::QuantumComputation synthesizedQc; + bool initialized = false; /** * @brief Evaluate a single proposed synthesis step. @@ -81,6 +82,7 @@ class HybridSynthesisMapper : public NeutralAtomMapper { mappedQc = qc::QuantumComputation(arch->getNpositions()); synthesizedQc = qc::QuantumComputation(nQubits); mapping = Mapping(nQubits); + initialized = true; } /** @@ -144,7 +146,7 @@ class HybridSynthesisMapper : public NeutralAtomMapper { * @brief Get the current device adjacency (connectivity) matrix. * @return Symmetric adjacency matrix for the neutral atom hardware. */ - [[nodiscard]] AdjacencyMatrix getCircuitAdjacencyMatrix() const; + AdjacencyMatrix getCircuitAdjacencyMatrix() const; }; } // namespace na diff --git a/src/hybridmap/HybridSynthesisMapper.cpp b/src/hybridmap/HybridSynthesisMapper.cpp index 3a530a530..af7dc0d94 100644 --- a/src/hybridmap/HybridSynthesisMapper.cpp +++ b/src/hybridmap/HybridSynthesisMapper.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -30,6 +31,10 @@ namespace na { std::vector HybridSynthesisMapper::evaluateSynthesisSteps(qcs& synthesisSteps, const bool alsoMap) { + if (!initialized) { + initMapping(synthesisSteps[0].getNqubits()); + initialized = true; + } std::vector> candidates; size_t qcIndex = 0; for (auto& qc : synthesisSteps) { @@ -70,6 +75,9 @@ HybridSynthesisMapper::evaluateSynthesisStep(qc::QuantumComputation& qc) const { void HybridSynthesisMapper::appendWithoutMapping( const qc::QuantumComputation& qc) { + if (mappedQc.empty()) { + initMapping(qc.getNqubits()); + } for (const auto& op : qc) { this->synthesizedQc.emplace_back(op->clone()); this->mapGate(op.get()); @@ -87,6 +95,10 @@ void HybridSynthesisMapper::appendWithMapping(qc::QuantumComputation& qc) { } AdjacencyMatrix HybridSynthesisMapper::getCircuitAdjacencyMatrix() const { + if (!initialized) { + throw std::runtime_error( + "Not yet initialized. Cannot get circuit adjacency matrix."); + } const auto numCircQubits = synthesizedQc.getNqubits(); AdjacencyMatrix adjMatrix(numCircQubits); diff --git a/test/hybridmap/test_hybrid_synthesis_map.cpp b/test/hybridmap/test_hybrid_synthesis_map.cpp index 8873fbccb..cef5fd344 100644 --- a/test/hybridmap/test_hybrid_synthesis_map.cpp +++ b/test/hybridmap/test_hybrid_synthesis_map.cpp @@ -19,6 +19,7 @@ #include "ir/QuantumComputation.hpp" #include +#include #include #include @@ -60,7 +61,10 @@ TEST_P(TestParametrizedHybridSynthesisMapper, EvaluateSynthesisStep) { auto params = MapperParameters(); params.verbose = true; auto mapper = HybridSynthesisMapper(arch, params); - mapper.initMapping(3); + // Intentionally not initializing the mapper to test error handling + EXPECT_THROW(mapper.getCircuitAdjacencyMatrix(), std::runtime_error); + // Initializing with too many qubits to test error handling + EXPECT_THROW(mapper.initMapping(50), std::runtime_error); const auto best = mapper.evaluateSynthesisSteps(circuits, true); EXPECT_EQ(best.size(), 2); EXPECT_GE(best[0], 0); From 80ea226190ee4948774b4feabde396359b566356 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 20 Nov 2025 13:47:47 +0100 Subject: [PATCH 275/394] =?UTF-8?q?=F0=9F=8E=A8=20handle=20case=20where=20?= =?UTF-8?q?no=20center=20can=20be=20found.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/Mapping.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/hybridmap/Mapping.cpp b/src/hybridmap/Mapping.cpp index 5232ae357..4bb3542d1 100644 --- a/src/hybridmap/Mapping.cpp +++ b/src/hybridmap/Mapping.cpp @@ -69,6 +69,9 @@ std::vector Mapping::graphMatching() { hwCenter = qubit; } } + if (hwCenter == std::numeric_limits::max()) { + hwCenter = 0; + } // make circuit graph std::vector>> circGraph(dag.size()); From 0d9a40abe706fed6db6644c0e6c2a0bf6f81e31a Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 20 Nov 2025 13:54:13 +0100 Subject: [PATCH 276/394] =?UTF-8?q?=F0=9F=8E=A8=20avoid=20mapping=20to=20c?= =?UTF-8?q?opy=20the=20Quantum=20Computation.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/Mapping.hpp | 2 +- test/hybridmap/test_mapping.cpp | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/include/hybridmap/Mapping.hpp b/include/hybridmap/Mapping.hpp index 0a771170f..fda8e0db8 100644 --- a/include/hybridmap/Mapping.hpp +++ b/include/hybridmap/Mapping.hpp @@ -80,7 +80,7 @@ class Mapping { * hardware qubits. */ Mapping(const size_t nQubits, const InitialMapping initialMapping, - qc::QuantumComputation qc, HardwareQubits hwQubitsArg) + qc::QuantumComputation& qc, HardwareQubits hwQubitsArg) : hwQubits(std::move(hwQubitsArg)), dag(qc::CircuitOptimizer::constructDAG(qc)) { diff --git a/test/hybridmap/test_mapping.cpp b/test/hybridmap/test_mapping.cpp index 1ff7a646c..8d9c09f66 100644 --- a/test/hybridmap/test_mapping.cpp +++ b/test/hybridmap/test_mapping.cpp @@ -25,7 +25,7 @@ TEST(MappingExceptions, CircuitExceedsHardwareThrows) { // Hardware has 1 available logical qubit, circuit needs 2 na::HardwareQubits const hw( arch, /*nQubits*/ 1, na::InitialCoordinateMapping::Trivial, /*seed*/ 0); - qc::QuantumComputation const qc(2); + qc::QuantumComputation qc(2); EXPECT_THROW((void)na::Mapping(2, na::InitialMapping::Identity, qc, hw), std::runtime_error); @@ -37,7 +37,7 @@ TEST(MappingExceptions, GetCircQubitThrowsIfHardwareNotMapped) { // Hardware has 4 logical spots, but circuit uses only 2 (identity mapping) na::HardwareQubits const hw( arch, /*nQubits*/ 4, na::InitialCoordinateMapping::Trivial, /*seed*/ 0); - qc::QuantumComputation const qc(2); + qc::QuantumComputation qc(2); na::Mapping const m(2, na::InitialMapping::Identity, qc, hw); // hw qubits 0 and 1 are mapped; 2 and 3 are not -> getCircQubit(2) should @@ -51,7 +51,7 @@ TEST(MappingExceptions, ApplySwapThrowsIfBothHardwareQubitsUnmapped) { // Hardware has 4, circuit maps only 2 via identity na::HardwareQubits const hw( arch, /*nQubits*/ 4, na::InitialCoordinateMapping::Trivial, /*seed*/ 0); - qc::QuantumComputation const qc(2); + qc::QuantumComputation qc(2); na::Mapping m(2, na::InitialMapping::Identity, qc, hw); // Swap two unmapped hardware qubits (2 and 3) -> should throw @@ -63,7 +63,7 @@ TEST(MappingExceptions, GetHwQubitThrowsOutOfRangeForInvalidCircuitIndex) { na::NeutralAtomArchitecture("architectures/rubidium_shuttling.json"); na::HardwareQubits const hw( arch, /*nQubits*/ 2, na::InitialCoordinateMapping::Trivial, /*seed*/ 0); - qc::QuantumComputation const qc(2); + qc::QuantumComputation qc(2); na::Mapping const m(2, na::InitialMapping::Identity, qc, hw); // Access circuit qubit index outside [0, nQubits) -> std::out_of_range From 06a55491bcfd5207e4a231ba7575c838dc58789c Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 20 Nov 2025 13:56:44 +0100 Subject: [PATCH 277/394] =?UTF-8?q?=F0=9F=8E=A8=20added=20additional=20che?= =?UTF-8?q?ck=20for=20the=20Mapping.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/Mapping.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/hybridmap/Mapping.hpp b/include/hybridmap/Mapping.hpp index fda8e0db8..1d8b5a013 100644 --- a/include/hybridmap/Mapping.hpp +++ b/include/hybridmap/Mapping.hpp @@ -87,6 +87,10 @@ class Mapping { if (qc.getNqubits() > hwQubits.getNumQubits()) { throw std::runtime_error("Not enough qubits in architecture for circuit"); } + if (nQubits > qc.getNqubits()) { + throw std::runtime_error( + "nQubits exceeds number of qubits in provided circuit"); + } switch (initialMapping) { case Identity: From 825e573eb5ed246a00bb36d0c4a8476a0153beed Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 20 Nov 2025 13:59:54 +0100 Subject: [PATCH 278/394] =?UTF-8?q?=F0=9F=8E=A8=20add=20comment=20on=20max?= =?UTF-8?q?=20bridge=20size.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/NeutralAtomArchitecture.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/hybridmap/NeutralAtomArchitecture.hpp b/include/hybridmap/NeutralAtomArchitecture.hpp index f5eade984..fb0f58b52 100644 --- a/include/hybridmap/NeutralAtomArchitecture.hpp +++ b/include/hybridmap/NeutralAtomArchitecture.hpp @@ -177,6 +177,7 @@ class NeutralAtomArchitecture { qc::SymmetricMatrix swapDistances; std::vector> nearbyCoordinates; + // Bridges only makes sense for short distances (<=5) so we limit its size BridgeCircuits bridgeCircuits = BridgeCircuits(10); /** From d5f541ee892be3226e82ec1f4f3e8e6394873141 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 20 Nov 2025 15:18:43 +0100 Subject: [PATCH 279/394] =?UTF-8?q?=F0=9F=8E=A8=20converted=20cout=20to=20?= =?UTF-8?q?SPDLOG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/NeutralAtomArchitecture.hpp | 9 +++++---- src/hybridmap/CMakeLists.txt | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/include/hybridmap/NeutralAtomArchitecture.hpp b/include/hybridmap/NeutralAtomArchitecture.hpp index fb0f58b52..4e3a27602 100644 --- a/include/hybridmap/NeutralAtomArchitecture.hpp +++ b/include/hybridmap/NeutralAtomArchitecture.hpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -348,8 +349,7 @@ class NeutralAtomArchitecture { */ [[nodiscard]] qc::fp getGateTime(const std::string& s) const { if (!parameters.gateTimes.contains(s)) { - std::cout << "Gate time for " << s << " not found\n" - << "Returning default value\n"; + SPDLOG_WARN("Gate time for '{}' not found. Returning default value.", s); return parameters.gateTimes.at("none"); } return parameters.gateTimes.at(s); @@ -364,8 +364,9 @@ class NeutralAtomArchitecture { */ [[nodiscard]] qc::fp getGateAverageFidelity(const std::string& s) const { if (!parameters.gateAverageFidelities.contains(s)) { - std::cout << "Gate average fidelity for " << s << " not found\n" - << "Returning default value\n"; + SPDLOG_WARN( + "Gate average fidelity for '{}' not found. Returning default value.", + s); return parameters.gateAverageFidelities.at("none"); } return parameters.gateAverageFidelities.at(s); diff --git a/src/hybridmap/CMakeLists.txt b/src/hybridmap/CMakeLists.txt index bb37cf827..bd62e5b7f 100644 --- a/src/hybridmap/CMakeLists.txt +++ b/src/hybridmap/CMakeLists.txt @@ -28,7 +28,7 @@ if(NOT TARGET ${MQT_QMAP_HYBRIDMAP_TARGET_NAME}) # link to the MQT::Core libraries target_link_libraries( ${MQT_QMAP_HYBRIDMAP_TARGET_NAME} - PUBLIC MQT::CoreIR MQT::CoreNA nlohmann_json::nlohmann_json + PUBLIC MQT::CoreIR MQT::CoreNA nlohmann_json::nlohmann_json spdlog::spdlog PRIVATE MQT::ProjectWarnings MQT::ProjectOptions MQT::CoreCircuitOptimizer) # add MQT alias From 1528131630278cf1a20aa36b21dcd91fc3b513c6 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 20 Nov 2025 16:27:48 +0100 Subject: [PATCH 280/394] =?UTF-8?q?=F0=9F=8E=A8=20removed=20default=20cons?= =?UTF-8?q?tructor=20of=20Mapper=20and=20pointer=20problems.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridNeutralAtomMapper.hpp | 7 ++++--- src/hybridmap/HybridSynthesisMapper.cpp | 2 +- test/hybridmap/test_hybridmap.cpp | 5 ++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index e2aef5ee5..354b18687 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -32,6 +32,7 @@ #include #include #include +#include #include namespace na { @@ -99,7 +100,7 @@ class NeutralAtomMapper { protected: MapperStats stats; // The considered architecture - const NeutralAtomArchitecture* arch = nullptr; + const NeutralAtomArchitecture* arch; // The mapped quantum circuit qc::QuantumComputation mappedQc; // The mapped quantum circuit converted to AOD movements @@ -455,9 +456,9 @@ class NeutralAtomMapper { public: // Constructors - NeutralAtomMapper() = default; + NeutralAtomMapper() = delete; explicit NeutralAtomMapper(const NeutralAtomArchitecture* architecture, - const MapperParameters* p = nullptr) + const MapperParameters* p) : arch(architecture), scheduler(*architecture), parameters(p), hardwareQubits(*arch, arch->getNqubits() - p->numFlyingAncillas, p->initialCoordMapping, p->seed), diff --git a/src/hybridmap/HybridSynthesisMapper.cpp b/src/hybridmap/HybridSynthesisMapper.cpp index af7dc0d94..e0a3a1ff7 100644 --- a/src/hybridmap/HybridSynthesisMapper.cpp +++ b/src/hybridmap/HybridSynthesisMapper.cpp @@ -65,7 +65,7 @@ HybridSynthesisMapper::evaluateSynthesisSteps(qcs& synthesisSteps, qc::fp HybridSynthesisMapper::evaluateSynthesisStep(qc::QuantumComputation& qc) const { - NeutralAtomMapper tempMapper; + NeutralAtomMapper tempMapper(this->arch, this->parameters); tempMapper.copyStateFrom(*this); auto mappedQc = tempMapper.map(qc, mapping); tempMapper.convertToAod(); diff --git a/test/hybridmap/test_hybridmap.cpp b/test/hybridmap/test_hybridmap.cpp index cd279b75a..4cde7ccae 100644 --- a/test/hybridmap/test_hybridmap.cpp +++ b/test/hybridmap/test_hybridmap.cpp @@ -140,11 +140,10 @@ class NeutralAtomMapperTest : public testing::Test { na::NeutralAtomArchitecture(testArchitecturePath); na::InitialMapping const initialMapping = na::InitialMapping::Graph; na::MapperParameters mapperParameters; - na::NeutralAtomMapper mapper; + na::NeutralAtomMapper mapper{arch, mapperParameters}; qc::QuantumComputation qc; void SetUp() override { - mapper = na::NeutralAtomMapper(arch); mapperParameters.initialCoordMapping = na::InitialCoordinateMapping::Trivial; mapperParameters.lookaheadDepth = 1; @@ -159,7 +158,7 @@ class NeutralAtomMapperTest : public testing::Test { mapperParameters.numFlyingAncillas = 1; mapperParameters.limitShuttlingLayer = 1; mapperParameters.usePassBy = true; - mapper.setParameters(mapperParameters); + mapper = na::NeutralAtomMapper(arch, mapperParameters); qc = qasm3::Importer::importf( "circuits/dj_nativegates_rigetti_qiskit_opt3_10.qasm"); } From 5511ab453cf2ae44a33d3483c9213c018e9d1f60 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 20 Nov 2025 16:33:03 +0100 Subject: [PATCH 281/394] =?UTF-8?q?=F0=9F=8E=A8=20log=20if=20animation=20f?= =?UTF-8?q?iles=20are=20asked=20for=20without=20scheduling.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/NeutralAtomScheduler.hpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/hybridmap/NeutralAtomScheduler.hpp b/include/hybridmap/NeutralAtomScheduler.hpp index 6b75cf004..6285b1cae 100644 --- a/include/hybridmap/NeutralAtomScheduler.hpp +++ b/include/hybridmap/NeutralAtomScheduler.hpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -163,6 +164,11 @@ class NeutralAtomScheduler { * artifact extensions). */ void saveAnimationFiles(const std::string& filename) const { + if (animation.empty() || animationMachine.empty()) { + SPDLOG_WARN("No animation data to save; did you run schedule() with " + "createAnimationCsv=true?"); + return; + } const auto filenameWithoutExtension = filename.substr(0, filename.find_last_of('.')); const auto filenameViz = filenameWithoutExtension + ".naviz"; From 5be628a8838edf9f5b4cbac0ed08fbaa06044412 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 20 Nov 2025 16:37:09 +0100 Subject: [PATCH 282/394] =?UTF-8?q?=F0=9F=8E=A8=20improve=20error=20messag?= =?UTF-8?q?e=20for=20initial=20Mapping?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/NeutralAtomUtils.hpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/include/hybridmap/NeutralAtomUtils.hpp b/include/hybridmap/NeutralAtomUtils.hpp index 9f9ec6068..04fd1268a 100644 --- a/include/hybridmap/NeutralAtomUtils.hpp +++ b/include/hybridmap/NeutralAtomUtils.hpp @@ -75,8 +75,10 @@ initialCoordinateMappingFromString( if (initialCoordinateMapping == "random" || initialCoordinateMapping == "1") { return Random; } - throw std::invalid_argument("Invalid initial coordinate mapping value: " + - initialCoordinateMapping); + throw std::invalid_argument( + "Invalid initial coordinate mapping value (expected \"trivial\"/\"0\" " + "or \"random\"/\"1\"): " + + initialCoordinateMapping); } /** @@ -94,8 +96,10 @@ initialMappingFromString(const std::string& initialMapping) { if (initialMapping == "graph" || initialMapping == "1") { return Graph; } - throw std::invalid_argument("Invalid initial mapping value: " + - initialMapping); + throw std::invalid_argument( + "Invalid initial mapping value (expected \"identity\"/\"0\" or " + "\"graph\"/\"1\"): " + + initialMapping); } /** From de669aa838f0621f9dd968d757be52e8f2e36355 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 20 Nov 2025 16:40:34 +0100 Subject: [PATCH 283/394] =?UTF-8?q?=F0=9F=8E=A8=20added=20default=20initia?= =?UTF-8?q?lization=20of=20PassBy=20and=20FA.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/NeutralAtomUtils.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/hybridmap/NeutralAtomUtils.hpp b/include/hybridmap/NeutralAtomUtils.hpp index 04fd1268a..0ce6fb63e 100644 --- a/include/hybridmap/NeutralAtomUtils.hpp +++ b/include/hybridmap/NeutralAtomUtils.hpp @@ -237,7 +237,7 @@ struct FlyingAncillaComb { /** Sequence of flying ancilla moves realizing an interaction. */ std::vector moves; /** Operation this combination implements or is associated with. */ - const qc::Operation* op; + const qc::Operation* op = nullptr; }; /** @@ -249,7 +249,7 @@ struct PassByComb { /** Sequence of atom moves realizing a pass-by maneuver. */ std::vector moves; /** Operation this combination implements or is associated with. */ - const qc::Operation* op; + const qc::Operation* op = nullptr; }; /** From fa6b22fbb41360e549be71ab680ef75ba90b1d04 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 20 Nov 2025 16:54:30 +0100 Subject: [PATCH 284/394] =?UTF-8?q?=F0=9F=90=9B=20fixed=20error=20where=20?= =?UTF-8?q?flying=20ancilla=20is=20computed=20for=20mulitqubit=20gates.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HardwareQubits.cpp | 6 ++++ src/hybridmap/HybridNeutralAtomMapper.cpp | 43 ++++++++++++++--------- 2 files changed, 32 insertions(+), 17 deletions(-) diff --git a/src/hybridmap/HardwareQubits.cpp b/src/hybridmap/HardwareQubits.cpp index 3dcdc1d8f..e814d13de 100644 --- a/src/hybridmap/HardwareQubits.cpp +++ b/src/hybridmap/HardwareQubits.cpp @@ -297,6 +297,7 @@ HardwareQubits::findClosestFreeCoord(const CoordIndex coord, HwQubit HardwareQubits::getClosestQubit(const CoordIndex coord, const HwQubits& ignored) const { HwQubit closestQubit = 0; + bool noneFound = true; auto minDistance = std::numeric_limits::max(); for (auto const& [qubit, idx] : hwToCoordIdx) { if (ignored.contains(qubit)) { @@ -306,8 +307,13 @@ HwQubit HardwareQubits::getClosestQubit(const CoordIndex coord, distance < minDistance) { minDistance = distance; closestQubit = qubit; + noneFound = false; } } + if (noneFound) { + throw std::runtime_error( + "No available qubit found when searching for closest qubit."); + } return closestQubit; } diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index bd0a688e8..779b07b6c 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -1473,27 +1473,36 @@ size_t NeutralAtomMapper::shuttlingBasedMapping( std::cout << "iteration " << i << '\n'; } auto bestComb = findBestAtomMove(); - auto bestFaComb = convertMoveCombToFlyingAncillaComb(bestComb); - auto bestPbComb = convertMoveCombToPassByComb(bestComb); - - switch ( - compareShuttlingAndFlyingAncilla(bestComb, bestFaComb, bestPbComb)) { - case MoveMethod: - // apply whole move combination at once + MappingMethod bestMethod = MoveMethod; + if (!multiQubitGates) { + auto bestFaComb = convertMoveCombToFlyingAncillaComb(bestComb); + auto bestPbComb = convertMoveCombToPassByComb(bestComb); + bestMethod = + compareShuttlingAndFlyingAncilla(bestComb, bestFaComb, bestPbComb); + + switch (bestMethod) { + case MoveMethod: + // apply whole move combination at once + for (const auto& move : bestComb.moves) { + applyMove(move); + } + // applyMove(bestComb.moves[0]); + break; + case FlyingAncillaMethod: + applyFlyingAncilla(frontLayer, bestFaComb); + break; + case PassByMethod: + applyPassBy(frontLayer, bestPbComb); + break; + default: + break; + } + } else { for (const auto& move : bestComb.moves) { applyMove(move); } - // applyMove(bestComb.moves[0]); - break; - case FlyingAncillaMethod: - applyFlyingAncilla(frontLayer, bestFaComb); - break; - case PassByMethod: - applyPassBy(frontLayer, bestPbComb); - break; - default: - break; } + mapAllPossibleGates(frontLayer, lookaheadLayer); reassignGatesToLayers(frontLayer.getGates(), lookaheadLayer.getGates()); if (this->parameters->verbose) { From c8fd4ad2eae5b093d1d887a655ddddce2bbaf2ab Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 20 Nov 2025 17:10:18 +0100 Subject: [PATCH 285/394] =?UTF-8?q?=F0=9F=94=A5=20removed=20unused=20code.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridAnimation.cpp | 193 ------------------------------ 1 file changed, 193 deletions(-) diff --git a/src/hybridmap/HybridAnimation.cpp b/src/hybridmap/HybridAnimation.cpp index c38ce3dcf..4f5a43706 100644 --- a/src/hybridmap/HybridAnimation.cpp +++ b/src/hybridmap/HybridAnimation.cpp @@ -157,197 +157,4 @@ std::string AnimationAtoms::opToNaViz(const std::unique_ptr& op, return opString; } -// std::string AnimationAtoms::getEndString(const qc::fp endTime) { -// std::string initString; -// for (const auto& [id, coord] : idToCoord) { -// initString += std::to_string(endTime) + ";" + std::to_string(id) + ";" -// + -// std::to_string(coord.first) + ";" + -// std::to_string(coord.second) + ";1;0;0;0;0;0;0;0\n"; -// } -// return initString; -// } -// -// AnimationAtoms::axesId AnimationAtoms::addAxis(const HwQubit id) { -// if (axesIds.find(id) == axesIds.end()) { -// axesIdCounter++; -// axesIds[id] = axesIdCounter; -// } else { -// throw std::invalid_argument( -// "Tried to add axis but axis already exists for qubit " + -// std::to_string(id)); -// } -// return axesIds[id]; -// } -// AnimationAtoms::marginId AnimationAtoms::addMargin(const HwQubit id) { -// if (marginIds.find(id) == marginIds.end()) { -// marginIdCounter++; -// marginIds[id] = marginIdCounter; -// } else { -// throw std::invalid_argument( -// "Tried to add margin but margin already exists for qubit " + -// std::to_string(id)); -// } -// return marginIds[id]; -// } -// -// std::string -// AnimationAtoms::createCsvOp(const std::unique_ptr& op, -// qc::fp startTime, qc::fp endTime, -// const NeutralAtomArchitecture& arch) { -// std::string csvLine; -// -// for (const auto& coordIdx : op->getUsedQubits()) { -// // if coordIdx unmapped -> continue except it is an AodDeactivate -// if (qc::OpType::AodDeactivate != op->getType() && -// coordIdxToId.find(coordIdx) == coordIdxToId.end()) { -// continue; -// } -// if (op->getType() == qc::OpType::AodDeactivate) { -// // check if there is a qubit at coordIdx -// // if yes -> update coordIdxToId with new coordIdx -// // if not -> throw exception -// for (const auto& idAndCoord : idToCoord) { -// auto id = idAndCoord.first; -// auto coord = idAndCoord.second; -// auto col = coordIdx % arch.getNcolumns(); -// auto row = coordIdx / arch.getNcolumns(); -// if (std::abs(coord.first - (col * arch.getInterQubitDistance())) < -// 0.0001 && -// std::abs(coord.second - (row * arch.getInterQubitDistance())) < -// 0.0001) { -// // remove old coordIdx with same id -// for (const auto& [oldCoordIdx, oldId] : coordIdxToId) { -// if (oldId == id) { -// coordIdxToId.erase(oldCoordIdx); -// break; -// } -// } -// // add new coordIdx with id -// coordIdxToId[coordIdx] = id; -// break; -// } -// } -// } -// if (coordIdxToId.find(coordIdx) == coordIdxToId.end() || -// idToCoord.find(coordIdxToId.at(coordIdx)) == idToCoord.end()) { -// throw std::invalid_argument( -// "Tried to create csv line for qubit at coordIdx " + -// std::to_string(coordIdx) + " but there is no qubit at this -// coordIdx"); -// } -// auto id = coordIdxToId.at(coordIdx); -// auto coord = idToCoord.at(id); -// if (op->getType() == qc::OpType::AodActivate) { -// addAxis(id); -// if (axesIds.find(id) == axesIds.end()) { -// throw std::invalid_argument( -// "Tried to activate qubit at coordIdx " + -// std::to_string(coordIdx) -// + " but there is no axis for qubit " + std::to_string(id)); -// } -// csvLine += createCsvLine(startTime, id, coord.first, coord.second, 1, -// colorSlm, true, axesIds.at(id)); -// csvLine += createCsvLine(endTime, id, coord.first, coord.second, 1, -// colorAod, true, axesIds.at(id)); -// } else if (op->getType() == qc::OpType::AodDeactivate) { -// if (axesIds.find(id) == axesIds.end()) { -// throw std::invalid_argument("Tried to deactivate qubit at coordIdx -// " -// + -// std::to_string(coordIdx) + -// " but there is no axis for qubit " + -// std::to_string(id)); -// } -// csvLine += createCsvLine(startTime, id, coord.first, coord.second, 1, -// colorAod, true, axesIds.at(id)); -// csvLine += createCsvLine(endTime, id, coord.first, coord.second, 1, -// colorSlm, true, axesIds.at(id)); -// removeAxis(id); -// -// } else if (op->getType() == qc::OpType::AodMove) { -// if (axesIds.find(id) == axesIds.end()) { -// throw std::invalid_argument( -// "Tried to move qubit at coordIdx " + std::to_string(coordIdx) + -// " but there is no axis for qubit " + std::to_string(id)); -// } -// csvLine += createCsvLine(startTime, id, coord.first, coord.second, 1, -// colorAod, true, axesIds.at(id)); -// -// // update atom coordinates -// auto startsX = -// dynamic_cast(op.get())->getStarts(Dimension::X); -// auto endsX = -// dynamic_cast(op.get())->getEnds(Dimension::X); auto -// startsY = -// dynamic_cast(op.get())->getStarts(Dimension::Y); -// auto endsY = -// dynamic_cast(op.get())->getEnds(Dimension::Y); for -// (size_t i = 0; i < startsX.size(); i++) { -// if (std::abs(startsX[i] - coord.first) < 0.0001) { -// coord.first = endsX[i]; -// } -// } -// for (size_t i = 0; i < startsY.size(); i++) { -// if (std::abs(startsY[i] - coord.second) < 0.0001) { -// coord.second = endsY[i]; -// } -// } -// // save new coordinates -// idToCoord[id] = coord; -// csvLine += createCsvLine(endTime, id, coord.first, coord.second, 1, -// colorAod, true, axesIds.at(id)); -// } else if (op->getUsedQubits().size() > 1) { // multi qubit gates -// addMargin(id); -// csvLine += createCsvLine(startTime, id, coord.first, coord.second, 1, -// colorSlm, false, 0, false, -// marginIds.at(id)); -// auto midTime = (startTime + endTime) / 2; -// csvLine += -// createCsvLine(midTime, id, coord.first, coord.second, 1, colorCz, -// false, 0, true, marginIds.at(id), -// arch.getBlockingFactor() * -// arch.getInteractionRadius() * -// arch.getInterQubitDistance()); -// csvLine += createCsvLine(endTime, id, coord.first, coord.second, 1, -// colorSlm, false, 0, false, -// marginIds.at(id)); -// removeMargin(id); -// -// } else { // single qubit gates -// csvLine += -// createCsvLine(startTime, id, coord.first, coord.second, 1, -// colorSlm); -// auto midTime = (startTime + endTime) / 2; -// csvLine += -// createCsvLine(midTime, id, coord.first, coord.second, 1, -// colorLocal); -// csvLine += -// createCsvLine(endTime, id, coord.first, coord.second, 1, -// colorSlm); -// } -// } -// return csvLine; -// } -// std::string AnimationAtoms::createCsvLine(const qc::fp startTime, -// const HwQubit id, const qc::fp x, -// const qc::fp y, const uint32_t -// size, const uint32_t color, const -// bool axes, const axesId axId, -// const bool margin, const marginId -// marginId, const qc::fp -// marginSize) -// { -// std::string csvLine; -// csvLine += -// std::to_string(startTime) + ";" + std::to_string(id) + ";" + -// std::to_string(x) + ";" + std::to_string(y) + ";" + -// std::to_string(size) + -// ";" + std::to_string(color) + ";" + std::to_string(color) + ";" + -// std::to_string(static_cast(axes)) + ";" + std::to_string(axId) + -// ";" + std::to_string(static_cast(margin)) + ";" + -// std::to_string(marginId) + ";" + std::to_string(marginSize) + "\n"; -// return csvLine; -// } - } // namespace na From 52d4ac6cfe6d4cb3e2075f339e96371e4c33f686 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 20 Nov 2025 17:14:22 +0100 Subject: [PATCH 286/394] =?UTF-8?q?=F0=9F=8E=A8=20improve=20handling=20of?= =?UTF-8?q?=20wrong=20indices=20during=20animation=20creation.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridAnimation.cpp | 40 ++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/src/hybridmap/HybridAnimation.cpp b/src/hybridmap/HybridAnimation.cpp index 4f5a43706..9609c5c72 100644 --- a/src/hybridmap/HybridAnimation.cpp +++ b/src/hybridmap/HybridAnimation.cpp @@ -16,11 +16,14 @@ #include "ir/operations/AodOperation.hpp" #include "ir/operations/OpType.hpp" +#include +#include #include #include #include #include #include +#include #include namespace na { @@ -91,36 +94,55 @@ std::string AnimationAtoms::opToNaViz(const std::unique_ptr& op, const auto endsY = dynamic_cast(op.get())->getEnds(Dimension::Y); const auto coordIndices = op->getTargets(); // renamed + // The list of targets for an AodMove operation must contain pairs of + // (origin, destination) coordinate indices. + if (coordIndices.size() % 2 != 0) { + throw std::logic_error( + "AodMove targets must be pairs of origin and target indices."); + } + + // Tolerance for floating point comparisons when matching start coordinates. + // use that coord indices are pairs of origin and target indices for (size_t i = 0; i < coordIndices.size(); i++) { if (i % 2 == 0) { + constexpr qc::fp FP_TOLERANCE = 0.0001; const auto coordIdx = coordIndices[i]; + if (!coordIdxToId.contains(coordIdx)) { + throw std::logic_error("AodMove origin index " + + std::to_string(coordIdx) + + " not found in coordIdxToId map."); + } const auto id = coordIdxToId.at(coordIdx); + if (!idToCoord.contains(id)) { + throw std::logic_error("Atom ID " + std::to_string(id) + + " not found in idToCoord map."); + } bool foundX = false; auto newX = std::numeric_limits::max(); bool foundY = false; auto newY = std::numeric_limits::max(); for (size_t j = 0; j < startsX.size(); j++) { - if (std::abs(startsX[j] - idToCoord.at(id).first) < 0.0001) { + if (std::abs(startsX[j] - idToCoord.at(id).first) < FP_TOLERANCE) { newX = endsX[j]; foundX = true; break; } } if (!foundX) { - // X coord is the same as before + // X coord is the same as before if no matching start is found. newX = idToCoord.at(id).first; } for (size_t j = 0; j < startsY.size(); j++) { - if (std::abs(startsY[j] - idToCoord.at(id).second) < 0.0001) { + if (std::abs(startsY[j] - idToCoord.at(id).second) < FP_TOLERANCE) { newY = endsY[j]; foundY = true; break; } } if (!foundY) { - // Y coord is the same as before + // Y coord is the same as before if no matching start is found. newY = idToCoord.at(id).second; } opString += "@" + std::to_string(startTime) + " move (" + @@ -132,8 +154,14 @@ std::string AnimationAtoms::opToNaViz(const std::unique_ptr& op, } else { // this is the target index -> update coordIdxToId const auto coordIdx = coordIndices[i]; - const auto id = coordIdxToId.at(coordIndices[i - 1]); - coordIdxToId.erase(coordIndices[i - 1]); + const auto prevCoordIdx = coordIndices[i - 1]; + if (!coordIdxToId.contains(prevCoordIdx)) { + throw std::logic_error( + "AodMove origin index " + std::to_string(prevCoordIdx) + + " not found in coordIdxToId map during update."); + } + const auto id = coordIdxToId.at(prevCoordIdx); + coordIdxToId.erase(prevCoordIdx); coordIdxToId[coordIdx] = id; } } From 0666ed160e3e4baa517fa4ee7e5ff76dbbf7c818 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 20 Nov 2025 17:32:56 +0100 Subject: [PATCH 287/394] =?UTF-8?q?=F0=9F=8E=A8=20improve=20structure=20of?= =?UTF-8?q?=20evalSynthesisSteps.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridSynthesisMapper.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/hybridmap/HybridSynthesisMapper.cpp b/src/hybridmap/HybridSynthesisMapper.cpp index e0a3a1ff7..a68d8f8c9 100644 --- a/src/hybridmap/HybridSynthesisMapper.cpp +++ b/src/hybridmap/HybridSynthesisMapper.cpp @@ -39,13 +39,14 @@ HybridSynthesisMapper::evaluateSynthesisSteps(qcs& synthesisSteps, size_t qcIndex = 0; for (auto& qc : synthesisSteps) { if (this->parameters->verbose) { - std::cout << "Evaluating synthesis step number " << qcIndex++ << "\n"; + std::cout << "Evaluating synthesis step number " << qcIndex << "\n"; } - candidates.emplace_back(qc, this->evaluateSynthesisStep(qc)); + const auto fidelity = this->evaluateSynthesisStep(qc); + candidates.emplace_back(qc, fidelity); if (this->parameters->verbose) { - std::cout << "Fidelity: " << candidates.back().second << "\n"; + std::cout << "Fidelity: " << fidelity << "\n"; } - qcIndex++; + ++qcIndex; } std::vector fidelities; size_t bestIndex = 0; @@ -57,7 +58,7 @@ HybridSynthesisMapper::evaluateSynthesisSteps(qcs& synthesisSteps, bestIndex = i; } } - if (alsoMap) { + if (alsoMap && !candidates.empty()) { this->appendWithMapping(candidates[bestIndex].first); } return fidelities; From 34c449a2fe24c3abb84271f7176f76024014ebea Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 20 Nov 2025 17:35:28 +0100 Subject: [PATCH 288/394] =?UTF-8?q?=F0=9F=8E=A8=20avoid=20change=20of=20pa?= =?UTF-8?q?ssed=20qc=20by=20creating=20a=20copy.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridSynthesisMapper.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/hybridmap/HybridSynthesisMapper.cpp b/src/hybridmap/HybridSynthesisMapper.cpp index a68d8f8c9..591f6db62 100644 --- a/src/hybridmap/HybridSynthesisMapper.cpp +++ b/src/hybridmap/HybridSynthesisMapper.cpp @@ -68,7 +68,9 @@ qc::fp HybridSynthesisMapper::evaluateSynthesisStep(qc::QuantumComputation& qc) const { NeutralAtomMapper tempMapper(this->arch, this->parameters); tempMapper.copyStateFrom(*this); - auto mappedQc = tempMapper.map(qc, mapping); + // Make a copy of qc to avoid modifying the original + auto qcCopy = qc; + auto mappedQc = tempMapper.map(qcCopy, mapping); tempMapper.convertToAod(); const auto results = tempMapper.schedule(); return results.totalFidelities; From 0ccfc91834437a22c0bd3a17bc75e6c401e5fe8d Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 20 Nov 2025 17:38:38 +0100 Subject: [PATCH 289/394] =?UTF-8?q?=F0=9F=8E=A8=20assign=20also=20other=20?= =?UTF-8?q?triangle=20of=20adjacency=20matrix.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridSynthesisMapper.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/hybridmap/HybridSynthesisMapper.cpp b/src/hybridmap/HybridSynthesisMapper.cpp index 591f6db62..d859539fb 100644 --- a/src/hybridmap/HybridSynthesisMapper.cpp +++ b/src/hybridmap/HybridSynthesisMapper.cpp @@ -106,13 +106,19 @@ AdjacencyMatrix HybridSynthesisMapper::getCircuitAdjacencyMatrix() const { AdjacencyMatrix adjMatrix(numCircQubits); for (uint32_t i = 0; i < numCircQubits; ++i) { - for (uint32_t j = 0; j < i; ++j) { + for (uint32_t j = 0; j < numCircQubits; ++j) { + if (i == j) { + adjMatrix(i, j) = 0; + continue; + } const auto mappedI = this->mapping.getHwQubit(i); const auto mappedJ = this->mapping.getHwQubit(j); if (this->arch->getSwapDistance(mappedI, mappedJ) == 0) { adjMatrix(i, j) = 1; + adjMatrix(j, i) = 1; } else { adjMatrix(i, j) = 0; + adjMatrix(j, i) = 0; } } } From 191ec1b86353ec75bad129c2bd2c64f2529d17ac Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 20 Nov 2025 17:44:05 +0100 Subject: [PATCH 290/394] =?UTF-8?q?=F0=9F=8E=A8=20improve=20invalid=20hand?= =?UTF-8?q?ling=20during=20graphMatching.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/Mapping.cpp | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/src/hybridmap/Mapping.cpp b/src/hybridmap/Mapping.cpp index 4bb3542d1..63a6aa955 100644 --- a/src/hybridmap/Mapping.cpp +++ b/src/hybridmap/Mapping.cpp @@ -43,11 +43,9 @@ void Mapping::applySwap(const Swap& swap) { } std::vector Mapping::graphMatching() { - - std::vector qubitIndices(dag.size(), std::numeric_limits::max()); - std::vector hwIndices(hwQubits.getNumQubits(), - std::numeric_limits::max()); - + constexpr auto invalid = std::numeric_limits::max(); + std::vector qubitIndices(dag.size(), invalid); + std::vector hwIndices(hwQubits.getNumQubits(), invalid); // make hardware graph std::unordered_map> hwGraph; for (qc::Qubit i = 0; i < hwQubits.getNumQubits(); ++i) { @@ -60,7 +58,6 @@ std::vector Mapping::graphMatching() { hwQubits.getNearbyQubits(b).size(); }); } - uint32_t hwCenter = std::numeric_limits::max(); size_t maxHwConnections = 0; for (const auto& [qubit, neighbors] : hwGraph) { @@ -69,10 +66,6 @@ std::vector Mapping::graphMatching() { hwCenter = qubit; } } - if (hwCenter == std::numeric_limits::max()) { - hwCenter = 0; - } - // make circuit graph std::vector>> circGraph(dag.size()); for (qc::Qubit qubit = 0; qubit < dag.size(); ++qubit) { @@ -96,7 +89,6 @@ std::vector Mapping::graphMatching() { }); circGraph[qubit] = std::move(neighbors); } - // circuit queue for graph matching std::vector>> nodes; for (size_t i = 0; i < circGraph.size(); ++i) { @@ -119,7 +111,6 @@ std::vector Mapping::graphMatching() { for (const auto& key : nodes | std::views::keys) { circGraphQueue.push(key); } - // graph matching -> return qubit Indices uint32_t nMapped = 0; bool firstCenter = true; @@ -127,7 +118,7 @@ std::vector Mapping::graphMatching() { auto qi = circGraphQueue.front(); HwQubit qI = std::numeric_limits::max(); // center mapping - if (qubitIndices[qi] == std::numeric_limits::max()) { + if (qubitIndices[qi] == invalid) { // first center if (firstCenter) { qI = hwCenter; @@ -137,7 +128,7 @@ std::vector Mapping::graphMatching() { else { auto minDistance = std::numeric_limits::max(); for (HwQubit qCandi = 0; qCandi < hwQubits.getNumQubits(); ++qCandi) { - if (hwIndices[qCandi] != std::numeric_limits::max()) { + if (hwIndices[qCandi] != invalid) { continue; } auto weightDistance = 0.0; @@ -145,7 +136,7 @@ std::vector Mapping::graphMatching() { auto qn = qnPair.first; auto qnWeight = qnPair.second; HwQubit const qN = qubitIndices[qn]; - if (qN == std::numeric_limits::max()) { + if (qN == invalid) { continue; } weightDistance += @@ -166,12 +157,12 @@ std::vector Mapping::graphMatching() { // neighbor mapping for (auto& key : circGraph[qi] | std::views::keys) { auto const qn = key; - if (qubitIndices[qn] != std::numeric_limits::max()) { + if (qubitIndices[qn] != invalid) { continue; } HwQubit qN = std::numeric_limits::max(); for (const auto& qCandi : hwGraph[qI]) { - if (hwIndices[qCandi] == std::numeric_limits::max()) { + if (hwIndices[qCandi] == invalid) { qN = qCandi; break; } @@ -184,7 +175,6 @@ std::vector Mapping::graphMatching() { } circGraphQueue.pop(); } - return qubitIndices; } } // namespace na From 69a916441e828ed2d830fb9a4b46aca973123a4f Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 20 Nov 2025 17:49:08 +0100 Subject: [PATCH 291/394] =?UTF-8?q?=F0=9F=8E=A8=20changed=20meaning/use=20?= =?UTF-8?q?of=20shuttlingSpeedFactor.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/na_hybrid.md | 4 ++-- src/hybridmap/NeutralAtomArchitecture.cpp | 11 ++++++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/docs/na_hybrid.md b/docs/na_hybrid.md index f6717a69e..50bbd0b4c 100644 --- a/docs/na_hybrid.md +++ b/docs/na_hybrid.md @@ -188,11 +188,11 @@ The first entry corresponds to the x-direction and the second to the y-direction ### Export animation files (optional) HyRoNA can write animation output files that can be visualized with [MQT NAViz](https://github.com/munich-quantum-toolkit/naviz). -Typically one has to accelerate the shuttling speed for better visualization by setting `shuttling_speed_factor` to a value smaller than one. +Typically one has to accelerate the shuttling speed for better visualization by setting `shuttling_speed_factor`. ```{code-cell} ipython3 # Re-run scheduling with animation output enabled -_ = mapper.schedule(verbose=False, create_animation_csv=True, shuttling_speed_factor=0.01) +_ = mapper.schedule(verbose=False, create_animation_csv=True, shuttling_speed_factor=100) # Save the files; the method writes both CSV and HTML next to the given base name mapper.save_animation_files("ghz8_hyrona_animation") diff --git a/src/hybridmap/NeutralAtomArchitecture.cpp b/src/hybridmap/NeutralAtomArchitecture.cpp index e3130ce14..dfc6e6411 100644 --- a/src/hybridmap/NeutralAtomArchitecture.cpp +++ b/src/hybridmap/NeutralAtomArchitecture.cpp @@ -258,20 +258,25 @@ NeutralAtomArchitecture::getNN(const CoordIndex idx) const { } std::string NeutralAtomArchitecture::getAnimationMachine( const qc::fp shuttlingSpeedFactor) const { + if (shuttlingSpeedFactor <= 0) { + throw std::runtime_error( + "Shuttling speed factor must be positive, but is " + + std::to_string(shuttlingSpeedFactor)); + } std::string animationMachine = "name: \"Hybrid_" + this->name + "\"\n"; animationMachine += "movement {\n\tmax_speed: " + - std::to_string(this->getShuttlingTime(qc::OpType::AodMove) / + std::to_string(this->getShuttlingTime(qc::OpType::AodMove) * shuttlingSpeedFactor) + "\n}\n"; animationMachine += "time {\n\tload: " + - std::to_string(this->getShuttlingTime(qc::OpType::AodActivate) * + std::to_string(this->getShuttlingTime(qc::OpType::AodActivate) / shuttlingSpeedFactor) + "\n\tstore: " + - std::to_string(this->getShuttlingTime(qc::OpType::AodDeactivate) * + std::to_string(this->getShuttlingTime(qc::OpType::AodDeactivate) / shuttlingSpeedFactor) + "\n\trz: " + std::to_string(this->getGateTime("x")) + "\n\try: " + std::to_string(this->getGateTime("x")) + From 79d769fb11939728d5176c4ef4cef8594b7b1bdb Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 20 Nov 2025 17:54:47 +0100 Subject: [PATCH 292/394] =?UTF-8?q?=F0=9F=8E=A8=20create=20AnimationAtoms?= =?UTF-8?q?=20only=20if=20animation=20is=20generated.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/NeutralAtomScheduler.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/hybridmap/NeutralAtomScheduler.cpp b/src/hybridmap/NeutralAtomScheduler.cpp index dcb12b32e..62adb692b 100644 --- a/src/hybridmap/NeutralAtomScheduler.cpp +++ b/src/hybridmap/NeutralAtomScheduler.cpp @@ -48,9 +48,10 @@ na::SchedulerResults na::NeutralAtomScheduler::schedule( qc::fp totalGateTime = 0; qc::fp totalGateFidelities = 1; - AnimationAtoms animationAtoms(initHwPos, initFaPos, *arch); + std::optional animationAtoms; if (createAnimationCsv) { - animation += animationAtoms.placeInitAtoms(); + animationAtoms.emplace(initHwPos, initFaPos, *arch); + animation += animationAtoms->placeInitAtoms(); animationMachine = arch->getAnimationMachine(shuttlingSpeedFactor); } @@ -163,7 +164,7 @@ na::SchedulerResults na::NeutralAtomScheduler::schedule( // update animation if (createAnimationCsv) { - animation += animationAtoms.opToNaViz(op, maxTime); + animation += animationAtoms->opToNaViz(op, maxTime); } } if (verbose) { From 582816d5bd2c97b963f244104d02b3ae596eec95 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 20 Nov 2025 18:01:46 +0100 Subject: [PATCH 293/394] =?UTF-8?q?=E2=9C=85=20use=20temp=20paths=20in=20p?= =?UTF-8?q?ython=20tests.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/python/hybrid_mapper/test_hybrid_synthesis.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/python/hybrid_mapper/test_hybrid_synthesis.py b/test/python/hybrid_mapper/test_hybrid_synthesis.py index 5dbf5b74e..e60890f06 100644 --- a/test/python/hybrid_mapper/test_hybrid_synthesis.py +++ b/test/python/hybrid_mapper/test_hybrid_synthesis.py @@ -61,7 +61,7 @@ def test_hybrid_synthesis(arch_filename: str) -> None: "rubidium_shuttling.json", ], ) -def test_hybrid_synthesis_input_output(arch_filename: str) -> None: +def test_hybrid_synthesis_input_output(arch_filename: str, tmp_path: Path) -> None: """Test printing and saving the produced circuits.""" arch = NeutralAtomHybridArchitecture(str(arch_dir / arch_filename)) synthesis_mapper = HybridSynthesisMapper(arch) @@ -73,20 +73,20 @@ def test_hybrid_synthesis_input_output(arch_filename: str) -> None: qasm = synthesis_mapper.get_mapped_qc() assert qasm is not None - filename_mapped = Path(__file__).parent / f"{arch_filename}_mapped.qasm" + filename_mapped = tmp_path / f"{arch_filename}_mapped.qasm" synthesis_mapper.save_mapped_qc(str(filename_mapped)) synthesis_mapper.convert_to_aod() qasm_aod = synthesis_mapper.get_mapped_qc_aod() assert qasm_aod is not None - filename_mapped_aod = Path(__file__).parent / f"{arch_filename}_mapped_aod.qasm" + filename_mapped_aod = tmp_path / f"{arch_filename}_mapped_aod.qasm" synthesis_mapper.save_mapped_qc_aod(str(filename_mapped_aod)) qasm_synth = synthesis_mapper.get_synthesized_qc() assert qasm_synth is not None - filename_synth = Path(__file__).parent / f"{arch_filename}_synthesized.qasm" + filename_synth = tmp_path / f"{arch_filename}_synthesized.qasm" synthesis_mapper.save_synthesized_qc(str(filename_synth)) From 082f9ee90e6baa2a1951d26a472df2a795cd63a5 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 20 Nov 2025 18:11:59 +0100 Subject: [PATCH 294/394] =?UTF-8?q?=F0=9F=90=8D=20fixed=20keep=20alive=20p?= =?UTF-8?q?ython=20binding?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bindings/hybrid_mapper/hybrid_mapper.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bindings/hybrid_mapper/hybrid_mapper.cpp b/bindings/hybrid_mapper/hybrid_mapper.cpp index 6f4dea64a..da626aa39 100644 --- a/bindings/hybrid_mapper/hybrid_mapper.cpp +++ b/bindings/hybrid_mapper/hybrid_mapper.cpp @@ -175,7 +175,8 @@ PYBIND11_MODULE(MQT_QMAP_MODULE_NAME, m, py::mod_gil_not_used()) { "Create Hybrid NA Mapper with mapper parameters", py::keep_alive<1, 2>(), py::keep_alive<1, 3>(), "arch"_a, "params"_a) .def("set_parameters", &na::NeutralAtomMapper::setParameters, - "Set the parameters for the Hybrid NA Mapper", "params"_a) + "Set the parameters for the Hybrid NA Mapper", "params"_a, + py::keep_alive<1, 2>()) .def( "get_init_hw_pos", &na::NeutralAtomMapper::getInitHwPos, "Get the initial hardware positions, required to create an animation") @@ -236,7 +237,8 @@ PYBIND11_MODULE(MQT_QMAP_MODULE_NAME, m, py::mod_gil_not_used()) { py::keep_alive<1, 2>(), py::keep_alive<1, 3>(), "arch"_a, "params"_a = na::MapperParameters()) .def("set_parameters", &na::HybridSynthesisMapper::setParameters, - "Set the parameters for the Hybrid Synthesis Mapper", "params"_a) + "Set the parameters for the Hybrid Synthesis Mapper", "params"_a, + py::keep_alive<1, 2>()) .def("init_mapping", &na::HybridSynthesisMapper::initMapping, "Initializes the synthesized and mapped circuits and mapping " "structures for the given number of qubits.", From d60d29432adf3018926cf2b5175d667cf2df41e3 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 20 Nov 2025 18:14:54 +0100 Subject: [PATCH 295/394] =?UTF-8?q?=F0=9F=8E=A8=20improve=20bookkeeping=20?= =?UTF-8?q?of=20removeHwQubit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HardwareQubits.hpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/include/hybridmap/HardwareQubits.hpp b/include/hybridmap/HardwareQubits.hpp index f8a3904de..d6085e69c 100644 --- a/include/hybridmap/HardwareQubits.hpp +++ b/include/hybridmap/HardwareQubits.hpp @@ -183,10 +183,18 @@ class HardwareQubits { * @param hwQubit Hardware qubit to remove. */ void removeHwQubit(const HwQubit hwQubit) { + const auto currentCoord = hwToCoordIdx.at(hwQubit); hwToCoordIdx.erase(hwQubit); - freeCoordinates.emplace_back(initialHwPos.at(hwQubit)); - occupiedCoordinates.emplace_back(initialHwPos.at(hwQubit)); initialHwPos.erase(hwQubit); + + if (auto it = std::ranges::find(occupiedCoordinates, currentCoord); + it != occupiedCoordinates.end()) { + occupiedCoordinates.erase(it); + } + if (std::ranges::find(freeCoordinates, currentCoord) == + freeCoordinates.end()) { + freeCoordinates.emplace_back(currentCoord); + } // set swap distances to -1 for (uint32_t i = 0; i < swapDistances.size(); ++i) { swapDistances(hwQubit, i) = -1; From f3ceea701e445c8fe88f936c9a847376276def62 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 20 Nov 2025 18:16:54 +0100 Subject: [PATCH 296/394] =?UTF-8?q?=F0=9F=94=A5=20removed=20unused=20DAG?= =?UTF-8?q?=20generation.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridNeutralAtomMapper.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index 354b18687..94435faa6 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -563,7 +563,6 @@ class NeutralAtomMapper { qc::QuantumComputation map(qc::QuantumComputation& qc, const InitialMapping initialMapping) { - const auto dag = qc::CircuitOptimizer::constructDAG(qc); const auto actualMapping = Mapping(qc.getNqubits(), initialMapping, qc, hardwareQubits); return map(qc, actualMapping); From 3250b279807d57beb82eaed9406f13d4dbe98ac3 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 20 Nov 2025 18:19:12 +0100 Subject: [PATCH 297/394] =?UTF-8?q?=F0=9F=8E=A8=20update=20types=20in=20Mo?= =?UTF-8?q?veToAodConverter=20for=20consistency?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/MoveToAodConverter.hpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/include/hybridmap/MoveToAodConverter.hpp b/include/hybridmap/MoveToAodConverter.hpp index 628814c61..84a188a23 100644 --- a/include/hybridmap/MoveToAodConverter.hpp +++ b/include/hybridmap/MoveToAodConverter.hpp @@ -26,7 +26,6 @@ #include #include #include -#include #include #include @@ -52,9 +51,10 @@ using MergeTypeXY = std::pair; class MoveToAodConverter { struct AncillaAtom { struct XAndY { - uint x; - uint y; - XAndY(const uint x, const uint y) : x(x), y(y) {} + std::uint32_t x; + std::uint32_t y; + XAndY(const std::uint32_t xCoord, const std::uint32_t yCoord) + : x(xCoord), y(yCoord) {} }; XAndY coord; @@ -355,7 +355,7 @@ class MoveToAodConverter { hardwareQubits(hardwareQubitsArg) { qcScheduled.addAncillaryRegister(arch.getNpositions()); qcScheduled.addAncillaryRegister(arch.getNpositions(), "fa"); - for (uint i = 0; i < flyingAncillas.getInitHwPos().size(); ++i) { + for (std::uint32_t i = 0; i < flyingAncillas.getInitHwPos().size(); ++i) { const auto coord = flyingAncillas.getInitHwPos().at(i) + (2 * arch.getNpositions()); const auto col = coord % arch.getNcolumns(); From eef684549cf70e59dedfc7cade84ee3a460bc7ea Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 20 Nov 2025 18:21:00 +0100 Subject: [PATCH 298/394] =?UTF-8?q?=F0=9F=90=9B=20reset=20multiQubitGates?= =?UTF-8?q?=20for=20each=20new=20circuit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridNeutralAtomMapper.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 779b07b6c..dedd46904 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -46,6 +46,7 @@ namespace na { void NeutralAtomMapper::mapAppend(qc::QuantumComputation& qc, const Mapping& initialMapping) { // check if multi-qubit gates are present + multiQubitGates = false; for (const auto& op : qc) { if (op->getUsedQubits().size() > 2) { // deactivate static mapping From b609bd3181d2aa2c0b8180ed7d74b8f77c9d1ebe Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 20 Nov 2025 18:24:29 +0100 Subject: [PATCH 299/394] =?UTF-8?q?=F0=9F=90=9B=20fix=20updateBlockedQubit?= =?UTF-8?q?s=20call=20to=20use=20HwQubits=20struct?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridNeutralAtomMapper.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index dedd46904..9b360f569 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -1655,7 +1655,7 @@ size_t NeutralAtomMapper::gateBasedMapping(NeutralAtomLayer& frontLayer, } if (bestMethod == SwapMethod) { lastSwap = bestSwap; - updateBlockedQubits(bestSwap); + updateBlockedQubits(HwQubits{bestSwap.first, bestSwap.second}); applySwap(bestSwap); } From fbc86c6eaff29fa5aee58853809db8fd5d32f37d Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 20 Nov 2025 18:36:16 +0100 Subject: [PATCH 300/394] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20Fixed=20Typo=20in?= =?UTF-8?q?=20Variable=20name.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bindings/hybrid_mapper/hybrid_mapper.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bindings/hybrid_mapper/hybrid_mapper.cpp b/bindings/hybrid_mapper/hybrid_mapper.cpp index da626aa39..5ea8f08be 100644 --- a/bindings/hybrid_mapper/hybrid_mapper.cpp +++ b/bindings/hybrid_mapper/hybrid_mapper.cpp @@ -214,8 +214,8 @@ PYBIND11_MODULE(MQT_QMAP_MODULE_NAME, m, py::mod_gil_not_used()) { .def( "schedule", [](na::NeutralAtomMapper& mapper, const bool verbose, - const bool creatAnimationCsv, const double shuttlingSpeedFactor) { - auto results = mapper.schedule(verbose, creatAnimationCsv, + const bool createAnimationCsv, const double shuttlingSpeedFactor) { + auto results = mapper.schedule(verbose, createAnimationCsv, shuttlingSpeedFactor); return results.toMap(); }, From 7cef35ec1a584bf37d635bf4cbff6f98fb23311a Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 20 Nov 2025 18:37:26 +0100 Subject: [PATCH 301/394] =?UTF-8?q?=F0=9F=8E=A8=20update=20documentation?= =?UTF-8?q?=20for=20adjacency=20matrix=20to=20clarify=20its=20purpose=20in?= =?UTF-8?q?=20mapping?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bindings/hybrid_mapper/hybrid_mapper.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bindings/hybrid_mapper/hybrid_mapper.cpp b/bindings/hybrid_mapper/hybrid_mapper.cpp index 5ea8f08be..0f8f17392 100644 --- a/bindings/hybrid_mapper/hybrid_mapper.cpp +++ b/bindings/hybrid_mapper/hybrid_mapper.cpp @@ -298,7 +298,8 @@ PYBIND11_MODULE(MQT_QMAP_MODULE_NAME, m, py::mod_gil_not_used()) { } return adjMatrix; }, - "Returns the current adjacency matrix of the neutral atom hardware.") + "Returns the current circuit-qubit adjacency matrix used for " + "mapping.") .def( "evaluate_synthesis_steps", [](na::HybridSynthesisMapper& mapper, From a9cea4bd43d34c7ddd0dff9a66daed997a6237bc Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 20 Nov 2025 18:39:11 +0100 Subject: [PATCH 302/394] =?UTF-8?q?=F0=9F=8E=A8=20update=20file=20saving?= =?UTF-8?q?=20documentation=20to=20specify=20output=20formats=20for=20anim?= =?UTF-8?q?ation=20files?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/na_hybrid.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/na_hybrid.md b/docs/na_hybrid.md index 50bbd0b4c..f932f8f6f 100644 --- a/docs/na_hybrid.md +++ b/docs/na_hybrid.md @@ -194,11 +194,13 @@ Typically one has to accelerate the shuttling speed for better visualization by # Re-run scheduling with animation output enabled _ = mapper.schedule(verbose=False, create_animation_csv=True, shuttling_speed_factor=100) -# Save the files; the method writes both CSV and HTML next to the given base name +# Save the files; the method writes `.naviz` and `.namachine` files +# next to the given base name mapper.save_animation_files("ghz8_hyrona_animation") ``` -This creates a `namachine` and a `.naviz` file which can then be imported into MQT NAViz for visualization. +This creates `.namachine` and `.naviz` files which can then be imported into +MQT NAViz for visualization. ![Animation](images/hybrid_shuttling.gif) From 024873d36168c6a0acb43f5bce9d75363264e72c Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 20 Nov 2025 18:47:37 +0100 Subject: [PATCH 303/394] =?UTF-8?q?=F0=9F=8E=A8=20update=20documentation?= =?UTF-8?q?=20for=20animation=20artifacts=20to=20remove=20outdated=20file?= =?UTF-8?q?=20extension?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/NeutralAtomScheduler.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/hybridmap/NeutralAtomScheduler.hpp b/include/hybridmap/NeutralAtomScheduler.hpp index 6285b1cae..1024fb406 100644 --- a/include/hybridmap/NeutralAtomScheduler.hpp +++ b/include/hybridmap/NeutralAtomScheduler.hpp @@ -157,7 +157,7 @@ class NeutralAtomScheduler { [[nodiscard]] std::string getAnimationViz() const { return animation; } /** - * @brief Write animation artifacts (.naviz/.namachine/.nastyle) to disk. + * @brief Write animation artifacts (.naviz/.namachine) to disk. * @details Uses the stem of the provided filename to derive target paths for * each artifact. * @param filename Base filename (its extension is stripped before appending From b47c27ba250e498bcd0ef8f5d7ce64d62e513742 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 20 Nov 2025 18:53:31 +0100 Subject: [PATCH 304/394] =?UTF-8?q?=F0=9F=90=9B=20fix=20insertion=20logic?= =?UTF-8?q?=20in=20HybridNeutralAtomMapper=20to=20correctly=20advance=20it?= =?UTF-8?q?erator=20after=20bridge=20operations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridNeutralAtomMapper.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 9b360f569..acce170bf 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -137,6 +137,7 @@ void NeutralAtomMapper::decomposeBridgeGates(qc::QuantumComputation& qc) const { if ((*it)->isStandardOperation() && (*it)->getType() == qc::Bridge) { const auto targets = (*it)->getTargets(); it = qc.erase(it); + size_t nInserted = 0; for (const auto& bridgeOp : this->arch->getBridgeCircuit(targets.size())) { const auto bridgeQubits = bridgeOp->getUsedQubits(); @@ -148,6 +149,11 @@ void NeutralAtomMapper::decomposeBridgeGates(qc::QuantumComputation& qc) const { qc::Control{targets[*bridgeQubits.begin()]}, targets[*bridgeQubits.rbegin()], qc::Z)); } + ++nInserted; + } + // Advance past all inserted operations + for (size_t i = 0; i < nInserted && it != qc.end(); ++i) { + ++it; } } else { ++it; From 5b588c1ed555925fa1664ff7b802a210c1d14e8f Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 20 Nov 2025 19:10:22 +0100 Subject: [PATCH 305/394] =?UTF-8?q?=F0=9F=90=9B=20add=20check=20for=20empt?= =?UTF-8?q?y=20synthesis=20steps=20in=20evaluateSynthesisSteps=20to=20prev?= =?UTF-8?q?ent=20processing=20errors?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridSynthesisMapper.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/hybridmap/HybridSynthesisMapper.cpp b/src/hybridmap/HybridSynthesisMapper.cpp index d859539fb..436174966 100644 --- a/src/hybridmap/HybridSynthesisMapper.cpp +++ b/src/hybridmap/HybridSynthesisMapper.cpp @@ -31,6 +31,9 @@ namespace na { std::vector HybridSynthesisMapper::evaluateSynthesisSteps(qcs& synthesisSteps, const bool alsoMap) { + if (synthesisSteps.empty()) { + return {}; + } if (!initialized) { initMapping(synthesisSteps[0].getNqubits()); initialized = true; From 4c16c42c3d9987b9664c8fd338161af1a9106512 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 20 Nov 2025 19:12:58 +0100 Subject: [PATCH 306/394] =?UTF-8?q?=F0=9F=90=9B=20fix=20loop=20index=20in?= =?UTF-8?q?=20NeutralAtomArchitecture=20to=20start=20from=201=20for=20corr?= =?UTF-8?q?ect=20qubit=20processing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/NeutralAtomArchitecture.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/hybridmap/NeutralAtomArchitecture.cpp b/src/hybridmap/NeutralAtomArchitecture.cpp index dfc6e6411..38971a30f 100644 --- a/src/hybridmap/NeutralAtomArchitecture.cpp +++ b/src/hybridmap/NeutralAtomArchitecture.cpp @@ -327,7 +327,8 @@ qc::fp NeutralAtomArchitecture::getOpTime(const qc::Operation* op) const { return (distanceX + distanceY) / v; } std::string opName; - for (size_t i = 0; i < op->getNqubits() - 1; ++i) { + const auto nQubits = op->getNqubits(); + for (size_t i = 1; i < nQubits; ++i) { opName += "c"; } if (op->getType() == qc::OpType::P || op->getType() == qc::OpType::RZ) { @@ -351,7 +352,8 @@ qc::fp NeutralAtomArchitecture::getOpFidelity(const qc::Operation* op) const { return getShuttlingAverageFidelity(op->getType()); } std::string opName; - for (size_t i = 0; i < op->getNqubits() - 1; ++i) { + const auto nQubits = op->getNqubits(); + for (size_t i = 1; i < nQubits; ++i) { opName += "c"; } opName += op->getName(); From 5048d2c4ec162276ce3598883526e16232824ee1 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 21 Nov 2025 07:56:20 +0100 Subject: [PATCH 307/394] =?UTF-8?q?=F0=9F=94=A5=20removed=20problematic=20?= =?UTF-8?q?test.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/hybridmap/test_hybridmap.cpp | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/test/hybridmap/test_hybridmap.cpp b/test/hybridmap/test_hybridmap.cpp index 4cde7ccae..84c980c66 100644 --- a/test/hybridmap/test_hybridmap.cpp +++ b/test/hybridmap/test_hybridmap.cpp @@ -257,21 +257,6 @@ TEST(NeutralAtomMapperExceptions, NoMultiQubitSpace) { std::runtime_error); } -TEST(NeutralAtomMapperExceptions, ImpossibleSwaps) { - // Create minimal arch JSON: 1x1 positions, nQubits = 1 => no free coords - const auto arch = - na::NeutralAtomArchitecture("architectures/arch_sparse.json"); - na::MapperParameters p; - p.shuttlingWeight = 0.0; - p.initialCoordMapping = na::InitialCoordinateMapping::Trivial; - p.verbose = true; - na::NeutralAtomMapper mapper(arch, p); - qc::QuantumComputation qc = - qasm3::Importer::importf("circuits/modulo_2.qasm"); - EXPECT_THROW(auto circ = mapper.map(qc, na::InitialMapping::Identity), - std::runtime_error); -} - TEST(NeutralAtomMapperExceptions, LongShuttling) { const auto arch = na::NeutralAtomArchitecture("architectures/rubidium_shuttling.json"); From 04471d82357de99f8f536e7fa62ba7541ff03d8f Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 21 Nov 2025 09:44:40 +0100 Subject: [PATCH 308/394] =?UTF-8?q?=E2=9C=85=20extend=20minimal=20arch=20t?= =?UTF-8?q?o=20two=20qubits=20to=20run=20laodArchitecures.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/hybridmap/architectures/arch_minimal.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/hybridmap/architectures/arch_minimal.json b/test/hybridmap/architectures/arch_minimal.json index 33a47ff4f..9c3b4c0a5 100644 --- a/test/hybridmap/architectures/arch_minimal.json +++ b/test/hybridmap/architectures/arch_minimal.json @@ -2,7 +2,7 @@ "name": "minimal", "properties": { "nRows": 1, - "nColumns": 1, + "nColumns": 2, "nAods": 1, "nAodCoordinates": 1, "interQubitDistance": 3, @@ -11,7 +11,7 @@ "blockingFactor": 1 }, "parameters": { - "nQubits": 1, + "nQubits": 2, "gateTimes": { "none": 0.5 }, From 0fc8b32ff8dec96ef1b610d7cbbb0c6ea2e8edcd Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 21 Nov 2025 09:58:56 +0100 Subject: [PATCH 309/394] =?UTF-8?q?=F0=9F=9A=A8Fixed=20Clang=20warnings.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bindings/hybrid_mapper/hybrid_mapper.cpp | 12 ++++++------ include/hybridmap/HardwareQubits.hpp | 2 +- include/hybridmap/HybridSynthesisMapper.hpp | 2 +- src/hybridmap/HybridAnimation.cpp | 6 +++--- src/hybridmap/NeutralAtomScheduler.cpp | 1 + test/hybridmap/test_hardware_qubits.cpp | 3 ++- test/hybridmap/test_hybrid_synthesis_map.cpp | 3 ++- 7 files changed, 16 insertions(+), 13 deletions(-) diff --git a/bindings/hybrid_mapper/hybrid_mapper.cpp b/bindings/hybrid_mapper/hybrid_mapper.cpp index 0f8f17392..22d66a134 100644 --- a/bindings/hybrid_mapper/hybrid_mapper.cpp +++ b/bindings/hybrid_mapper/hybrid_mapper.cpp @@ -191,9 +191,9 @@ PYBIND11_MODULE(MQT_QMAP_MODULE_NAME, m, py::mod_gil_not_used()) { .def( "map_qasm_file", [](na::NeutralAtomMapper& mapper, const std::string& filename, - const na::InitialMapping initial_mapping) { + const na::InitialMapping initialMapping) { auto circ = qasm3::Importer::importf(filename); - mapper.map(circ, initial_mapping); + mapper.map(circ, initialMapping); }, "Map a quantum circuit to the neutral atom quantum computer", "filename"_a, "initial_mapping"_a = na::InitialMapping::Identity) @@ -314,10 +314,10 @@ PYBIND11_MODULE(MQT_QMAP_MODULE_NAME, m, py::mod_gil_not_used()) { "initial_mapping"_a = na::InitialMapping::Identity) .def( "schedule", - [](na::HybridSynthesisMapper& mapper, bool verbose, - bool create_animation_csv, double shuttling_speed_factor) { - auto results = mapper.schedule(verbose, create_animation_csv, - shuttling_speed_factor); + [](na::HybridSynthesisMapper& mapper, const bool verbose, + const bool createAnimationCsv, const double shuttlingSpeedFactor) { + const auto results = mapper.schedule(verbose, createAnimationCsv, + shuttlingSpeedFactor); return results.toMap(); }, "Schedule the mapped circuit", "verbose"_a = false, diff --git a/include/hybridmap/HardwareQubits.hpp b/include/hybridmap/HardwareQubits.hpp index d6085e69c..ca4da4858 100644 --- a/include/hybridmap/HardwareQubits.hpp +++ b/include/hybridmap/HardwareQubits.hpp @@ -256,7 +256,7 @@ class HardwareQubits { * @return Hardware qubit ID. * @throw std::runtime_error If no qubit occupies the coordinate. */ - HwQubit getHwQubit(const CoordIndex coordIndex) const { + [[nodiscard]] HwQubit getHwQubit(const CoordIndex coordIndex) const { for (auto const& [hwQubit, index] : hwToCoordIdx) { if (index == coordIndex) { return hwQubit; diff --git a/include/hybridmap/HybridSynthesisMapper.hpp b/include/hybridmap/HybridSynthesisMapper.hpp index 3a5266a7b..6f2bd7d7b 100644 --- a/include/hybridmap/HybridSynthesisMapper.hpp +++ b/include/hybridmap/HybridSynthesisMapper.hpp @@ -146,7 +146,7 @@ class HybridSynthesisMapper : public NeutralAtomMapper { * @brief Get the current device adjacency (connectivity) matrix. * @return Symmetric adjacency matrix for the neutral atom hardware. */ - AdjacencyMatrix getCircuitAdjacencyMatrix() const; + [[nodiscard]] AdjacencyMatrix getCircuitAdjacencyMatrix() const; }; } // namespace na diff --git a/src/hybridmap/HybridAnimation.cpp b/src/hybridmap/HybridAnimation.cpp index 9609c5c72..339b44f4a 100644 --- a/src/hybridmap/HybridAnimation.cpp +++ b/src/hybridmap/HybridAnimation.cpp @@ -106,7 +106,7 @@ std::string AnimationAtoms::opToNaViz(const std::unique_ptr& op, // use that coord indices are pairs of origin and target indices for (size_t i = 0; i < coordIndices.size(); i++) { if (i % 2 == 0) { - constexpr qc::fp FP_TOLERANCE = 0.0001; + constexpr qc::fp fpTolerance = 0.0001; const auto coordIdx = coordIndices[i]; if (!coordIdxToId.contains(coordIdx)) { throw std::logic_error("AodMove origin index " + @@ -123,7 +123,7 @@ std::string AnimationAtoms::opToNaViz(const std::unique_ptr& op, bool foundY = false; auto newY = std::numeric_limits::max(); for (size_t j = 0; j < startsX.size(); j++) { - if (std::abs(startsX[j] - idToCoord.at(id).first) < FP_TOLERANCE) { + if (std::abs(startsX[j] - idToCoord.at(id).first) < fpTolerance) { newX = endsX[j]; foundX = true; break; @@ -135,7 +135,7 @@ std::string AnimationAtoms::opToNaViz(const std::unique_ptr& op, } for (size_t j = 0; j < startsY.size(); j++) { - if (std::abs(startsY[j] - idToCoord.at(id).second) < FP_TOLERANCE) { + if (std::abs(startsY[j] - idToCoord.at(id).second) < fpTolerance) { newY = endsY[j]; foundY = true; break; diff --git a/src/hybridmap/NeutralAtomScheduler.cpp b/src/hybridmap/NeutralAtomScheduler.cpp index 62adb692b..b872725ab 100644 --- a/src/hybridmap/NeutralAtomScheduler.cpp +++ b/src/hybridmap/NeutralAtomScheduler.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include diff --git a/test/hybridmap/test_hardware_qubits.cpp b/test/hybridmap/test_hardware_qubits.cpp index f8b3528b6..bbbbdf51b 100644 --- a/test/hybridmap/test_hardware_qubits.cpp +++ b/test/hybridmap/test_hardware_qubits.cpp @@ -27,7 +27,8 @@ TEST(HardwareQubitsExceptions, AccessEmptyCoordThrows) { 0); constexpr auto emptyCoord = static_cast(3); - EXPECT_THROW(hw.getHwQubit(emptyCoord), std::runtime_error); + EXPECT_THROW(static_cast(hw.getHwQubit(emptyCoord)), + std::runtime_error); } TEST(HardwareQubitsExceptions, MoveInvalidCoordinateThrows) { diff --git a/test/hybridmap/test_hybrid_synthesis_map.cpp b/test/hybridmap/test_hybrid_synthesis_map.cpp index cef5fd344..eac08d4a7 100644 --- a/test/hybridmap/test_hybrid_synthesis_map.cpp +++ b/test/hybridmap/test_hybrid_synthesis_map.cpp @@ -62,7 +62,8 @@ TEST_P(TestParametrizedHybridSynthesisMapper, EvaluateSynthesisStep) { params.verbose = true; auto mapper = HybridSynthesisMapper(arch, params); // Intentionally not initializing the mapper to test error handling - EXPECT_THROW(mapper.getCircuitAdjacencyMatrix(), std::runtime_error); + EXPECT_THROW(static_cast(mapper.getCircuitAdjacencyMatrix()), + std::runtime_error); // Initializing with too many qubits to test error handling EXPECT_THROW(mapper.initMapping(50), std::runtime_error); const auto best = mapper.evaluateSynthesisSteps(circuits, true); From 4e857e0e23f6d109bbf0d1ff234b4d253cb566d9 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 21 Nov 2025 10:00:02 +0100 Subject: [PATCH 310/394] =?UTF-8?q?=F0=9F=90=9B=20update=20test=20descript?= =?UTF-8?q?ion=20for=20NeutralAtomMapperExceptions=20to=20clarify=20multi-?= =?UTF-8?q?qubit=20gate=20mapping=20behavior?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/hybridmap/test_hybridmap.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/hybridmap/test_hybridmap.cpp b/test/hybridmap/test_hybridmap.cpp index 84c980c66..1140247f8 100644 --- a/test/hybridmap/test_hybridmap.cpp +++ b/test/hybridmap/test_hybridmap.cpp @@ -246,7 +246,7 @@ TEST(NeutralAtomMapperExceptions, NoFreeCoordsForShuttlingConstructor) { EXPECT_THROW(mapper.setParameters(p1), std::runtime_error); } TEST(NeutralAtomMapperExceptions, NoMultiQubitSpace) { - // Create minimal arch JSON: 1x1 positions, nQubits = 1 => no free coords + // Test that mapping throws when multi-qubit gates cannot be executed const auto arch = na::NeutralAtomArchitecture("architectures/rubidium_gate.json"); constexpr na::MapperParameters p; From 000916fbb675d88b75c25240a03ea5d0d0633e8a Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 21 Nov 2025 11:00:18 +0100 Subject: [PATCH 311/394] =?UTF-8?q?=F0=9F=8E=A8=20removed=20default=20cons?= =?UTF-8?q?tructor=20of=20NeutralAtomScheduler=20to=20avoid=20pointer=20pr?= =?UTF-8?q?oblems.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/NeutralAtomScheduler.hpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/include/hybridmap/NeutralAtomScheduler.hpp b/include/hybridmap/NeutralAtomScheduler.hpp index 1024fb406..9a69d8f7b 100644 --- a/include/hybridmap/NeutralAtomScheduler.hpp +++ b/include/hybridmap/NeutralAtomScheduler.hpp @@ -102,15 +102,12 @@ struct SchedulerResults { */ class NeutralAtomScheduler { protected: - const NeutralAtomArchitecture* arch = nullptr; + const NeutralAtomArchitecture* arch; std::string animation; std::string animationMachine; public: - /** - * @brief Default constructor (no associated architecture yet). - */ - NeutralAtomScheduler() = default; + NeutralAtomScheduler() = delete; /** * @brief Construct with a given neutral atom architecture. * @param architecture Architecture reference whose timing data is used. From 00c6871bc5d8bcc6b9b3ae94cbc20340f6923fe3 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 21 Nov 2025 11:01:23 +0100 Subject: [PATCH 312/394] =?UTF-8?q?=F0=9F=93=9D=20updated=20documentation?= =?UTF-8?q?=20of=20schedule.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/NeutralAtomScheduler.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/hybridmap/NeutralAtomScheduler.hpp b/include/hybridmap/NeutralAtomScheduler.hpp index 9a69d8f7b..a83c468da 100644 --- a/include/hybridmap/NeutralAtomScheduler.hpp +++ b/include/hybridmap/NeutralAtomScheduler.hpp @@ -126,7 +126,7 @@ class NeutralAtomScheduler { * qubit. * @param verbose If true, prints progress and summary to stdout. * @param createAnimationCsv If true, records animation artifacts - * (.naviz/.namachine/.nastyle). + * (.naviz/.namachine). * @param shuttlingSpeedFactor Factor scaling AOD move/activation durations * (1.0 = unchanged). * @return SchedulerResults containing makespan, idle time, fidelity metrics, From 0bb30554840bedf58b1b3a4ead8c4bd996f94eeb Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 21 Nov 2025 11:06:52 +0100 Subject: [PATCH 313/394] =?UTF-8?q?=F0=9F=90=9B=20refactor=20gate=20time?= =?UTF-8?q?=20and=20fidelity=20handling=20to=20ensure=20explicit=20fallbac?= =?UTF-8?q?ks=20and=20avoid=20json=20problems.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/NeutralAtomArchitecture.cpp | 30 +++++++++++++---------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/src/hybridmap/NeutralAtomArchitecture.cpp b/src/hybridmap/NeutralAtomArchitecture.cpp index 38971a30f..22e00721f 100644 --- a/src/hybridmap/NeutralAtomArchitecture.cpp +++ b/src/hybridmap/NeutralAtomArchitecture.cpp @@ -73,25 +73,29 @@ void NeutralAtomArchitecture::loadJson(const std::string& filename) { for (const auto& [key, value] : jsonDataParameters["gateTimes"].items()) { gateTimes.emplace(key, value); } - // check if cz and h gates are present - if (!gateTimes.contains("cz")) { - gateTimes["cz"] = gateTimes["none"]; - } - if (!gateTimes.contains("h")) { - gateTimes["h"] = gateTimes["none"]; - } + // check if cz and h gates are present (require explicit fallback) + auto ensureGateWithFallback = [](auto& map, const std::string& gate, + const std::string& fallback) { + if (map.contains(gate)) { + return; + } + if (!map.contains(fallback)) { + throw std::runtime_error("Missing gate entry \"" + gate + + "\" and fallback \"" + fallback + "\""); + } + map[gate] = map.at(fallback); + }; + + ensureGateWithFallback(gateTimes, "cz", "none"); + ensureGateWithFallback(gateTimes, "h", "none"); this->parameters.gateTimes = gateTimes; std::map gateAverageFidelities; for (const auto& [key, value] : jsonDataParameters["gateAverageFidelities"].items()) { gateAverageFidelities.emplace(key, value); } - if (!gateAverageFidelities.contains("cz")) { - gateAverageFidelities["cz"] = gateAverageFidelities["none"]; - } - if (!gateAverageFidelities.contains("h")) { - gateAverageFidelities["h"] = gateAverageFidelities["none"]; - } + ensureGateWithFallback(gateAverageFidelities, "cz", "none"); + ensureGateWithFallback(gateAverageFidelities, "h", "none"); this->parameters.gateAverageFidelities = gateAverageFidelities; std::map shuttlingTimes; From 1cbd1579176959985c18fcbf84d9b3ed254975d0 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 21 Nov 2025 11:11:03 +0100 Subject: [PATCH 314/394] =?UTF-8?q?=F0=9F=8E=A8=20refactor=20parameter=20n?= =?UTF-8?q?ormalization=20to=20use=20fmod=20for=20improved=20accuracy=20in?= =?UTF-8?q?=20gate=20time=20calculations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/NeutralAtomArchitecture.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/hybridmap/NeutralAtomArchitecture.cpp b/src/hybridmap/NeutralAtomArchitecture.cpp index 22e00721f..45362845e 100644 --- a/src/hybridmap/NeutralAtomArchitecture.cpp +++ b/src/hybridmap/NeutralAtomArchitecture.cpp @@ -339,9 +339,10 @@ qc::fp NeutralAtomArchitecture::getOpTime(const qc::Operation* op) const { // use time of theta = pi and linearly scale opName += "z"; auto param = std::abs(op->getParameter().back()); - constexpr auto pi = std::numbers::pi_v; - while (param > pi) { - param = std::abs(param - (2 * pi)); + constexpr auto twoPi = 2 * std::numbers::pi_v; + param = std::fmod(param, twoPi); + if (param > std::numbers::pi_v) { + param = twoPi - param; // map to [0, pi] } return getGateTime(opName) * param / std::numbers::pi_v; } From c56743736ff80ff0280d4985d1947372395e98d4 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 21 Nov 2025 11:16:35 +0100 Subject: [PATCH 315/394] =?UTF-8?q?=F0=9F=93=9D=20update=20animation=20fil?= =?UTF-8?q?e=20documentation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bindings/hybrid_mapper/hybrid_mapper.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bindings/hybrid_mapper/hybrid_mapper.cpp b/bindings/hybrid_mapper/hybrid_mapper.cpp index 22d66a134..00d967115 100644 --- a/bindings/hybrid_mapper/hybrid_mapper.cpp +++ b/bindings/hybrid_mapper/hybrid_mapper.cpp @@ -222,10 +222,11 @@ PYBIND11_MODULE(MQT_QMAP_MODULE_NAME, m, py::mod_gil_not_used()) { "Schedule the mapped circuit", "verbose"_a = false, "create_animation_csv"_a = false, "shuttling_speed_factor"_a = 1.0) .def("save_animation_files", &na::NeutralAtomMapper::saveAnimationFiles, - "Saves the animation files (csv and html) for the last scheduling", + "Saves the animation files (.naviz and .namachine) for the last " + + "scheduling", "filename"_a) .def("get_animation_viz", &na::NeutralAtomMapper::getAnimationViz, - "Returns the animation csv string for the last scheduling"); + "Returns the .naviz event-log content for the last scheduling"); py::class_( m, "HybridSynthesisMapper", From ef841d9ed257d32af4ed718aed565abda2b47848 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 21 Nov 2025 11:20:00 +0100 Subject: [PATCH 316/394] =?UTF-8?q?=F0=9F=8E=A8=20optimize=20loop=20bounds?= =?UTF-8?q?=20for=20bridge=20gate=20computation=20to=20prevent=20out-of-bo?= =?UTF-8?q?unds=20access?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/NeutralAtomArchitecture.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/hybridmap/NeutralAtomArchitecture.cpp b/src/hybridmap/NeutralAtomArchitecture.cpp index 45362845e..24141f9a5 100644 --- a/src/hybridmap/NeutralAtomArchitecture.cpp +++ b/src/hybridmap/NeutralAtomArchitecture.cpp @@ -114,7 +114,10 @@ void NeutralAtomArchitecture::loadJson(const std::string& filename) { // compute values for Bridge gate // precompute bridge circuits - for (size_t i = 3; i < 10; i++) { + const auto maxIdx = + std::min({bridgeCircuits.czDepth.size(), bridgeCircuits.hDepth.size(), + bridgeCircuits.czs.size(), bridgeCircuits.hs.size()}); + for (size_t i = 3; i < std::min(10, maxIdx); ++i) { qc::fp const bridgeGateTime = (static_cast(bridgeCircuits.czDepth[i]) * gateTimes.at("cz")) + From c5e0d5f3e4797d744077f04307355817eb91f4e2 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 21 Nov 2025 11:22:13 +0100 Subject: [PATCH 317/394] =?UTF-8?q?=F0=9F=8E=A8=20add=20comment=20on=20def?= =?UTF-8?q?ault=20value=20in=20architecture.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/NeutralAtomArchitecture.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/hybridmap/NeutralAtomArchitecture.hpp b/include/hybridmap/NeutralAtomArchitecture.hpp index 4e3a27602..b934ac591 100644 --- a/include/hybridmap/NeutralAtomArchitecture.hpp +++ b/include/hybridmap/NeutralAtomArchitecture.hpp @@ -42,6 +42,7 @@ namespace na { * JSON, derives coordinate layout, connectivity (swap distances), and proximity * lists. Provides timing and fidelity queries, distance helpers, and optional * animation export. + * It requires at last one default "none" entry in gate times and fidelities. */ class NeutralAtomArchitecture { /** From 4d5026b7434a4d3d34eba19951f6bcc54471e5f9 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 21 Nov 2025 11:28:09 +0100 Subject: [PATCH 318/394] =?UTF-8?q?=F0=9F=90=9B=20add=20runtime=20checks?= =?UTF-8?q?=20for=20qubit=20mapping=20to=20ensure=20valid=20hardware=20qub?= =?UTF-8?q?it=20usage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/Mapping.cpp | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/hybridmap/Mapping.cpp b/src/hybridmap/Mapping.cpp index 63a6aa955..4518e2427 100644 --- a/src/hybridmap/Mapping.cpp +++ b/src/hybridmap/Mapping.cpp @@ -44,6 +44,11 @@ void Mapping::applySwap(const Swap& swap) { std::vector Mapping::graphMatching() { constexpr auto invalid = std::numeric_limits::max(); + constexpr auto invalidHw = std::numeric_limits::max(); + if (dag.size() > hwQubits.getNumQubits()) { + throw std::runtime_error( + "graphMatching: more circuit qubits than hardware qubits"); + } std::vector qubitIndices(dag.size(), invalid); std::vector hwIndices(hwQubits.getNumQubits(), invalid); // make hardware graph @@ -58,7 +63,7 @@ std::vector Mapping::graphMatching() { hwQubits.getNearbyQubits(b).size(); }); } - uint32_t hwCenter = std::numeric_limits::max(); + HwQubit hwCenter = invalidHw; size_t maxHwConnections = 0; for (const auto& [qubit, neighbors] : hwGraph) { if (neighbors.size() > maxHwConnections) { @@ -116,11 +121,14 @@ std::vector Mapping::graphMatching() { bool firstCenter = true; while (!circGraphQueue.empty() && nMapped != dag.size()) { auto qi = circGraphQueue.front(); - HwQubit qI = std::numeric_limits::max(); + HwQubit qI = invalidHw; // center mapping if (qubitIndices[qi] == invalid) { // first center if (firstCenter) { + if (hwCenter == invalidHw) { + throw std::runtime_error("graphMatching: no hardware center qubit"); + } qI = hwCenter; firstCenter = false; } @@ -147,6 +155,10 @@ std::vector Mapping::graphMatching() { qI = qCandi; } } + if (qI == invalidHw) { + throw std::runtime_error( + "graphMatching: no free hardware qubit for circuit qubit"); + } } qubitIndices[qi] = qI; hwIndices[qI] = qi; @@ -160,14 +172,14 @@ std::vector Mapping::graphMatching() { if (qubitIndices[qn] != invalid) { continue; } - HwQubit qN = std::numeric_limits::max(); + HwQubit qN = invalidHw; for (const auto& qCandi : hwGraph[qI]) { if (hwIndices[qCandi] == invalid) { qN = qCandi; break; } } - if (qN != std::numeric_limits::max()) { + if (qN != invalidHw) { qubitIndices[qn] = qN; hwIndices[qN] = qn; nMapped++; From 6c142a8cc1d020c715c382c98faadb4ca44c46f5 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 21 Nov 2025 11:30:17 +0100 Subject: [PATCH 319/394] =?UTF-8?q?=E2=9C=85=20update=20synthesis=20evalua?= =?UTF-8?q?tion=20to=20include=20mapping=20verification=20and=20enhance=20?= =?UTF-8?q?output=20validation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/python/hybrid_mapper/test_hybrid_synthesis.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/test/python/hybrid_mapper/test_hybrid_synthesis.py b/test/python/hybrid_mapper/test_hybrid_synthesis.py index e60890f06..0ad0676ce 100644 --- a/test/python/hybrid_mapper/test_hybrid_synthesis.py +++ b/test/python/hybrid_mapper/test_hybrid_synthesis.py @@ -48,9 +48,15 @@ def test_hybrid_synthesis(arch_filename: str) -> None: synthesis_mapper = HybridSynthesisMapper(arch) synthesis_mapper.init_mapping(3) - best_circuit = synthesis_mapper.evaluate_synthesis_steps([qc1, qc2], True) - - assert best_circuit is not None + best_circuit = synthesis_mapper.evaluate_synthesis_steps( + [qc1, qc2], + also_map=True, + ) + + assert isinstance(best_circuit, list) + assert len(best_circuit) == 2 + assert best_circuit[0] <= 1 + assert best_circuit[0] >= 0 @pytest.mark.parametrize( From 1835d774d60836b363cc0cb878d03b846ca01131 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 21 Nov 2025 11:30:31 +0100 Subject: [PATCH 320/394] =?UTF-8?q?=F0=9F=8E=A8=20add=20comments=20to=20cl?= =?UTF-8?q?arify=20indices=20for=20ancillary=20registers=20in=20HybridNeut?= =?UTF-8?q?ralAtomMapper?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridNeutralAtomMapper.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index acce170bf..b8ce77661 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -56,7 +56,9 @@ void NeutralAtomMapper::mapAppend(qc::QuantumComputation& qc, } // only add flying ancillas if not already present if (mappedQc.getNancillae() == 0) { + // ancilla register has indices [npositions, 2*npositions-1] mappedQc.addAncillaryRegister(this->arch->getNpositions()); + // flying ancilla register has indices [2*npositions, 3*npositions-1] mappedQc.addAncillaryRegister(this->arch->getNpositions(), "fa"); } From 57af6f9664b65f13576518b87dc3d41671174ea5 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 21 Nov 2025 11:32:45 +0100 Subject: [PATCH 321/394] =?UTF-8?q?=F0=9F=90=9B=20refactor=20AodMove=20ope?= =?UTF-8?q?ration=20to=20improve=20type=20safety=20and=20reduce=20redundan?= =?UTF-8?q?cy=20in=20coordinate=20retrieval?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridAnimation.cpp | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/hybridmap/HybridAnimation.cpp b/src/hybridmap/HybridAnimation.cpp index 339b44f4a..17eb147f5 100644 --- a/src/hybridmap/HybridAnimation.cpp +++ b/src/hybridmap/HybridAnimation.cpp @@ -85,14 +85,13 @@ std::string AnimationAtoms::opToNaViz(const std::unique_ptr& op, opString += "]\n"; } else if (op->getType() == qc::OpType::AodMove) { // update atom coordinates - const auto startsX = - dynamic_cast(op.get())->getStarts(Dimension::X); - const auto endsX = - dynamic_cast(op.get())->getEnds(Dimension::X); - const auto startsY = - dynamic_cast(op.get())->getStarts(Dimension::Y); - const auto endsY = - dynamic_cast(op.get())->getEnds(Dimension::Y); + const auto* aodOp = dynamic_cast(op.get()); + assert(aodOp != nullptr && + "OpType::AodMove must be backed by AodOperation"); + const auto startsX = aodOp->getStarts(Dimension::X); + const auto endsX = aodOp->getEnds(Dimension::X); + const auto startsY = aodOp->getStarts(Dimension::Y); + const auto endsY = aodOp->getEnds(Dimension::Y); const auto coordIndices = op->getTargets(); // renamed // The list of targets for an AodMove operation must contain pairs of // (origin, destination) coordinate indices. From 23e927ac3c78e47320a6963730ac3fddcf604acc Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 21 Nov 2025 11:33:58 +0100 Subject: [PATCH 322/394] =?UTF-8?q?=F0=9F=8E=A8=20mark=20placeInitAtoms=20?= =?UTF-8?q?method=20as=20const?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridAnimation.hpp | 2 +- src/hybridmap/HybridAnimation.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/hybridmap/HybridAnimation.hpp b/include/hybridmap/HybridAnimation.hpp index 8f390995b..80daff1b2 100644 --- a/include/hybridmap/HybridAnimation.hpp +++ b/include/hybridmap/HybridAnimation.hpp @@ -73,7 +73,7 @@ class AnimationAtoms { * (e.g., "atom (x, y) atom"). * @return Concatenated lines suitable for a NaViz animation file. */ - std::string placeInitAtoms(); + [[nodiscard]] std::string placeInitAtoms() const; /** * @brief Convert a quantum operation into NaViz animation commands. * @details Supports AOD load/store/move operations and standard gates. diff --git a/src/hybridmap/HybridAnimation.cpp b/src/hybridmap/HybridAnimation.cpp index 17eb147f5..d13aecd94 100644 --- a/src/hybridmap/HybridAnimation.cpp +++ b/src/hybridmap/HybridAnimation.cpp @@ -56,7 +56,7 @@ void AnimationAtoms::initPositions( } } -std::string AnimationAtoms::placeInitAtoms() { +std::string AnimationAtoms::placeInitAtoms() const { std::string initString; for (const auto& [id, coords] : idToCoord) { initString += "atom (" + std::to_string(coords.first) + ", " + From b8b5d70b50f6987f53148f46134e1c0f71692696 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 21 Nov 2025 15:08:22 +0100 Subject: [PATCH 323/394] =?UTF-8?q?=F0=9F=90=8D=20updated=20docstring=20in?= =?UTF-8?q?=20python.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bindings/hybrid_mapper/hybrid_mapper.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bindings/hybrid_mapper/hybrid_mapper.cpp b/bindings/hybrid_mapper/hybrid_mapper.cpp index 00d967115..055493952 100644 --- a/bindings/hybrid_mapper/hybrid_mapper.cpp +++ b/bindings/hybrid_mapper/hybrid_mapper.cpp @@ -222,8 +222,8 @@ PYBIND11_MODULE(MQT_QMAP_MODULE_NAME, m, py::mod_gil_not_used()) { "Schedule the mapped circuit", "verbose"_a = false, "create_animation_csv"_a = false, "shuttling_speed_factor"_a = 1.0) .def("save_animation_files", &na::NeutralAtomMapper::saveAnimationFiles, - "Saves the animation files (.naviz and .namachine) for the last " + - "scheduling", + "Saves the animation files (.naviz and .namachine) for the " + "scheduling", "filename"_a) .def("get_animation_viz", &na::NeutralAtomMapper::getAnimationViz, "Returns the .naviz event-log content for the last scheduling"); From 4faca35e4378bd0f2d46fc74c5e23251f82bc425 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 21 Nov 2025 17:43:04 +0100 Subject: [PATCH 324/394] =?UTF-8?q?=F0=9F=90=9B=20Fixed=20wrong=20cast=20i?= =?UTF-8?q?n=20swapDistance=20computation.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/NeutralAtomArchitecture.hpp | 4 ++-- src/hybridmap/NeutralAtomArchitecture.cpp | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/include/hybridmap/NeutralAtomArchitecture.hpp b/include/hybridmap/NeutralAtomArchitecture.hpp index b934ac591..a22c5a42d 100644 --- a/include/hybridmap/NeutralAtomArchitecture.hpp +++ b/include/hybridmap/NeutralAtomArchitecture.hpp @@ -308,8 +308,8 @@ class NeutralAtomArchitecture { [[nodiscard]] SwapDistance getSwapDistance(const Location& c1, const Location& c2) const { return swapDistances( - static_cast(c1.x + c1.y) * properties.getNcolumns(), - static_cast(c2.x + c2.y) * properties.getNcolumns()); + static_cast(c1.x + (c1.y * properties.getNcolumns())), + static_cast(c2.x + (c2.y * properties.getNcolumns()))); } /** diff --git a/src/hybridmap/NeutralAtomArchitecture.cpp b/src/hybridmap/NeutralAtomArchitecture.cpp index 24141f9a5..694f27d5d 100644 --- a/src/hybridmap/NeutralAtomArchitecture.cpp +++ b/src/hybridmap/NeutralAtomArchitecture.cpp @@ -224,6 +224,9 @@ void NeutralAtomArchitecture::computeSwapDistances( deltaY -= diagonalDistance.y; } } + if (swapDistance == 0) { + swapDistance = 1; + } // save swap distance in matrix this->swapDistances(coordIndex1, coordIndex2) = swapDistance - 1; this->swapDistances(coordIndex2, coordIndex1) = swapDistance - 1; From 2d5069613f6a7241657551bfe27927dfb771a44c Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 21 Nov 2025 18:10:28 +0100 Subject: [PATCH 325/394] =?UTF-8?q?=F0=9F=8E=A8=20avoid=20unused=20returne?= =?UTF-8?q?d=20variable?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/hybridmap/test_hybridmap.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/hybridmap/test_hybridmap.cpp b/test/hybridmap/test_hybridmap.cpp index 1140247f8..9aed1582b 100644 --- a/test/hybridmap/test_hybridmap.cpp +++ b/test/hybridmap/test_hybridmap.cpp @@ -253,7 +253,7 @@ TEST(NeutralAtomMapperExceptions, NoMultiQubitSpace) { na::NeutralAtomMapper mapper(arch, p); qc::QuantumComputation qc = qasm3::Importer::importf("circuits/multi_qubit.qasm"); - EXPECT_THROW(auto circ = mapper.map(qc, na::InitialMapping::Identity), + EXPECT_THROW(static_cast(mapper.map(qc, na::InitialMapping::Identity)), std::runtime_error); } From e823a8ecd5576cfa9ff8a6e5ee59a553fa68a538 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 21 Nov 2025 18:13:42 +0100 Subject: [PATCH 326/394] =?UTF-8?q?=F0=9F=90=9B=20add=20assertion=20to=20v?= =?UTF-8?q?alidate=20number=20of=20qubits=20against=20available=20position?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HardwareQubits.hpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/hybridmap/HardwareQubits.hpp b/include/hybridmap/HardwareQubits.hpp index ca4da4858..be6f9e53b 100644 --- a/include/hybridmap/HardwareQubits.hpp +++ b/include/hybridmap/HardwareQubits.hpp @@ -20,6 +20,7 @@ #include "ir/operations/Operation.hpp" #include +#include #include #include #include @@ -103,6 +104,8 @@ class HardwareQubits { uint32_t seed = 0) : arch(&architecture), nQubits(nQubits) { + assert(nQubits <= architecture.getNpositions() && + "Number of hardware qubits exceeds available positions."); swapDistances = qc::SymmetricMatrix(this->nQubits); switch (initialCoordinateMapping) { From 334897b25bda48cf5234154f2b339ccb7b93187d Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 21 Nov 2025 18:15:01 +0100 Subject: [PATCH 327/394] =?UTF-8?q?=F0=9F=8E=A8=20used=20correct=20type=20?= =?UTF-8?q?synonym.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HardwareQubits.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/hybridmap/HardwareQubits.hpp b/include/hybridmap/HardwareQubits.hpp index be6f9e53b..3652510de 100644 --- a/include/hybridmap/HardwareQubits.hpp +++ b/include/hybridmap/HardwareQubits.hpp @@ -368,7 +368,7 @@ class HardwareQubits { * @return Map of hardware qubit to initial coordinate index. */ [[nodiscard]] std::map getInitHwPos() const { - std::map initialHwPosMap; + std::map initialHwPosMap; for (auto const& pair : initialHwPos) { initialHwPosMap[pair.first] = pair.second; } From 0064fb57af7888438187fb9538fa11ad38c8a99a Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 21 Nov 2025 18:20:05 +0100 Subject: [PATCH 328/394] =?UTF-8?q?=F0=9F=90=9B=20add=20Aod=20Information?= =?UTF-8?q?=20to=20python=20bindings.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/NeutralAtomScheduler.hpp | 2 ++ test/hybridmap/test_scheduler.cpp | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/include/hybridmap/NeutralAtomScheduler.hpp b/include/hybridmap/NeutralAtomScheduler.hpp index a83c468da..4931148e8 100644 --- a/include/hybridmap/NeutralAtomScheduler.hpp +++ b/include/hybridmap/NeutralAtomScheduler.hpp @@ -89,6 +89,8 @@ struct SchedulerResults { result["totalGateFidelities"] = totalGateFidelities; result["totalFidelities"] = totalFidelities; result["nCZs"] = nCZs; + result["nAodActivate"] = static_cast(nAodActivate); + result["nAodMove"] = static_cast(nAodMove); return result; } }; diff --git a/test/hybridmap/test_scheduler.cpp b/test/hybridmap/test_scheduler.cpp index c707d4ca4..a604bd560 100644 --- a/test/hybridmap/test_scheduler.cpp +++ b/test/hybridmap/test_scheduler.cpp @@ -25,7 +25,7 @@ TEST(NeutralAtomSchedulerTests, SchedulerResultsToMapForPython) { const auto m = res.toMap(); // Only the documented keys are exported - ASSERT_EQ(m.size(), 5U); + ASSERT_EQ(m.size(), 7U); EXPECT_TRUE(m.count("totalExecutionTime")); EXPECT_TRUE(m.count("totalIdleTime")); EXPECT_TRUE(m.count("totalGateFidelities")); From 3b83627ab7dd76e894249759124bf28e246d5487 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 21 Nov 2025 18:28:04 +0100 Subject: [PATCH 329/394] =?UTF-8?q?=F0=9F=90=9B=20clean=20animation=20outp?= =?UTF-8?q?ut.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/NeutralAtomScheduler.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/hybridmap/NeutralAtomScheduler.cpp b/src/hybridmap/NeutralAtomScheduler.cpp index b872725ab..58e7d2c6a 100644 --- a/src/hybridmap/NeutralAtomScheduler.cpp +++ b/src/hybridmap/NeutralAtomScheduler.cpp @@ -36,6 +36,8 @@ na::SchedulerResults na::NeutralAtomScheduler::schedule( const std::map& initHwPos, const std::map& initFaPos, const bool verbose, const bool createAnimationCsv, const qc::fp shuttlingSpeedFactor) { + animation.clear(); + animationMachine.clear(); if (verbose) { std::cout << "\n* schedule start!\n"; } From 75d11f1ca9dca4c588b6fc9550a12e54c2624bc9 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 21 Nov 2025 18:30:03 +0100 Subject: [PATCH 330/394] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20fixed=20typo=20in?= =?UTF-8?q?=20test=20name?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/hybridmap/test_hybrid_synthesis_map.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/hybridmap/test_hybrid_synthesis_map.cpp b/test/hybridmap/test_hybrid_synthesis_map.cpp index eac08d4a7..572221be6 100644 --- a/test/hybridmap/test_hybrid_synthesis_map.cpp +++ b/test/hybridmap/test_hybrid_synthesis_map.cpp @@ -47,7 +47,7 @@ class TestParametrizedHybridSynthesisMapper // Test the HybridSynthesisMapper class }; -TEST_P(TestParametrizedHybridSynthesisMapper, AdjaencyMatrix) { +TEST_P(TestParametrizedHybridSynthesisMapper, AdjacencyMatrix) { const auto arch = NeutralAtomArchitecture(testArchitecturePath); auto mapper = HybridSynthesisMapper(arch); mapper.initMapping(3); From c39397e537897a05a096d8f0d358236bce7b93ed Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 21 Nov 2025 18:31:53 +0100 Subject: [PATCH 331/394] =?UTF-8?q?=F0=9F=8E=A8=20added=20info=20on=20life?= =?UTF-8?q?time=20in=20docstring?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridAnimation.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/hybridmap/HybridAnimation.hpp b/include/hybridmap/HybridAnimation.hpp index 80daff1b2..9111690d2 100644 --- a/include/hybridmap/HybridAnimation.hpp +++ b/include/hybridmap/HybridAnimation.hpp @@ -29,6 +29,8 @@ namespace na { * atom identifiers, as well as continuous coordinates derived from the * architecture's grid geometry. Provides utilities to emit initial placement * lines and per-operation animation snippets. + * @note The architecture reference passed to the constructor must remain valid + * for the lifetime of this object. */ class AnimationAtoms { protected: From 5d2edf0bebb22c7578ee294a737c23e4c38c3938 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 21 Nov 2025 18:34:02 +0100 Subject: [PATCH 332/394] =?UTF-8?q?=F0=9F=8E=A8=20comment=20on=20visualiza?= =?UTF-8?q?tion=20output?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridAnimation.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/hybridmap/HybridAnimation.cpp b/src/hybridmap/HybridAnimation.cpp index d13aecd94..94cafedb7 100644 --- a/src/hybridmap/HybridAnimation.cpp +++ b/src/hybridmap/HybridAnimation.cpp @@ -165,6 +165,9 @@ std::string AnimationAtoms::opToNaViz(const std::unique_ptr& op, } } // must be a gate + // For visualization: + // - All multi-qubit gates → cz + // - All single-qubit gates → rz } else if (op->getNqubits() > 1) { opString += "@" + std::to_string(startTime) + " cz {"; for (const auto& coordIdx : op->getUsedQubits()) { From 8a62fea5bfcf9342c4cb0a968aac47877e034086 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 21 Nov 2025 19:13:19 +0100 Subject: [PATCH 333/394] =?UTF-8?q?=F0=9F=94=A5removed=20unused=20python?= =?UTF-8?q?=20binding.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bindings/hybrid_mapper/hybrid_mapper.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/bindings/hybrid_mapper/hybrid_mapper.cpp b/bindings/hybrid_mapper/hybrid_mapper.cpp index 055493952..e0cdec21e 100644 --- a/bindings/hybrid_mapper/hybrid_mapper.cpp +++ b/bindings/hybrid_mapper/hybrid_mapper.cpp @@ -124,9 +124,6 @@ PYBIND11_MODULE(MQT_QMAP_MODULE_NAME, m, py::mod_gil_not_used()) { .def_property_readonly( "num_aods", &na::NeutralAtomArchitecture::getNAods, "Number of independent 2D acousto-optic deflectors") - .def_property_readonly("naod_coordinates", - &na::NeutralAtomArchitecture::getNAodCoordinates, - "Maximal number of AOD rows/columns (NOT USED) ") .def_property_readonly("num_qubits", &na::NeutralAtomArchitecture::getNqubits, "Number of atoms in the neutral atom quantum " From 135043bf1bca3caca103cd3ece0c50c3d90228d6 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 21 Nov 2025 19:40:50 +0100 Subject: [PATCH 334/394] =?UTF-8?q?=F0=9F=8E=A8=20add=20trailing=20period?= =?UTF-8?q?=20in=20python=20docstings.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bindings/hybrid_mapper/hybrid_mapper.cpp | 126 +++++++++++------------ 1 file changed, 63 insertions(+), 63 deletions(-) diff --git a/bindings/hybrid_mapper/hybrid_mapper.cpp b/bindings/hybrid_mapper/hybrid_mapper.cpp index e0cdec21e..d94c75ddf 100644 --- a/bindings/hybrid_mapper/hybrid_mapper.cpp +++ b/bindings/hybrid_mapper/hybrid_mapper.cpp @@ -49,117 +49,117 @@ PYBIND11_MODULE(MQT_QMAP_MODULE_NAME, m, py::mod_gil_not_used()) { .export_values() .finalize(); - py::class_(m, "MapperParameters", - "Parameters controlling the mapper behavior") + py::class_( + m, "MapperParameters", "Parameters controlling the mapper behavior.") .def(py::init<>(), - "Create a MapperParameters instance with default values") + "Create a MapperParameters instance with default values.") .def_readwrite("lookahead_depth", &na::MapperParameters::lookaheadDepth, - "Depth of lookahead for mapping decisions") + "Depth of lookahead for mapping decisions.") .def_readwrite("lookahead_weight_swaps", &na::MapperParameters::lookaheadWeightSwaps, - "Weight assigned to swap operations during lookahead") + "Weight assigned to swap operations during lookahead.") .def_readwrite("lookahead_weight_moves", &na::MapperParameters::lookaheadWeightMoves, - "Weight assigned to move operations during lookahead") + "Weight assigned to move operations during lookahead.") .def_readwrite("decay", &na::MapperParameters::decay, - "Decay factor for gate blocking") + "Decay factor for gate blocking.") .def_readwrite("shuttling_time_weight", &na::MapperParameters::shuttlingTimeWeight, - "Weight for shuttling time in cost evaluation") + "Weight for shuttling time in cost evaluation.") .def_readwrite( "dynamic_mapping_weight", &na::MapperParameters::dynamicMappingWeight, - "Weight for dynamic remapping (SWAPs or MOVEs) in cost evaluation") + "Weight for dynamic remapping (SWAPs or MOVEs) in cost evaluation.") .def_readwrite("gate_weight", &na::MapperParameters::gateWeight, - "Weight for gate execution in cost evaluation") + "Weight for gate execution in cost evaluation.") .def_readwrite("shuttling_weight", &na::MapperParameters::shuttlingWeight, - "Weight for shuttling operations in cost evaluation") + "Weight for shuttling operations in cost evaluation.") .def_readwrite( "seed", &na::MapperParameters::seed, - "Random seed for stochastic decisions (initial mapping, etc.)") + "Random seed for stochastic decisions (initial mapping, etc.).") .def_readwrite("num_flying_ancillas", &na::MapperParameters::numFlyingAncillas, - "Number of ancilla qubits to be used (0 or 1 for now)") + "Number of ancilla qubits to be used (0 or 1 for now).") .def_readwrite("limit_shuttling_layer", &na::MapperParameters::limitShuttlingLayer, - "Maximum allowed shuttling layer (default: unlimited)") + "Maximum allowed shuttling layer (default: unlimited).") .def_readwrite("max_bridge_distance", &na::MapperParameters::maxBridgeDistance, - "Maximum distance for bridge operations") + "Maximum distance for bridge operations.") .def_readwrite("use_pass_by", &na::MapperParameters::usePassBy, - "Enable or disable pass-by operations") + "Enable or disable pass-by operations.") .def_readwrite("verbose", &na::MapperParameters::verbose, - "Enable verbose logging for debugging") + "Enable verbose logging for debugging.") .def_readwrite("initial_coord_mapping", &na::MapperParameters::initialCoordMapping, - "Strategy for initial coordinate mapping"); + "Strategy for initial coordinate mapping."); py::class_(m, "MapperStats") .def(py::init<>()) .def_readwrite("num_swaps", &na::MapperStats::nSwaps, - "Number of swap operations performed") + "Number of swap operations performed.") .def_readwrite("num_bridges", &na::MapperStats::nBridges, - "Number of bridge operations performed") + "Number of bridge operations performed.") .def_readwrite("num_f_ancillas", &na::MapperStats::nFAncillas, - "Number of fresh ancilla qubits used") + "Number of fresh ancilla qubits used.") .def_readwrite("num_moves", &na::MapperStats::nMoves, - "Number of move operations performed") + "Number of move operations performed.") .def_readwrite("num_pass_by", &na::MapperStats::nPassBy, - "Number of pass-by operations performed"); + "Number of pass-by operations performed."); py::class_(m, "NeutralAtomHybridArchitecture") .def(py::init(), "filename"_a) .def("load_json", &na::NeutralAtomArchitecture::loadJson, "json_filename"_a) .def_readwrite("name", &na::NeutralAtomArchitecture::name, - "Name of the architecture") + "Name of the architecture.") .def_property_readonly( "num_rows", &na::NeutralAtomArchitecture::getNrows, - "Number of rows in a rectangular grid SLM arrangement") + "Number of rows in a rectangular grid SLM arrangement.") .def_property_readonly( "num_columns", &na::NeutralAtomArchitecture::getNcolumns, - "Number of columns in a rectangular grid SLM arrangement") + "Number of columns in a rectangular grid SLM arrangement.") .def_property_readonly( "num_positions", &na::NeutralAtomArchitecture::getNpositions, - "Total number of positions in a rectangular grid SLM arrangement") + "Total number of positions in a rectangular grid SLM arrangement.") .def_property_readonly( "num_aods", &na::NeutralAtomArchitecture::getNAods, - "Number of independent 2D acousto-optic deflectors") + "Number of independent 2D acousto-optic deflectors.") .def_property_readonly("num_qubits", &na::NeutralAtomArchitecture::getNqubits, "Number of atoms in the neutral atom quantum " - "computer that can be used as qubits") + "computer that can be used as qubits.") .def_property_readonly( "inter_qubit_distance", &na::NeutralAtomArchitecture::getInterQubitDistance, - "Distance between SLM traps in micrometers") + "Distance between SLM traps in micrometers.") .def_property_readonly("interaction_radius", &na::NeutralAtomArchitecture::getInteractionRadius, - "Interaction radius in inter-qubit distances") + "Interaction radius in inter-qubit distances.") .def_property_readonly("blocking_factor", &na::NeutralAtomArchitecture::getBlockingFactor, - "Blocking factor for parallel Rydberg gates") + "Blocking factor for parallel Rydberg gates.") .def_property_readonly( "naod_intermediate_levels", &na::NeutralAtomArchitecture::getNAodIntermediateLevels, - "Number of possible AOD positions between two SLM traps") + "Number of possible AOD positions between two SLM traps.") .def_property_readonly("decoherence_time", &na::NeutralAtomArchitecture::getDecoherenceTime, - "Decoherence time in microseconds") + "Decoherence time in microseconds.") .def("compute_swap_distance", static_cast( &na::NeutralAtomArchitecture::getSwapDistance), - "Number of SWAP gates required between two positions", + "Number of SWAP gates required between two positions.", py::arg("idx1"), py::arg("idx2")) .def("get_gate_time", &na::NeutralAtomArchitecture::getGateTime, - "Execution time of certain gate in microseconds", "s"_a) + "Execution time of certain gate in microseconds.", "s"_a) .def("get_gate_average_fidelity", &na::NeutralAtomArchitecture::getGateAverageFidelity, - "Average gate fidelity from [0,1]", "s"_a) + "Average gate fidelity from [0,1].", "s"_a) .def("get_nearby_coordinates", &na::NeutralAtomArchitecture::getNearbyCoordinates, "Positions that are within the interaction radius of the passed " - "position", + "position.", "idx"_a); py::class_( @@ -169,21 +169,21 @@ PYBIND11_MODULE(MQT_QMAP_MODULE_NAME, m, py::mod_gil_not_used()) { "computer.") .def( py::init(), - "Create Hybrid NA Mapper with mapper parameters", + "Create Hybrid NA Mapper with mapper parameters.", py::keep_alive<1, 2>(), py::keep_alive<1, 3>(), "arch"_a, "params"_a) .def("set_parameters", &na::NeutralAtomMapper::setParameters, - "Set the parameters for the Hybrid NA Mapper", "params"_a, + "Set the parameters for the Hybrid NA Mapper.", "params"_a, py::keep_alive<1, 2>()) - .def( - "get_init_hw_pos", &na::NeutralAtomMapper::getInitHwPos, - "Get the initial hardware positions, required to create an animation") + .def("get_init_hw_pos", &na::NeutralAtomMapper::getInitHwPos, + "Get the initial hardware positions, required to create an " + "animation.") .def( "map", [](na::NeutralAtomMapper& mapper, qc::QuantumComputation& circ, const na::InitialMapping initialMapping) { mapper.map(circ, initialMapping); }, - "Map a quantum circuit object to the neutral atom quantum computer", + "Map a quantum circuit object to the neutral atom quantum computer.", "qc"_a, "initial_mapping"_a = na::InitialMapping::Identity) .def( "map_qasm_file", @@ -192,21 +192,21 @@ PYBIND11_MODULE(MQT_QMAP_MODULE_NAME, m, py::mod_gil_not_used()) { auto circ = qasm3::Importer::importf(filename); mapper.map(circ, initialMapping); }, - "Map a quantum circuit to the neutral atom quantum computer", + "Map a quantum circuit to the neutral atom quantum computer.", "filename"_a, "initial_mapping"_a = na::InitialMapping::Identity) .def("get_stats", &na::NeutralAtomMapper::getStatsMap, - "Returns the statistics of the mapping") + "Returns the statistics of the mapping.") .def("get_mapped_qc", &na::NeutralAtomMapper::getMappedQc, - "Returns the mapped circuit") + "Returns the mapped circuit.") .def("get_mapped_qc_qasm", &na::NeutralAtomMapper::getMappedQcQasm, - "Returns the mapped circuit as an extended qasm2 string") + "Returns the mapped circuit as an extended qasm2 string.") .def("get_mapped_qc_aod_qasm", &na::NeutralAtomMapper::getMappedQcAodQasm, "Returns the mapped circuit with AOD operations as an extended " - "qasm2 string") + "qasm2 string.") .def("save_mapped_qc_aod_qasm", &na::NeutralAtomMapper::saveMappedQcAodQasm, "Saves the mapped circuit with AOD operations as an extended qasm2 " - "string", + "string.", "filename"_a) .def( "schedule", @@ -216,14 +216,14 @@ PYBIND11_MODULE(MQT_QMAP_MODULE_NAME, m, py::mod_gil_not_used()) { shuttlingSpeedFactor); return results.toMap(); }, - "Schedule the mapped circuit", "verbose"_a = false, + "Schedule the mapped circuit.", "verbose"_a = false, "create_animation_csv"_a = false, "shuttling_speed_factor"_a = 1.0) .def("save_animation_files", &na::NeutralAtomMapper::saveAnimationFiles, "Saves the animation files (.naviz and .namachine) for the " - "scheduling", + "scheduling.", "filename"_a) .def("get_animation_viz", &na::NeutralAtomMapper::getAnimationViz, - "Returns the .naviz event-log content for the last scheduling"); + "Returns the .naviz event-log content for the last scheduling."); py::class_( m, "HybridSynthesisMapper", @@ -231,41 +231,41 @@ PYBIND11_MODULE(MQT_QMAP_MODULE_NAME, m, py::mod_gil_not_used()) { "to choose the best one.") .def(py::init(), - "Create Hybrid Synthesis Mapper with mapper parameters", + "Create Hybrid Synthesis Mapper with mapper parameters.", py::keep_alive<1, 2>(), py::keep_alive<1, 3>(), "arch"_a, "params"_a = na::MapperParameters()) .def("set_parameters", &na::HybridSynthesisMapper::setParameters, - "Set the parameters for the Hybrid Synthesis Mapper", "params"_a, + "Set the parameters for the Hybrid Synthesis Mapper.", "params"_a, py::keep_alive<1, 2>()) .def("init_mapping", &na::HybridSynthesisMapper::initMapping, "Initializes the synthesized and mapped circuits and mapping " "structures for the given number of qubits.", "n_qubits"_a) .def("get_mapped_qc", &na::HybridSynthesisMapper::getMappedQcQasm, - "Returns the mapped circuit as an extended qasm2 string") + "Returns the mapped circuit as an extended qasm2 string.") .def("save_mapped_qc", &na::HybridSynthesisMapper::saveMappedQcQasm, - "Saves the mapped circuit as an extended qasm2 to a file", + "Saves the mapped circuit as an extended qasm2 to a file.", "filename"_a) .def("convert_to_aod", &na::HybridSynthesisMapper::convertToAod, "Converts the mapped circuit to " - "native AOD movements") + "native AOD movements.") .def( "get_mapped_qc_aod", &na::HybridSynthesisMapper::getMappedQcAodQasm, "Returns the mapped circuit with native AOD movements as an extended " - "qasm2 string") + "qasm2 string.") .def("save_mapped_qc_aod", &na::HybridSynthesisMapper::saveMappedQcAodQasm, "Saves the mapped circuit with native AOD movements as an extended " "qasm2 to a " - "file", + "file.", "filename"_a) .def("get_synthesized_qc", &na::HybridSynthesisMapper::getSynthesizedQcQASM, "Returns the synthesized circuit with all gates but not " - "mapped to the hardware as a qasm2 string") + "mapped to the hardware as a qasm2 string.") .def("save_synthesized_qc", &na::HybridSynthesisMapper::saveSynthesizedQc, "Saves the synthesized circuit with all gates but not " - "mapped to the hardware as qasm2 to a file", + "mapped to the hardware as qasm2 to a file.", "filename"_a) .def( "append_without_mapping", @@ -318,6 +318,6 @@ PYBIND11_MODULE(MQT_QMAP_MODULE_NAME, m, py::mod_gil_not_used()) { shuttlingSpeedFactor); return results.toMap(); }, - "Schedule the mapped circuit", "verbose"_a = false, + "Schedule the mapped circuit.", "verbose"_a = false, "create_animation_csv"_a = false, "shuttling_speed_factor"_a = 1.0); } From bff341de1a4a4af445304f8da4c4c08ade43d6d0 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 21 Nov 2025 19:41:55 +0100 Subject: [PATCH 335/394] =?UTF-8?q?=F0=9F=8E=A8=20update=20bridge=20length?= =?UTF-8?q?=20comment.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/NeutralAtomArchitecture.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/hybridmap/NeutralAtomArchitecture.hpp b/include/hybridmap/NeutralAtomArchitecture.hpp index a22c5a42d..f9fedd0f7 100644 --- a/include/hybridmap/NeutralAtomArchitecture.hpp +++ b/include/hybridmap/NeutralAtomArchitecture.hpp @@ -179,7 +179,7 @@ class NeutralAtomArchitecture { qc::SymmetricMatrix swapDistances; std::vector> nearbyCoordinates; - // Bridges only makes sense for short distances (<=5) so we limit its size + // Bridges only makes sense for short distances (3-9) so we limit its size BridgeCircuits bridgeCircuits = BridgeCircuits(10); /** From 9dbf60df2d2b5b58b03149170cb9876e4cbd9c62 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 21 Nov 2025 19:43:02 +0100 Subject: [PATCH 336/394] =?UTF-8?q?=F0=9F=8E=A8=20update=20SchedulerResult?= =?UTF-8?q?s=20Docstring.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/NeutralAtomScheduler.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/hybridmap/NeutralAtomScheduler.hpp b/include/hybridmap/NeutralAtomScheduler.hpp index 4931148e8..c13293fa2 100644 --- a/include/hybridmap/NeutralAtomScheduler.hpp +++ b/include/hybridmap/NeutralAtomScheduler.hpp @@ -78,7 +78,7 @@ struct SchedulerResults { /** * @brief Export selected metrics to a key-value map. * @details Includes totalExecutionTime, totalIdleTime, totalGateFidelities, - * totalFidelities, and nCZs (omits AOD counts for brevity). + * totalFidelities, and nCZs. * @return Unordered map from metric names to numeric values. */ [[maybe_unused]] [[nodiscard]] std::unordered_map From 04ab1f08750b5e96dd915c027f60c30bb8b441b9 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 21 Nov 2025 19:45:19 +0100 Subject: [PATCH 337/394] =?UTF-8?q?=F0=9F=8E=A8=20avoid=20unnecessary=20co?= =?UTF-8?q?py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridAnimation.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hybridmap/HybridAnimation.cpp b/src/hybridmap/HybridAnimation.cpp index 94cafedb7..744d52b65 100644 --- a/src/hybridmap/HybridAnimation.cpp +++ b/src/hybridmap/HybridAnimation.cpp @@ -92,7 +92,7 @@ std::string AnimationAtoms::opToNaViz(const std::unique_ptr& op, const auto endsX = aodOp->getEnds(Dimension::X); const auto startsY = aodOp->getStarts(Dimension::Y); const auto endsY = aodOp->getEnds(Dimension::Y); - const auto coordIndices = op->getTargets(); // renamed + const auto& coordIndices = op->getTargets(); // renamed // The list of targets for an AodMove operation must contain pairs of // (origin, destination) coordinate indices. if (coordIndices.size() % 2 != 0) { From 8e7d531f6bfdd560782c98502a2652d3214048e4 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 21 Nov 2025 19:47:46 +0100 Subject: [PATCH 338/394] =?UTF-8?q?=E2=9C=85=20add=20checks=20for=20nAodAc?= =?UTF-8?q?tivate=20and=20nAodMove=20in=20test=5Fscheduler?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/hybridmap/test_scheduler.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/hybridmap/test_scheduler.cpp b/test/hybridmap/test_scheduler.cpp index a604bd560..94d8f62ab 100644 --- a/test/hybridmap/test_scheduler.cpp +++ b/test/hybridmap/test_scheduler.cpp @@ -31,6 +31,8 @@ TEST(NeutralAtomSchedulerTests, SchedulerResultsToMapForPython) { EXPECT_TRUE(m.count("totalGateFidelities")); EXPECT_TRUE(m.count("totalFidelities")); EXPECT_TRUE(m.count("nCZs")); + EXPECT_TRUE(m.count("nAodActivate")); + EXPECT_TRUE(m.count("nAodMove")); // Values preserved exactly EXPECT_DOUBLE_EQ(m.at("totalExecutionTime"), 10.5); @@ -38,4 +40,6 @@ TEST(NeutralAtomSchedulerTests, SchedulerResultsToMapForPython) { EXPECT_DOUBLE_EQ(m.at("totalGateFidelities"), 0.9); EXPECT_DOUBLE_EQ(m.at("totalFidelities"), 0.85); EXPECT_DOUBLE_EQ(m.at("nCZs"), 3.0); + EXPECT_DOUBLE_EQ(m.at("nAodActivate"), 4.0); + EXPECT_DOUBLE_EQ(m.at("nAodMove"), 5.0); } From 052478d9d77ccbd40a30706400ea862cf877b8c0 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 21 Nov 2025 20:14:42 +0100 Subject: [PATCH 339/394] =?UTF-8?q?=F0=9F=8E=A8=20avoid=20uint=20and=20use?= =?UTF-8?q?=20HwQubit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/Mapping.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hybridmap/Mapping.cpp b/src/hybridmap/Mapping.cpp index 4518e2427..c10218ab8 100644 --- a/src/hybridmap/Mapping.cpp +++ b/src/hybridmap/Mapping.cpp @@ -52,13 +52,13 @@ std::vector Mapping::graphMatching() { std::vector qubitIndices(dag.size(), invalid); std::vector hwIndices(hwQubits.getNumQubits(), invalid); // make hardware graph - std::unordered_map> hwGraph; + std::unordered_map hwGraph; for (qc::Qubit i = 0; i < hwQubits.getNumQubits(); ++i) { auto neighbors = hwQubits.getNearbyQubits(i); hwGraph[i] = std::vector(neighbors.begin(), neighbors.end()); } for (auto& neighbors : hwGraph | std::views::values) { - std::ranges::sort(neighbors, [this](const uint32_t a, const uint32_t b) { + std::ranges::sort(neighbors, [this](const HwQubit a, const HwQubit b) { return hwQubits.getNearbyQubits(a).size() > hwQubits.getNearbyQubits(b).size(); }); From 9acddecf7c6688111f530f1b5113ada89940abb5 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 21 Nov 2025 20:20:02 +0100 Subject: [PATCH 340/394] =?UTF-8?q?=F0=9F=8E=A8=20use=20temporary=20files?= =?UTF-8?q?=20for=20tests.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/hybridmap/test_hybrid_synthesis_map.cpp | 8 ++++++- test/hybridmap/test_hybridmap.cpp | 25 +++++++++++++++++--- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/test/hybridmap/test_hybrid_synthesis_map.cpp b/test/hybridmap/test_hybrid_synthesis_map.cpp index 572221be6..951f19c11 100644 --- a/test/hybridmap/test_hybrid_synthesis_map.cpp +++ b/test/hybridmap/test_hybrid_synthesis_map.cpp @@ -18,6 +18,7 @@ #include "hybridmap/NeutralAtomUtils.hpp" #include "ir/QuantumComputation.hpp" +#include #include #include #include @@ -125,7 +126,12 @@ TEST_F(TestHybridSynthesisMapper, Output) { mapper.appendWithMapping(qc); const auto qasm = mapper.getSynthesizedQcQASM(); EXPECT_FALSE(qasm.empty()); - mapper.saveSynthesizedQc("test_output.qasm"); + const auto tempDir = std::filesystem::temp_directory_path(); + const auto qasmPath = tempDir / "test_output.qasm"; + mapper.saveSynthesizedQc(qasmPath.string()); + EXPECT_TRUE(std::filesystem::exists(qasmPath)); + EXPECT_GT(std::filesystem::file_size(qasmPath), 0); + std::filesystem::remove(qasmPath); } } // namespace na diff --git a/test/hybridmap/test_hybridmap.cpp b/test/hybridmap/test_hybridmap.cpp index 9aed1582b..673de3dd0 100644 --- a/test/hybridmap/test_hybridmap.cpp +++ b/test/hybridmap/test_hybridmap.cpp @@ -167,12 +167,22 @@ class NeutralAtomMapperTest : public testing::Test { TEST_F(NeutralAtomMapperTest, Output) { auto qcMapped = mapper.map(qc, initialMapping); // write to file - mapper.saveMappedQcQasm("test.qasm"); + const auto tempDir = std::filesystem::temp_directory_path(); + const auto qasmPath = tempDir / "test.qasm"; + mapper.saveMappedQcQasm(qasmPath.string()); const auto qcMappedFromFile = mapper.getMappedQcQasm(); EXPECT_GT(qcMappedFromFile.size(), 0); - mapper.saveMappedQcAodQasm("test_aod.qasm"); + EXPECT_TRUE(std::filesystem::exists(qasmPath)); + EXPECT_GT(std::filesystem::file_size(qasmPath), 0); + std::filesystem::remove(qasmPath); + + const auto aodQasmPath = tempDir / "test_aod.qasm"; + mapper.saveMappedQcAodQasm(aodQasmPath.string()); const auto qcMappedAod = mapper.getMappedQcAodQasm(); EXPECT_GT(qcMappedAod.size(), 0); + EXPECT_TRUE(std::filesystem::exists(aodQasmPath)); + EXPECT_GT(std::filesystem::file_size(aodQasmPath), 0); + std::filesystem::remove(aodQasmPath); const auto mapperStats = mapper.getStats(); EXPECT_GE(mapperStats.nSwaps + mapperStats.nBridges + mapperStats.nFAncillas + @@ -189,7 +199,16 @@ TEST_F(NeutralAtomMapperTest, Output) { const auto scheduleResults = mapper.schedule(true, true); const auto animationViz = mapper.getAnimationViz(); EXPECT_GT(animationViz.size(), 0); - mapper.saveAnimationFiles("test"); + const auto animationPath = tempDir / "test"; + mapper.saveAnimationFiles(animationPath.string()); + const auto machinePath = animationPath.string() + ".namachine"; + const auto vizPath = animationPath.string() + ".naviz"; + EXPECT_TRUE(std::filesystem::exists(machinePath)); + EXPECT_GT(std::filesystem::file_size(machinePath), 0); + std::filesystem::remove(machinePath); + EXPECT_TRUE(std::filesystem::exists(vizPath)); + EXPECT_GT(std::filesystem::file_size(vizPath), 0); + std::filesystem::remove(vizPath); std::cout << scheduleResults.toCsv(); From b437b35b5070366191a7b3d058f5076008a94818 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 21 Nov 2025 20:29:58 +0100 Subject: [PATCH 341/394] =?UTF-8?q?=F0=9F=8E=A8=20add=20check=20to=20avoid?= =?UTF-8?q?=20division=20by=200.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/NeutralAtomArchitecture.hpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/include/hybridmap/NeutralAtomArchitecture.hpp b/include/hybridmap/NeutralAtomArchitecture.hpp index f9fedd0f7..dde0829fa 100644 --- a/include/hybridmap/NeutralAtomArchitecture.hpp +++ b/include/hybridmap/NeutralAtomArchitecture.hpp @@ -21,6 +21,7 @@ #include "na/entities/Location.hpp" #include +#include #include #include #include @@ -84,10 +85,11 @@ class NeutralAtomArchitecture { const qc::fp qubitDistance, const qc::fp radius, const qc::fp blockingFac, const qc::fp aodDist) : nRows(rows), nColumns(columns), nAods(aods), - nAodIntermediateLevels( - static_cast(qubitDistance / aodDist)), nAodCoordinates(aodCoordinates), interQubitDistance(qubitDistance), - interactionRadius(radius), blockingFactor(blockingFac) {} + interactionRadius(radius), blockingFactor(blockingFac) { + assert(aodDist > 0); + nAodIntermediateLevels = static_cast(qubitDistance / aodDist); + } /** * @brief Total grid sites (rows*columns). * @return Number of positions. From 139c9b45874ea42034f547205848ec0b7d38bef3 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 21 Nov 2025 20:32:56 +0100 Subject: [PATCH 342/394] =?UTF-8?q?=F0=9F=8E=A8=20add=20check=20to=20avoid?= =?UTF-8?q?=20out=20of=20range.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/NeutralAtomArchitecture.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/hybridmap/NeutralAtomArchitecture.hpp b/include/hybridmap/NeutralAtomArchitecture.hpp index dde0829fa..12ae58f95 100644 --- a/include/hybridmap/NeutralAtomArchitecture.hpp +++ b/include/hybridmap/NeutralAtomArchitecture.hpp @@ -426,6 +426,7 @@ class NeutralAtomArchitecture { */ [[nodiscard]] [[maybe_unused]] qc::QuantumComputation getBridgeCircuit(const size_t length) const { + assert(length < bridgeCircuits.bridgeCircuits.size()); return bridgeCircuits.bridgeCircuits[length]; } From 6629074d1f3d17bd8115946ad92825e8a5662356 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 21 Nov 2025 20:35:03 +0100 Subject: [PATCH 343/394] =?UTF-8?q?=F0=9F=8E=A8=20add=20check=20to=20assur?= =?UTF-8?q?e=20that=20aod=20variables=20have=20same=20size.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridAnimation.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/hybridmap/HybridAnimation.cpp b/src/hybridmap/HybridAnimation.cpp index 744d52b65..8e8877ee0 100644 --- a/src/hybridmap/HybridAnimation.cpp +++ b/src/hybridmap/HybridAnimation.cpp @@ -92,6 +92,8 @@ std::string AnimationAtoms::opToNaViz(const std::unique_ptr& op, const auto endsX = aodOp->getEnds(Dimension::X); const auto startsY = aodOp->getStarts(Dimension::Y); const auto endsY = aodOp->getEnds(Dimension::Y); + assert(startsX.size() == endsX.size()); + assert(startsY.size() == endsY.size()); const auto& coordIndices = op->getTargets(); // renamed // The list of targets for an AodMove operation must contain pairs of // (origin, destination) coordinate indices. From 028d3b5d1840f8f2a81d83c9f9165a32475fa539 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 21 Nov 2025 21:16:54 +0100 Subject: [PATCH 344/394] =?UTF-8?q?=F0=9F=8E=A8=20reworked=20AdjMatrix=20c?= =?UTF-8?q?reation.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bindings/hybrid_mapper/hybrid_mapper.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/bindings/hybrid_mapper/hybrid_mapper.cpp b/bindings/hybrid_mapper/hybrid_mapper.cpp index d94c75ddf..1a1ca0fe7 100644 --- a/bindings/hybrid_mapper/hybrid_mapper.cpp +++ b/bindings/hybrid_mapper/hybrid_mapper.cpp @@ -287,11 +287,11 @@ PYBIND11_MODULE(MQT_QMAP_MODULE_NAME, m, py::mod_gil_not_used()) { "get_circuit_adjacency_matrix", [](const na::HybridSynthesisMapper& mapper) { const auto symAdjMatrix = mapper.getCircuitAdjacencyMatrix(); - std::vector> adjMatrix = {}; - for (size_t i = 0; i < symAdjMatrix.size(); ++i) { - adjMatrix.emplace_back(); - for (size_t j = 0; j < symAdjMatrix.size(); ++j) { - adjMatrix[i].emplace_back(symAdjMatrix(i, j)); + const auto n = symAdjMatrix.size(); + std::vector> adjMatrix(n, std::vector(n)); + for (size_t i = 0; i < n; ++i) { + for (size_t j = 0; j < n; ++j) { + adjMatrix[i][j] = symAdjMatrix(i, j); } } return adjMatrix; From 60e84c7539c1aedb53333e1adc9089d4b8b51085 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 21 Nov 2025 21:27:34 +0100 Subject: [PATCH 345/394] =?UTF-8?q?=F0=9F=8E=A8=20Changed=20uint32=20to=20?= =?UTF-8?q?HwQubit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/Mapping.hpp | 2 +- src/hybridmap/Mapping.cpp | 23 +++++++++++------------ 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/include/hybridmap/Mapping.hpp b/include/hybridmap/Mapping.hpp index 1d8b5a013..a4733b7a1 100644 --- a/include/hybridmap/Mapping.hpp +++ b/include/hybridmap/Mapping.hpp @@ -53,7 +53,7 @@ class Mapping { * qubit index i. */ [[nodiscard]] - std::vector graphMatching(); + std::vector graphMatching(); public: /** diff --git a/src/hybridmap/Mapping.cpp b/src/hybridmap/Mapping.cpp index c10218ab8..29587b612 100644 --- a/src/hybridmap/Mapping.cpp +++ b/src/hybridmap/Mapping.cpp @@ -42,15 +42,14 @@ void Mapping::applySwap(const Swap& swap) { } } -std::vector Mapping::graphMatching() { - constexpr auto invalid = std::numeric_limits::max(); +std::vector Mapping::graphMatching() { constexpr auto invalidHw = std::numeric_limits::max(); if (dag.size() > hwQubits.getNumQubits()) { throw std::runtime_error( "graphMatching: more circuit qubits than hardware qubits"); } - std::vector qubitIndices(dag.size(), invalid); - std::vector hwIndices(hwQubits.getNumQubits(), invalid); + std::vector qubitIndices(dag.size(), invalidHw); + std::vector hwIndices(hwQubits.getNumQubits(), invalidHw); // make hardware graph std::unordered_map hwGraph; for (qc::Qubit i = 0; i < hwQubits.getNumQubits(); ++i) { @@ -95,7 +94,7 @@ std::vector Mapping::graphMatching() { circGraph[qubit] = std::move(neighbors); } // circuit queue for graph matching - std::vector>> nodes; + std::vector>> nodes; for (size_t i = 0; i < circGraph.size(); ++i) { const auto degree = circGraph[i].size(); double weightSum = 0; @@ -112,18 +111,18 @@ std::vector Mapping::graphMatching() { } return a.second.first > b.second.first; }); - std::queue circGraphQueue; + std::queue circGraphQueue; for (const auto& key : nodes | std::views::keys) { circGraphQueue.push(key); } // graph matching -> return qubit Indices - uint32_t nMapped = 0; + HwQubit nMapped = 0; bool firstCenter = true; while (!circGraphQueue.empty() && nMapped != dag.size()) { auto qi = circGraphQueue.front(); HwQubit qI = invalidHw; // center mapping - if (qubitIndices[qi] == invalid) { + if (qubitIndices[qi] == invalidHw) { // first center if (firstCenter) { if (hwCenter == invalidHw) { @@ -136,7 +135,7 @@ std::vector Mapping::graphMatching() { else { auto minDistance = std::numeric_limits::max(); for (HwQubit qCandi = 0; qCandi < hwQubits.getNumQubits(); ++qCandi) { - if (hwIndices[qCandi] != invalid) { + if (hwIndices[qCandi] != invalidHw) { continue; } auto weightDistance = 0.0; @@ -144,7 +143,7 @@ std::vector Mapping::graphMatching() { auto qn = qnPair.first; auto qnWeight = qnPair.second; HwQubit const qN = qubitIndices[qn]; - if (qN == invalid) { + if (qN == invalidHw) { continue; } weightDistance += @@ -169,12 +168,12 @@ std::vector Mapping::graphMatching() { // neighbor mapping for (auto& key : circGraph[qi] | std::views::keys) { auto const qn = key; - if (qubitIndices[qn] != invalid) { + if (qubitIndices[qn] != invalidHw) { continue; } HwQubit qN = invalidHw; for (const auto& qCandi : hwGraph[qI]) { - if (hwIndices[qCandi] == invalid) { + if (hwIndices[qCandi] == invalidHw) { qN = qCandi; break; } From 9d99116de88b235a9a6f9fcb9ab5d7ea4a7b6254 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 21 Nov 2025 21:28:50 +0100 Subject: [PATCH 346/394] =?UTF-8?q?=F0=9F=8E=A8=20update=20doc=20string=20?= =?UTF-8?q?(again)=20for=20Scheduler.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/NeutralAtomScheduler.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/hybridmap/NeutralAtomScheduler.hpp b/include/hybridmap/NeutralAtomScheduler.hpp index c13293fa2..bd62031b4 100644 --- a/include/hybridmap/NeutralAtomScheduler.hpp +++ b/include/hybridmap/NeutralAtomScheduler.hpp @@ -78,7 +78,7 @@ struct SchedulerResults { /** * @brief Export selected metrics to a key-value map. * @details Includes totalExecutionTime, totalIdleTime, totalGateFidelities, - * totalFidelities, and nCZs. + * totalFidelities, nCZs, nAodActivate, nAodMove. * @return Unordered map from metric names to numeric values. */ [[maybe_unused]] [[nodiscard]] std::unordered_map From 2134f9a197dd2e2963503c1adee36e4dacdff233 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 21 Nov 2025 21:37:42 +0100 Subject: [PATCH 347/394] =?UTF-8?q?=F0=9F=9A=A8=20removed=20unused=20impor?= =?UTF-8?q?t=20for=20linter.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/Mapping.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/hybridmap/Mapping.cpp b/src/hybridmap/Mapping.cpp index 29587b612..cc937db07 100644 --- a/src/hybridmap/Mapping.cpp +++ b/src/hybridmap/Mapping.cpp @@ -15,7 +15,6 @@ #include #include -#include #include #include #include From 214889b2c9051531d42a6eeb55280ad8c92a163a Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 21 Nov 2025 22:20:40 +0100 Subject: [PATCH 348/394] =?UTF-8?q?=F0=9F=8E=A8Fix=20nodes=20sort=20compar?= =?UTF-8?q?ator=20parameter=20types=20in=20graphMatching?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/Mapping.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/hybridmap/Mapping.cpp b/src/hybridmap/Mapping.cpp index cc937db07..32526e4f1 100644 --- a/src/hybridmap/Mapping.cpp +++ b/src/hybridmap/Mapping.cpp @@ -103,8 +103,7 @@ std::vector Mapping::graphMatching() { nodes.emplace_back(i, std::make_pair(degree, weightSum)); } std::ranges::sort(nodes.begin(), nodes.end(), - [](const std::pair>& a, - const std::pair>& b) { + [](const auto& a, const auto& b) { if (a.second.first == b.second.first) { return a.second.second > b.second.second; } From c46a38dcad9b84819ae1f67d4c339dd22f4e9051 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 21 Nov 2025 22:22:33 +0100 Subject: [PATCH 349/394] =?UTF-8?q?=F0=9F=8E=A8added=20another=20Check=20f?= =?UTF-8?q?or=20nAodLevels.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/NeutralAtomArchitecture.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/hybridmap/NeutralAtomArchitecture.hpp b/include/hybridmap/NeutralAtomArchitecture.hpp index 12ae58f95..39bf12712 100644 --- a/include/hybridmap/NeutralAtomArchitecture.hpp +++ b/include/hybridmap/NeutralAtomArchitecture.hpp @@ -89,6 +89,7 @@ class NeutralAtomArchitecture { interactionRadius(radius), blockingFactor(blockingFac) { assert(aodDist > 0); nAodIntermediateLevels = static_cast(qubitDistance / aodDist); + assert(nAodIntermediateLevels >= 1); } /** * @brief Total grid sites (rows*columns). From 615dc1cb204be4e338509e98a92e65b6db098b3f Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 21 Nov 2025 23:20:28 +0100 Subject: [PATCH 350/394] =?UTF-8?q?=F0=9F=8E=A8change=20Qubit=20to=20HwQub?= =?UTF-8?q?it.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/Mapping.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/hybridmap/Mapping.cpp b/src/hybridmap/Mapping.cpp index 32526e4f1..003e5cd54 100644 --- a/src/hybridmap/Mapping.cpp +++ b/src/hybridmap/Mapping.cpp @@ -51,7 +51,7 @@ std::vector Mapping::graphMatching() { std::vector hwIndices(hwQubits.getNumQubits(), invalidHw); // make hardware graph std::unordered_map hwGraph; - for (qc::Qubit i = 0; i < hwQubits.getNumQubits(); ++i) { + for (HwQubit i = 0; i < hwQubits.getNumQubits(); ++i) { auto neighbors = hwQubits.getNearbyQubits(i); hwGraph[i] = std::vector(neighbors.begin(), neighbors.end()); } @@ -70,9 +70,9 @@ std::vector Mapping::graphMatching() { } } // make circuit graph - std::vector>> circGraph(dag.size()); - for (qc::Qubit qubit = 0; qubit < dag.size(); ++qubit) { - std::unordered_map weightMap; + std::vector>> circGraph(dag.size()); + for (HwQubit qubit = 0; qubit < dag.size(); ++qubit) { + std::unordered_map weightMap; for (const auto& opPtr : dag[qubit]) { const auto* op = opPtr->get(); auto usedQubits = op->getUsedQubits(); @@ -84,10 +84,10 @@ std::vector Mapping::graphMatching() { } } } - std::vector> neighbors(weightMap.begin(), - weightMap.end()); - std::ranges::sort(neighbors, [](const std::pair& a, - const std::pair& b) { + std::vector> neighbors(weightMap.begin(), + weightMap.end()); + std::ranges::sort(neighbors, [](const std::pair& a, + const std::pair& b) { return a.second > b.second; }); circGraph[qubit] = std::move(neighbors); From 282b1135782c62eb8bb08e4555a1fe38c494ca0e Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 21 Nov 2025 23:21:25 +0100 Subject: [PATCH 351/394] =?UTF-8?q?=F0=9F=8E=A8fixed=20counter=20type.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/Mapping.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hybridmap/Mapping.cpp b/src/hybridmap/Mapping.cpp index 003e5cd54..a7cc3c156 100644 --- a/src/hybridmap/Mapping.cpp +++ b/src/hybridmap/Mapping.cpp @@ -114,7 +114,7 @@ std::vector Mapping::graphMatching() { circGraphQueue.push(key); } // graph matching -> return qubit Indices - HwQubit nMapped = 0; + size_t nMapped = 0; bool firstCenter = true; while (!circGraphQueue.empty() && nMapped != dag.size()) { auto qi = circGraphQueue.front(); From c9bd3819c55d0e7e9e8b04385fb366e4c2e43040 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 21 Nov 2025 23:24:30 +0100 Subject: [PATCH 352/394] =?UTF-8?q?=F0=9F=8E=A8fixed=20counter=20type.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/Mapping.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hybridmap/Mapping.cpp b/src/hybridmap/Mapping.cpp index a7cc3c156..7866ef49c 100644 --- a/src/hybridmap/Mapping.cpp +++ b/src/hybridmap/Mapping.cpp @@ -93,7 +93,7 @@ std::vector Mapping::graphMatching() { circGraph[qubit] = std::move(neighbors); } // circuit queue for graph matching - std::vector>> nodes; + std::vector>> nodes; for (size_t i = 0; i < circGraph.size(); ++i) { const auto degree = circGraph[i].size(); double weightSum = 0; From 90e72da37301dde5a3543734ee35c358b5f737cf Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Sun, 23 Nov 2025 11:05:23 +0100 Subject: [PATCH 353/394] =?UTF-8?q?=F0=9F=8E=A8fixed=20types=20in=20Mappin?= =?UTF-8?q?g.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/Mapping.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/hybridmap/Mapping.cpp b/src/hybridmap/Mapping.cpp index 7866ef49c..46210a42b 100644 --- a/src/hybridmap/Mapping.cpp +++ b/src/hybridmap/Mapping.cpp @@ -70,9 +70,9 @@ std::vector Mapping::graphMatching() { } } // make circuit graph - std::vector>> circGraph(dag.size()); - for (HwQubit qubit = 0; qubit < dag.size(); ++qubit) { - std::unordered_map weightMap; + std::vector>> circGraph(dag.size()); + for (qc::Qubit qubit = 0; qubit < dag.size(); ++qubit) { + std::unordered_map weightMap; for (const auto& opPtr : dag[qubit]) { const auto* op = opPtr->get(); auto usedQubits = op->getUsedQubits(); @@ -84,16 +84,16 @@ std::vector Mapping::graphMatching() { } } } - std::vector> neighbors(weightMap.begin(), - weightMap.end()); - std::ranges::sort(neighbors, [](const std::pair& a, - const std::pair& b) { + std::vector> neighbors(weightMap.begin(), + weightMap.end()); + std::ranges::sort(neighbors, [](const std::pair& a, + const std::pair& b) { return a.second > b.second; }); circGraph[qubit] = std::move(neighbors); } // circuit queue for graph matching - std::vector>> nodes; + std::vector>> nodes; for (size_t i = 0; i < circGraph.size(); ++i) { const auto degree = circGraph[i].size(); double weightSum = 0; @@ -109,7 +109,7 @@ std::vector Mapping::graphMatching() { } return a.second.first > b.second.first; }); - std::queue circGraphQueue; + std::queue circGraphQueue; for (const auto& key : nodes | std::views::keys) { circGraphQueue.push(key); } @@ -137,7 +137,7 @@ std::vector Mapping::graphMatching() { continue; } auto weightDistance = 0.0; - for (auto qnPair : circGraph[qi]) { + for (const auto& qnPair : circGraph[qi]) { auto qn = qnPair.first; auto qnWeight = qnPair.second; HwQubit const qN = qubitIndices[qn]; From bb2f5da631e7f2511816f819cbabc29eba99884c Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Sun, 23 Nov 2025 11:36:53 +0100 Subject: [PATCH 354/394] =?UTF-8?q?=F0=9F=8E=A8fixed=20types=20in=20Mappin?= =?UTF-8?q?g=20again.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/Mapping.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/hybridmap/Mapping.cpp b/src/hybridmap/Mapping.cpp index 46210a42b..8f622de03 100644 --- a/src/hybridmap/Mapping.cpp +++ b/src/hybridmap/Mapping.cpp @@ -43,12 +43,13 @@ void Mapping::applySwap(const Swap& swap) { std::vector Mapping::graphMatching() { constexpr auto invalidHw = std::numeric_limits::max(); + constexpr auto invalidCirc = std::numeric_limits::max(); if (dag.size() > hwQubits.getNumQubits()) { throw std::runtime_error( "graphMatching: more circuit qubits than hardware qubits"); } std::vector qubitIndices(dag.size(), invalidHw); - std::vector hwIndices(hwQubits.getNumQubits(), invalidHw); + std::vector hwIndices(hwQubits.getNumQubits(), invalidCirc); // make hardware graph std::unordered_map hwGraph; for (HwQubit i = 0; i < hwQubits.getNumQubits(); ++i) { @@ -133,7 +134,7 @@ std::vector Mapping::graphMatching() { else { auto minDistance = std::numeric_limits::max(); for (HwQubit qCandi = 0; qCandi < hwQubits.getNumQubits(); ++qCandi) { - if (hwIndices[qCandi] != invalidHw) { + if (hwIndices[qCandi] != invalidCirc) { continue; } auto weightDistance = 0.0; @@ -171,7 +172,7 @@ std::vector Mapping::graphMatching() { } HwQubit qN = invalidHw; for (const auto& qCandi : hwGraph[qI]) { - if (hwIndices[qCandi] == invalidHw) { + if (hwIndices[qCandi] == invalidCirc) { qN = qCandi; break; } From a8259c2f50e9ef553426ed57e2ccd118905c6fae Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 27 Nov 2025 09:06:10 +0100 Subject: [PATCH 355/394] =?UTF-8?q?=F0=9F=8E=A8=20changed=20MapperParams?= =?UTF-8?q?=20to=20avoid=20the=20use=20of=20Pointers.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridNeutralAtomMapper.hpp | 30 +++---- src/hybridmap/HybridNeutralAtomMapper.cpp | 78 +++++++++---------- src/hybridmap/HybridSynthesisMapper.cpp | 4 +- 3 files changed, 56 insertions(+), 56 deletions(-) diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index 94435faa6..2771f1817 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -118,7 +118,7 @@ class NeutralAtomMapper { // The minimal weight for any multi-qubit gate qc::fp twoQubitSwapWeight = 1; // The runtime parameters of the mapper - const MapperParameters* parameters = nullptr; + MapperParameters parameters; // The qubits that are blocked by the last swap std::deque> lastBlockedQubits; // The last swap that has been executed @@ -458,29 +458,29 @@ class NeutralAtomMapper { // Constructors NeutralAtomMapper() = delete; explicit NeutralAtomMapper(const NeutralAtomArchitecture* architecture, - const MapperParameters* p) + const MapperParameters& p) : arch(architecture), scheduler(*architecture), parameters(p), - hardwareQubits(*arch, arch->getNqubits() - p->numFlyingAncillas, - p->initialCoordMapping, p->seed), - flyingAncillas(*arch, p->numFlyingAncillas, Trivial, p->seed) { + hardwareQubits(*arch, arch->getNqubits() - p.numFlyingAncillas, + p.initialCoordMapping, p.seed), + flyingAncillas(*arch, p.numFlyingAncillas, Trivial, p.seed) { if (arch->getNpositions() - arch->getNqubits() < 1 && - p->shuttlingWeight > 0) { + p.shuttlingWeight > 0) { throw std::runtime_error( "No free coordinates for shuttling but shuttling " "weight is greater than 0."); } - if (parameters->numFlyingAncillas > 1) { + if (parameters.numFlyingAncillas > 1) { throw std::runtime_error("Only one flying ancilla is supported for now."); } // precompute exponential decay weights this->decayWeights.reserve(this->arch->getNcolumns()); for (uint32_t i = this->arch->getNcolumns(); i > 0; --i) { - this->decayWeights.emplace_back(std::exp(-this->parameters->decay * i)); + this->decayWeights.emplace_back(std::exp(-this->parameters.decay * i)); } } explicit NeutralAtomMapper(const NeutralAtomArchitecture& architecture, const MapperParameters& p = MapperParameters()) - : NeutralAtomMapper(&architecture, &p) {} + : NeutralAtomMapper(&architecture, p) {} /** * @brief Set/replace runtime parameters and reset internal state. @@ -489,7 +489,7 @@ class NeutralAtomMapper { * unsupported number of flying ancillas. */ void setParameters(const MapperParameters& p) { - this->parameters = &p; + this->parameters = p; if (arch->getNpositions() - arch->getNqubits() < 1 && p.shuttlingWeight > 0) { throw std::runtime_error( @@ -520,11 +520,11 @@ class NeutralAtomMapper { * ancillas). */ void reset() { - hardwareQubits = HardwareQubits( - *arch, arch->getNqubits() - parameters->numFlyingAncillas, - parameters->initialCoordMapping, parameters->seed); - flyingAncillas = HardwareQubits(*arch, parameters->numFlyingAncillas, - Trivial, parameters->seed); + hardwareQubits = + HardwareQubits(*arch, arch->getNqubits() - parameters.numFlyingAncillas, + parameters.initialCoordMapping, parameters.seed); + flyingAncillas = HardwareQubits(*arch, parameters.numFlyingAncillas, + Trivial, parameters.seed); } // Methods diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index b8ce77661..937a1f56f 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -71,7 +71,7 @@ void NeutralAtomMapper::mapAppend(qc::QuantumComputation& qc, mapping = initialMapping; - if (this->parameters->verbose) { + if (this->parameters.verbose) { std::cout << "* Init Coord Mapping w/ [row:" << arch->getNrows() << " X col:" << arch->getNcolumns() << "] hardware" << "\n"; for (uint32_t q = 0; q < qc.getNqubits(); q++) { @@ -85,9 +85,9 @@ void NeutralAtomMapper::mapAppend(qc::QuantumComputation& qc, } // init layers - NeutralAtomLayer lookaheadLayer(dag, false, this->parameters->lookaheadDepth); + NeutralAtomLayer lookaheadLayer(dag, false, this->parameters.lookaheadDepth); lookaheadLayer.initAllQubits(); - NeutralAtomLayer frontLayer(dag, true, this->parameters->lookaheadDepth); + NeutralAtomLayer frontLayer(dag, true, this->parameters.lookaheadDepth); frontLayer.initAllQubits(); lookaheadLayer.removeGatesAndUpdate(frontLayer.getGates()); mapAllPossibleGates(frontLayer, lookaheadLayer); @@ -102,7 +102,7 @@ void NeutralAtomMapper::mapAppend(qc::QuantumComputation& qc, while (!frontLayer.getGates().empty()) { // assign gates to layers reassignGatesToLayers(frontLayer.getGates(), lookaheadLayer.getGates()); - if (this->parameters->verbose) { + if (this->parameters.verbose) { std::cout << "Iteration " << i << '\n'; printLayers(); } @@ -111,7 +111,7 @@ void NeutralAtomMapper::mapAppend(qc::QuantumComputation& qc, i = shuttlingBasedMapping(frontLayer, lookaheadLayer, i); } - if (this->parameters->verbose) { + if (this->parameters.verbose) { std::cout << "nSwaps: " << stats.nSwaps << '\n'; std::cout << "nBridges: " << stats.nBridges << '\n'; std::cout << "nFAncillas: " << stats.nFAncillas << '\n'; @@ -174,7 +174,7 @@ qc::QuantumComputation NeutralAtomMapper::convertToAod() { // decompose AOD moves MoveToAodConverter aodScheduler(*arch, hardwareQubits, flyingAncillas); mappedQcAOD = aodScheduler.schedule(mappedQc); - if (this->parameters->verbose) { + if (this->parameters.verbose) { std::cout << "nMoveGroups: " << aodScheduler.getNMoveGroups() << '\n'; } return mappedQcAOD; @@ -195,7 +195,7 @@ void NeutralAtomMapper::applyPassBy(NeutralAtomLayer& frontLayer, for (const auto& passBy : pbComb.moves) { mappedQc.move(passBy.c1, passBy.c2 + arch->getNpositions()); - if (this->parameters->verbose) { + if (this->parameters.verbose) { std::cout << "passby " << passBy.c1 << " " << passBy.c2 << '\n'; } auto itT = std::ranges::find(targetCoords, passBy.c1); @@ -219,7 +219,7 @@ void NeutralAtomMapper::applyPassBy(NeutralAtomLayer& frontLayer, // mapGate(faComb.op); for (const auto& passBy : pbComb.moves) { mappedQc.move(passBy.c2 + arch->getNpositions(), passBy.c1); - if (this->parameters->verbose) { + if (this->parameters.verbose) { std::cout << "passby " << passBy.c2 << " " << passBy.c1 << '\n'; } } @@ -259,7 +259,7 @@ void NeutralAtomMapper::reassignGatesToLayers(const GateList& frontGates, } void NeutralAtomMapper::mapGate(const qc::Operation* op) { - if (this->parameters->verbose) { + if (this->parameters.verbose) { std::cout << "mapped " << op->getName() << " "; for (const auto qubit : op->getUsedQubits()) { std::cout << qubit << " "; @@ -349,7 +349,7 @@ void NeutralAtomMapper::applySwap(const Swap& swap) { const auto idxFirst = this->hardwareQubits.getCoordIndex(swap.first); const auto idxSecond = this->hardwareQubits.getCoordIndex(swap.second); this->mappedQc.swap(idxFirst, idxSecond); - if (this->parameters->verbose) { + if (this->parameters.verbose) { std::cout << "swapped " << swap.first << " " << swap.second; std::cout << " logical qubits: "; if (this->mapping.isMapped(swap.first)) { @@ -374,7 +374,7 @@ void NeutralAtomMapper::applyMove(AtomMove move) { mappedQc.move(move.c1, move.c2); const auto toMoveHwQubit = this->hardwareQubits.getHwQubit(move.c1); this->hardwareQubits.move(toMoveHwQubit, move.c2); - if (this->parameters->verbose) { + if (this->parameters.verbose) { std::cout << "moved " << move.c1 << " to " << move.c2; if (this->mapping.isMapped(toMoveHwQubit)) { std::cout << " logical qubit: " @@ -390,7 +390,7 @@ void NeutralAtomMapper::applyBridge(NeutralAtomLayer& frontLayer, const auto coordIndices = this->hardwareQubits.getCoordIndices(bridge.second); mappedQc.bridge(coordIndices); - if (this->parameters->verbose) { + if (this->parameters.verbose) { std::cout << "bridged " << bridge.first->getName() << " "; for (const auto qubit : bridge.second) { std::cout << qubit << " "; @@ -441,7 +441,7 @@ void NeutralAtomMapper::applyFlyingAncilla(NeutralAtomLayer& frontLayer, } i += 2; - if (this->parameters->verbose) { + if (this->parameters.verbose) { std::cout << "passby (flying ancilla) " << passBy.origin << " " << passBy.q1 << " " << passBy.q2 << '\n'; } @@ -470,7 +470,7 @@ void NeutralAtomMapper::applyFlyingAncilla(NeutralAtomLayer& frontLayer, this->flyingAncillas.move(static_cast(passBy.index), passBy.q1); } - if (this->parameters->verbose) { + if (this->parameters.verbose) { std::cout << "passby (flying ancilla) " << passBy.q2 << " " << passBy.q1 << '\n'; } @@ -584,7 +584,7 @@ Bridges NeutralAtomMapper::getShortestBridges(const Swap& bestSwap) { // shortcut if distance already larger than minBridgeLength const auto dist = this->hardwareQubits.getAllToAllSwapDistance(usedHwQubits); - if (dist > this->parameters->maxBridgeDistance || + if (dist > this->parameters.maxBridgeDistance || dist > static_cast(minBridgeLength)) { continue; } @@ -683,7 +683,7 @@ FlyingAncillaComb NeutralAtomMapper::convertMoveCombToFlyingAncillaComb( PassByComb NeutralAtomMapper::convertMoveCombToPassByComb(const MoveComb& moveComb) const { - if (!this->parameters->usePassBy) { + if (!this->parameters.usePassBy) { return {}; } const auto usedQubits = moveComb.op->getUsedQubits(); @@ -715,11 +715,11 @@ qc::fp NeutralAtomMapper::swapCost( swapCostPerLayer(swap, swapCloseByLookahead, swapExactLookahead) / static_cast(this->lookaheadLayerGate.size()); } - auto cost = (parameters->lookaheadWeightSwaps * distanceChangeLookahead / - this->parameters->lookaheadDepth) + + auto cost = (parameters.lookaheadWeightSwaps * distanceChangeLookahead / + this->parameters.lookaheadDepth) + distanceChangeFront; // compute the last time one of the swap qubits was used - if (this->parameters->decay != 0) { + if (this->parameters.decay != 0) { uint32_t idxLastUsed = 0; for (uint32_t i = 0; i < this->lastBlockedQubits.size(); ++i) { if (this->lastBlockedQubits[i].contains(swap.first) || @@ -797,7 +797,7 @@ NeutralAtomMapper::initSwaps(const GateList& layer) { } else { // for multi-qubit gates, find the best position around the gate qubits auto bestPos = getBestMultiQubitPosition(gate); - if (this->parameters->verbose) { + if (this->parameters.verbose) { std::cout << "bestPos: "; for (const auto qubit : bestPos) { std::cout << qubit << " "; @@ -1151,12 +1151,12 @@ qc::fp NeutralAtomMapper::moveCostComb(const MoveComb& moveComb) const { const auto lookaheadDistReduction = moveCombDistanceReduction(moveComb, this->lookaheadLayerShuttling) / static_cast(this->lookaheadLayerShuttling.size()); - costComb -= parameters->lookaheadWeightMoves * lookaheadDistReduction / - static_cast(this->parameters->lookaheadDepth); + costComb -= parameters.lookaheadWeightMoves * lookaheadDistReduction / + static_cast(this->parameters.lookaheadDepth); } if (!this->lastMoves.empty()) { const auto parallelMovecCost = - parameters->shuttlingTimeWeight * parallelMoveCost(moveComb) / + parameters.shuttlingTimeWeight * parallelMoveCost(moveComb) / static_cast(this->frontLayerShuttling.size()); costComb += parallelMovecCost; } @@ -1324,7 +1324,7 @@ MoveCombs NeutralAtomMapper::getAllMoveCombinations() { for (const auto& bestPos : bestPositions) { auto moves = getMoveCombinationsToPosition(usedHwQubits, bestPos); moves.setOperation(op, bestPos); - if (allMoves.size() > this->parameters->limitShuttlingLayer) { + if (allMoves.size() > this->parameters.limitShuttlingLayer) { break; } allMoves.addMoveCombs(moves); @@ -1478,7 +1478,7 @@ size_t NeutralAtomMapper::shuttlingBasedMapping( NeutralAtomLayer& frontLayer, NeutralAtomLayer& lookaheadLayer, size_t i) { while (!this->frontLayerShuttling.empty()) { ++i; - if (this->parameters->verbose) { + if (this->parameters.verbose) { std::cout << "iteration " << i << '\n'; } auto bestComb = findBestAtomMove(); @@ -1514,7 +1514,7 @@ size_t NeutralAtomMapper::shuttlingBasedMapping( mapAllPossibleGates(frontLayer, lookaheadLayer); reassignGatesToLayers(frontLayer.getGates(), lookaheadLayer.getGates()); - if (this->parameters->verbose) { + if (this->parameters.verbose) { printLayers(); } } @@ -1634,8 +1634,8 @@ bool NeutralAtomMapper::swapGateBetter(const qc::Operation* opPointer) { qc::OpType::AodDeactivate), minMoves); - return fidSwaps * parameters->gateWeight > - fidMoves * parameters->shuttlingWeight; + return fidSwaps * parameters.gateWeight > + fidMoves * parameters.shuttlingWeight; } size_t NeutralAtomMapper::gateBasedMapping(NeutralAtomLayer& frontLayer, @@ -1646,13 +1646,13 @@ size_t NeutralAtomMapper::gateBasedMapping(NeutralAtomLayer& frontLayer, GateList gatesToExecute; while (gatesToExecute.empty()) { ++i; - if (this->parameters->verbose) { + if (this->parameters.verbose) { std::cout << "iteration " << i << '\n'; } auto bestSwap = findBestSwap(lastSwap); MappingMethod bestMethod = SwapMethod; - if (parameters->maxBridgeDistance > 0 && !multiQubitGates) { + if (parameters.maxBridgeDistance > 0 && !multiQubitGates) { auto bestBridge = findBestBridge(bestSwap); bestMethod = compareSwapAndBridge(bestSwap, bestBridge); if (bestMethod == BridgeMethod) { @@ -1671,7 +1671,7 @@ size_t NeutralAtomMapper::gateBasedMapping(NeutralAtomLayer& frontLayer, } mapAllPossibleGates(frontLayer, lookaheadLayer); reassignGatesToLayers(frontLayer.getGates(), lookaheadLayer.getGates()); - if (this->parameters->verbose) { + if (this->parameters.verbose) { printLayers(); } } @@ -1684,15 +1684,15 @@ NeutralAtomMapper::compareSwapAndBridge(const Swap& bestSwap, if (bestBridge == Bridge()) { return SwapMethod; } - if (this->parameters->dynamicMappingWeight == 0) { + if (this->parameters.dynamicMappingWeight == 0) { return BridgeMethod; } // swap distance reduction qc::fp const swapDistReduction = swapDistanceReduction(bestSwap, this->frontLayerGate) + - (this->parameters->lookaheadWeightSwaps * + (this->parameters.lookaheadWeightSwaps * swapDistanceReduction(bestSwap, this->lookaheadLayerGate) / - this->parameters->lookaheadDepth); + this->parameters.lookaheadDepth); // bridge distance reduction qc::fp const bridgeDistReduction = @@ -1708,7 +1708,7 @@ NeutralAtomMapper::compareSwapAndBridge(const Swap& bestSwap, std::exp(-this->arch->getGateTime(bridgeName) / this->arch->getDecoherenceTime()); const auto swap = std::log(swapFidelity) / swapDistReduction / - parameters->dynamicMappingWeight; + parameters.dynamicMappingWeight; const auto bridge = std::log(bridgeFidelity) / bridgeDistReduction; if (swap >= bridge) { return SwapMethod; @@ -1719,7 +1719,7 @@ NeutralAtomMapper::compareSwapAndBridge(const Swap& bestSwap, MappingMethod NeutralAtomMapper::compareShuttlingAndFlyingAncilla( const MoveComb& bestMoveComb, const FlyingAncillaComb& bestFaComb, const PassByComb& bestPbComb) const { - if (flyingAncillas.getNumQubits() == 0 && !parameters->usePassBy) { + if (flyingAncillas.getNumQubits() == 0 && !parameters.usePassBy) { return MoveMethod; } if (multiQubitGates) { @@ -1733,7 +1733,7 @@ MappingMethod NeutralAtomMapper::compareShuttlingAndFlyingAncilla( auto moveDistReductionLookAhead = 0.0; if (!this->lookaheadLayerShuttling.empty()) { moveDistReductionLookAhead = - this->parameters->lookaheadWeightMoves * + this->parameters.lookaheadWeightMoves * moveCombDistanceReduction(bestMoveComb, this->lookaheadLayerShuttling) / static_cast(this->lookaheadLayerShuttling.size()); } @@ -1783,7 +1783,7 @@ MappingMethod NeutralAtomMapper::compareShuttlingAndFlyingAncilla( // passby auto pbDistReduction = 0.0; auto passByFidelity = 0.0; - if (parameters->usePassBy) { + if (parameters.usePassBy) { auto const pbCoords = this->hardwareQubits.getCoordIndices( this->mapping.getHwQubits(bestPbComb.op->getUsedQubits())); pbDistReduction = this->arch->getAllToAllEuclideanDistance(pbCoords); @@ -1823,7 +1823,7 @@ MappingMethod NeutralAtomMapper::compareShuttlingAndFlyingAncilla( // higher is better const auto move = std::log(moveFidelity) / moveDistReduction / - parameters->dynamicMappingWeight; + parameters.dynamicMappingWeight; const auto fa = std::log(faFidelity) / faDistReduction; const auto passBy = std::log(passByFidelity) / pbDistReduction; diff --git a/src/hybridmap/HybridSynthesisMapper.cpp b/src/hybridmap/HybridSynthesisMapper.cpp index 436174966..921ec3bcb 100644 --- a/src/hybridmap/HybridSynthesisMapper.cpp +++ b/src/hybridmap/HybridSynthesisMapper.cpp @@ -41,12 +41,12 @@ HybridSynthesisMapper::evaluateSynthesisSteps(qcs& synthesisSteps, std::vector> candidates; size_t qcIndex = 0; for (auto& qc : synthesisSteps) { - if (this->parameters->verbose) { + if (this->parameters.verbose) { std::cout << "Evaluating synthesis step number " << qcIndex << "\n"; } const auto fidelity = this->evaluateSynthesisStep(qc); candidates.emplace_back(qc, fidelity); - if (this->parameters->verbose) { + if (this->parameters.verbose) { std::cout << "Fidelity: " << fidelity << "\n"; } ++qcIndex; From 99c19f1a34232c92f4efb1ba80f32071e5a92c89 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 27 Nov 2025 09:08:28 +0100 Subject: [PATCH 356/394] =?UTF-8?q?=F0=9F=8E=A8=20changed=20unsigned=20sub?= =?UTF-8?q?straction=20comparison.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridNeutralAtomMapper.hpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index 2771f1817..8706fcc74 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -463,8 +463,9 @@ class NeutralAtomMapper { hardwareQubits(*arch, arch->getNqubits() - p.numFlyingAncillas, p.initialCoordMapping, p.seed), flyingAncillas(*arch, p.numFlyingAncillas, Trivial, p.seed) { - if (arch->getNpositions() - arch->getNqubits() < 1 && - p.shuttlingWeight > 0) { + const auto nPositions = static_cast(arch->getNpositions()); + const auto nQubits = static_cast(arch->getNqubits()); + if (nPositions - nQubits < 1 && p.shuttlingWeight > 0) { throw std::runtime_error( "No free coordinates for shuttling but shuttling " "weight is greater than 0."); @@ -490,8 +491,9 @@ class NeutralAtomMapper { */ void setParameters(const MapperParameters& p) { this->parameters = p; - if (arch->getNpositions() - arch->getNqubits() < 1 && - p.shuttlingWeight > 0) { + const auto nPositions = static_cast(arch->getNpositions()); + const auto nQubits = static_cast(arch->getNqubits()); + if (nPositions - nQubits < 1 && p.shuttlingWeight > 0) { throw std::runtime_error( "No free coordinates for shuttling but shuttling " "weight is greater than 0."); From 6bc26b14a6fc6639407fa75f31db46e86dc57ec3 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 27 Nov 2025 09:09:20 +0100 Subject: [PATCH 357/394] =?UTF-8?q?=F0=9F=8E=A8=20updated=20bindings=20doc?= =?UTF-8?q?string.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bindings/hybrid_mapper/hybrid_mapper.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/hybrid_mapper/hybrid_mapper.cpp b/bindings/hybrid_mapper/hybrid_mapper.cpp index 1a1ca0fe7..0ecc90ed3 100644 --- a/bindings/hybrid_mapper/hybrid_mapper.cpp +++ b/bindings/hybrid_mapper/hybrid_mapper.cpp @@ -81,7 +81,7 @@ PYBIND11_MODULE(MQT_QMAP_MODULE_NAME, m, py::mod_gil_not_used()) { "Number of ancilla qubits to be used (0 or 1 for now).") .def_readwrite("limit_shuttling_layer", &na::MapperParameters::limitShuttlingLayer, - "Maximum allowed shuttling layer (default: unlimited).") + "Maximum allowed shuttling layer (default: 10).") .def_readwrite("max_bridge_distance", &na::MapperParameters::maxBridgeDistance, "Maximum distance for bridge operations.") From 0866b6cc99dedc7e8e07cd7ef605cfb081439462 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 27 Nov 2025 09:14:35 +0100 Subject: [PATCH 358/394] =?UTF-8?q?=F0=9F=8E=A8=20return=20circuit=20by=20?= =?UTF-8?q?const=20ref.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridNeutralAtomMapper.hpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index 8706fcc74..5985a113b 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -599,7 +599,9 @@ class NeutralAtomMapper { * @brief Get current mapped circuit (abstract operations form). * @return Mapped circuit object. */ - [[nodiscard]] qc::QuantumComputation getMappedQc() const { return mappedQc; } + [[nodiscard]] const qc::QuantumComputation& getMappedQc() const { + return mappedQc; + } /** * @brief Serialize mapped circuit (abstract operations) to extended OpenQASM. From 5e11c1aab8e459c12d750afbd1824033f548f87a Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 27 Nov 2025 09:14:47 +0100 Subject: [PATCH 359/394] =?UTF-8?q?=F0=9F=8E=A8=20use=20copy=20for=20compl?= =?UTF-8?q?ete=20remap?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridSynthesisMapper.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/hybridmap/HybridSynthesisMapper.hpp b/include/hybridmap/HybridSynthesisMapper.hpp index 6f2bd7d7b..28de3a15a 100644 --- a/include/hybridmap/HybridSynthesisMapper.hpp +++ b/include/hybridmap/HybridSynthesisMapper.hpp @@ -90,7 +90,8 @@ class HybridSynthesisMapper : public NeutralAtomMapper { * @param initMapping Initial mapping heuristic (defaults to Identity). */ void completeRemap(const InitialMapping initMapping = Identity) { - this->map(synthesizedQc, initMapping); + auto qcCopy = synthesizedQc; + this->map(qcCopy, initMapping); } /** From af3047c98eff50d53d0d4668ddd7d5e374fb7d78 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 27 Nov 2025 10:13:29 +0100 Subject: [PATCH 360/394] =?UTF-8?q?=F0=9F=8E=A8=20add=20missing=20check.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridNeutralAtomMapper.hpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index 5985a113b..bc75e010b 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -498,6 +498,9 @@ class NeutralAtomMapper { "No free coordinates for shuttling but shuttling " "weight is greater than 0."); } + if (parameters.numFlyingAncillas > 1) { + throw std::runtime_error("Only one flying ancilla is supported for now."); + } this->reset(); } From 6e2383e48cdf5359e5ac9d634dae4df517d3cfd7 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 27 Nov 2025 10:15:34 +0100 Subject: [PATCH 361/394] =?UTF-8?q?=F0=9F=8E=A8=20remove=20throw=20from=20?= =?UTF-8?q?doc=20string.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridNeutralAtomMapper.hpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index bc75e010b..4fdcfcb2e 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -619,7 +619,6 @@ class NeutralAtomMapper { /** * @brief Save mapped abstract circuit (SWAP/MOVE) to file in OpenQASM. * @param filename Output file path. - * @throw std::runtime_error On file I/O failure. */ [[maybe_unused]] void saveMappedQcQasm(const std::string& filename) const { std::ofstream ofs(filename); @@ -642,7 +641,6 @@ class NeutralAtomMapper { /** * @brief Save AOD-native mapped circuit to file. * @param filename Output file path. - * @throw std::runtime_error On file I/O failure. */ [[maybe_unused]] void saveMappedQcAodQasm(const std::string& filename) { if (this->mappedQcAOD.empty()) { @@ -683,7 +681,6 @@ class NeutralAtomMapper { /** * @brief Persist animation CSV assets to disk. * @param filename Base filename for output. - * @throw std::runtime_error On file I/O failure. */ [[maybe_unused]] void saveAnimationFiles(const std::string& filename) const { scheduler.saveAnimationFiles(filename); From 80b42f57fb40bc96891ecfd9add4eef43f5b0a49 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 27 Nov 2025 10:20:07 +0100 Subject: [PATCH 362/394] =?UTF-8?q?=F0=9F=90=9B=20fixed=20wrong=20call=20t?= =?UTF-8?q?o=20compute=20Euclidean=20Distance.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridNeutralAtomMapper.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 937a1f56f..7c2971190 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -657,8 +657,8 @@ FlyingAncillaComb NeutralAtomMapper::convertMoveCombToFlyingAncillaComb( const auto nearSecond = this->flyingAncillas.getCoordIndex(nearSecondIdx); if (usedQubits.size() == 2) { // both directions possible, check if reversed is better - if (this->arch->getEuclideanDistance(nearFirstIdx, move.c1) < - this->arch->getEuclideanDistance(nearSecondIdx, move.c2)) { + if (this->arch->getEuclideanDistance(nearFirst, move.c1) < + this->arch->getEuclideanDistance(nearSecond, move.c2)) { bestFA.q1 = move.c2; bestFA.q2 = move.c1; bestFA.origin = nearSecond; From f27a1204d22781d002fc870d4aa55689191df177 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 27 Nov 2025 10:30:02 +0100 Subject: [PATCH 363/394] =?UTF-8?q?=F0=9F=8E=A8=20improved=20impossible=20?= =?UTF-8?q?SWAP=20handling.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridNeutralAtomMapper.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 7c2971190..a8187b985 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -494,8 +494,7 @@ Swap NeutralAtomMapper::findBestSwap(const Swap& lastSwapUsed) { // no swap possible if (swaps.empty()) { - return {std::numeric_limits::max(), - std::numeric_limits::max()}; + return {}; } std::vector> swapCosts; swapCosts.reserve(swaps.size()); @@ -1662,6 +1661,10 @@ size_t NeutralAtomMapper::gateBasedMapping(NeutralAtomLayer& frontLayer, } } if (bestMethod == SwapMethod) { + if (bestSwap == Swap() || bestSwap.first == bestSwap.second) { + throw std::runtime_error( + "No possible SWAP found to execute gates in front layer."); + } lastSwap = bestSwap; updateBlockedQubits(HwQubits{bestSwap.first, bestSwap.second}); applySwap(bestSwap); From 4c9c296e8423248891aab0e8973c6da5a6b0b796 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 27 Nov 2025 11:05:00 +0100 Subject: [PATCH 364/394] =?UTF-8?q?=F0=9F=94=A5removed=20unused=20RoutingT?= =?UTF-8?q?ype.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridNeutralAtomMapper.hpp | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index 4fdcfcb2e..d6056532d 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -71,17 +71,6 @@ struct MapperStats { uint32_t nPassBy = 0; ///< Number of pass-by combinations. }; -/** - * @brief Enumeration of supported routing primitives. - */ -enum RoutingType : uint8_t { - SwapType, ///< Conventional SWAP gate routing. - BridgeType, ///< Bridge circuit using intermediate ancillas. - MoveType, ///< Physical MOVE (shuttling) of atoms. - PassByType, ///< Pass-by movement combination. - FlyingAncillaType ///< Flying ancilla mediated interaction. -}; - /** * @brief Maps a quantum circuit onto a neutral atom architecture using hybrid * routing. From 93fb7fd66a6e6a970c6ade0143052202d365d0a6 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 27 Nov 2025 11:05:51 +0100 Subject: [PATCH 365/394] =?UTF-8?q?=F0=9F=90=9Badded=20flying=20ancillas?= =?UTF-8?q?=20to=20copyStateFrom?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridNeutralAtomMapper.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index d6056532d..b99b537f5 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -507,6 +507,7 @@ class NeutralAtomMapper { this->lastBlockedQubits = mapper.lastBlockedQubits; this->scheduler = mapper.scheduler; this->decayWeights = mapper.decayWeights; + this->flyingAncillas = mapper.flyingAncillas; } /** From 8fc3b6fc49545f2c08ab26c19988e41e16a6cb4b Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 27 Nov 2025 11:08:57 +0100 Subject: [PATCH 366/394] =?UTF-8?q?=F0=9F=90=9B=20fixed=20wrong=20update?= =?UTF-8?q?=20of=20blocked=20qubits.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridNeutralAtomMapper.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index a8187b985..41a1b7443 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -1656,7 +1656,7 @@ size_t NeutralAtomMapper::gateBasedMapping(NeutralAtomLayer& frontLayer, bestMethod = compareSwapAndBridge(bestSwap, bestBridge); if (bestMethod == BridgeMethod) { updateBlockedQubits( - {bestBridge.second.begin(), bestBridge.second.end()}); + HwQubits(bestBridge.second.begin(), bestBridge.second.end())); applyBridge(frontLayer, bestBridge); } } From 49c9e3401e20131dea3662f18c7230b37b37e5d3 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 27 Nov 2025 11:11:13 +0100 Subject: [PATCH 367/394] =?UTF-8?q?=F0=9F=90=9B=20fixed=20wrong=20update?= =?UTF-8?q?=20of=20qubit=20usage.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridNeutralAtomMapper.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 41a1b7443..02c19e5f8 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -558,7 +558,7 @@ Bridge NeutralAtomMapper::findBestBridge(const Swap& bestSwap) { for (size_t i = 0; i < allBridges.size(); ++i) { size_t usage = 0; for (const auto qubit : allBridges[i].second) { - usage += qubitUsages[qubit]; + usage += qubitUsages[hardwareQubits.getCoordIndex(qubit)]; } if (usage < minUsage) { minUsage = usage; From e15c3874505ac32f5171713368d09dfd810795cf Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 27 Nov 2025 11:35:37 +0100 Subject: [PATCH 368/394] =?UTF-8?q?=F0=9F=90=9B=20extend=20coordUsages=20t?= =?UTF-8?q?o=20FlyingAncillas.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridNeutralAtomMapper.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 02c19e5f8..404588627 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -606,7 +606,8 @@ Bridges NeutralAtomMapper::getShortestBridges(const Swap& bestSwap) { return allBridges; } CoordIndices NeutralAtomMapper::computeCurrentCoordUsages() const { - CoordIndices coordUsages(mappedQc.getNqubits(), 0); + // Size to cover all register spaces: logical + ancilla + flying ancilla + CoordIndices coordUsages(arch->getNpositions() * 3U, 0); // in front layer for (const auto* const op : this->frontLayerGate) { for (const auto qubit : op->getUsedQubits()) { From 6858379efe87d779b0c62f7964307fc793790753 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 27 Nov 2025 11:39:16 +0100 Subject: [PATCH 369/394] =?UTF-8?q?=F0=9F=90=9B=20extend=20coordUsages=20t?= =?UTF-8?q?o=20FlyingAncillas=20clang=20fix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridNeutralAtomMapper.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 404588627..cd4b722ae 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -607,7 +607,8 @@ Bridges NeutralAtomMapper::getShortestBridges(const Swap& bestSwap) { } CoordIndices NeutralAtomMapper::computeCurrentCoordUsages() const { // Size to cover all register spaces: logical + ancilla + flying ancilla - CoordIndices coordUsages(arch->getNpositions() * 3U, 0); + CoordIndices coordUsages(static_cast(arch->getNpositions() * 3U), + 0); // in front layer for (const auto* const op : this->frontLayerGate) { for (const auto qubit : op->getUsedQubits()) { From 968bbf2c92ff0b51dd21f953e40ea86bb180b83e Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 4 Dec 2025 08:55:07 +0100 Subject: [PATCH 370/394] =?UTF-8?q?=F0=9F=8E=A8=20set=20default=20dynamic?= =?UTF-8?q?=20mapping=20weight=20to=201?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridNeutralAtomMapper.hpp | 2 +- test/hybridmap/test_hybridmap.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index b99b537f5..8ff594dfe 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -48,7 +48,7 @@ struct MapperParameters { qc::fp lookaheadWeightMoves = 0.1; qc::fp decay = 0.1; qc::fp shuttlingTimeWeight = 0.1; - qc::fp dynamicMappingWeight = 2; + qc::fp dynamicMappingWeight = 1; qc::fp gateWeight = 1; qc::fp shuttlingWeight = 1; uint32_t seed = 42; diff --git a/test/hybridmap/test_hybridmap.cpp b/test/hybridmap/test_hybridmap.cpp index 673de3dd0..dd76561c3 100644 --- a/test/hybridmap/test_hybridmap.cpp +++ b/test/hybridmap/test_hybridmap.cpp @@ -68,7 +68,7 @@ class NeutralAtomMapperTestParams qc::fp gateWeight = 1; qc::fp shuttlingWeight = 1; qc::fp lookAheadWeight = 1; - qc::fp dynamicMappingWeight = 2; + qc::fp dynamicMappingWeight = 1; na::InitialCoordinateMapping initialCoordinateMapping = na::InitialCoordinateMapping::Random; // fixed From d0f38d6fd0837d113a786e9648f829b382288284 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 4 Dec 2025 09:02:35 +0100 Subject: [PATCH 371/394] =?UTF-8?q?=F0=9F=90=9B=20fix=20problem=20that=20b?= =?UTF-8?q?arrier=20is=20considered=20multi-qubit=20gate.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridNeutralAtomMapper.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index cd4b722ae..c90e53c8a 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -45,11 +46,17 @@ namespace na { void NeutralAtomMapper::mapAppend(qc::QuantumComputation& qc, const Mapping& initialMapping) { + // remove barriers and measurements + qc::CircuitOptimizer::removeFinalMeasurements(qc); // check if multi-qubit gates are present multiQubitGates = false; for (const auto& op : qc) { if (op->getUsedQubits().size() > 2) { // deactivate static mapping + spdlog::warn( + "The circuit contains multi-qubit gates (more than 2 qubits). " + "Bridge gates will NOT be used for mapping."); + multiQubitGates = true; break; } From 684f67ce1346166a0d6a54ed7331a150ca7d2c1c Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 4 Dec 2025 09:03:19 +0100 Subject: [PATCH 372/394] =?UTF-8?q?=F0=9F=90=9B=20fix=20removal=20of=20ope?= =?UTF-8?q?ration=20from=20front=20layer=20for=20Bridge,=20PassBy,=20FA.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridNeutralAtomMapper.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index c90e53c8a..32512260d 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -232,6 +232,8 @@ void NeutralAtomMapper::applyPassBy(NeutralAtomLayer& frontLayer, } frontLayer.removeGatesAndUpdate({pbComb.op}); + this->frontLayerShuttling.erase( + std::ranges::find(this->frontLayerShuttling, pbComb.op)); stats.nPassBy += pbComb.moves.size(); } @@ -408,6 +410,7 @@ void NeutralAtomMapper::applyBridge(NeutralAtomLayer& frontLayer, // // remove gate from frontLayer const auto* op = bridge.first; frontLayer.removeGatesAndUpdate({op}); + this->frontLayerGate.erase(std::ranges::find(this->frontLayerGate, op)); stats.nBridges++; } @@ -484,6 +487,8 @@ void NeutralAtomMapper::applyFlyingAncilla(NeutralAtomLayer& frontLayer, } frontLayer.removeGatesAndUpdate({faComb.op}); + this->frontLayerShuttling.erase( + std::ranges::find(this->frontLayerShuttling, faComb.op)); stats.nFAncillas += faComb.moves.size(); } From 3005b0a9bb789e6049c4b262bf7653734182efad Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 4 Dec 2025 09:03:56 +0100 Subject: [PATCH 373/394] =?UTF-8?q?=F0=9F=90=9B=20fix=20bug=20where=20no?= =?UTF-8?q?=20bridge=20is=20found=20if=20no=20SWAP=20was=20found.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridNeutralAtomMapper.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 32512260d..c5f48fe1f 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -589,7 +589,7 @@ Bridges NeutralAtomMapper::getShortestBridges(const Swap& bestSwap) { auto usedQuBits = op->getUsedQubits(); auto usedHwQubits = this->mapping.getHwQubits(usedQuBits); if (!usedHwQubits.contains(bestSwap.first) && - !usedHwQubits.contains(bestSwap.second)) { + !usedHwQubits.contains(bestSwap.second) && bestSwap != Swap()) { continue; } // shortcut if distance already larger than minBridgeLength From 644f52b4fbe335e8a1d7cf5702b8572a607705ef Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 4 Dec 2025 09:04:25 +0100 Subject: [PATCH 374/394] =?UTF-8?q?=F0=9F=90=9B=20fix=20bug=20routing=20do?= =?UTF-8?q?es=20no=20end=20if=20last=20gate=20was=20a=20bridge=20gate.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridNeutralAtomMapper.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index c5f48fe1f..a581eb70c 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -1657,7 +1657,7 @@ size_t NeutralAtomMapper::gateBasedMapping(NeutralAtomLayer& frontLayer, // first do all gate based mapping gates while (!this->frontLayerGate.empty()) { GateList gatesToExecute; - while (gatesToExecute.empty()) { + while (gatesToExecute.empty() && !this->frontLayerGate.empty()) { ++i; if (this->parameters.verbose) { std::cout << "iteration " << i << '\n'; From 923811aca3ac9f4b35e20b251584c18085f42e0e Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 4 Dec 2025 09:05:00 +0100 Subject: [PATCH 375/394] =?UTF-8?q?=F0=9F=90=9B=20improved=20bridge=20cost?= =?UTF-8?q?=20function.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridNeutralAtomMapper.cpp | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index a581eb70c..5c0d51ef6 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -1704,11 +1704,13 @@ NeutralAtomMapper::compareSwapAndBridge(const Swap& bestSwap, if (this->parameters.dynamicMappingWeight == 0) { return BridgeMethod; } - // swap distance reduction - qc::fp const swapDistReduction = - swapDistanceReduction(bestSwap, this->frontLayerGate) + - (this->parameters.lookaheadWeightSwaps * - swapDistanceReduction(bestSwap, this->lookaheadLayerGate) / + const auto swapFrontDistReduction = + swapDistanceReduction(bestSwap, this->frontLayerGate); + const auto swapLookaheadDistReduction = + swapDistanceReduction(bestSwap, this->lookaheadLayerGate); + const auto swapDistReduction = + swapFrontDistReduction + + (this->parameters.lookaheadWeightSwaps * swapLookaheadDistReduction / this->parameters.lookaheadDepth); // bridge distance reduction @@ -1716,9 +1718,12 @@ NeutralAtomMapper::compareSwapAndBridge(const Swap& bestSwap, static_cast(bestBridge.second.size()) - 2; // fidelity comparison - qc::fp const swapFidelity = this->arch->getGateAverageFidelity("swap") * - std::exp(-this->arch->getGateTime("swap") / - this->arch->getDecoherenceTime()); + qc::fp const swapTime = + this->arch->getGateTime("swap") + this->arch->getGateTime("cz"); + qc::fp const swapFidelity = + this->arch->getGateAverageFidelity("swap") * + this->arch->getGateAverageFidelity("cz") * + std::exp(-swapTime / this->arch->getDecoherenceTime()); const std::string bridgeName = "bridge" + std::to_string(bestBridge.second.size()); qc::fp const bridgeFidelity = this->arch->getGateAverageFidelity(bridgeName) * From f668d0895b8a3a2f79c8a93bdebb572eb931823a Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 4 Dec 2025 15:44:07 +0100 Subject: [PATCH 376/394] =?UTF-8?q?=F0=9F=8E=A8=20changed=20ifndef=20to=20?= =?UTF-8?q?pragma=20once.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HardwareQubits.hpp | 5 +---- include/hybridmap/HybridAnimation.hpp | 5 +---- include/hybridmap/HybridNeutralAtomMapper.hpp | 5 +---- include/hybridmap/HybridSynthesisMapper.hpp | 5 +---- include/hybridmap/Mapping.hpp | 5 +---- include/hybridmap/MoveToAodConverter.hpp | 5 +---- include/hybridmap/NeutralAtomArchitecture.hpp | 5 +---- include/hybridmap/NeutralAtomDefinitions.hpp | 5 +---- include/hybridmap/NeutralAtomLayer.hpp | 5 +---- include/hybridmap/NeutralAtomScheduler.hpp | 5 +---- include/hybridmap/NeutralAtomUtils.hpp | 5 +---- 11 files changed, 11 insertions(+), 44 deletions(-) diff --git a/include/hybridmap/HardwareQubits.hpp b/include/hybridmap/HardwareQubits.hpp index 3652510de..8cd39f882 100644 --- a/include/hybridmap/HardwareQubits.hpp +++ b/include/hybridmap/HardwareQubits.hpp @@ -8,8 +8,7 @@ * Licensed under the MIT License */ -#ifndef HYBRIDMAP_HARDWARE_QUBITS_HPP -#define HYBRIDMAP_HARDWARE_QUBITS_HPP +#pragma once #include "datastructures/SymmetricMatrix.hpp" #include "hybridmap/NeutralAtomArchitecture.hpp" @@ -376,5 +375,3 @@ class HardwareQubits { } }; } // namespace na - -#endif // HYBRIDMAP_HARDWARE_QUBITS_HPP diff --git a/include/hybridmap/HybridAnimation.hpp b/include/hybridmap/HybridAnimation.hpp index 9111690d2..0a8f2c930 100644 --- a/include/hybridmap/HybridAnimation.hpp +++ b/include/hybridmap/HybridAnimation.hpp @@ -8,8 +8,7 @@ * Licensed under the MIT License */ -#ifndef HYBRIDMAP_HYBRID_ANIMATION_HPP -#define HYBRIDMAP_HYBRID_ANIMATION_HPP +#pragma once #include "NeutralAtomArchitecture.hpp" #include "NeutralAtomDefinitions.hpp" @@ -97,5 +96,3 @@ class AnimationAtoms { }; } // namespace na - -#endif // HYBRIDMAP_HYBRID_ANIMATION_HPP diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index 8ff594dfe..befe4909d 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -8,8 +8,7 @@ * Licensed under the MIT License */ -#ifndef HYBRIDMAP_HYBRID_NEUTRAL_ATOM_MAPPER_HPP -#define HYBRIDMAP_HYBRID_NEUTRAL_ATOM_MAPPER_HPP +#pragma once #include "circuit_optimizer/CircuitOptimizer.hpp" #include "hybridmap/HardwareQubits.hpp" @@ -698,5 +697,3 @@ class NeutralAtomMapper { }; } // namespace na - -#endif // HYBRIDMAP_HYBRID_NEUTRAL_ATOM_MAPPER_HPP diff --git a/include/hybridmap/HybridSynthesisMapper.hpp b/include/hybridmap/HybridSynthesisMapper.hpp index 28de3a15a..5f0431737 100644 --- a/include/hybridmap/HybridSynthesisMapper.hpp +++ b/include/hybridmap/HybridSynthesisMapper.hpp @@ -13,8 +13,7 @@ // See README.md or go to https://github.com/cda-tum/qmap for more information. // -#ifndef HYBRIDMAP_HYBRID_SYNTHESIS_MAPPER_HPP -#define HYBRIDMAP_HYBRID_SYNTHESIS_MAPPER_HPP +#pragma once #include "HybridNeutralAtomMapper.hpp" #include "NeutralAtomArchitecture.hpp" @@ -150,5 +149,3 @@ class HybridSynthesisMapper : public NeutralAtomMapper { [[nodiscard]] AdjacencyMatrix getCircuitAdjacencyMatrix() const; }; } // namespace na - -#endif // HYBRIDMAP_HYBRID_SYNTHESIS_MAPPER_HPP diff --git a/include/hybridmap/Mapping.hpp b/include/hybridmap/Mapping.hpp index a4733b7a1..54dbd94eb 100644 --- a/include/hybridmap/Mapping.hpp +++ b/include/hybridmap/Mapping.hpp @@ -8,8 +8,7 @@ * Licensed under the MIT License */ -#ifndef HYBRIDMAP_MAPPING_HPP -#define HYBRIDMAP_MAPPING_HPP +#pragma once #include "HardwareQubits.hpp" #include "NeutralAtomArchitecture.hpp" @@ -215,5 +214,3 @@ class Mapping { }; } // namespace na - -#endif // HYBRIDMAP_MAPPING_HPP diff --git a/include/hybridmap/MoveToAodConverter.hpp b/include/hybridmap/MoveToAodConverter.hpp index 84a188a23..a5a733a57 100644 --- a/include/hybridmap/MoveToAodConverter.hpp +++ b/include/hybridmap/MoveToAodConverter.hpp @@ -8,8 +8,7 @@ * Licensed under the MIT License */ -#ifndef HYBRIDMAP_MOVE_TO_AOD_CONVERTER_HPP -#define HYBRIDMAP_MOVE_TO_AOD_CONVERTER_HPP +#pragma once #include "hybridmap/HardwareQubits.hpp" #include "hybridmap/NeutralAtomArchitecture.hpp" @@ -380,5 +379,3 @@ class MoveToAodConverter { }; } // namespace na - -#endif // HYBRIDMAP_MOVE_TO_AOD_CONVERTER_HPP diff --git a/include/hybridmap/NeutralAtomArchitecture.hpp b/include/hybridmap/NeutralAtomArchitecture.hpp index 39bf12712..36c5b8c7a 100644 --- a/include/hybridmap/NeutralAtomArchitecture.hpp +++ b/include/hybridmap/NeutralAtomArchitecture.hpp @@ -8,8 +8,7 @@ * Licensed under the MIT License */ -#ifndef HYBRIDMAP_NEUTRAL_ATOM_ARCHITECTURE_HPP -#define HYBRIDMAP_NEUTRAL_ATOM_ARCHITECTURE_HPP +#pragma once #include "datastructures/SymmetricMatrix.hpp" #include "hybridmap/NeutralAtomDefinitions.hpp" @@ -600,5 +599,3 @@ class NeutralAtomArchitecture { }; } // namespace na - -#endif // HYBRIDMAP_NEUTRAL_ATOM_ARCHITECTURE_HPP diff --git a/include/hybridmap/NeutralAtomDefinitions.hpp b/include/hybridmap/NeutralAtomDefinitions.hpp index d320903dd..3f8dda7aa 100644 --- a/include/hybridmap/NeutralAtomDefinitions.hpp +++ b/include/hybridmap/NeutralAtomDefinitions.hpp @@ -8,8 +8,7 @@ * Licensed under the MIT License */ -#ifndef HYBRIDMAP_NEUTRAL_ATOM_DEFINITIONS_HPP -#define HYBRIDMAP_NEUTRAL_ATOM_DEFINITIONS_HPP +#pragma once #include "datastructures/SymmetricMatrix.hpp" #include "ir/Definitions.hpp" @@ -109,5 +108,3 @@ using GateList = std::vector; using GateLists = std::vector; } // namespace na - -#endif // HYBRIDMAP_NEUTRAL_ATOM_DEFINITIONS_HPP diff --git a/include/hybridmap/NeutralAtomLayer.hpp b/include/hybridmap/NeutralAtomLayer.hpp index 2e8efa580..3555925a5 100644 --- a/include/hybridmap/NeutralAtomLayer.hpp +++ b/include/hybridmap/NeutralAtomLayer.hpp @@ -8,8 +8,7 @@ * Licensed under the MIT License */ -#ifndef HYBRIDMAP_NEUTRAL_ATOM_LAYER_HPP -#define HYBRIDMAP_NEUTRAL_ATOM_LAYER_HPP +#pragma once #include "hybridmap/NeutralAtomDefinitions.hpp" #include "ir/Definitions.hpp" @@ -151,5 +150,3 @@ bool commutesWithAtQubit(const GateList& layer, const qc::Operation* opPointer, bool commuteAtQubit(const qc::Operation* op1, const qc::Operation* op2, const qc::Qubit& qubit); } // namespace na - -#endif // HYBRIDMAP_NEUTRAL_ATOM_LAYER_HPP diff --git a/include/hybridmap/NeutralAtomScheduler.hpp b/include/hybridmap/NeutralAtomScheduler.hpp index bd62031b4..a68ea8fbb 100644 --- a/include/hybridmap/NeutralAtomScheduler.hpp +++ b/include/hybridmap/NeutralAtomScheduler.hpp @@ -8,8 +8,7 @@ * Licensed under the MIT License */ -#ifndef HYBRIDMAP_NEUTRAL_ATOM_SCHEDULER_HPP -#define HYBRIDMAP_NEUTRAL_ATOM_SCHEDULER_HPP +#pragma once #include "hybridmap/NeutralAtomArchitecture.hpp" #include "hybridmap/NeutralAtomDefinitions.hpp" @@ -202,5 +201,3 @@ class NeutralAtomScheduler { }; } // namespace na - -#endif // HYBRIDMAP_NEUTRAL_ATOM_SCHEDULER_HPP diff --git a/include/hybridmap/NeutralAtomUtils.hpp b/include/hybridmap/NeutralAtomUtils.hpp index 0ce6fb63e..f30404418 100644 --- a/include/hybridmap/NeutralAtomUtils.hpp +++ b/include/hybridmap/NeutralAtomUtils.hpp @@ -8,8 +8,7 @@ * Licensed under the MIT License */ -#ifndef HYBRIDMAP_NEUTRAL_ATOM_UTILS_HPP -#define HYBRIDMAP_NEUTRAL_ATOM_UTILS_HPP +#pragma once #include "circuit_optimizer/CircuitOptimizer.hpp" #include "hybridmap/NeutralAtomDefinitions.hpp" @@ -468,5 +467,3 @@ class BridgeCircuits { bridgeExpand(const qc::QuantumComputation& qcBridge, size_t qubit); }; } // namespace na - -#endif // HYBRIDMAP_NEUTRAL_ATOM_UTILS_HPP From ec7ecc8f42590d2034c7eb8dc7d84aef3205cecc Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 4 Dec 2025 15:48:19 +0100 Subject: [PATCH 377/394] =?UTF-8?q?=F0=9F=8E=A8=20add=20assert=20if=20Move?= =?UTF-8?q?Group=20has=20neither=20normal=20nor=20FA=20move.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/MoveToAodConverter.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/hybridmap/MoveToAodConverter.hpp b/include/hybridmap/MoveToAodConverter.hpp index a5a733a57..95b4182db 100644 --- a/include/hybridmap/MoveToAodConverter.hpp +++ b/include/hybridmap/MoveToAodConverter.hpp @@ -285,6 +285,7 @@ class MoveToAodConverter { */ [[nodiscard]] uint32_t getFirstIdx() const { + assert(!moves.empty() || !movesFa.empty()); if (moves.empty()) { return movesFa.front().second; } From e3fb023eef31fdf927813f247d639d324047bac1 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 4 Dec 2025 15:54:05 +0100 Subject: [PATCH 378/394] =?UTF-8?q?=F0=9F=8E=A8=20add=20<=20operator=20for?= =?UTF-8?q?=20AtomMove?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/NeutralAtomDefinitions.hpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/include/hybridmap/NeutralAtomDefinitions.hpp b/include/hybridmap/NeutralAtomDefinitions.hpp index 3f8dda7aa..8703bfbd3 100644 --- a/include/hybridmap/NeutralAtomDefinitions.hpp +++ b/include/hybridmap/NeutralAtomDefinitions.hpp @@ -16,6 +16,7 @@ #include #include +#include #include #include @@ -96,6 +97,16 @@ struct AtomMove { * @return True if any field differs. */ bool operator!=(const AtomMove& other) const { return !(*this == other); } + + /** + * @brief Less-than comparison for ordering. + * @param other Move to compare. + * @return True if this move is less than the other in lexicographical order. + */ + bool operator<(const AtomMove& other) const { + return std::tie(c1, c2, load1, load2) < + std::tie(other.c1, other.c2, other.load1, other.load2); + } }; /** From 9b42d14ee9a9fe6cac78e529cc7d7ed1c9ad08f1 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 4 Dec 2025 16:00:20 +0100 Subject: [PATCH 379/394] =?UTF-8?q?=F0=9F=90=8D=20make=20python=20bindings?= =?UTF-8?q?=20more=20consistent.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bindings/hybrid_mapper/hybrid_mapper.cpp | 11 +++++------ python/mqt/qmap/hybrid_mapper.pyi | 9 ++++----- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/bindings/hybrid_mapper/hybrid_mapper.cpp b/bindings/hybrid_mapper/hybrid_mapper.cpp index 0ecc90ed3..4d68a01ff 100644 --- a/bindings/hybrid_mapper/hybrid_mapper.cpp +++ b/bindings/hybrid_mapper/hybrid_mapper.cpp @@ -196,8 +196,6 @@ PYBIND11_MODULE(MQT_QMAP_MODULE_NAME, m, py::mod_gil_not_used()) { "filename"_a, "initial_mapping"_a = na::InitialMapping::Identity) .def("get_stats", &na::NeutralAtomMapper::getStatsMap, "Returns the statistics of the mapping.") - .def("get_mapped_qc", &na::NeutralAtomMapper::getMappedQc, - "Returns the mapped circuit.") .def("get_mapped_qc_qasm", &na::NeutralAtomMapper::getMappedQcQasm, "Returns the mapped circuit as an extended qasm2 string.") .def("get_mapped_qc_aod_qasm", &na::NeutralAtomMapper::getMappedQcAodQasm, @@ -241,19 +239,20 @@ PYBIND11_MODULE(MQT_QMAP_MODULE_NAME, m, py::mod_gil_not_used()) { "Initializes the synthesized and mapped circuits and mapping " "structures for the given number of qubits.", "n_qubits"_a) - .def("get_mapped_qc", &na::HybridSynthesisMapper::getMappedQcQasm, + .def("get_mapped_qc_qasm", &na::HybridSynthesisMapper::getMappedQcQasm, "Returns the mapped circuit as an extended qasm2 string.") - .def("save_mapped_qc", &na::HybridSynthesisMapper::saveMappedQcQasm, + .def("save_mapped_qc_qasm", &na::HybridSynthesisMapper::saveMappedQcQasm, "Saves the mapped circuit as an extended qasm2 to a file.", "filename"_a) .def("convert_to_aod", &na::HybridSynthesisMapper::convertToAod, "Converts the mapped circuit to " "native AOD movements.") .def( - "get_mapped_qc_aod", &na::HybridSynthesisMapper::getMappedQcAodQasm, + "get_mapped_qc_aod_qasm", + &na::HybridSynthesisMapper::getMappedQcAodQasm, "Returns the mapped circuit with native AOD movements as an extended " "qasm2 string.") - .def("save_mapped_qc_aod", + .def("save_mapped_qc_aod_qasm", &na::HybridSynthesisMapper::saveMappedQcAodQasm, "Saves the mapped circuit with native AOD movements as an extended " "qasm2 to a " diff --git a/python/mqt/qmap/hybrid_mapper.pyi b/python/mqt/qmap/hybrid_mapper.pyi index 641b54d91..bab9cace9 100644 --- a/python/mqt/qmap/hybrid_mapper.pyi +++ b/python/mqt/qmap/hybrid_mapper.pyi @@ -82,7 +82,6 @@ class HybridNAMapper: def __init__(self, arch: NeutralAtomHybridArchitecture, params: MapperParameters) -> None: ... def get_animation_viz(self) -> str: ... def get_init_hw_pos(self) -> dict[int, int]: ... - def get_mapped_qc(self) -> QuantumComputation: ... def get_mapped_qc_qasm(self) -> str: ... def get_mapped_qc_aod_qasm(self) -> str: ... def get_stats(self) -> dict[str, float]: ... @@ -109,12 +108,12 @@ class HybridSynthesisMapper: self, synthesis_steps: list[QuantumComputation], also_map: bool = ... ) -> list[float]: ... def get_circuit_adjacency_matrix(self) -> list[list[int]]: ... - def get_mapped_qc(self) -> str: ... - def get_mapped_qc_aod(self) -> str: ... + def get_mapped_qc_qasm(self) -> str: ... + def get_mapped_qc_aod_qasm(self) -> str: ... def get_synthesized_qc(self) -> str: ... def init_mapping(self, n_qubits: typing.SupportsInt) -> None: ... - def save_mapped_qc(self, filename: str) -> None: ... - def save_mapped_qc_aod(self, filename: str) -> None: ... + def save_mapped_qc_qasm(self, filename: str) -> None: ... + def save_mapped_qc_aod_qasm(self, filename: str) -> None: ... def save_synthesized_qc(self, filename: str) -> None: ... def schedule( self, verbose: bool = ..., create_animation_csv: bool = ..., shuttling_speed_factor: typing.SupportsFloat = ... From 74758c0a364f3f5e8bdb36e0b871564580446c63 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 4 Dec 2025 16:02:27 +0100 Subject: [PATCH 380/394] =?UTF-8?q?=F0=9F=94=A5=20removed=20unnecessary=20?= =?UTF-8?q?comment.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridNeutralAtomMapper.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 5c0d51ef6..92a9227a9 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -28,7 +28,7 @@ #include #include #include -#include // added +#include #include #include #include From 1514df4e529e38c3d421038efeb3964134eef345 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Thu, 4 Dec 2025 16:02:53 +0100 Subject: [PATCH 381/394] =?UTF-8?q?=F0=9F=94=A5=20removed=20another=20unne?= =?UTF-8?q?cessary=20comment.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/MoveToAodConverter.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/hybridmap/MoveToAodConverter.cpp b/src/hybridmap/MoveToAodConverter.cpp index e5cb69324..a6eecc62c 100644 --- a/src/hybridmap/MoveToAodConverter.cpp +++ b/src/hybridmap/MoveToAodConverter.cpp @@ -40,7 +40,6 @@ MoveToAodConverter::schedule(qc::QuantumComputation& qc) { return qc; } processMoveGroups(); - // postProcessMoveGroups(); // create new quantum circuit and insert AOD operations at the correct // indices From 23b25e8ebdffacd04687d3161c215e9a3d074142 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 5 Dec 2025 13:27:23 +0100 Subject: [PATCH 382/394] =?UTF-8?q?=F0=9F=8E=A8=20changed=20std::cout=20to?= =?UTF-8?q?=20spdlog?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridNeutralAtomMapper.cpp | 112 ++++++++++------------ src/hybridmap/HybridSynthesisMapper.cpp | 6 +- src/hybridmap/NeutralAtomScheduler.cpp | 31 +++--- 3 files changed, 68 insertions(+), 81 deletions(-) diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index 92a9227a9..bea445a40 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -79,16 +79,13 @@ void NeutralAtomMapper::mapAppend(qc::QuantumComputation& qc, mapping = initialMapping; if (this->parameters.verbose) { - std::cout << "* Init Coord Mapping w/ [row:" << arch->getNrows() - << " X col:" << arch->getNcolumns() << "] hardware" << "\n"; + spdlog::info("* Init Coord Mapping w/ [row:{} X col:{}] hardware", + arch->getNrows(), arch->getNcolumns()); for (uint32_t q = 0; q < qc.getNqubits(); q++) { - std::cout << "q " << std::setw(3) << q; const auto hwQubit = this->mapping.getHwQubit(q); - std::cout << " -> h " << std::setw(3) << hwQubit; - std::cout << " -> c " << std::setw(3) - << hardwareQubits.getCoordIndex(hwQubit) << "\n"; + spdlog::info("q {:3} -> h {:3} -> c {:3}", q, hwQubit, + hardwareQubits.getCoordIndex(hwQubit)); } - std::cout << "\n"; } // init layers @@ -110,7 +107,7 @@ void NeutralAtomMapper::mapAppend(qc::QuantumComputation& qc, // assign gates to layers reassignGatesToLayers(frontLayer.getGates(), lookaheadLayer.getGates()); if (this->parameters.verbose) { - std::cout << "Iteration " << i << '\n'; + spdlog::info("Iteration {}", i); printLayers(); } @@ -119,11 +116,11 @@ void NeutralAtomMapper::mapAppend(qc::QuantumComputation& qc, } if (this->parameters.verbose) { - std::cout << "nSwaps: " << stats.nSwaps << '\n'; - std::cout << "nBridges: " << stats.nBridges << '\n'; - std::cout << "nFAncillas: " << stats.nFAncillas << '\n'; - std::cout << "nMoves: " << stats.nMoves << '\n'; - std::cout << "nPassBy: " << stats.nPassBy << '\n'; + spdlog::info("nSwaps: {}", stats.nSwaps); + spdlog::info("nBridges: {}", stats.nBridges); + spdlog::info("nFAncillas: {}", stats.nFAncillas); + spdlog::info("nMoves: {}", stats.nMoves); + spdlog::info("nPassBy: {}", stats.nPassBy); } } @@ -182,7 +179,7 @@ qc::QuantumComputation NeutralAtomMapper::convertToAod() { MoveToAodConverter aodScheduler(*arch, hardwareQubits, flyingAncillas); mappedQcAOD = aodScheduler.schedule(mappedQc); if (this->parameters.verbose) { - std::cout << "nMoveGroups: " << aodScheduler.getNMoveGroups() << '\n'; + spdlog::info("nMoveGroups: {}", aodScheduler.getNMoveGroups()); } return mappedQcAOD; } @@ -203,7 +200,7 @@ void NeutralAtomMapper::applyPassBy(NeutralAtomLayer& frontLayer, for (const auto& passBy : pbComb.moves) { mappedQc.move(passBy.c1, passBy.c2 + arch->getNpositions()); if (this->parameters.verbose) { - std::cout << "passby " << passBy.c1 << " " << passBy.c2 << '\n'; + spdlog::info("passby {} {}", passBy.c1, passBy.c2); } auto itT = std::ranges::find(targetCoords, passBy.c1); if (itT != targetCoords.end()) { @@ -227,7 +224,7 @@ void NeutralAtomMapper::applyPassBy(NeutralAtomLayer& frontLayer, for (const auto& passBy : pbComb.moves) { mappedQc.move(passBy.c2 + arch->getNpositions(), passBy.c1); if (this->parameters.verbose) { - std::cout << "passby " << passBy.c2 << " " << passBy.c1 << '\n'; + spdlog::info("passby {} {}", passBy.c2, passBy.c1); } } @@ -269,11 +266,10 @@ void NeutralAtomMapper::reassignGatesToLayers(const GateList& frontGates, void NeutralAtomMapper::mapGate(const qc::Operation* op) { if (this->parameters.verbose) { - std::cout << "mapped " << op->getName() << " "; + spdlog::info("mapped {}", op->getName()); for (const auto qubit : op->getUsedQubits()) { - std::cout << qubit << " "; + spdlog::info("{}", qubit); } - std::cout << "\n"; } // convert circuit qubits to CoordIndex and append to mappedQc const auto opCopyUnique = op->clone(); @@ -293,42 +289,38 @@ bool NeutralAtomMapper::isExecutable(const qc::Operation* opPointer) { } void NeutralAtomMapper::printLayers() const { - std::cout << "f,g: "; + spdlog::info("f,g:"); for (const auto* op : this->frontLayerGate) { - std::cout << op->getName() << " "; + spdlog::info("{}", op->getName()); for (const auto qubit : op->getUsedQubits()) { - std::cout << qubit << " "; + spdlog::info("{}", qubit); } - std::cout << '\n'; } - std::cout << '\n'; - std::cout << "f,s: "; + spdlog::info(""); + spdlog::info("f,s:"); for (const auto* op : this->frontLayerShuttling) { - std::cout << op->getName() << " "; + spdlog::info("{}", op->getName()); for (const auto qubit : op->getUsedQubits()) { - std::cout << qubit << " "; + spdlog::info("{}", qubit); } - std::cout << '\n'; } - std::cout << '\n'; - std::cout << "l,g: "; + spdlog::info(""); + spdlog::info("l,g:"); for (const auto* op : this->lookaheadLayerGate) { - std::cout << op->getName() << " "; + spdlog::info("{}", op->getName()); for (const auto qubit : op->getUsedQubits()) { - std::cout << qubit << " "; + spdlog::info("{}", qubit); } - std::cout << '\n'; } - std::cout << '\n'; - std::cout << "l,s: "; + spdlog::info(""); + spdlog::info("l,s:"); for (const auto* op : this->lookaheadLayerShuttling) { - std::cout << op->getName() << " "; + spdlog::info("{}", op->getName()); for (const auto qubit : op->getUsedQubits()) { - std::cout << qubit << " "; + spdlog::info("{}", qubit); } - std::cout << '\n'; } - std::cout << '\n'; + spdlog::info(""); } GateList NeutralAtomMapper::getExecutableGates(const GateList& gates) { @@ -359,19 +351,18 @@ void NeutralAtomMapper::applySwap(const Swap& swap) { const auto idxSecond = this->hardwareQubits.getCoordIndex(swap.second); this->mappedQc.swap(idxFirst, idxSecond); if (this->parameters.verbose) { - std::cout << "swapped " << swap.first << " " << swap.second; - std::cout << " logical qubits: "; + spdlog::info("swapped {} {}", swap.first, swap.second); + spdlog::info(" logical qubits:"); if (this->mapping.isMapped(swap.first)) { - std::cout << this->mapping.getCircQubit(swap.first); + spdlog::info("{}", this->mapping.getCircQubit(swap.first)); } else { - std::cout << "not mapped"; + spdlog::info("not mapped"); } if (this->mapping.isMapped(swap.second)) { - std::cout << " " << this->mapping.getCircQubit(swap.second); + spdlog::info(" {}", this->mapping.getCircQubit(swap.second)); } else { - std::cout << " not mapped"; + spdlog::info(" not mapped"); } - std::cout << '\n'; } } @@ -384,12 +375,12 @@ void NeutralAtomMapper::applyMove(AtomMove move) { const auto toMoveHwQubit = this->hardwareQubits.getHwQubit(move.c1); this->hardwareQubits.move(toMoveHwQubit, move.c2); if (this->parameters.verbose) { - std::cout << "moved " << move.c1 << " to " << move.c2; + spdlog::info("moved {} to {}", move.c1, move.c2); if (this->mapping.isMapped(toMoveHwQubit)) { - std::cout << " logical qubit: " - << this->mapping.getCircQubit(toMoveHwQubit) << '\n'; + spdlog::info(" logical qubit: {}", + this->mapping.getCircQubit(toMoveHwQubit)); } else { - std::cout << " not mapped" << '\n'; + spdlog::info(" not mapped"); } } stats.nMoves++; @@ -400,11 +391,10 @@ void NeutralAtomMapper::applyBridge(NeutralAtomLayer& frontLayer, mappedQc.bridge(coordIndices); if (this->parameters.verbose) { - std::cout << "bridged " << bridge.first->getName() << " "; + spdlog::info("bridged {}", bridge.first->getName()); for (const auto qubit : bridge.second) { - std::cout << qubit << " "; + spdlog::info("{}", qubit); } - std::cout << '\n'; } // // remove gate from frontLayer @@ -452,8 +442,8 @@ void NeutralAtomMapper::applyFlyingAncilla(NeutralAtomLayer& frontLayer, i += 2; if (this->parameters.verbose) { - std::cout << "passby (flying ancilla) " << passBy.origin << " " - << passBy.q1 << " " << passBy.q2 << '\n'; + spdlog::info("passby (flying ancilla) {} {} {}", passBy.origin, passBy.q1, + passBy.q2); } } const auto opCopy = faComb.op->clone(); @@ -481,8 +471,7 @@ void NeutralAtomMapper::applyFlyingAncilla(NeutralAtomLayer& frontLayer, } if (this->parameters.verbose) { - std::cout << "passby (flying ancilla) " << passBy.q2 << " " << passBy.q1 - << '\n'; + spdlog::info("passby (flying ancilla) {} {}", passBy.q2, passBy.q1); } } @@ -811,11 +800,10 @@ NeutralAtomMapper::initSwaps(const GateList& layer) { // for multi-qubit gates, find the best position around the gate qubits auto bestPos = getBestMultiQubitPosition(gate); if (this->parameters.verbose) { - std::cout << "bestPos: "; + spdlog::info("bestPos:"); for (const auto qubit : bestPos) { - std::cout << qubit << " "; + spdlog::info("{}", qubit); } - std::cout << '\n'; } // then compute the exact moves to get to the best position auto exactSwapsToPos = getExactSwapsToPosition(gate, bestPos); @@ -1492,7 +1480,7 @@ size_t NeutralAtomMapper::shuttlingBasedMapping( while (!this->frontLayerShuttling.empty()) { ++i; if (this->parameters.verbose) { - std::cout << "iteration " << i << '\n'; + spdlog::info("iteration {}", i); } auto bestComb = findBestAtomMove(); MappingMethod bestMethod = MoveMethod; @@ -1660,7 +1648,7 @@ size_t NeutralAtomMapper::gateBasedMapping(NeutralAtomLayer& frontLayer, while (gatesToExecute.empty() && !this->frontLayerGate.empty()) { ++i; if (this->parameters.verbose) { - std::cout << "iteration " << i << '\n'; + spdlog::info("iteration {}", i); } auto bestSwap = findBestSwap(lastSwap); diff --git a/src/hybridmap/HybridSynthesisMapper.cpp b/src/hybridmap/HybridSynthesisMapper.cpp index 921ec3bcb..af137fca0 100644 --- a/src/hybridmap/HybridSynthesisMapper.cpp +++ b/src/hybridmap/HybridSynthesisMapper.cpp @@ -21,7 +21,7 @@ #include #include -#include +#include #include #include #include @@ -42,12 +42,12 @@ HybridSynthesisMapper::evaluateSynthesisSteps(qcs& synthesisSteps, size_t qcIndex = 0; for (auto& qc : synthesisSteps) { if (this->parameters.verbose) { - std::cout << "Evaluating synthesis step number " << qcIndex << "\n"; + spdlog::info("Evaluating synthesis step number {}", qcIndex); } const auto fidelity = this->evaluateSynthesisStep(qc); candidates.emplace_back(qc, fidelity); if (this->parameters.verbose) { - std::cout << "Fidelity: " << fidelity << "\n"; + spdlog::info("Fidelity: {}", fidelity); } ++qcIndex; } diff --git a/src/hybridmap/NeutralAtomScheduler.cpp b/src/hybridmap/NeutralAtomScheduler.cpp index 58e7d2c6a..538ac8bc7 100644 --- a/src/hybridmap/NeutralAtomScheduler.cpp +++ b/src/hybridmap/NeutralAtomScheduler.cpp @@ -23,10 +23,10 @@ #include #include #include -#include #include #include #include +#include #include #include #include @@ -39,7 +39,7 @@ na::SchedulerResults na::NeutralAtomScheduler::schedule( animation.clear(); animationMachine.clear(); if (verbose) { - std::cout << "\n* schedule start!\n"; + spdlog::info("* schedule start!"); } const auto nPositions = static_cast(arch->getNpositions()); @@ -65,7 +65,7 @@ na::SchedulerResults na::NeutralAtomScheduler::schedule( for (const auto& op : qc) { index++; if (verbose) { - std::cout << "\n" << index << "\n"; + spdlog::info("{}", index); } if (op->getType() == qc::AodActivate) { nAodActivate++; @@ -85,17 +85,16 @@ na::SchedulerResults na::NeutralAtomScheduler::schedule( // DEBUG info if (verbose) { - std::cout << op->getName() << " "; + spdlog::info("{}", op->getName()); // print control qubits for (const auto& c : op->getControls()) { - std::cout << "c" << c.qubit << " "; + spdlog::info("c{} ", c.qubit); } // print target qubits for (const auto& t : op->getTargets()) { - std::cout << "q" << t << " "; + spdlog::info("q{} ", t); } - std::cout << "-> time: " << opTime << ", fidelity: " << opFidelity - << "\n"; + spdlog::info("-> time: {}, fidelity: {}", opTime, opFidelity); } qc::fp maxTime = 0; @@ -171,7 +170,7 @@ na::SchedulerResults na::NeutralAtomScheduler::schedule( } } if (verbose) { - std::cout << "\n* schedule end!\n"; + spdlog::info("* schedule end!"); } const auto maxExecutionTime = *std::ranges::max_element(totalExecutionTimes); @@ -197,11 +196,11 @@ void na::NeutralAtomScheduler::printSchedulerResults( const uint32_t nCZs, const uint32_t nAodActivate, const uint32_t nAodMove) { const auto totalExecutionTime = *std::ranges::max_element( totalExecutionTimes.begin(), totalExecutionTimes.end()); - std::cout << "\ntotalExecutionTimes: " << totalExecutionTime << "\n"; - std::cout << "totalIdleTime: " << totalIdleTime << "\n"; - std::cout << "totalGateFidelities: " << totalGateFidelities << "\n"; - std::cout << "totalFidelities: " << totalFidelities << "\n"; - std::cout << "totalNumCZs: " << nCZs << "\n"; - std::cout << "nAodActivate: " << nAodActivate << "\n"; - std::cout << "nAodMove: " << nAodMove << "\n"; + spdlog::info("totalExecutionTimes: {}", totalExecutionTime); + spdlog::info("totalIdleTime: {}", totalIdleTime); + spdlog::info("totalGateFidelities: {}", totalGateFidelities); + spdlog::info("totalFidelities: {}", totalFidelities); + spdlog::info("totalNumCZs: {}", nCZs); + spdlog::info("nAodActivate: {}", nAodActivate); + spdlog::info("nAodMove: {}", nAodMove); } From 699cdbb23b7cc83cca4b838711a99be3f5ae8f48 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 5 Dec 2025 13:35:26 +0100 Subject: [PATCH 383/394] =?UTF-8?q?=F0=9F=8E=A8=20removed=20this->?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HardwareQubits.hpp | 12 +- include/hybridmap/HybridNeutralAtomMapper.hpp | 44 +++--- include/hybridmap/HybridSynthesisMapper.hpp | 4 +- include/hybridmap/Mapping.hpp | 4 +- include/hybridmap/NeutralAtomArchitecture.hpp | 14 +- src/hybridmap/HardwareQubits.cpp | 10 +- src/hybridmap/HybridSynthesisMapper.cpp | 24 +-- src/hybridmap/Mapping.cpp | 18 +-- src/hybridmap/MoveToAodConverter.cpp | 6 +- src/hybridmap/NeutralAtomArchitecture.cpp | 144 +++++++++--------- src/hybridmap/NeutralAtomLayer.cpp | 2 +- 11 files changed, 137 insertions(+), 145 deletions(-) diff --git a/include/hybridmap/HardwareQubits.hpp b/include/hybridmap/HardwareQubits.hpp index 8cd39f882..6b88fa914 100644 --- a/include/hybridmap/HardwareQubits.hpp +++ b/include/hybridmap/HardwareQubits.hpp @@ -105,11 +105,11 @@ class HardwareQubits { assert(nQubits <= architecture.getNpositions() && "Number of hardware qubits exceeds available positions."); - swapDistances = qc::SymmetricMatrix(this->nQubits); + swapDistances = qc::SymmetricMatrix(nQubits); switch (initialCoordinateMapping) { case Trivial: - for (uint32_t i = 0; i < this->nQubits; ++i) { + for (uint32_t i = 0; i < nQubits; ++i) { hwToCoordIdx.emplace(i, i); occupiedCoordinates.emplace_back(i); } @@ -123,12 +123,12 @@ class HardwareQubits { } std::mt19937 g(seed); std::ranges::shuffle(indices, g); - for (uint32_t i = 0; i < this->nQubits; ++i) { + for (uint32_t i = 0; i < nQubits; ++i) { hwToCoordIdx.emplace(i, indices[i]); occupiedCoordinates.emplace_back(indices[i]); } - swapDistances = qc::SymmetricMatrix(this->nQubits, -1); + swapDistances = qc::SymmetricMatrix(nQubits, -1); } initNearbyQubits(); @@ -237,7 +237,7 @@ class HardwareQubits { getCoordIndices(const std::set& hwQubits) const { std::set coordIndices; for (auto const& hwQubit : hwQubits) { - coordIndices.emplace(this->getCoordIndex(hwQubit)); + coordIndices.emplace(getCoordIndex(hwQubit)); } return coordIndices; } @@ -247,7 +247,7 @@ class HardwareQubits { std::vector coordIndices; coordIndices.reserve(hwQubits.size()); for (auto const& hwQubit : hwQubits) { - coordIndices.emplace_back(this->getCoordIndex(hwQubit)); + coordIndices.emplace_back(getCoordIndex(hwQubit)); } return coordIndices; } diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index befe4909d..a6a1a4002 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -462,9 +462,9 @@ class NeutralAtomMapper { throw std::runtime_error("Only one flying ancilla is supported for now."); } // precompute exponential decay weights - this->decayWeights.reserve(this->arch->getNcolumns()); - for (uint32_t i = this->arch->getNcolumns(); i > 0; --i) { - this->decayWeights.emplace_back(std::exp(-this->parameters.decay * i)); + decayWeights.reserve(arch->getNcolumns()); + for (uint32_t i = arch->getNcolumns(); i > 0; --i) { + decayWeights.emplace_back(std::exp(-parameters.decay * i)); } } explicit NeutralAtomMapper(const NeutralAtomArchitecture& architecture, @@ -478,7 +478,7 @@ class NeutralAtomMapper { * unsupported number of flying ancillas. */ void setParameters(const MapperParameters& p) { - this->parameters = p; + parameters = p; const auto nPositions = static_cast(arch->getNpositions()); const auto nQubits = static_cast(arch->getNqubits()); if (nPositions - nQubits < 1 && p.shuttlingWeight > 0) { @@ -489,7 +489,7 @@ class NeutralAtomMapper { if (parameters.numFlyingAncillas > 1) { throw std::runtime_error("Only one flying ancilla is supported for now."); } - this->reset(); + reset(); } /** @@ -498,15 +498,15 @@ class NeutralAtomMapper { * @param mapper Source mapper. */ void copyStateFrom(const NeutralAtomMapper& mapper) { - this->arch = mapper.arch; - this->parameters = mapper.parameters; - this->mapping = mapper.mapping; - this->hardwareQubits = mapper.hardwareQubits; - this->lastMoves = mapper.lastMoves; - this->lastBlockedQubits = mapper.lastBlockedQubits; - this->scheduler = mapper.scheduler; - this->decayWeights = mapper.decayWeights; - this->flyingAncillas = mapper.flyingAncillas; + arch = mapper.arch; + parameters = mapper.parameters; + mapping = mapper.mapping; + hardwareQubits = mapper.hardwareQubits; + lastMoves = mapper.lastMoves; + lastBlockedQubits = mapper.lastBlockedQubits; + scheduler = mapper.scheduler; + decayWeights = mapper.decayWeights; + flyingAncillas = mapper.flyingAncillas; } /** @@ -601,7 +601,7 @@ class NeutralAtomMapper { */ [[nodiscard]] [[maybe_unused]] std::string getMappedQcQasm() const { std::stringstream ss; - this->mappedQc.dumpOpenQASM(ss, false); + mappedQc.dumpOpenQASM(ss, false); return ss.str(); } @@ -611,7 +611,7 @@ class NeutralAtomMapper { */ [[maybe_unused]] void saveMappedQcQasm(const std::string& filename) const { std::ofstream ofs(filename); - this->mappedQc.dumpOpenQASM(ofs, false); + mappedQc.dumpOpenQASM(ofs, false); } /** @@ -619,11 +619,11 @@ class NeutralAtomMapper { * @return OpenQASM string (AOD-native). */ [[maybe_unused]] std::string getMappedQcAodQasm() { - if (this->mappedQcAOD.empty()) { - this->convertToAod(); + if (mappedQcAOD.empty()) { + convertToAod(); } std::stringstream ss; - this->mappedQcAOD.dumpOpenQASM(ss, false); + mappedQcAOD.dumpOpenQASM(ss, false); return ss.str(); } @@ -632,11 +632,11 @@ class NeutralAtomMapper { * @param filename Output file path. */ [[maybe_unused]] void saveMappedQcAodQasm(const std::string& filename) { - if (this->mappedQcAOD.empty()) { - this->convertToAod(); + if (mappedQcAOD.empty()) { + convertToAod(); } std::ofstream ofs(filename); - this->mappedQcAOD.dumpOpenQASM(ofs, false); + mappedQcAOD.dumpOpenQASM(ofs, false); } /** diff --git a/include/hybridmap/HybridSynthesisMapper.hpp b/include/hybridmap/HybridSynthesisMapper.hpp index 5f0431737..600bf86f7 100644 --- a/include/hybridmap/HybridSynthesisMapper.hpp +++ b/include/hybridmap/HybridSynthesisMapper.hpp @@ -90,7 +90,7 @@ class HybridSynthesisMapper : public NeutralAtomMapper { */ void completeRemap(const InitialMapping initMapping = Identity) { auto qcCopy = synthesizedQc; - this->map(qcCopy, initMapping); + map(qcCopy, initMapping); } /** @@ -98,7 +98,7 @@ class HybridSynthesisMapper : public NeutralAtomMapper { * @return Synthesized QuantumComputation. */ [[nodiscard]] qc::QuantumComputation getSynthesizedQc() const { - return this->synthesizedQc; + return synthesizedQc; } /** diff --git a/include/hybridmap/Mapping.hpp b/include/hybridmap/Mapping.hpp index 54dbd94eb..749bd8702 100644 --- a/include/hybridmap/Mapping.hpp +++ b/include/hybridmap/Mapping.hpp @@ -136,7 +136,7 @@ class Mapping { getHwQubits(const std::set& qubits) const { std::set hw; for (const auto& qubit : qubits) { - hw.emplace(this->getHwQubit(qubit)); + hw.emplace(getHwQubit(qubit)); } return hw; } @@ -153,7 +153,7 @@ class Mapping { std::vector hw; hw.reserve(qubits.size()); for (const auto& qubit : qubits) { - hw.emplace_back(this->getHwQubit(qubit)); + hw.emplace_back(getHwQubit(qubit)); } return hw; } diff --git a/include/hybridmap/NeutralAtomArchitecture.hpp b/include/hybridmap/NeutralAtomArchitecture.hpp index 36c5b8c7a..3941b5583 100644 --- a/include/hybridmap/NeutralAtomArchitecture.hpp +++ b/include/hybridmap/NeutralAtomArchitecture.hpp @@ -439,8 +439,7 @@ class NeutralAtomArchitecture { */ [[nodiscard]] qc::fp getEuclideanDistance(const CoordIndex idx1, const CoordIndex idx2) const { - return this->coordinates.at(idx1).getEuclideanDistance( - this->coordinates.at(idx2)); + return coordinates.at(idx1).getEuclideanDistance(coordinates.at(idx2)); } /** * @brief Sum of pairwise Euclidean distances among a set of indices. @@ -522,8 +521,7 @@ class NeutralAtomArchitecture { [[nodiscard]] CoordIndex getManhattanDistanceX(const CoordIndex idx1, const CoordIndex idx2) const { return static_cast( - this->coordinates.at(idx1).getManhattanDistanceX( - this->coordinates.at(idx2))); + coordinates.at(idx1).getManhattanDistanceX(coordinates.at(idx2))); } /** * @brief Manhattan distance in Y between two indices. @@ -564,8 +562,8 @@ class NeutralAtomArchitecture { */ [[nodiscard]] MoveVector getVector(const CoordIndex idx1, const CoordIndex idx2) const { - return {this->coordinates[idx1].x, this->coordinates[idx1].y, - this->coordinates[idx2].x, this->coordinates[idx2].y}; + return {coordinates[idx1].x, coordinates[idx1].y, coordinates[idx2].x, + coordinates[idx2].y}; } /** * @brief Estimate time to move along a MoveVector. @@ -573,8 +571,8 @@ class NeutralAtomArchitecture { * @return Shuttling time proportional to Euclidean length and device speed. */ [[nodiscard]] qc::fp getVectorShuttlingTime(const MoveVector& v) const { - return v.getLength() * this->getInterQubitDistance() / - this->getShuttlingTime(qc::OpType::Move); + return v.getLength() * getInterQubitDistance() / + getShuttlingTime(qc::OpType::Move); } /** diff --git a/src/hybridmap/HardwareQubits.cpp b/src/hybridmap/HardwareQubits.cpp index e814d13de..a1fde115b 100644 --- a/src/hybridmap/HardwareQubits.cpp +++ b/src/hybridmap/HardwareQubits.cpp @@ -117,7 +117,7 @@ HardwareQubits::computeAllShortestPaths(const HwQubit q1, continue; } - for (const auto& neighbor : this->getNearbyQubits(currentQubit)) { + for (const auto& neighbor : getNearbyQubits(currentQubit)) { if (std::ranges::find(currentPath, neighbor) == currentPath.end()) { auto newPath = currentPath; newPath.push_back(neighbor); @@ -244,8 +244,8 @@ HardwareQubits::getBlockedQubits(const std::set& qubits) const { std::set HardwareQubits::getNearbyFreeCoordinatesByCoord(const CoordIndex idx) const { std::set nearbyFreeCoordinates; - for (auto const& coordIndex : this->arch->getNearbyCoordinates(idx)) { - if (!this->isMapped(coordIndex)) { + for (auto const& coordIndex : arch->getNearbyCoordinates(idx)) { + if (!isMapped(coordIndex)) { nearbyFreeCoordinates.emplace(coordIndex); } } @@ -254,8 +254,8 @@ HardwareQubits::getNearbyFreeCoordinatesByCoord(const CoordIndex idx) const { std::set HardwareQubits::getNearbyOccupiedCoordinatesByCoord( const CoordIndex idx) const { - const auto nearbyHwQubits = this->getNearbyQubits(this->getHwQubit(idx)); - return this->getCoordIndices(nearbyHwQubits); + const auto nearbyHwQubits = getNearbyQubits(getHwQubit(idx)); + return getCoordIndices(nearbyHwQubits); } std::vector diff --git a/src/hybridmap/HybridSynthesisMapper.cpp b/src/hybridmap/HybridSynthesisMapper.cpp index af137fca0..941196877 100644 --- a/src/hybridmap/HybridSynthesisMapper.cpp +++ b/src/hybridmap/HybridSynthesisMapper.cpp @@ -41,12 +41,12 @@ HybridSynthesisMapper::evaluateSynthesisSteps(qcs& synthesisSteps, std::vector> candidates; size_t qcIndex = 0; for (auto& qc : synthesisSteps) { - if (this->parameters.verbose) { + if (parameters.verbose) { spdlog::info("Evaluating synthesis step number {}", qcIndex); } - const auto fidelity = this->evaluateSynthesisStep(qc); + const auto fidelity = evaluateSynthesisStep(qc); candidates.emplace_back(qc, fidelity); - if (this->parameters.verbose) { + if (parameters.verbose) { spdlog::info("Fidelity: {}", fidelity); } ++qcIndex; @@ -62,14 +62,14 @@ HybridSynthesisMapper::evaluateSynthesisSteps(qcs& synthesisSteps, } } if (alsoMap && !candidates.empty()) { - this->appendWithMapping(candidates[bestIndex].first); + appendWithMapping(candidates[bestIndex].first); } return fidelities; } qc::fp HybridSynthesisMapper::evaluateSynthesisStep(qc::QuantumComputation& qc) const { - NeutralAtomMapper tempMapper(this->arch, this->parameters); + NeutralAtomMapper tempMapper(arch, parameters); tempMapper.copyStateFrom(*this); // Make a copy of qc to avoid modifying the original auto qcCopy = qc; @@ -85,8 +85,8 @@ void HybridSynthesisMapper::appendWithoutMapping( initMapping(qc.getNqubits()); } for (const auto& op : qc) { - this->synthesizedQc.emplace_back(op->clone()); - this->mapGate(op.get()); + synthesizedQc.emplace_back(op->clone()); + mapGate(op.get()); } } @@ -94,9 +94,9 @@ void HybridSynthesisMapper::appendWithMapping(qc::QuantumComputation& qc) { if (mappedQc.empty()) { initMapping(qc.getNqubits()); } - mapAppend(qc, this->mapping); + mapAppend(qc, mapping); for (const auto& op : qc) { - this->synthesizedQc.emplace_back(op->clone()); + synthesizedQc.emplace_back(op->clone()); } } @@ -114,9 +114,9 @@ AdjacencyMatrix HybridSynthesisMapper::getCircuitAdjacencyMatrix() const { adjMatrix(i, j) = 0; continue; } - const auto mappedI = this->mapping.getHwQubit(i); - const auto mappedJ = this->mapping.getHwQubit(j); - if (this->arch->getSwapDistance(mappedI, mappedJ) == 0) { + const auto mappedI = mapping.getHwQubit(i); + const auto mappedJ = mapping.getHwQubit(j); + if (arch->getSwapDistance(mappedI, mappedJ) == 0) { adjMatrix(i, j) = 1; adjMatrix(j, i) = 1; } else { diff --git a/src/hybridmap/Mapping.cpp b/src/hybridmap/Mapping.cpp index 8f622de03..9271e8c5b 100644 --- a/src/hybridmap/Mapping.cpp +++ b/src/hybridmap/Mapping.cpp @@ -27,15 +27,15 @@ namespace na { void Mapping::applySwap(const Swap& swap) { const auto q1 = swap.first; const auto q2 = swap.second; - if (this->isMapped(q1) && this->isMapped(q2)) { - const auto circQ1 = this->getCircQubit(q1); - const auto circQ2 = this->getCircQubit(q2); - this->setCircuitQubit(circQ2, q1); - this->setCircuitQubit(circQ1, q2); - } else if (this->isMapped(q1) && !this->isMapped(q2)) { - this->setCircuitQubit(this->getCircQubit(q1), q2); - } else if (this->isMapped(q2) && !this->isMapped(q1)) { - this->setCircuitQubit(this->getCircQubit(q2), q1); + if (isMapped(q1) && isMapped(q2)) { + const auto circQ1 = getCircQubit(q1); + const auto circQ2 = getCircQubit(q2); + setCircuitQubit(circQ2, q1); + setCircuitQubit(circQ1, q2); + } else if (isMapped(q1) && !isMapped(q2)) { + setCircuitQubit(getCircQubit(q1), q2); + } else if (isMapped(q2) && !isMapped(q1)) { + setCircuitQubit(getCircQubit(q2), q1); } else { throw std::runtime_error("Cannot swap unmapped qubits"); } diff --git a/src/hybridmap/MoveToAodConverter.cpp b/src/hybridmap/MoveToAodConverter.cpp index a6eecc62c..d859c1e31 100644 --- a/src/hybridmap/MoveToAodConverter.cpp +++ b/src/hybridmap/MoveToAodConverter.cpp @@ -617,9 +617,9 @@ void MoveToAodConverter::AodActivationHelper::computeInitAndOffsetOperations( std::vector& initOperations, std::vector& offsetOperations) const { - const auto d = this->arch->getInterQubitDistance(); - const auto interD = this->arch->getInterQubitDistance() / - this->arch->getNAodIntermediateLevels(); + const auto d = arch->getInterQubitDistance(); + const auto interD = + arch->getInterQubitDistance() / arch->getNAodIntermediateLevels(); initOperations.emplace_back(dimension, static_cast(aodMove->init) * d, static_cast(aodMove->init) * d); diff --git a/src/hybridmap/NeutralAtomArchitecture.cpp b/src/hybridmap/NeutralAtomArchitecture.cpp index 694f27d5d..7de065171 100644 --- a/src/hybridmap/NeutralAtomArchitecture.cpp +++ b/src/hybridmap/NeutralAtomArchitecture.cpp @@ -51,7 +51,7 @@ void NeutralAtomArchitecture::loadJson(const std::string& filename) { // Load properties nlohmann::basic_json<> jsonDataProperties = jsonData["properties"]; - this->properties = Properties( + properties = Properties( jsonDataProperties["nRows"], jsonDataProperties["nColumns"], jsonDataProperties["nAods"], jsonDataProperties["nAodCoordinates"], jsonDataProperties["interQubitDistance"], @@ -61,11 +61,11 @@ void NeutralAtomArchitecture::loadJson(const std::string& filename) { // Load parameters const nlohmann::basic_json<> jsonDataParameters = jsonData["parameters"]; - this->parameters = Parameters(); - this->parameters.nQubits = jsonDataParameters["nQubits"]; + parameters = Parameters(); + parameters.nQubits = jsonDataParameters["nQubits"]; // check if qubits can fit in the architecture - if (this->parameters.nQubits > this->properties.getNpositions()) { + if (parameters.nQubits > properties.getNpositions()) { throw std::runtime_error("Number of qubits exceeds number of positions"); } @@ -88,7 +88,7 @@ void NeutralAtomArchitecture::loadJson(const std::string& filename) { ensureGateWithFallback(gateTimes, "cz", "none"); ensureGateWithFallback(gateTimes, "h", "none"); - this->parameters.gateTimes = gateTimes; + parameters.gateTimes = gateTimes; std::map gateAverageFidelities; for (const auto& [key, value] : jsonDataParameters["gateAverageFidelities"].items()) { @@ -96,7 +96,7 @@ void NeutralAtomArchitecture::loadJson(const std::string& filename) { } ensureGateWithFallback(gateAverageFidelities, "cz", "none"); ensureGateWithFallback(gateAverageFidelities, "h", "none"); - this->parameters.gateAverageFidelities = gateAverageFidelities; + parameters.gateAverageFidelities = gateAverageFidelities; std::map shuttlingTimes; for (const auto& [key, value] : @@ -109,8 +109,8 @@ void NeutralAtomArchitecture::loadJson(const std::string& filename) { qc::fp const swapGateFidelity = std::pow(gateAverageFidelities.at("cz"), 3) * std::pow(gateAverageFidelities.at("h"), 6); - this->parameters.gateTimes.emplace("swap", swapGateTime); - this->parameters.gateAverageFidelities.emplace("swap", swapGateFidelity); + parameters.gateTimes.emplace("swap", swapGateTime); + parameters.gateAverageFidelities.emplace("swap", swapGateFidelity); // compute values for Bridge gate // precompute bridge circuits @@ -125,21 +125,21 @@ void NeutralAtomArchitecture::loadJson(const std::string& filename) { qc::fp const bridgeFidelity = std::pow(gateAverageFidelities.at("cz"), bridgeCircuits.czs[i]) * std::pow(gateAverageFidelities.at("h"), bridgeCircuits.hs[i]); - this->parameters.gateTimes.emplace("bridge" + std::to_string(i), - bridgeGateTime); - this->parameters.gateAverageFidelities.emplace( - "bridge" + std::to_string(i), bridgeFidelity); + parameters.gateTimes.emplace("bridge" + std::to_string(i), + bridgeGateTime); + parameters.gateAverageFidelities.emplace("bridge" + std::to_string(i), + bridgeFidelity); } - this->parameters.shuttlingTimes = shuttlingTimes; + parameters.shuttlingTimes = shuttlingTimes; std::map shuttlingAverageFidelities; for (const auto& [key, value] : jsonDataParameters["shuttlingAverageFidelities"].items()) { shuttlingAverageFidelities.emplace(qc::opTypeFromString(key), value); } - this->parameters.shuttlingAverageFidelities = shuttlingAverageFidelities; + parameters.shuttlingAverageFidelities = shuttlingAverageFidelities; - this->parameters.decoherenceTimes = Parameters::DecoherenceTimes{ + parameters.decoherenceTimes = Parameters::DecoherenceTimes{ .t1 = jsonDataParameters["decoherenceTimes"]["t1"], .t2 = jsonDataParameters["decoherenceTimes"]["t2"]}; @@ -149,23 +149,23 @@ void NeutralAtomArchitecture::loadJson(const std::string& filename) { } // apply changes to the object - this->name = jsonData["name"]; + name = jsonData["name"]; - this->createCoordinates(); - this->computeSwapDistances(this->properties.getInteractionRadius()); - this->computeNearbyCoordinates(); + createCoordinates(); + computeSwapDistances(properties.getInteractionRadius()); + computeNearbyCoordinates(); } void NeutralAtomArchitecture::createCoordinates() { coordinates.reserve(properties.getNpositions()); - for (std::uint16_t i = 0; i < this->properties.getNpositions(); i++) { - this->coordinates.emplace_back( - Location{.x = static_cast(i % this->properties.getNcolumns()), + for (std::uint16_t i = 0; i < properties.getNpositions(); i++) { + coordinates.emplace_back( + Location{.x = static_cast(i % properties.getNcolumns()), // NOLINTNEXTLINE(bugprone-integer-division) - .y = static_cast(i / this->properties.getNcolumns())}); + .y = static_cast(i / properties.getNcolumns())}); } } NeutralAtomArchitecture::NeutralAtomArchitecture(const std::string& filename) { - this->loadJson(filename); + loadJson(filename); } void NeutralAtomArchitecture::computeSwapDistances( @@ -178,8 +178,8 @@ void NeutralAtomArchitecture::computeSwapDistances( }; std::vector diagonalDistances; - for (uint32_t i = 0; i < this->getNcolumns() && i < interactionRadius; i++) { - for (uint32_t j = i; j < this->getNrows(); j++) { + for (uint32_t i = 0; i < getNcolumns() && i < interactionRadius; i++) { + for (uint32_t j = i; j < getNrows(); j++) { const auto dist = getEuclideanDistance( Location{.x = 0.0, .y = 0.0}, Location{.x = static_cast(i), .y = static_cast(j)}); @@ -205,14 +205,12 @@ void NeutralAtomArchitecture::computeSwapDistances( }); // compute swap distances - this->swapDistances = - qc::SymmetricMatrix(this->getNpositions()); + swapDistances = qc::SymmetricMatrix(getNpositions()); - for (uint32_t coordIndex1 = 0; coordIndex1 < this->getNpositions(); - coordIndex1++) { + for (uint32_t coordIndex1 = 0; coordIndex1 < getNpositions(); coordIndex1++) { for (uint32_t coordIndex2 = 0; coordIndex2 < coordIndex1; coordIndex2++) { - auto deltaX = this->getManhattanDistanceX(coordIndex1, coordIndex2); - auto deltaY = this->getManhattanDistanceY(coordIndex1, coordIndex2); + auto deltaX = getManhattanDistanceX(coordIndex1, coordIndex2); + auto deltaY = getManhattanDistanceY(coordIndex1, coordIndex2); // check if one can go diagonal to reduce the swap distance int32_t swapDistance = 0; @@ -228,22 +226,20 @@ void NeutralAtomArchitecture::computeSwapDistances( swapDistance = 1; } // save swap distance in matrix - this->swapDistances(coordIndex1, coordIndex2) = swapDistance - 1; - this->swapDistances(coordIndex2, coordIndex1) = swapDistance - 1; + swapDistances(coordIndex1, coordIndex2) = swapDistance - 1; + swapDistances(coordIndex2, coordIndex1) = swapDistance - 1; } } } void NeutralAtomArchitecture::computeNearbyCoordinates() { - this->nearbyCoordinates = - std::vector(this->getNpositions(), std::set()); - for (CoordIndex coordIndex = 0; coordIndex < this->getNpositions(); - coordIndex++) { + nearbyCoordinates = std::vector(getNpositions(), std::set()); + for (CoordIndex coordIndex = 0; coordIndex < getNpositions(); coordIndex++) { for (CoordIndex otherCoordIndex = 0; otherCoordIndex < coordIndex; otherCoordIndex++) { - if (this->getSwapDistance(coordIndex, otherCoordIndex) == 0) { - this->nearbyCoordinates.at(coordIndex).emplace(otherCoordIndex); - this->nearbyCoordinates.at(otherCoordIndex).emplace(coordIndex); + if (getSwapDistance(coordIndex, otherCoordIndex) == 0) { + nearbyCoordinates.at(coordIndex).emplace(otherCoordIndex); + nearbyCoordinates.at(otherCoordIndex).emplace(coordIndex); } } } @@ -252,17 +248,17 @@ void NeutralAtomArchitecture::computeNearbyCoordinates() { std::vector NeutralAtomArchitecture::getNN(const CoordIndex idx) const { std::vector nn; - if (idx % this->getNcolumns() != 0) { + if (idx % getNcolumns() != 0) { nn.emplace_back(idx - 1); } - if (idx % this->getNcolumns() != this->getNcolumns() - 1U) { + if (idx % getNcolumns() != getNcolumns() - 1U) { nn.emplace_back(idx + 1); } - if (idx >= this->getNcolumns()) { - nn.emplace_back(idx - this->getNcolumns()); + if (idx >= getNcolumns()) { + nn.emplace_back(idx - getNcolumns()); } - if (std::cmp_less(idx, this->getNpositions() - this->getNcolumns())) { - nn.emplace_back(idx + this->getNcolumns()); + if (std::cmp_less(idx, getNpositions() - getNcolumns())) { + nn.emplace_back(idx + getNcolumns()); } return nn; } @@ -273,50 +269,48 @@ std::string NeutralAtomArchitecture::getAnimationMachine( "Shuttling speed factor must be positive, but is " + std::to_string(shuttlingSpeedFactor)); } - std::string animationMachine = "name: \"Hybrid_" + this->name + "\"\n"; + std::string animationMachine = "name: \"Hybrid_" + name + "\"\n"; - animationMachine += - "movement {\n\tmax_speed: " + - std::to_string(this->getShuttlingTime(qc::OpType::AodMove) * - shuttlingSpeedFactor) + - "\n}\n"; + animationMachine += "movement {\n\tmax_speed: " + + std::to_string(getShuttlingTime(qc::OpType::AodMove) * + shuttlingSpeedFactor) + + "\n}\n"; animationMachine += "time {\n\tload: " + - std::to_string(this->getShuttlingTime(qc::OpType::AodActivate) / + std::to_string(getShuttlingTime(qc::OpType::AodActivate) / shuttlingSpeedFactor) + "\n\tstore: " + - std::to_string(this->getShuttlingTime(qc::OpType::AodDeactivate) / + std::to_string(getShuttlingTime(qc::OpType::AodDeactivate) / shuttlingSpeedFactor) + - "\n\trz: " + std::to_string(this->getGateTime("x")) + - "\n\try: " + std::to_string(this->getGateTime("x")) + - "\n\tcz: " + std::to_string(this->getGateTime("cz")) + - "\n\tunit: \"us\"\n}\n"; - - animationMachine += "distance {\n\tinteraction: " + - std::to_string(this->getInteractionRadius() * - this->getInterQubitDistance()) + - "\n\tunit: \"um\"\n}\n"; - const auto zoneStart = -this->getInterQubitDistance(); - const auto zoneEndX = this->getNcolumns() * this->getInterQubitDistance() + - this->getInterQubitDistance(); - const auto zoneEndY = this->getNrows() * this->getInterQubitDistance() + - this->getInterQubitDistance(); + "\n\trz: " + std::to_string(getGateTime("x")) + + "\n\try: " + std::to_string(getGateTime("x")) + + "\n\tcz: " + std::to_string(getGateTime("cz")) + "\n\tunit: \"us\"\n}\n"; + + animationMachine += + "distance {\n\tinteraction: " + + std::to_string(getInteractionRadius() * getInterQubitDistance()) + + "\n\tunit: \"um\"\n}\n"; + const auto zoneStart = -getInterQubitDistance(); + const auto zoneEndX = + getNcolumns() * getInterQubitDistance() + getInterQubitDistance(); + const auto zoneEndY = + getNrows() * getInterQubitDistance() + getInterQubitDistance(); animationMachine += "zone hybrid {\n\tfrom: (" + std::to_string(zoneStart) + ", " + std::to_string(zoneStart) + ")\n\tto: (" + std::to_string(zoneEndX) + ", " + std::to_string(zoneEndY) + ") \n}\n"; - for (size_t colIdx = 0; colIdx < this->getNcolumns(); colIdx++) { - for (size_t rowIdx = 0; rowIdx < this->getNrows(); rowIdx++) { - const auto coordIdx = colIdx + (rowIdx * this->getNcolumns()); + for (size_t colIdx = 0; colIdx < getNcolumns(); colIdx++) { + for (size_t rowIdx = 0; rowIdx < getNrows(); rowIdx++) { + const auto coordIdx = colIdx + (rowIdx * getNcolumns()); animationMachine += "trap trap" + std::to_string(coordIdx) + " {\n\tposition: (" + std::to_string(static_cast(colIdx) * - this->getInterQubitDistance()) + + getInterQubitDistance()) + ", " + std::to_string(static_cast(rowIdx) * - this->getInterQubitDistance()) + + getInterQubitDistance()) + ")\n}\n"; } } @@ -330,7 +324,7 @@ qc::fp NeutralAtomArchitecture::getOpTime(const qc::Operation* op) const { return getShuttlingTime(op->getType()); } if (op->getType() == qc::OpType::AodMove) { - const auto v = this->parameters.shuttlingTimes.at(op->getType()); + const auto v = parameters.shuttlingTimes.at(op->getType()); const auto* const opAodMove = dynamic_cast(op); const auto distanceX = opAodMove->getMaxDistance(Dimension::X); const auto distanceY = opAodMove->getMaxDistance(Dimension::Y); diff --git a/src/hybridmap/NeutralAtomLayer.cpp b/src/hybridmap/NeutralAtomLayer.cpp index 82ebb5a6c..9b6cef126 100644 --- a/src/hybridmap/NeutralAtomLayer.cpp +++ b/src/hybridmap/NeutralAtomLayer.cpp @@ -30,7 +30,7 @@ void NeutralAtomLayer::updateByQubits( void NeutralAtomLayer::initAllQubits() { std::set allQubits; - for (std::size_t i = 0; i < this->dag.size(); ++i) { + for (std::size_t i = 0; i < dag.size(); ++i) { allQubits.emplace(static_cast(i)); } updateByQubits(allQubits); From 588fcf26742bc4c87e92c1bf5837f7a0ea6dcdc7 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 5 Dec 2025 13:41:16 +0100 Subject: [PATCH 384/394] =?UTF-8?q?=F0=9F=90=9B=20fix=20insertion=20logic?= =?UTF-8?q?=20for=20targetQubits=20in=20MoveToAodConverter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/MoveToAodConverter.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/hybridmap/MoveToAodConverter.cpp b/src/hybridmap/MoveToAodConverter.cpp index d859c1e31..ed6dfc517 100644 --- a/src/hybridmap/MoveToAodConverter.cpp +++ b/src/hybridmap/MoveToAodConverter.cpp @@ -541,8 +541,9 @@ AodOperation MoveToAodConverter::MoveGroup::connectAodOperations( targetQubits.emplace_back(starts[i]); targetQubits.emplace_back(ends[i]); } else { - // insert one before the found position - const auto newPos = targetQubits.insert(pos - 1, ends[i]); + // insert the (end, start) pair immediately before the existing + // start + const auto newPos = targetQubits.insert(pos, ends[i]); targetQubits.insert(newPos, starts[i]); } } From 9fa38b73f764a18ca7f87789d767cdef7b4080ad Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 5 Dec 2025 13:47:36 +0100 Subject: [PATCH 385/394] =?UTF-8?q?=E2=9C=A8=20Update=20synthesis=5Fmapper?= =?UTF-8?q?=20methods=20to=20include=20QASM=20suffix=20for=20clarity?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/python/hybrid_mapper/test_hybrid_synthesis.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/python/hybrid_mapper/test_hybrid_synthesis.py b/test/python/hybrid_mapper/test_hybrid_synthesis.py index 0ad0676ce..5cdc2cc99 100644 --- a/test/python/hybrid_mapper/test_hybrid_synthesis.py +++ b/test/python/hybrid_mapper/test_hybrid_synthesis.py @@ -76,18 +76,18 @@ def test_hybrid_synthesis_input_output(arch_filename: str, tmp_path: Path) -> No synthesis_mapper.append_with_mapping(qc1) synthesis_mapper.append_without_mapping(qc2) - qasm = synthesis_mapper.get_mapped_qc() + qasm = synthesis_mapper.get_mapped_qc_qasm() assert qasm is not None filename_mapped = tmp_path / f"{arch_filename}_mapped.qasm" - synthesis_mapper.save_mapped_qc(str(filename_mapped)) + synthesis_mapper.save_mapped_qc_qasm(str(filename_mapped)) synthesis_mapper.convert_to_aod() - qasm_aod = synthesis_mapper.get_mapped_qc_aod() + qasm_aod = synthesis_mapper.get_mapped_qc_aod_qasm() assert qasm_aod is not None filename_mapped_aod = tmp_path / f"{arch_filename}_mapped_aod.qasm" - synthesis_mapper.save_mapped_qc_aod(str(filename_mapped_aod)) + synthesis_mapper.save_mapped_qc_aod_qasm(str(filename_mapped_aod)) qasm_synth = synthesis_mapper.get_synthesized_qc() assert qasm_synth is not None From 735ee77a343f8af894e15c54b5af634c65e37c4b Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 5 Dec 2025 14:11:19 +0100 Subject: [PATCH 386/394] =?UTF-8?q?=F0=9F=93=9D=20updated=20CHANGELOG.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fab293a18..36fac3c58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ _If you are upgrading: please see [`UPGRADING.md`](UPGRADING.md#unreleased)._ - ✨ Enable code generation for relaxed routing constraints ([#848]) ([**@ystade**]) - ✨ Add `max_filling_factor` to scheduler in Zoned Neutral Atom Compiler ([#847]) ([**@ystade**]) +- ✨ Added extension to the hybrid routing mapper to also support Bridge gates, Passby moves and Flying ancillas ([#832]) ([**@lsschmid**]) +- ✨ Added hybrid synthesis routing for iterative circuit constructions ([#832]) ([**@lsschmid**]) ## [3.4.0] - 2025-10-15 @@ -160,6 +162,7 @@ _📚 Refer to the [GitHub Release Notes] for previous changelogs._ [#848]: https://github.com/munich-quantum-toolkit/qmap/pull/848 [#847]: https://github.com/munich-quantum-toolkit/qmap/pull/847 +[#832]: https://github.com/munich-quantum-toolkit/qmap/pull/832 [#804]: https://github.com/munich-quantum-toolkit/qmap/pull/804 [#803]: https://github.com/munich-quantum-toolkit/qmap/pull/803 [#796]: https://github.com/munich-quantum-toolkit/qmap/pull/796 @@ -192,6 +195,7 @@ _📚 Refer to the [GitHub Release Notes] for previous changelogs._ [**@burgholzer**]: https://github.com/burgholzer [**@ystade**]: https://github.com/ystade [**@denialhaag**]: https://github.com/denialhaag +[**@lsschmid**]: https://github.com/lsschmid From 08254f4f98b97b388f3f41c6eec7f2b5c395f543 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 5 Dec 2025 14:12:12 +0100 Subject: [PATCH 387/394] =?UTF-8?q?=F0=9F=93=9D=20updated=20UPGRADING.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- UPGRADING.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/UPGRADING.md b/UPGRADING.md index 766757259..d354703c4 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -12,6 +12,14 @@ The code generator of the zoned neutral atom compiler is updated to also handle In contrast to the strict routing, a relaxed routing can change the relative order of atoms. The constraint that remains is that atoms previously in one row (column) must remain in the same row (column) after the routing. +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. + +Existing workflows should continue to function. +The optionally new parameters are `usePassBy=False`, `numFlyingAncillas=0`, and `maxBridgeDistance=0` which can all be disabled with the above values to recover the previous behavior. +Enabling/increasing the corresponding parameters allows enabling individually single routing strategies. + +The hybrid mapper now also optionally yields a `.naviz` output which can be handled similarly to the zoned architecture compiler. + ## [3.4.0] ### End of support for Python 3.9 From d602473f6596b85aac29e4f2f1e382152d98a975 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 5 Dec 2025 14:14:25 +0100 Subject: [PATCH 388/394] =?UTF-8?q?=F0=9F=9A=A8removed=20unused=20header?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hybridmap/HybridNeutralAtomMapper.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index bea445a40..149e81dda 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -28,8 +28,6 @@ #include #include #include -#include -#include #include #include #include From a636bdf9ad60b075960683eef006e35e17ae8001 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 5 Dec 2025 14:27:20 +0100 Subject: [PATCH 389/394] =?UTF-8?q?=F0=9F=8E=A8changed=20qasm=20suffix=20a?= =?UTF-8?q?lso=20for=20synthesized=20circuit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bindings/hybrid_mapper/hybrid_mapper.cpp | 5 +++-- python/mqt/qmap/hybrid_mapper.pyi | 4 ++-- test/python/hybrid_mapper/test_hybrid_synthesis.py | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/bindings/hybrid_mapper/hybrid_mapper.cpp b/bindings/hybrid_mapper/hybrid_mapper.cpp index 4d68a01ff..baedf2407 100644 --- a/bindings/hybrid_mapper/hybrid_mapper.cpp +++ b/bindings/hybrid_mapper/hybrid_mapper.cpp @@ -258,11 +258,12 @@ PYBIND11_MODULE(MQT_QMAP_MODULE_NAME, m, py::mod_gil_not_used()) { "qasm2 to a " "file.", "filename"_a) - .def("get_synthesized_qc", + .def("get_synthesized_qc_qasm", &na::HybridSynthesisMapper::getSynthesizedQcQASM, "Returns the synthesized circuit with all gates but not " "mapped to the hardware as a qasm2 string.") - .def("save_synthesized_qc", &na::HybridSynthesisMapper::saveSynthesizedQc, + .def("save_synthesized_qc_qasm", + &na::HybridSynthesisMapper::saveSynthesizedQc, "Saves the synthesized circuit with all gates but not " "mapped to the hardware as qasm2 to a file.", "filename"_a) diff --git a/python/mqt/qmap/hybrid_mapper.pyi b/python/mqt/qmap/hybrid_mapper.pyi index bab9cace9..21d2985a0 100644 --- a/python/mqt/qmap/hybrid_mapper.pyi +++ b/python/mqt/qmap/hybrid_mapper.pyi @@ -110,11 +110,11 @@ class HybridSynthesisMapper: def get_circuit_adjacency_matrix(self) -> list[list[int]]: ... def get_mapped_qc_qasm(self) -> str: ... def get_mapped_qc_aod_qasm(self) -> str: ... - def get_synthesized_qc(self) -> str: ... + def get_synthesized_qc_qasm(self) -> str: ... def init_mapping(self, n_qubits: typing.SupportsInt) -> None: ... def save_mapped_qc_qasm(self, filename: str) -> None: ... def save_mapped_qc_aod_qasm(self, filename: str) -> None: ... - def save_synthesized_qc(self, filename: str) -> None: ... + def save_synthesized_qc_qasm(self, filename: str) -> None: ... def schedule( self, verbose: bool = ..., create_animation_csv: bool = ..., shuttling_speed_factor: typing.SupportsFloat = ... ) -> dict[str, float]: ... diff --git a/test/python/hybrid_mapper/test_hybrid_synthesis.py b/test/python/hybrid_mapper/test_hybrid_synthesis.py index 5cdc2cc99..33c97cfbb 100644 --- a/test/python/hybrid_mapper/test_hybrid_synthesis.py +++ b/test/python/hybrid_mapper/test_hybrid_synthesis.py @@ -89,11 +89,11 @@ def test_hybrid_synthesis_input_output(arch_filename: str, tmp_path: Path) -> No filename_mapped_aod = tmp_path / f"{arch_filename}_mapped_aod.qasm" synthesis_mapper.save_mapped_qc_aod_qasm(str(filename_mapped_aod)) - qasm_synth = synthesis_mapper.get_synthesized_qc() + qasm_synth = synthesis_mapper.get_synthesized_qc_qasm() assert qasm_synth is not None filename_synth = tmp_path / f"{arch_filename}_synthesized.qasm" - synthesis_mapper.save_synthesized_qc(str(filename_synth)) + synthesis_mapper.save_synthesized_qc_qasm(str(filename_synth)) def test_adjacency_matrix() -> None: From 6ad9d172c790cb742e5323e75a4d92e6154cb7c3 Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 5 Dec 2025 14:27:39 +0100 Subject: [PATCH 390/394] =?UTF-8?q?=F0=9F=93=9D=20updated=20to=20use=20new?= =?UTF-8?q?=20qasm=20suffix.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/na_hybrid.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/na_hybrid.md b/docs/na_hybrid.md index f932f8f6f..7fcab65e7 100644 --- a/docs/na_hybrid.md +++ b/docs/na_hybrid.md @@ -265,7 +265,7 @@ Similar to the normal Hybrid Mapper, you can retrieve the mapped circuit and the ```{code-cell} ipython3 # Retrieve mapped circuit (extended QASM2) -qasm_mapped = synth.get_mapped_qc() +qasm_mapped = synth.get_mapped_qc_qasm() print("\n... Mapped QASM snippet ...\n" + "\n".join(qasm_mapped.splitlines()[:30]) + "\n...") @@ -274,7 +274,7 @@ print("\n... Mapped QASM snippet ...\n" + One can also access the circuits which is constructed step-by-step in a unmapped state (e.g. to use a different mapper). ```{code-cell} ipython3 -qasm_synth = synth.get_synthesized_qc() +qasm_synth = synth.get_synthesized_qc_qasm() print("\n... Synthesized QASM snippet ...\n" + "\n".join(qasm_synth.splitlines()[:30]) + "\n...") From ffa0787d3a7e37f17f9c37120f3c09f09f08e0aa Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Fri, 5 Dec 2025 14:31:36 +0100 Subject: [PATCH 391/394] =?UTF-8?q?=F0=9F=94=A5=20removed=20default=20cons?= =?UTF-8?q?tructor=20for=20AodMove.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/MoveToAodConverter.hpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/include/hybridmap/MoveToAodConverter.hpp b/include/hybridmap/MoveToAodConverter.hpp index 95b4182db..7c36eb275 100644 --- a/include/hybridmap/MoveToAodConverter.hpp +++ b/include/hybridmap/MoveToAodConverter.hpp @@ -88,8 +88,6 @@ class MoveToAodConverter { // delta of the actual move qc::fp delta; - AodMove() = default; - AodMove(const uint32_t initMove, const qc::fp deltaMove, const int32_t offsetMove, const bool loadMove) : init(initMove), load(loadMove), offset(offsetMove), From c6404daa9bbc636f643f92128307b83fdc91d9bd Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Sat, 6 Dec 2025 09:23:19 +0100 Subject: [PATCH 392/394] =?UTF-8?q?=F0=9F=93=9D=20added=20missing=20punctu?= =?UTF-8?q?ation=20in=20docs.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/na_hybrid.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/na_hybrid.md b/docs/na_hybrid.md index 7fcab65e7..f454ec378 100644 --- a/docs/na_hybrid.md +++ b/docs/na_hybrid.md @@ -47,7 +47,8 @@ qc.draw(output="mpl") ### Load a hybrid NA architecture -The hybrid mapper expects an architecture specification in JSON. This repository ships several ready-to-use examples +The hybrid mapper expects an architecture specification in JSON. +This repository ships several ready-to-use examples. ```{code-cell} ipython3 @@ -231,7 +232,8 @@ For more details, please check the source code documentation of `MapperParameter ## Hybrid Synthesis Mapper -The Hybrid Synthesis Mapper helps you compare and stitch together alternative circuit fragments while keeping track of the current mapping on the NA device. Below is a compact example that mirrors the unit test flow and shows how to +The Hybrid Synthesis Mapper helps you compare and stitch together alternative circuit fragments while keeping track of the current mapping on the NA device. +Below is a compact example that mirrors the unit test flow and shows how to: - evaluate multiple candidate fragments and pick the best, - append fragments, From 64daf5a77f302d700ef2bac99b403f2a924f8a1e Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Sat, 6 Dec 2025 09:25:31 +0100 Subject: [PATCH 393/394] =?UTF-8?q?=F0=9F=9A=A8=20added=20error=20handling?= =?UTF-8?q?=20for=20file=20opening=20in=20HybridNeutralAtomMapper?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/hybridmap/HybridNeutralAtomMapper.hpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/hybridmap/HybridNeutralAtomMapper.hpp b/include/hybridmap/HybridNeutralAtomMapper.hpp index a6a1a4002..bf369ee7f 100644 --- a/include/hybridmap/HybridNeutralAtomMapper.hpp +++ b/include/hybridmap/HybridNeutralAtomMapper.hpp @@ -636,6 +636,9 @@ class NeutralAtomMapper { convertToAod(); } std::ofstream ofs(filename); + if (!ofs) { + throw std::runtime_error("Failed to open file: " + filename); + } mappedQcAOD.dumpOpenQASM(ofs, false); } From a921751c89a829bc5268a766718e7102e191e7eb Mon Sep 17 00:00:00 2001 From: Ludwig Schmid Date: Sat, 6 Dec 2025 09:27:32 +0100 Subject: [PATCH 394/394] =?UTF-8?q?=F0=9F=93=9D=20fixed=20typo=20in=20UPGR?= =?UTF-8?q?ADING.md=20for=20clarity?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- UPGRADING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UPGRADING.md b/UPGRADING.md index d354703c4..1daff2c89 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -12,7 +12,7 @@ The code generator of the zoned neutral atom compiler is updated to also handle In contrast to the strict routing, a relaxed routing can change the relative order of atoms. The constraint that remains is that atoms previously in one row (column) must remain in the same row (column) after the routing. -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. Existing workflows should continue to function. The optionally new parameters are `usePassBy=False`, `numFlyingAncillas=0`, and `maxBridgeDistance=0` which can all be disabled with the above values to recover the previous behavior.