diff --git a/extern/mqt-core b/extern/mqt-core index 6ab9ffa3..0e4ff9e0 160000 --- a/extern/mqt-core +++ b/extern/mqt-core @@ -1 +1 @@ -Subproject commit 6ab9ffa3e5e99514090bc57f13a5c7bd4d5d6102 +Subproject commit 0e4ff9e0521886449027b252c65913e1afa863b0 diff --git a/include/hybridmap/NeutralAtomArchitecture.hpp b/include/hybridmap/NeutralAtomArchitecture.hpp index 0e814938..51d1abe8 100644 --- a/include/hybridmap/NeutralAtomArchitecture.hpp +++ b/include/hybridmap/NeutralAtomArchitecture.hpp @@ -382,9 +382,9 @@ class NeutralAtomArchitecture { * @param idx2 The index of the second coordinate * @return The Euclidean distance between the two coordinate indices */ - [[nodiscard]] qc::fp getEuclidianDistance(CoordIndex idx1, - CoordIndex idx2) const { - return static_cast(this->coordinates.at(idx1).getEuclidianDistance( + [[nodiscard]] qc::fp getEuclideanDistance(const CoordIndex idx1, + const CoordIndex idx2) const { + return static_cast(this->coordinates.at(idx1).getEuclideanDistance( this->coordinates.at(idx2))); } /** @@ -393,9 +393,9 @@ class NeutralAtomArchitecture { * @param c2 The second coordinate * @return The Euclidean distance between the two coordinates */ - [[nodiscard]] static qc::fp getEuclidianDistance(const Point& c1, + [[nodiscard]] static qc::fp getEuclideanDistance(const Point& c1, const Point& c2) { - return static_cast(c1.getEuclidianDistance(c2)); + return static_cast(c1.getEuclideanDistance(c2)); } /** * @brief Get the Manhattan distance between two coordinate indices diff --git a/noxfile.py b/noxfile.py index 62a31ff2..87e2362d 100644 --- a/noxfile.py +++ b/noxfile.py @@ -85,7 +85,6 @@ def minimums(session: nox.Session) -> None: session, install_args=["--resolution=lowest-direct"], run_args=["-Wdefault"], - extras=["qiskit", "evaluation"], ) session.run("uv", "pip", "list") diff --git a/src/hybridmap/HardwareQubits.cpp b/src/hybridmap/HardwareQubits.cpp index fd121127..d1307d34 100644 --- a/src/hybridmap/HardwareQubits.cpp +++ b/src/hybridmap/HardwareQubits.cpp @@ -134,7 +134,7 @@ void HardwareQubits::computeNearbyQubits(HwQubit q) { if (coord.first == q) { continue; } - if (arch->getEuclidianDistance(coordQ, coord.second) <= + if (arch->getEuclideanDistance(coordQ, coord.second) <= arch->getInteractionRadius()) { newNearbyQubits.emplace(coord.first); } @@ -169,7 +169,7 @@ HardwareQubits::getBlockedQubits(const std::set& qubits) { continue; } // TODO improve by using the nearby coords as a preselection - auto const distance = arch->getEuclidianDistance(hwToCoordIdx.at(qubit), + auto const distance = arch->getEuclideanDistance(hwToCoordIdx.at(qubit), hwToCoordIdx.at(i)); if (distance <= arch->getBlockingFactor() * arch->getInteractionRadius()) { diff --git a/src/hybridmap/HybridNeutralAtomMapper.cpp b/src/hybridmap/HybridNeutralAtomMapper.cpp index cbe99bc9..6cc4e605 100644 --- a/src/hybridmap/HybridNeutralAtomMapper.cpp +++ b/src/hybridmap/HybridNeutralAtomMapper.cpp @@ -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.getEuclideanDistance( 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.getEuclideanDistance( this->hardwareQubits.getCoordIndex(hwQubit), move.second); distanceAfter += dist; } diff --git a/src/hybridmap/NeutralAtomArchitecture.cpp b/src/hybridmap/NeutralAtomArchitecture.cpp index 7ef72344..135a2260 100644 --- a/src/hybridmap/NeutralAtomArchitecture.cpp +++ b/src/hybridmap/NeutralAtomArchitecture.cpp @@ -136,7 +136,7 @@ void NeutralAtomArchitecture::computeSwapDistances(qc::fp interactionRadius) { for (uint32_t i = 0; i < this->getNcolumns() && i < interactionRadius; i++) { for (uint32_t j = i; j < this->getNrows(); j++) { - auto const dist = NeutralAtomArchitecture::getEuclidianDistance( + auto const dist = NeutralAtomArchitecture::getEuclideanDistance( Point(0, 0), Point(i, j)); if (dist <= interactionRadius) { if (dist == 0) { @@ -266,7 +266,7 @@ NeutralAtomArchitecture::getBlockedCoordIndices(const qc::Operation* op) const { } // do a preselection // now check exact difference - auto const distance = getEuclidianDistance(coord, i); + auto const distance = getEuclideanDistance(coord, i); if (distance <= getBlockingFactor() * getInteractionRadius()) { blockedCoordIndices.emplace(i); } diff --git a/src/na/NAMapper.cpp b/src/na/NAMapper.cpp index 806467c9..095ba6db 100644 --- a/src/na/NAMapper.cpp +++ b/src/na/NAMapper.cpp @@ -42,7 +42,7 @@ namespace na { auto NAMapper::validateCircuit() -> void { for (const auto& op : initialQc) { - if (op->isCompoundOperation() and isGlobal(*op, initialQc.getNqubits())) { + if (op->isCompoundOperation() && isGlobal(*op, initialQc.getNqubits())) { const auto& co = dynamic_cast(*op); if (!arch.isAllowedGlobally({co.at(0)->getType(), 0})) { std::stringstream ss; @@ -50,7 +50,7 @@ auto NAMapper::validateCircuit() -> void { << FullOpType{op->getType(), 0} << " globally."; throw std::invalid_argument(ss.str()); } - } else if (op->isStandardOperation() and op->isSingleQubitGate()) { + } else if (op->isStandardOperation() && op->isSingleQubitGate()) { assert(op->getNcontrols() == 0); if (!arch.isAllowedLocally({op->getType(), 0})) { std::stringstream ss; @@ -58,7 +58,7 @@ auto NAMapper::validateCircuit() -> void { << FullOpType{op->getType(), 0} << " locally."; throw std::invalid_argument(ss.str()); } - } else if (op->isStandardOperation() and + } else if (op->isStandardOperation() && op->getNcontrols() + op->getNtargets() == 2) { assert(!op->isSingleQubitGate()); if (!arch.isAllowedLocally({op->getType(), op->getNcontrols()})) { @@ -433,8 +433,8 @@ auto NAMapper::store(std::vector& initialFreeSites, } std::sort(freeSitesPerRow.begin(), freeSitesPerRow.end(), [&](const auto& a, const auto& b) { - return (std::get<1>(a) == std::get<1>(b) and - std::get<0>(a) < std::get<0>(b)) or + return (std::get<1>(a) == std::get<1>(b) && + std::get<0>(a) < std::get<0>(b)) || std::get<1>(a) > std::get<1>(b); }); std::size_t moveableSpotsNeeded = qubits.size(); @@ -446,7 +446,7 @@ auto NAMapper::store(std::vector& initialFreeSites, if (n != std::get<1>(freeSitesPerRow.at(firstIWithSameN))) { firstIWithSameN = i; } - if (i + 1 == freeSitesPerRow.size() or + if (i + 1 == freeSitesPerRow.size() || std::get<1>(freeSitesPerRow[i + 1]) < moveableSpotsNeeded) { const auto [rF, nF] = freeSitesPerRow.at(firstIWithSameN); moveableSelectedRows.emplace_back(rF, moveableSpotsNeeded); @@ -493,7 +493,7 @@ auto NAMapper::store(std::vector& initialFreeSites, currentFreeSites.at(site) = false; initialFreeSites.at(site) = false; n -= 1; - } else if (j < sitesInRow.size() and + } else if (j < sitesInRow.size() && currentFreeSites.at(sitesInRow.at(j))) { const auto& s = sitesInRow.at(j); const auto& sPos = arch.getPositionOfSite(s); @@ -585,7 +585,7 @@ auto NAMapper::pickUp(std::vector& initialFreeSites, [&](const auto& acc, const auto& s) { return acc + (initialFreeSites.at(s) ? 1 : 0); }); - if (freeSpotsInRow <= spotsNeeded and n > freeSpotsInRow) { + if (freeSpotsInRow <= spotsNeeded && n > freeSpotsInRow) { freeSpotsInRow = n; zone = z; row = r; @@ -642,7 +642,7 @@ auto NAMapper::pickUp(std::vector& initialFreeSites, // check whether j can be // picked up if (placement.at(p).positionStatus == Atom::PositionStatus::DEFINED) { - if (placement.at(p).currentPosition->y == y and + if (placement.at(p).currentPosition->y == y && placement.at(p).currentPosition->x <= x - d) { // pick up p pickUpOrder.erase( @@ -673,7 +673,7 @@ auto NAMapper::pickUp(std::vector& initialFreeSites, } const auto site = *siteOpt; freeX = arch.getPositionOfSite(site).x; - if (initialFreeSites.at(site) and + if (initialFreeSites.at(site) && std::find(placement.at(p).zones.cbegin(), placement.at(p).zones.cend(), arch.getZoneOfSite(site)) != @@ -730,7 +730,7 @@ auto NAMapper::pickUp(std::vector& initialFreeSites, } else { // check whether j can be picked up if (placement.at(p).positionStatus == Atom::PositionStatus::DEFINED) { - if (placement.at(p).currentPosition->y == y and + if (placement.at(p).currentPosition->y == y && placement.at(p).currentPosition->x >= x - d) { // pick up p pickUpOrder.erase( @@ -760,7 +760,7 @@ auto NAMapper::pickUp(std::vector& initialFreeSites, } const auto site = *siteOpt; freeX = arch.getPositionOfSite(site).x; - if (initialFreeSites.at(site) and + if (initialFreeSites.at(site) && std::find(placement.at(p).zones.cbegin(), placement.at(p).zones.cend(), arch.getZoneOfSite(site)) != @@ -852,7 +852,7 @@ auto NAMapper::map(const qc::QuantumComputation& qc) -> void { const auto* const co = dynamic_cast(op); mappedQc.emplaceBack( FullOpType{co->at(0)->getType(), 0}, co->at(0)->getParameter()); - } else if (isGlobal(*op, nqubits) and + } else if (isGlobal(*op, nqubits) && arch.isAllowedGlobally( {op->getType(), op->getNcontrols()})) { mappedQc.emplaceBack( @@ -865,8 +865,12 @@ auto NAMapper::map(const qc::QuantumComputation& qc) -> void { for (const auto& v : layer.getExecutablesOfType(op->getType(), op->getNcontrols())) { const auto* const op2 = v->getOperation(); - if (checkApplicability(op2, placement) and - op->getParameter() == op2->getParameter()) { + if (checkApplicability(op2, placement) && + op->getParameter() == op2->getParameter() && + std::find( + positions.cbegin(), positions.cend(), + placement.at(op2->getTargets().front()).currentPosition) == + positions.cend()) { updatePlacement(op2, placement); v->execute(); positions.emplace_back( @@ -890,7 +894,7 @@ auto NAMapper::map(const qc::QuantumComputation& qc) -> void { // of the same type and two targets, i.e. cz gates if (config.getMethod() == NAMappingMethod::Naive) { const qc::Operation* op = (*it)->getOperation(); - if (op->getType() != qc::OpType::Z or op->getNtargets() != 1 or + if (op->getType() != qc::OpType::Z || op->getNtargets() != 1 || op->getNcontrols() != 1) { throw std::logic_error( "Other gates than cz are not supported for mapping yet."); @@ -1055,7 +1059,7 @@ auto NAMapper::map(const qc::QuantumComputation& qc) -> void { ss << "Atom " << q << " was unexpectedly not picked up."; throw std::logic_error(ss.str()); } - if (x >= 0 and static_cast(x) < sites.size()) { + if (x >= 0 && static_cast(x) < sites.size()) { const auto pos = arch.getPositionOfSite(sites.at(static_cast(x))); placement.at(q).currentPosition = @@ -1097,8 +1101,14 @@ auto NAMapper::map(const qc::QuantumComputation& qc) -> void { moveableOrdered, storageZone); // ------------------------------------------------------------- std::vector fixedVector; - std::transform(fixed.cbegin(), fixed.cend(), back_inserter(fixedVector), + fixedVector.reserve(fixed.size()); + std::transform(fixed.cbegin(), fixed.cend(), + std::back_inserter(fixedVector), [](const auto& v) { return v.first; }); + std::sort(fixedVector.begin(), fixedVector.end(), + [&fixed](const auto& a, const auto& b) { + return fixed.at(a) < fixed.at(b); + }); store(initialFreeSites, currentFreeSites, placement, currentlyShuttling, fixedVector, storageZone); } else { @@ -1135,8 +1145,8 @@ auto NAMapper::map(const qc::QuantumComputation& qc) -> void { stats.numInitialGates = qc.getNops(); stats.numEntanglingGates = static_cast( std::count_if(qc.cbegin(), qc.cend(), [](const auto& op) { - return (op->getType() == qc::OpType::Z and - op->getType() == qc::OpType::X) or + return (op->getType() == qc::OpType::Z && + op->getType() == qc::OpType::X) || op->getNcontrols() > 0; })); stats.initialDepth = qc.getDepth(); diff --git a/test/hybridmap/test_hybridmap.cpp b/test/hybridmap/test_hybridmap.cpp index 8d2e7a06..9d7ffed9 100644 --- a/test/hybridmap/test_hybridmap.cpp +++ b/test/hybridmap/test_hybridmap.cpp @@ -40,7 +40,7 @@ TEST_P(NeutralAtomArchitectureTest, LoadArchitecures) { EXPECT_GE(arch.getGateTime("cz"), 0); EXPECT_GE(arch.getGateAverageFidelity("cz"), 0); // Test distance functions - EXPECT_GE(arch.getEuclidianDistance(c1, c2), 0); + EXPECT_GE(arch.getEuclideanDistance(c1, c2), 0); // Test MoveVector functions auto mv = arch.getVector(0, 1); EXPECT_GE(arch.getVectorShuttlingTime(mv), 0); diff --git a/test/na/test_namapper.cpp b/test/na/test_namapper.cpp index b8ff84b8..6a9fbec9 100644 --- a/test/na/test_namapper.cpp +++ b/test/na/test_namapper.cpp @@ -5,14 +5,190 @@ #include "Architecture.hpp" #include "Configuration.hpp" +#include "Definitions.hpp" #include "NAMapper.hpp" #include "QuantumComputation.hpp" +#include "datastructures/Layer.hpp" +#include "na/NAComputation.hpp" +#include "na/operations/NAGlobalOperation.hpp" +#include "na/operations/NALocalOperation.hpp" +#include "na/operations/NAShuttlingOperation.hpp" +#include "operations/CompoundOperation.hpp" +#include "operations/OpType.hpp" +#include "operations/StandardOperation.hpp" -#include "gtest/gtest.h" +#include #include +#include +#include +#include #include #include #include +#include +#include + +namespace na { +auto validateAODConstraints(const NAComputation& comp) -> bool { + for (const auto& naOp : comp) { + if (naOp->isShuttlingOperation()) { + const auto& shuttlingOp = + dynamic_cast(*naOp); + if (shuttlingOp.getStart().size() != shuttlingOp.getEnd().size()) { + return false; + } + for (std::size_t i = 0; i < shuttlingOp.getStart().size(); ++i) { + for (std::size_t j = i + 1; j < shuttlingOp.getStart().size(); ++j) { + const auto& s1 = shuttlingOp.getStart()[i]; + const auto& s2 = shuttlingOp.getStart()[j]; + const auto& e1 = shuttlingOp.getEnd()[i]; + const auto& e2 = shuttlingOp.getEnd()[j]; + if (*s1 == *s2) { + return false; + } + if (*e1 == *e2) { + return false; + } + if (s1->x == s2->x && e1->x != e2->x) { + return false; + } + if (s1->y == s2->y && e1->y != e2->y) { + return false; + } + if (s1->x < s2->x && e1->x >= e2->x) { + return false; + } + if (s1->y < s2->y && e1->y >= e2->y) { + return false; + } + if (s1->x > s2->x && e1->x <= e2->x) { + return false; + } + if (s1->y > s2->y && e1->y <= e2->y) { + return false; + } + } + } + } else if (naOp->isLocalOperation()) { + const auto& localOp = dynamic_cast(*naOp); + for (std::size_t i = 0; i < localOp.getPositions().size(); ++i) { + for (std::size_t j = i + 1; j < localOp.getPositions().size(); ++j) { + const auto& a = localOp.getPositions()[i]; + const auto& b = localOp.getPositions()[j]; + if (*a == *b) { + return false; + } + } + } + } + } + return true; +} + +auto retrieveQuantumComputation(const NAComputation& nac, + const Architecture& arch) + -> qc::QuantumComputation { + qc::QuantumComputation qComp(nac.getInitialPositions().size()); + std::vector positionOfQubits; + std::unordered_map positionToQubit; + positionOfQubits.reserve(nac.getInitialPositions().size()); + qc::Qubit n = 0; + for (const auto& p : nac.getInitialPositions()) { + positionToQubit[*p] = n++; + positionOfQubits.emplace_back(*p); + } + for (const auto& naOp : nac) { + if (naOp->isLocalOperation()) { + const auto& localOp = dynamic_cast(*naOp); + if (localOp.getType().nControls != 0 || + !isSingleQubitGate(localOp.getType().type)) { + throw std::invalid_argument("Only single qubit gates are supported."); + } + for (const auto& pos : localOp.getPositions()) { + qComp.emplace_back( + positionToQubit[*pos], localOp.getType().type, localOp.getParams()); + } + } else if (naOp->isShuttlingOperation()) { + const auto& shuttlingOp = + dynamic_cast(*naOp); + for (std::size_t i = 0; i < shuttlingOp.getStart().size(); ++i) { + positionOfQubits[positionToQubit[*shuttlingOp.getStart()[i]]] = + *shuttlingOp.getEnd()[i]; + } + positionToQubit.clear(); + for (qc::Qubit i = 0; i < positionOfQubits.size(); ++i) { + positionToQubit[positionOfQubits[i]] = i; + } + } else if (naOp->isGlobalOperation()) { + const auto& globalOp = dynamic_cast(*naOp); + const auto& zones = + arch.getPropertiesOfOperation(globalOp.getType()).zones; + if (!isSingleQubitGate(globalOp.getType().type) || + globalOp.getType().nControls > 1) { + throw std::invalid_argument("Only 1Q- and 2Q-gates are supported."); + } + if (globalOp.getType().nControls == 1) { + for (std::size_t i1 = 0; i1 < positionOfQubits.size(); ++i1) { + const auto& pos1 = positionOfQubits[i1]; + for (std::size_t i2 = i1 + 1; i2 < positionOfQubits.size(); ++i2) { + const auto& pos2 = positionOfQubits[i2]; + if ((pos1 - pos2).length() <= arch.getInteractionRadius() && + std::any_of(zones.cbegin(), zones.cend(), + [&arch, &pos1](const auto& z) { + return arch.getZoneAt(pos1) == z; + }) && + std::any_of(zones.cbegin(), zones.cend(), + [&arch, &pos2](const auto& z) { + return arch.getZoneAt(pos2) == z; + })) { + qComp.emplace_back( + i1, i2, globalOp.getType().type, globalOp.getParams()); + } + } + } + } else { + qc::CompoundOperation compoundOp; + for (std::size_t i = 0; i < positionOfQubits.size(); ++i) { + compoundOp.emplace_back( + i, globalOp.getType().type, globalOp.getParams()); + } + qComp.emplace_back(compoundOp); + } + } + } + return qComp; +} + +auto checkEquivalence(const qc::QuantumComputation& circ, + const NAComputation& nac, + const Architecture& arch) -> bool { + auto naQComp = retrieveQuantumComputation(nac, arch); + const qc::Layer qLayer(circ); + int line = 0; + for (const auto& op : naQComp) { + ++line; + const auto& executableSet = qLayer.getExecutableSet(); + const auto& it = std::find_if( + executableSet.begin(), executableSet.end(), + [&op](const std::shared_ptr& vertex) { + return *vertex->getOperation() == *op; + }); + if (it == executableSet.end()) { + std::cout << "Quantum computations seem not to be equal (operations " + "might not occur in the input circuit, operation " + << line << ").\n"; + return false; + } + (*it)->execute(); + } + if (!qLayer.getExecutableSet().empty()) { + std::cout << "Quantum computations seem not to be equal (not all " + "operations have been executed).\n"; + return false; + } + return true; +} +} // namespace na TEST(NAMapper, Exceptions) { std::istringstream archIS(R"({ @@ -558,14 +734,26 @@ rz(3.9927041) q[7];)"; // --------------------------------------------------------------------- na::NAMapper mapper( arch, na::Configuration( - 3, 3, na::NAMappingMethod::MaximizeParallelismHeuristic)); + 1, 1, na::NAMappingMethod::MaximizeParallelismHeuristic)); mapper.map(circ); - std::ignore = mapper.getResult(); + const auto& result = mapper.getResult(); + EXPECT_TRUE(na::validateAODConstraints(result)); + EXPECT_TRUE(na::checkEquivalence(circ, result, arch)); std::ignore = mapper.getStats(); // --------------------------------------------------------------------- - na::NAMapper mapper2(arch, - na::Configuration(1, 1, na::NAMappingMethod::Naive)); + na::NAMapper mapper2( + arch, na::Configuration( + 3, 3, na::NAMappingMethod::MaximizeParallelismHeuristic)); mapper2.map(circ); + const auto& result2 = mapper2.getResult(); + EXPECT_TRUE(na::validateAODConstraints(result2)); + // --------------------------------------------------------------------- + na::NAMapper mapper3(arch, + na::Configuration(1, 1, na::NAMappingMethod::Naive)); + mapper3.map(circ); + const auto& result3 = mapper3.getResult(); + EXPECT_TRUE(na::validateAODConstraints(result3)); + EXPECT_TRUE(na::checkEquivalence(circ, result3, arch)); // --------------------------------------------------------------------- } @@ -802,5 +990,5 @@ ry(2.2154814) q;)"; 3, 2, na::NAMappingMethod::MaximizeParallelismHeuristic)); mapper.map(circ); std::ignore = mapper.getStats(); - std::ignore = mapper.getResult(); + EXPECT_TRUE(na::validateAODConstraints(mapper.getResult())); }