diff --git a/include/mqt-core/na/NAComputation.hpp b/include/mqt-core/na/NAComputation.hpp index 7e6b2214c..4caf55daa 100644 --- a/include/mqt-core/na/NAComputation.hpp +++ b/include/mqt-core/na/NAComputation.hpp @@ -98,6 +98,13 @@ class NAComputation final : protected std::vector> { -> std::ostream& { return os << qc.toString(); } + /// Validates the NAComputation and checks whether all AOD constraints are + /// fulfilled. + /// I.e., + /// - each atom is loaded before it is moved + /// - the relative order of loaded atoms is preserved + /// - each atom is loaded before it is stored + /// - each atom is stored before it is loaded (again) [[nodiscard]] auto validate() const -> bool; }; } // namespace na diff --git a/include/mqt-core/na/entities/Location.hpp b/include/mqt-core/na/entities/Location.hpp index 06e20da51..1d2271454 100644 --- a/include/mqt-core/na/entities/Location.hpp +++ b/include/mqt-core/na/entities/Location.hpp @@ -10,7 +10,6 @@ #pragma once #include -#include #include #include #include diff --git a/src/na/NAComputation.cpp b/src/na/NAComputation.cpp index 46429ba7d..bbfd0d727 100644 --- a/src/na/NAComputation.cpp +++ b/src/na/NAComputation.cpp @@ -66,18 +66,34 @@ auto NAComputation::toString() const -> std::string { return ss.str(); } auto NAComputation::validate() const -> bool { - std::size_t counter = 1; // the first operation is `init at ...;` + // This counter is used to display the operation number where an error + // occurred. + // As every operation might not correspond to one line in the output, + // this may not be identical with the line number in the output. + // However, the first operation initializes the atom and because of that, the + // counter starts with 1. + std::size_t counter = 1; if (atoms.size() != initialLocations.size()) { std::cout << "Number of atoms and initial locations must be equal\n"; } + // This map is used to keep track of each atom's current location to check + // the constraints when shuttling atoms. std::unordered_map currentLocations = initialLocations; + // This set is used to keep track of the atoms that are currently shuttling, + // i.e., they are loaded but not yet stored again. std::unordered_set currentlyShuttling{}; for (const auto& op : *this) { ++counter; if (op->is()) { + //===----------------------------------------------------------------===// + // Shuttling Operations + //===----------------------------------------------------------------===// const auto& shuttlingOp = op->as(); const auto& opAtoms = shuttlingOp.getAtoms(); if (shuttlingOp.is()) { + //===-----------------------------------------------------------------// + // Load Operations + //-----------------------------------------------------------------===// if (std::any_of(opAtoms.begin(), opAtoms.end(), [¤tlyShuttling](const auto* atom) { return currentlyShuttling.find(atom) != @@ -92,6 +108,9 @@ auto NAComputation::validate() const -> bool { currentlyShuttling.insert(atom); }); } else { + //===-----------------------------------------------------------------// + // Move and Store Operations + //-----------------------------------------------------------------===// if (std::any_of(opAtoms.begin(), opAtoms.end(), [¤tlyShuttling](const auto* atom) { return currentlyShuttling.find(atom) == @@ -102,8 +121,12 @@ auto NAComputation::validate() const -> bool { return false; } } + //===----------------------------------------------------------------===// + // All Shuttling Operations that move atoms + //===----------------------------------------------------------------===// if ((op->is() && op->as().hasTargetLocations()) || - (op->is() && op->as().hasTargetLocations())) { + (op->is() && op->as().hasTargetLocations()) || + op->is()) { const auto& targetLocations = shuttlingOp.getTargetLocations(); for (std::size_t i = 0; i < opAtoms.size(); ++i) { const auto* a = opAtoms[i]; @@ -160,11 +183,17 @@ auto NAComputation::validate() const -> bool { } } if (shuttlingOp.is()) { + //===-----------------------------------------------------------------// + // Store Operations + //-----------------------------------------------------------------===// std::for_each(opAtoms.begin(), opAtoms.end(), [&](const auto* atom) { currentlyShuttling.erase(atom); }); } } else if (op->is()) { + //===----------------------------------------------------------------===// + // Local Operations + //===----------------------------------------------------------------===// const auto& opAtoms = op->as().getAtoms(); for (std::size_t i = 0; i < opAtoms.size(); ++i) { const auto* a = opAtoms[i]; diff --git a/test/na/test_nacomputation.cpp b/test/na/test_nacomputation.cpp index 931671915..3ededf68a 100644 --- a/test/na/test_nacomputation.cpp +++ b/test/na/test_nacomputation.cpp @@ -90,47 +90,70 @@ TEST(NAComputation, ValidateAODConstraints) { EXPECT_FALSE(qc.validate()); qc.clear(); // two atoms identical - qc.emplaceBack(std::vector{atom0, atom0}, + qc.emplaceBack(std::vector{atom0, atom0}); + qc.emplaceBack(std::vector{atom0, atom0}, std::vector{Location{0, 1}, Location{1, 1}}); EXPECT_FALSE(qc.validate()); qc.clear(); // two end points identical - qc.emplaceBack(std::vector{atom0, atom1}, + qc.emplaceBack(std::vector{atom0, atom1}); + qc.emplaceBack(std::vector{atom0, atom1}, std::vector{Location{0, 1}, Location{0, 1}}); EXPECT_FALSE(qc.validate()); qc.clear(); // columns not preserved - qc.emplaceBack(std::vector{atom1, atom3}, + qc.emplaceBack(std::vector{atom1, atom3}); + qc.emplaceBack(std::vector{atom1, atom3}, std::vector{Location{0, 1}, Location{2, 2}}); EXPECT_FALSE(qc.validate()); qc.clear(); // rows not preserved - qc.emplaceBack(std::vector{atom0, atom1}, + qc.emplaceBack(std::vector{atom0, atom1}); + qc.emplaceBack(std::vector{atom0, atom1}, std::vector{Location{0, 1}, Location{1, -1}}); EXPECT_FALSE(qc.validate()); qc.clear(); // column order not preserved - qc.emplaceBack(std::vector{atom0, atom3}, + qc.emplaceBack(std::vector{atom0, atom3}); + qc.emplaceBack(std::vector{atom0, atom3}, std::vector{Location{1, 1}, Location{0, 1}}); EXPECT_FALSE(qc.validate()); qc.clear(); // row order not preserved - qc.emplaceBack(std::vector{atom0, atom3}, + qc.emplaceBack(std::vector{atom0, atom3}); + qc.emplaceBack(std::vector{atom0, atom3}, std::vector{Location{0, 1}, Location{2, 0}}); EXPECT_FALSE(qc.validate()); qc.clear(); // column order not preserved - qc.emplaceBack(std::vector{atom2, atom1}, - std::vector{Location{1, 3}, Location{0, 1}}); + qc.emplaceBack(std::vector{atom2, atom1}); + qc.emplaceBack(std::vector{atom1, atom2}, + std::vector{Location{0, 1}, Location{1, 3}}); EXPECT_FALSE(qc.validate()); qc.clear(); // row order not preserved - qc.emplaceBack(std::vector{atom2, atom1}, + qc.emplaceBack(std::vector{atom2, atom1}); + qc.emplaceBack(std::vector{atom2, atom1}, std::vector{Location{0, 1}, Location{2, 2}}); EXPECT_FALSE(qc.validate()); qc.clear(); // two atoms identical qc.emplaceBack(qc::PI_2, std::vector{atom0, atom0}); EXPECT_FALSE(qc.validate()); + qc.clear(); + // store unloaded atom + qc.emplaceBack(std::vector{atom0}); + EXPECT_FALSE(qc.validate()); +} + +TEST(NAComputation, GetPositionOfAtomAfterOperation) { + auto qc = NAComputation(); + const auto* const atom0 = qc.emplaceBackAtom("atom0"); + qc.emplaceInitialLocation(atom0, 0, 0); + qc.emplaceBack(std::vector{atom0}); + qc.emplaceBack(std::vector{atom0}, std::vector{Location{1, 1}}); + qc.emplaceBack(std::vector{atom0}); + EXPECT_EQ(qc.getLocationOfAtomAfterOperation(atom0, qc[0]), (Location{0, 0})); + EXPECT_EQ(qc.getLocationOfAtomAfterOperation(atom0, qc[2]), (Location{1, 1})); } } // namespace na