diff --git a/cmake/ExternalDependencies.cmake b/cmake/ExternalDependencies.cmake index 38e0bae61..90f29e58e 100644 --- a/cmake/ExternalDependencies.cmake +++ b/cmake/ExternalDependencies.cmake @@ -104,5 +104,21 @@ if(BUILD_MQT_QMAP_BINDINGS) endif() endif() +# Add YAML-CPP as a dependency. +set(YAML_VERSION + 0.8.0 + CACHE STRING "YAML-CPP version") +set(YAML_URL https://github.com/jbeder/yaml-cpp/archive/refs/tags/${YAML_VERSION}.tar.gz) +if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.24) + FetchContent_Declare(yaml-cpp URL ${YAML_URL} FIND_PACKAGE_ARGS ${YAML_VERSION}) + list(APPEND FETCH_PACKAGES yaml-cpp) +else() + find_package(yaml-cpp ${YAML_VERSION} QUIET) + if(NOT yaml-cpp_FOUND) + FetchContent_Declare(yaml-cpp URL ${YAML_URL}) + list(APPEND FETCH_PACKAGES yaml-cpp) + endif() +endif() + # Make all declared dependencies available. FetchContent_MakeAvailable(${FETCH_PACKAGES}) diff --git a/include/na/Architecture.hpp b/include/na/Architecture.hpp index e7293fd30..01e3c5de7 100644 --- a/include/na/Architecture.hpp +++ b/include/na/Architecture.hpp @@ -1,13 +1,7 @@ -// -// 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/mqt-qmap for more -// information. -// - #pragma once -#include "Configuration.hpp" #include "Definitions.hpp" +#include "na/Configuration.hpp" #include "na/NADefinitions.hpp" #include @@ -231,6 +225,7 @@ class Architecture { [[nodiscard]] auto isAllowedGlobally(const FullOpType& t, const Zone& zone) const -> bool; [[nodiscard]] auto getNrowsInZone(const Zone& z) const -> Index; + [[nodiscard]] auto getNColsInZone(const Zone& z) const -> Index; [[nodiscard]] auto getSitesInRow(const Zone& z, const Index& row) const -> std::vector; [[nodiscard]] auto getNearestXLeft(const Number& x, const Zone& z, diff --git a/include/na/Configuration.hpp b/include/na/Configuration.hpp index cb2fa212e..cf4658620 100644 --- a/include/na/Configuration.hpp +++ b/include/na/Configuration.hpp @@ -1,9 +1,3 @@ -// -// 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/mqt-qmap for more -// information. -// - #pragma once #include @@ -14,6 +8,7 @@ #include #include #include + namespace na { enum class NAMappingMethod : std::uint8_t { diff --git a/include/na/NAGraphAlgorithms.hpp b/include/na/nalac/NAGraphAlgorithms.hpp similarity index 100% rename from include/na/NAGraphAlgorithms.hpp rename to include/na/nalac/NAGraphAlgorithms.hpp diff --git a/include/na/NAMapper.hpp b/include/na/nalac/NAMapper.hpp similarity index 98% rename from include/na/NAMapper.hpp rename to include/na/nalac/NAMapper.hpp index 6666de093..08cca7ded 100644 --- a/include/na/NAMapper.hpp +++ b/include/na/nalac/NAMapper.hpp @@ -6,11 +6,11 @@ #pragma once -#include "Architecture.hpp" -#include "Configuration.hpp" #include "Definitions.hpp" #include "ir/QuantumComputation.hpp" #include "ir/operations/Operation.hpp" +#include "na/Architecture.hpp" +#include "na/Configuration.hpp" #include "na/NAComputation.hpp" #include "na/NADefinitions.hpp" diff --git a/include/na/nasp/CodeGenerator.hpp b/include/na/nasp/CodeGenerator.hpp new file mode 100644 index 000000000..1f25d2b7d --- /dev/null +++ b/include/na/nasp/CodeGenerator.hpp @@ -0,0 +1,110 @@ +#pragma once + +#include "Solver.hpp" +#include "ir/QuantumComputation.hpp" +#include "na/NAComputation.hpp" +#include "na/NADefinitions.hpp" + +#include + +namespace na { +using namespace qc; + +class CodeGenerator { +private: + /** + * @brief Calculate the coordinates of a point in the 2D grid from its + * discrete coordinates. + * + * @details The parameters specify the measures that were used for the + * abstraction of the 2D grid. In the abstraction the 2D plane is divided into + * interaction sites. Each site is identified by a discrete coordinate. The + * exact (discrete) position of an atom in a site is given by the discrete + * offset from the sites' origin. + * + * @param q A qubit that contains the discrete coordinates of the interaction + * site together with the discrete offset. + * @param maxHOffset The maximal possible horizontal offset of an atom in a + * site. This value determines the width of a site in x-direction. + * @param maxVOffset The maximal possible vertical offset of an atom in a + * site. This value determines the height of a site in y-direction. + * @param minEntanglingY The minimal y-coordinate of the entangling zone. All + * discrete y-coordinates smaller than this value are considered to be in the + * top storage zone. If this value is 0, there is no top storage zone. + * @param maxEntanglingY The maximal y-coordinate of the entangling zone. All + * discrete y-coordinates greater than this value are considered to be in the + * bottom storage zone. + * @param minAtomDist The minimal distance between two atoms in the 2D grid, + * i.e., between the discrete sites in one interaction site. + * @param noInteractionRadius The radius of atoms in order to avoid + * interactions. This is used as the minimal distance between two atoms in two + * different interaction sites. + * @param zoneDist The distance between the top storage zone and the + * entangling zone and between the entangling zone and the bottom storage + * zone. This distance already includes the minimal distance between two atoms + * and the no interaction radius, i.e., it is the minimal distance between two + * atoms in different zones. + * @return The coordinates of the point in the 2D grid. + */ + static auto coordFromDiscrete(NASolver::Result::Qubit q, int64_t maxHOffset, + int64_t maxVOffset, int64_t minEntanglingY, + int64_t maxEntanglingY, int64_t minAtomDist, + int64_t noInteractionRadius, int64_t zoneDist) + -> Point; + +public: + /** + * @brief Generate a NAComputation from a QuantumComputation and a NASolver + * result. + * + * @details The function generates a NAComputation from a QuantumComputation + * and a NASolver result. The solver works with an abstraction of the 2D grid. + * In the abstraction the 2D plane is divided into + * interaction sites. Each site is identified by a discrete coordinate. The + * exact (discrete) position of an atom in a site is given by the discrete + * offset from the sites' origin. This function calculates the exact position + * of each qubit in the 2D grid from the discrete coordinates and offsets. For + * the calculation, the measures used for the abstraction are required. + * + * @warning This function expects a QuantumComputation that was used as input + * for the NASolver. Additionally, this function assumes the quantum circuit + * represented by the QuantumComputation to be of the following form: + * First, all qubits are initialized in the |+> state by applying a Hadamard + * gate to each qubit. Then, a set of entangling gates (CZ) is applied to the + * qubits. Finally, hadamard gates are applied to some qubits. Unfortunately, + * the function cannot deal with arbitrary quantum circuits as the NASolver + * cannot do either. + * + * @param input The QuantumComputation that was used as input for the + * NASolver. + * @param result The result of the NASolver. + * @param maxHOffset The maximal possible horizontal offset of an atom in a + * site. This value determines the width of a site in x-direction. + * @param maxVOffset The maximal possible vertical offset of an atom in a + * site. This value determines the height of a site in y-direction. + * @param minEntanglingY The minimal y-coordinate of the entangling zone. All + * discrete y-coordinates smaller than this value are considered to be in the + * top storage zone. If this value is 0, there is no top storage zone. + * @param maxEntanglingY The maximal y-coordinate of the entangling zone. All + * discrete y-coordinates greater than this value are considered to be in the + * bottom storage zone. + * @param minAtomDist The minimal distance between two atoms in the 2D grid, + * i.e., between the discrete sites in one interaction site. + * @param noInteractionRadius The radius of atoms in order to avoid + * interactions. This is used as the minimal distance between two atoms in two + * different interaction sites. + * @param zoneDist The distance between the top storage zone and the + * entangling zone and between the entangling zone and the bottom storage + * zone. This distance already includes the minimal distance between two atoms + * and the no interaction radius, i.e., it is the minimal distance between two + * atoms in different zones. + * @return The generated NAComputation. + */ + [[nodiscard]] static auto + generate(const QuantumComputation& input, const NASolver::Result& result, + uint16_t maxHOffset, uint16_t maxVOffset, uint16_t minEntanglingY, + uint16_t maxEntanglingY, uint16_t minAtomDist = 1, + uint16_t noInteractionRadius = 10, uint16_t zoneDist = 24) + -> NAComputation; +}; +} // namespace na diff --git a/include/na/nasp/Solver.hpp b/include/na/nasp/Solver.hpp new file mode 100644 index 000000000..b1b1b78a2 --- /dev/null +++ b/include/na/nasp/Solver.hpp @@ -0,0 +1,457 @@ +#pragma once + +#include "Definitions.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace na { +using namespace z3; + +class NASolver { +private: + /// Z3 context used throughout the solver instance + std::shared_ptr ctx; + + uint16_t maxX = 0; ///< maximum x-coordinate of an interaction site + uint16_t maxY = 0; ///< maximum y-coordinate of an interaction site + /** + * @brief minimum y-coordinate of the entangling zone. + * @details All discrete + * y-coordinates smaller than this value are considered to be in the top + * storage zone. If this value is 0, there is no top storage zone. + */ + uint16_t minEntanglingY = 0; + /** + * @brief maximum y-coordinate of the entangling zone. + * @details All discrete + * y-coordinates greater than this value are considered to be in the bottom + * storage zone. If this value is maxY, there is no bottom storage zone. + */ + uint16_t maxEntanglingY = 0; + /** + * @brief maximum index of an AOD column. + * @details Limits the number of AOD columns. + */ + uint16_t maxC = 0; + /** + * @brief maximum index of an AOD row. + * @details Limits the number of AOD rows. + */ + uint16_t maxR = 0; + /** + * @brief maximum horizontal offset from the SLM trap. + * @details Limits the columns within one interaction site. The number of + * columns is 2 * maxHOffset + 1. + */ + uint16_t maxHOffset = 0; + /** + * @brief maximum vertical offset from the SLM trap. + * @details Limits the rows within one interaction site. The number of rows is + * 2 * maxVOffset + 1. + */ + uint16_t maxVOffset = 0; + /** + * @brief maximum horizontal distance between two atoms in order to interact. + * @details The distance between two atoms in the 2D grid is at most (maxVDist + * + maxHDist) * minAtomDist. If maxHDist = 1, this means that two atoms can + * interact if they are in the same column or in adjacent columns. + */ + uint16_t maxHDist = 0; + /** + * @brief maximum vertical distance between two atoms in order to interact. + * @details The distance between two atoms in the 2D grid is at most (maxVDist + * + maxHDist) * minAtomDist. If maxVDist = 1, this means that two atoms can + * interact if they are in the same row or in adjacent rows. + */ + uint16_t maxVDist = 0; + + enum class Storage : uint8_t { None, Bottom, TwoSided }; + + Storage storage = Storage::None; + + static auto minBitsToRepresentUInt(uint16_t num) -> uint32_t; + static auto minBitsToRepresentInt(int32_t num) -> uint32_t; + + /// A class to collect all variables associated with one qubit + class Qubit { + private: + uint16_t id; ///< unique identifier of the qubit + expr x; ///< x-coordinate of the site the atom is loaded in + expr y; ///< y-coordinate of the site the atom is loaded in + /** + * @brief boolean variable to indicate whether the atom is loaded in an AOD, + * SLM otherwise + */ + expr a; + /** + * @brief if the atom is loaded in an AOD, this is the index of the AOD + * column, otherwise it has no meaning + */ + expr c; + /** + * @brief if the atom is loaded in an AOD, this is the index of the AOD row, + * otherwise it has no meaning + */ + expr r; + /** + * @brief denotes the horizontal offset from the SLM trap if the atom is + * loaded in an AOD + */ + expr h; + /** + * @brief denotes the vertical offset from the SLM trap if the atom is + * loaded in an AOD + */ + expr v; + + public: + /** + * @brief Construct a new Qubit object + * @param ctx the solvers context + * @param id the unique identifier of the qubit + * @param t the stage the qubit is in + * @param maxX the maximum possible discrete x-coordinate used to determine + * the bit width for x + * @param maxY the maximum possible discrete y-coordinate used to determine + * the bit width for y + * @param maxC the maximum possible AOD column index used to determine the + * bit width for c + * @param maxR the maximum possible AOD row index used to determine the bit + * width for r + * @param maxHOffset the maximum possible horizontal offset used to + * determine the bit width for h + * @param maxVOffset the maximum possible vertical offset used to determine + * the bit width for v + */ + [[nodiscard]] Qubit(context& ctx, uint16_t id, uint16_t t, uint16_t maxX, + uint16_t maxY, uint16_t maxC, uint16_t maxR, + uint16_t maxHOffset, uint16_t maxVOffset); + + /// @see id + [[nodiscard]] uint16_t getId() const { return id; } + + /// @see x + [[nodiscard]] const expr& getX() const { return x; } + + /// @see y + [[nodiscard]] const expr& getY() const { return y; } + + /// @see a + [[nodiscard]] const expr& getA() const { return a; } + + /// @see c + [[nodiscard]] const expr& getC() const { return c; } + + /// @see r + [[nodiscard]] const expr& getR() const { return r; } + + /// @see h + [[nodiscard]] const expr& getH() const { return h; } + + /// @see v + [[nodiscard]] const expr& getV() const { return v; } + }; + + class Stage { + private: + uint16_t t; ///< the index of the stage + std::vector qubits; ///< the location of all qubits in this stage + /** + * @brief boolean variables to indicate whether the column is loaded at this + * stage. + * @details The index of the vector corresponds to the column index. + * When a column is loaded at a certain stage, then all atoms on this column + * must be loaded at this stage. + */ + std::vector loadCols; + /** + * @brief boolean variables to indicate whether the row is loaded at this + * stage. + * @details The index of the vector corresponds to the row index. + * When a row is loaded at a certain stage, then all atoms on this row + * must be loaded at this stage. + */ + std::vector loadRows; + /** + * @brief boolean variables to indicate whether the column is stored at this + * stage. + * @details The index of the vector corresponds to the column index. + * When a column is stored at a certain stage, then all atoms on this column + * must be stored at this stage. + */ + std::vector storeCols; + /** + * @brief boolean variables to indicate whether the row is stored at this + * stage. + * @details The index of the vector corresponds to the row index. + * When a row is stored at a certain stage, then all atoms on this row + * must be stored at this stage. + */ + std::vector storeRows; + + public: + [[nodiscard]] explicit Stage(context& ctx, uint16_t t, uint16_t numQubits, + uint16_t maxX, uint16_t maxY, uint16_t maxC, + uint16_t maxR, uint16_t maxHOffset, + uint16_t maxVOffset); + + [[nodiscard]] uint16_t getT() const { return t; } + + [[nodiscard]] auto getQubit(const size_t i) const -> const Qubit& { + return qubits[i]; + } + + [[nodiscard]] auto numQubits() const { + return static_cast(qubits.size()); + } + + [[nodiscard]] auto getLoadCol(const size_t i) const -> const expr& { + return loadCols[i]; + } + + [[nodiscard]] auto getLoadRow(const size_t i) const -> const expr& { + return loadRows[i]; + } + + [[nodiscard]] auto getStoreCol(const size_t i) const -> const expr& { + return storeCols[i]; + } + + [[nodiscard]] auto getStoreRow(const size_t i) const -> const expr& { + return storeRows[i]; + } + }; + + uint16_t numQubits = 0; + uint16_t numStages = 0; + std::optional numTransfers = std::nullopt; + std::vector stages; + std::vector transfers; + std::vector gates; + + /// Initializes the variables for all stages and all qubits + auto initVariables() -> void; + + /// Return constraints ensuring that exactly @code numTransfers@endcode + /// transfers take place + [[nodiscard]] auto getExactNumTransfersConstraints() const + -> std::vector; + + /// Returns the constraint @code (x_t^(q0) = x_t^(q1)) ∧ (y_t^(q0) = y_t^(q1)) + /// @endcode + [[nodiscard]] auto getHaveSamePositionConstraint(uint16_t q0, uint16_t q1, + uint16_t t) const -> expr; + + /// Returns the constraint @code (x_t^(q0) ≠ x_t^(q1)) ∨ (y_t^(q0) ≠ y_t^(q1)) + /// @endcode + [[nodiscard]] auto + getHaveDifferentPositionConstraint(uint16_t q0, uint16_t q1, uint16_t t) const + -> expr; + + /// Return constraints ensuring that the qubits is in the entangling zone at + /// stage t + [[nodiscard]] auto getAffectedByRydbergBeamConstraint(uint16_t q, + uint16_t t) const + -> expr; + + /// Return constraints ensuring that the qubits is in the entangling zone at + /// stage t + [[nodiscard]] auto getShieldedFromRydbergBeamConstraint(uint16_t q, + uint16_t t) const + -> expr; + + /// Returns a vector of constraints ensuring that transition from a Rydberg + /// stage to the next stage is valid + [[nodiscard]] auto getValidRydbergTransitionConstraints(uint16_t t) const + -> std::vector; + + /// Returns a vector of constraints ensuring that transition from a Transfer + /// stage to the next stage is valid + [[nodiscard]] auto getValidTransferTransitionConstraints(uint16_t t) const + -> std::vector; + + /** + * @brief Returns the constraints extracted from the quantum circuit to ensure + * execution of each gate and not more. + * @details Creates the variables @code gate_i@endcode for every gate between + * the qubits @code q0, q1@endcode and returns the following constraints: + * @code + * (0 ≤ gate_i) ∧ (gate_i < numStages) for all i + * + * (gate_i = t) ⟷ rydbergStage(t) ∧ haveSamePosition(q0, q1, t) for all i, t + * and (q0, q1) is a gate_i + * + * rydbergStage(t) ⟶ haveDifferentPosition(q, q', t) for all q, q', t where + * (q, q') is not a gate + * @endcode + * @return a vector of the constraints described above + */ + [[nodiscard]] auto getCircuitExecutionConstraints( + const std::vector>& ops, + bool mindOpsOrder, bool shieldIdleAtoms) -> std::vector; + + /// Returns a constraint expressing that this stage is a Rydberg stage, that + /// is if + /// @code numTransfers_{t-1} = numTransfers_t @endcode + [[nodiscard]] auto getRydbergStageConstraint(uint16_t t) const -> expr; + + /// Returns a constraint expressing that this stage is a Transfer stage, that + /// is if + /// @code numTransfers_{t-1} + 1 = numTransfers_t @endcode + [[nodiscard]] auto getTransferStageConstraint(uint16_t t) const -> expr; + + /// Returns constraints ensuring that the state at the given stage is valid + [[nodiscard]] auto getValidStageConstraints(uint16_t t) const + -> std::vector; + +public: + /** + * @brief Construct a new NASolver object with the given parameters that + * define the abstraction of the 2D grid used by the solver. + * @param newMaxX is the maximal discrete x-coordinate of an interaction site + * @param newMaxY is the maximal discrete y-coordinate of an interaction site + * @param newMaxC is the maximal index of an AOD column, i.e., it limits the + * number of AOD columns + * @param newMaxR is the maximal index of an AOD row, i.e., it limits the + * number of AOD rows + * @param newMaxHOffset is the maximal horizontal offset from the SLM trap + * @param newMaxVOffset is the maximal vertical offset from the SLM trap + * @param newMaxHDist is the maximal horizontal distance between two atoms in + * order to interact + * @param newMaxVDist is the maximal vertical distance between two atoms in + * order to interact + * @param newMinEntanglingY is the minimal y-coordinate of the entangling + * zone, i.e., all discrete y-coordinates smaller than this value are + * considered to be in the top storage zone. If this value is 0, there is no + * top storage zone. + * @param newMaxEntanglingY is the maximal y-coordinate of the entangling + * zone, i.e., all discrete y-coordinates greater than this value are + * considered to be in the bottom storage zone. If this value is maxY, there + * is no bottom storage zone. + * @throws illegal_argument if newMinEntanglingY > newMaxEntanglingY + */ + [[nodiscard]] NASolver(uint16_t newMaxX, uint16_t newMaxY, uint16_t newMaxC, + uint16_t newMaxR, uint16_t newMaxHOffset, + uint16_t newMaxVOffset, uint16_t newMaxHDist, + uint16_t newMaxVDist, uint16_t newMinEntanglingY, + uint16_t newMaxEntanglingY); + [[nodiscard]] NASolver(const NASolver& other) = default; + [[nodiscard]] NASolver& operator=(const NASolver& other) = default; + virtual ~NASolver() = default; + + /// This struct wraps the result of the solver + struct Result { + public: + /// The types for the members of the result is chosen to be compatible with + /// what Z3 returns by default + struct Qubit { + public: + /// discrete x-coordinate of the site the atom is located in + uint32_t x; + /// discrete y-coordinate of the site the atom is located in + uint32_t y; + /// boolean variable to indicate whether the atom is loaded in an AOD, + /// SLM otherwise + bool a; + /// if the atom is loaded in an AOD, this is the index of the AOD column, + /// otherwise it has no meaning + uint32_t c; + /// if the atom is loaded in an AOD, this is the index of the AOD row, + /// otherwise it has no meaning + uint32_t r; + /// denotes the horizontal offset from the SLM trap if the atom is loaded + /// in an AOD + int32_t h; + /// denotes the vertical offset from the SLM trap if the atom is loaded in + /// an AOD + int32_t v; + + [[nodiscard]] static auto fromYAML(const YAML::Node& yaml) -> Qubit; + + [[nodiscard]] auto yaml(std::size_t indent, bool item = true, + bool compact = true) const -> std::string; + [[nodiscard]] auto operator==(const Qubit& other) const -> bool; + }; + + struct Gate { + public: + uint16_t stage = 0; + std::pair qubits; + + [[nodiscard]] static auto fromYAML(const YAML::Node& yaml) -> Gate; + + [[nodiscard]] auto yaml(std::size_t indent, bool item = true, + bool compact = true) const -> std::string; + + [[nodiscard]] auto operator==(const Gate& other) const -> bool; + }; + + struct Stage { + public: + bool rydberg = true; + std::vector qubits; + std::vector gates; + + [[nodiscard]] static auto fromYAML(const YAML::Node& yaml) -> Stage; + + [[nodiscard]] auto yaml(std::size_t indent, bool item = true, + bool compact = true) const -> std::string; + + [[nodiscard]] auto operator==(const Stage& other) const -> bool; + }; + + bool sat = false; + std::vector stages; + + [[nodiscard]] static auto fromYAML(const YAML::Node& yaml) -> Result; + + [[nodiscard]] auto yaml(std::size_t indent = 0, bool compact = true) const + -> std::string; + + [[nodiscard]] auto operator==(const Result& other) const -> bool; + }; + + /** + * @brief The core function of the solver that solves one instance of the + * problem. + * @details The solver takes a list of operations and returns a list of stages + * where each stage contains the location of all atoms and the gates that + * should be executed in this stage. The solver takes several parameters to + * configure the problem that are explained in the following. + * @param ops a list of entangling operations represented as a list of qubit + * pairs + * @param newNumQubits the overall number of qubits in the quantum circuit + * @param newNumStages the exact number of stages the computation should be + * divided into + * @param newNumTransfers the number of stages that are transfer stages. This + * is an optional parameter and if not set, the number of transfer stages is + * variable and not fixed. + * @param mindOpsOrder if true, the solver schedules the operations in the + * order they are given in the list. If false, the solver is free to choose + * the order of the operations. + * @param shieldIdleQubits if true, the solver ensures that qubits that are + * not involved in an operation are shielded from the Rydberg beam, i.e., + * moved to one storage zone. If there is no storage zone and this parameter + * is true, the solver will return an exception. + * @throws illegal_argument if there is no storage zone and shieldIdleQubits + * is true + */ + [[nodiscard]] auto + solve(const std::vector>& ops, + uint16_t newNumQubits, uint16_t newNumStages, + std::optional newNumTransfers = std::nullopt, + bool mindOpsOrder = false, bool shieldIdleQubits = true) -> Result; +}; + +struct ExprHash { + uint32_t operator()(const expr& e) const { return e.hash(); } +}; +} // namespace na diff --git a/include/na/nasp/SolverFactory.hpp b/include/na/nasp/SolverFactory.hpp new file mode 100644 index 000000000..ba8640dbc --- /dev/null +++ b/include/na/nasp/SolverFactory.hpp @@ -0,0 +1,57 @@ +#pragma once + +#include "Definitions.hpp" +#include "Solver.hpp" +#include "ir/QuantumComputation.hpp" +#include "na/Architecture.hpp" +#include "na/NADefinitions.hpp" + +#include +#include + +namespace na { +class SolverFactory { +public: + /** + * @brief Create a NASolver from a given architecture. + * + * @details The solver considers an abstraction of the real architecture. This + * function derives the parameters for the abstraction from the given + * architecture. These parameters are used to initialize the solver, which + * is then returned. + * + * @param arch The architecture to create the solver for. + * @return The created NASolver. + */ + [[nodiscard]] static auto create(const Architecture& arch) -> NASolver; + + /** + * @brief Get the list of entangling operations that the solver takes as + * input. + * + * @details The solver only considers the entangling operations of a circuit. + * For that it receives a list of qubit pairs that represent each one + * entangling operation. This function generates this list from a given + * QuantumComputation and a FullOpType that specifies the entangling + * operation. + * + * @warning This function expects a QuantumComputation that was used as input + * for the NASolver. Additionally, this function assumes the quantum circuit + * represented by the QuantumComputation to be of the following form: + * First, all qubits are initialized in the |+> state by applying a Hadamard + * gate to each qubit. Then, a set of entangling gates (CZ) is applied to the + * qubits. Finally, hadamard gates are applied to some qubits. Unfortunately, + * the function cannot deal with arbitrary quantum circuits as the NASolver + * cannot do either. + * + * @param circ + * @param opType + * @param quiet + * @return + */ + [[nodiscard]] static auto getOpsForSolver(const qc::QuantumComputation& circ, + FullOpType opType, + bool quiet = false) + -> std::vector>; +}; +} // namespace na diff --git a/src/na/Architecture.cpp b/src/na/Architecture.cpp index 46f20ba6d..6d64b0745 100644 --- a/src/na/Architecture.cpp +++ b/src/na/Architecture.cpp @@ -1,9 +1,3 @@ -// -// 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/mqt-qmap for more -// information. -// - #include "na/Architecture.hpp" #include "Definitions.hpp" @@ -224,8 +218,11 @@ auto Architecture::getColsInZone(const Zone& z) const -> std::vector { std::sort(result.begin(), result.end()); return result; } +auto Architecture::getNColsInZone(const Zone& z) const -> Index { + return getColsInZone(z).size(); +} auto Architecture::getNrowsInZone(const Zone& z) const -> Index { - return Architecture::getRowsInZone(z).size(); + return getRowsInZone(z).size(); } auto Architecture::getSitesInRow(const Zone& z, const Index& row) const -> std::vector { diff --git a/src/na/CMakeLists.txt b/src/na/CMakeLists.txt index 216ebe548..c9bc9725b 100644 --- a/src/na/CMakeLists.txt +++ b/src/na/CMakeLists.txt @@ -1,22 +1,19 @@ -# -# 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/mqt-qmap for more information. -# - set(MQT_QMAP_NA_TARGET_NAME ${MQT_QMAP_TARGET_NAME}-na) if(NOT TARGET ${MQT_QMAP_NA_TARGET_NAME}) - file(GLOB_RECURSE NA_HEADERS ${MQT_QMAP_INCLUDE_BUILD_DIR}/na/*.hpp) - file(GLOB_RECURSE NA_SOURCES *.cpp) + file(GLOB NA_HEADERS ${MQT_QMAP_INCLUDE_BUILD_DIR}/na/*.hpp) + file(GLOB NA_SOURCES *.cpp) add_library(${MQT_QMAP_NA_TARGET_NAME} ${NA_HEADERS} ${NA_SOURCES}) target_include_directories(${MQT_QMAP_NA_TARGET_NAME} PUBLIC $) - target_link_libraries(${MQT_QMAP_NA_TARGET_NAME} PUBLIC MQT::CoreIR MQT::CoreNA - nlohmann_json::nlohmann_json) + target_link_libraries(${MQT_QMAP_NA_TARGET_NAME} PUBLIC MQT::CoreNA nlohmann_json::nlohmann_json) target_link_libraries(${MQT_QMAP_NA_TARGET_NAME} PRIVATE MQT::ProjectOptions MQT::ProjectWarnings) add_library(MQT::QMapNA ALIAS ${MQT_QMAP_NA_TARGET_NAME}) endif() + +add_subdirectory(nalac) +add_subdirectory(nasp) diff --git a/src/na/Configuration.cpp b/src/na/Configuration.cpp index 8204f3b1e..2f2742439 100644 --- a/src/na/Configuration.cpp +++ b/src/na/Configuration.cpp @@ -1,9 +1,3 @@ -// -// 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/mqt-qmap for more -// information. -// - #include "na/Configuration.hpp" #include diff --git a/src/na/nalac/CMakeLists.txt b/src/na/nalac/CMakeLists.txt new file mode 100644 index 000000000..b6dce7717 --- /dev/null +++ b/src/na/nalac/CMakeLists.txt @@ -0,0 +1,18 @@ +set(MQT_QMAP_NALAC_TARGET_NAME ${MQT_QMAP_TARGET_NAME}-nalac) + +if(NOT TARGET ${MQT_QMAP_NALAC_TARGET_NAME}) + file(GLOB NALAC_HEADERS ${MQT_QMAP_INCLUDE_BUILD_DIR}/na/nalac/*.hpp) + file(GLOB NALAC_SOURCES *.cpp) + + add_library(${MQT_QMAP_NALAC_TARGET_NAME} ${NALAC_HEADERS} ${NALAC_SOURCES}) + + target_include_directories(${MQT_QMAP_NALAC_TARGET_NAME} + PUBLIC $) + + target_link_libraries( + ${MQT_QMAP_NALAC_TARGET_NAME} + PUBLIC MQT::QMapNA + PRIVATE MQT::ProjectOptions MQT::ProjectWarnings) + + add_library(MQT::NALAC ALIAS ${MQT_QMAP_NALAC_TARGET_NAME}) +endif() diff --git a/src/na/NAGraphAlgorithms.cpp b/src/na/nalac/NAGraphAlgorithms.cpp similarity index 99% rename from src/na/NAGraphAlgorithms.cpp rename to src/na/nalac/NAGraphAlgorithms.cpp index f4305b428..6528fd16d 100644 --- a/src/na/NAGraphAlgorithms.cpp +++ b/src/na/nalac/NAGraphAlgorithms.cpp @@ -1,10 +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/mqt-qmap for more -// information. -// - -#include "na/NAGraphAlgorithms.hpp" +#include "na/nalac/NAGraphAlgorithms.hpp" #include "Definitions.hpp" #include "datastructures/DirectedAcyclicGraph.hpp" diff --git a/src/na/NAMapper.cpp b/src/na/nalac/NAMapper.cpp similarity index 99% rename from src/na/NAMapper.cpp rename to src/na/nalac/NAMapper.cpp index 858e5813c..0f156faf2 100644 --- a/src/na/NAMapper.cpp +++ b/src/na/nalac/NAMapper.cpp @@ -1,10 +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/mqt-qmap for more -// information. -// - -#include "na/NAMapper.hpp" +#include "na/nalac/NAMapper.hpp" #include "Definitions.hpp" #include "datastructures/Layer.hpp" @@ -15,7 +9,7 @@ #include "na/Architecture.hpp" #include "na/Configuration.hpp" #include "na/NADefinitions.hpp" -#include "na/NAGraphAlgorithms.hpp" +#include "na/nalac/NAGraphAlgorithms.hpp" #include "na/operations/NAGlobalOperation.hpp" #include "na/operations/NALocalOperation.hpp" #include "na/operations/NAShuttlingOperation.hpp" diff --git a/src/na/nasp/CMakeLists.txt b/src/na/nasp/CMakeLists.txt new file mode 100644 index 000000000..6d0c78153 --- /dev/null +++ b/src/na/nasp/CMakeLists.txt @@ -0,0 +1,18 @@ +set(MQT_QMAP_NASP_TARGET_NAME ${MQT_QMAP_TARGET_NAME}-nasp) + +if(NOT TARGET ${MQT_QMAP_NASP_TARGET_NAME}) + file(GLOB NASP_HEADERS ${MQT_QMAP_INCLUDE_BUILD_DIR}/na/nasp/*.hpp) + file(GLOB NASP_SOURCES *.cpp) + + add_library(${MQT_QMAP_NASP_TARGET_NAME} ${NASP_HEADERS} ${NASP_SOURCES}) + + target_include_directories(${MQT_QMAP_NASP_TARGET_NAME} + PUBLIC $) + + target_link_libraries( + ${MQT_QMAP_NASP_TARGET_NAME} + PUBLIC MQT::QMapNA MQT::CoreCircuitOptimizer z3::z3lib yaml-cpp + PRIVATE MQT::ProjectOptions MQT::ProjectWarnings) + + add_library(MQT::NASP ALIAS ${MQT_QMAP_NASP_TARGET_NAME}) +endif() diff --git a/src/na/nasp/CodeGenerator.cpp b/src/na/nasp/CodeGenerator.cpp new file mode 100644 index 000000000..18ca29a4e --- /dev/null +++ b/src/na/nasp/CodeGenerator.cpp @@ -0,0 +1,224 @@ +#include "na/nasp/CodeGenerator.hpp" + +#include "Definitions.hpp" +#include "circuit_optimizer/CircuitOptimizer.hpp" +#include "datastructures/Layer.hpp" +#include "ir/QuantumComputation.hpp" +#include "ir/operations/OpType.hpp" +#include "na/NAComputation.hpp" +#include "na/NADefinitions.hpp" +#include "na/nasp/Solver.hpp" +#include "na/operations/NAGlobalOperation.hpp" +#include "na/operations/NALocalOperation.hpp" +#include "na/operations/NAShuttlingOperation.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace na { +using namespace qc; + +auto CodeGenerator::coordFromDiscrete( + const NASolver::Result::Qubit q, const int64_t maxHOffset, + const int64_t maxVOffset, const int64_t minEntanglingY, + const int64_t maxEntanglingY, const int64_t minAtomDist, + const int64_t noInteractionRadius, const int64_t zoneDist) -> Point { + const auto dx = noInteractionRadius + (2LL * maxHOffset * minAtomDist); + const auto dy = noInteractionRadius + (2LL * maxVOffset * minAtomDist); + const auto x = q.x; + const auto y = q.y; + const auto h = q.h; + const auto v = q.v; + if (minEntanglingY == 0) { + // no top storage zone + if (y <= maxEntanglingY) { + return {(x * dx) + (h * minAtomDist), (y * dy) + (v * minAtomDist)}; + } + return {(x * dx) + (h * minAtomDist), + zoneDist + ((y - 1) * dy) + (v * minAtomDist)}; + } + // top storage zone + if (y < minEntanglingY) { + return {(x * dx) + (h * minAtomDist), (y * dy) + (v * minAtomDist)}; + } + if (y <= maxEntanglingY) { + return {(x * dx) + (h * minAtomDist), + zoneDist + ((y - 1) * dy) + (v * minAtomDist)}; + } + return {(x * dx) + (h * minAtomDist), + (2LL * zoneDist) + ((y - 2) * dy) + (v * minAtomDist)}; +} + +auto CodeGenerator::generate( + const QuantumComputation& input, const NASolver::Result& result, + const uint16_t maxHOffset, const uint16_t maxVOffset, + const uint16_t minEntanglingY, const uint16_t maxEntanglingY, + const uint16_t minAtomDist, const uint16_t noInteractionRadius, + const uint16_t zoneDist) -> NAComputation { + auto flattened = input; + CircuitOptimizer::flattenOperations(flattened); + const Layer layer(flattened); + NAComputation code; + std::vector> oldPositions; + std::vector wasAOD; + oldPositions.reserve(result.stages.front().qubits.size()); + wasAOD.reserve(result.stages.front().qubits.size()); + // initialize atoms in SLM and load required ones into AOD + { + std::unordered_map> hAODLines{}; + std::unordered_map> vAODLines{}; + for (const auto& q : result.stages.front().qubits) { + if (q.a) { + hAODLines[static_cast(q.y)].emplace(q.r); + vAODLines[static_cast(q.x)].emplace(q.c); + } + } + std::vector> loadPositions; + for (const auto& q : result.stages.front().qubits) { + auto pos = std::make_shared(coordFromDiscrete( + q, maxHOffset, maxVOffset, minEntanglingY, maxEntanglingY, + minAtomDist, noInteractionRadius, zoneDist)); + wasAOD.emplace_back(q.a); + if (q.a) { + loadPositions.emplace_back(pos); + } + oldPositions.emplace_back(pos); + code.emplaceInitialPosition(pos); + } + if (!loadPositions.empty()) { + code.emplaceBack(LOAD, loadPositions, + loadPositions); + } + } + const auto ops = layer.getExecutablesOfType(H, 0); + std::unordered_set affectedQubits; + std::transform( + ops.cbegin(), ops.cend(), + std::inserter(affectedQubits, affectedQubits.end()), + [](const auto& v) { return v->getOperation()->getTargets().front(); }); + if (affectedQubits.size() != flattened.getNqubits() || + ops.size() != affectedQubits.size()) { + throw std::invalid_argument("Not all atoms are initialized to plus state."); + } + // initialize atoms in to |+> state starting in |0> state + code.emplaceBack(std::make_unique(FullOpType{RY, 0}, + std::vector{PI_2})); + std::for_each(ops.cbegin(), ops.cend(), [](const auto& v) { v->execute(); }); + // Reference to the executable set of the input circuit + const auto& executableSet = layer.getExecutableSet(); + if (result.stages.front().rydberg) { + code.emplaceBack(std::make_unique(FullOpType{Z, 1})); + // find and execute corresponding gates in input circuit + for (const auto& g : result.stages.front().gates) { + const auto& it = std::find_if( + executableSet.begin(), executableSet.end(), [g](const auto& v) { + if (v->getOperation()->getType() == Z && + v->getOperation()->getNcontrols() == 1) { + const auto& usedQubits = v->getOperation()->getUsedQubits(); + const auto [first, second] = g.qubits; + return std::set{first, second} == usedQubits; + } + return false; + }); + if (it == executableSet.end()) { + throw std::invalid_argument( + "Gate in input circuit has no correspondence in solution."); + } + (*it)->execute(); + } + } + for (uint16_t t = 1; t < static_cast(result.stages.size()); ++t) { + std::vector> newPositions; + newPositions.reserve(oldPositions.size()); + std::vector> startPositions; + std::vector> endPositions; + std::vector> loadStartPositions; + std::vector> loadEndPositions; + std::vector> storeStartPositions; + std::vector> storeEndPositions; + for (uint16_t i = 0; + static_cast(i) < result.stages.at(t).qubits.size(); ++i) { + const auto& q = result.stages.at(t).qubits.at(i); + auto pos = std::make_shared(coordFromDiscrete( + q, maxHOffset, maxVOffset, minEntanglingY, maxEntanglingY, + minAtomDist, noInteractionRadius, zoneDist)); + if (wasAOD[i] && q.a) { + startPositions.emplace_back(oldPositions[i]); + endPositions.emplace_back(pos); + } else if (wasAOD[i] && !q.a) { + storeStartPositions.emplace_back(oldPositions[i]); + storeEndPositions.emplace_back(pos); + } else if (!wasAOD[i] && q.a) { + loadStartPositions.emplace_back(oldPositions[i]); + loadEndPositions.emplace_back(loadStartPositions.back()); + startPositions.emplace_back(loadEndPositions.back()); + endPositions.emplace_back(pos); + } + newPositions.emplace_back(pos); + wasAOD[i] = q.a; + } + if (!storeEndPositions.empty()) { + code.emplaceBack(STORE, storeStartPositions, + storeEndPositions); + } + if (!loadEndPositions.empty()) { + code.emplaceBack(LOAD, loadStartPositions, + loadEndPositions); + } + if (!startPositions.empty()) { + code.emplaceBack(MOVE, startPositions, + endPositions); + } + if (result.stages.at(t).rydberg) { + code.emplaceBack(std::make_unique(FullOpType{Z, 1})); + } + oldPositions = std::move(newPositions); + // find and execute corresponding gates in input circuit + for (const auto& g : result.stages.at(t).gates) { + const auto& it = std::find_if( + executableSet.begin(), executableSet.end(), [g](const auto& v) { + if (v->getOperation()->getType() == Z && + v->getOperation()->getNcontrols() == 1) { + const auto& usedQubits = v->getOperation()->getUsedQubits(); + const auto [first, second] = g.qubits; + return std::set{first, second} == usedQubits; + } + return false; + }); + if (it == executableSet.end()) { + throw std::invalid_argument( + "Gate in input circuit has no correspondence in solution."); + } + (*it)->execute(); + } + } + if (!executableSet.empty()) { + code.emplaceBack(std::make_unique(FullOpType{RY, 0}, + std::vector{-PI_4})); + while (!executableSet.empty()) { + const auto& v = (*executableSet.cbegin()); + if (v->getOperation()->getType() != H) { + throw std::invalid_argument( + "Not all non CZ-gates in input circuit are executed."); + } + const auto q = v->getOperation()->getTargets().front(); + const auto& pos = oldPositions[q]; + code.emplaceBack(std::make_unique( + FullOpType{RZ, 0}, std::vector{PI}, pos)); + v->execute(); + } + code.emplaceBack(std::make_unique(FullOpType{RY, 0}, + std::vector{PI_4})); + } + return code; +} +} // namespace na diff --git a/src/na/nasp/Solver.cpp b/src/na/nasp/Solver.cpp new file mode 100644 index 000000000..efc511eeb --- /dev/null +++ b/src/na/nasp/Solver.cpp @@ -0,0 +1,785 @@ +#include "na/nasp/Solver.hpp" + +#include "Definitions.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +// NOLINTNEXTLINE (misc-include-cleaner) +#include +#include + +namespace na { + +NASolver::Qubit::Qubit(context& ctx, uint16_t id, const uint16_t t, + const uint16_t maxX, const uint16_t maxY, + const uint16_t maxC, const uint16_t maxR, + const uint16_t maxHOffset, const uint16_t maxVOffset) + : id(id), x(ctx.bv_const( + ("x" + std::to_string(t) + "^" + std::to_string(id)).c_str(), + minBitsToRepresentUInt(maxX))), + y(ctx.bv_const( + ("y" + std::to_string(t) + "^" + std::to_string(id)).c_str(), + minBitsToRepresentUInt(maxY))), + a(ctx.bool_const( + ("a" + std::to_string(t) + "^" + std::to_string(id)).c_str())), + c(ctx.bv_const( + ("c" + std::to_string(t) + "^" + std::to_string(id)).c_str(), + minBitsToRepresentUInt(maxC))), + r(ctx.bv_const( + ("r" + std::to_string(t) + "^" + std::to_string(id)).c_str(), + minBitsToRepresentUInt(maxR))), + h(ctx.bv_const( + ("h" + std::to_string(t) + "^" + std::to_string(id)).c_str(), + minBitsToRepresentInt(maxHOffset))), + v(ctx.bv_const( + ("v" + std::to_string(t) + "^" + std::to_string(id)).c_str(), + minBitsToRepresentInt(maxVOffset))) {} + +NASolver::Stage::Stage(context& ctx, const uint16_t t, const uint16_t numQubits, + const uint16_t maxX, const uint16_t maxY, + const uint16_t maxC, const uint16_t maxR, + const uint16_t maxHOffset, const uint16_t maxVOffset) + : t(t) { + qubits.reserve(numQubits); + for (uint16_t id = 0; id < numQubits; ++id) { + qubits.emplace_back(ctx, id, t, maxX, maxY, maxC, maxR, maxHOffset, + maxVOffset); + } + loadCols.reserve(maxC); + storeCols.reserve(maxC); + for (uint16_t c = 0; c <= maxC; ++c) { + std::stringstream suffixStream; + suffixStream << "_" << t << "^c" << c; + const auto& suffix = suffixStream.str(); + loadCols.emplace_back(ctx.bool_const(("load" + suffix).c_str())); + storeCols.emplace_back(ctx.bool_const(("store" + suffix).c_str())); + } + loadRows.reserve(maxR); + storeRows.reserve(maxR); + for (uint16_t r = 0; r <= maxR; ++r) { + std::stringstream suffixStream; + suffixStream << "_" << t << "^r" << r; + const auto& suffix = suffixStream.str(); + loadRows.emplace_back(ctx.bool_const(("load" + suffix).c_str())); + storeRows.emplace_back(ctx.bool_const(("store" + suffix).c_str())); + } +} + +auto NASolver::minBitsToRepresentUInt(uint16_t num) -> uint32_t { + uint32_t bits = 0; + while (num > 0) { + num >>= 1; + ++bits; + } + return bits; +} + +auto NASolver::minBitsToRepresentInt(const int32_t num) -> uint32_t { + return minBitsToRepresentUInt(static_cast(std::abs(num))) + 1; +} + +auto NASolver::initVariables() -> void { + stages.clear(); + stages.reserve(numStages); + for (uint16_t t = 0; t < numStages; ++t) { + stages.emplace_back(*ctx, t, numQubits, maxX, maxY, maxC, maxR, maxHOffset, + maxVOffset); + } + transfers.clear(); + if (numTransfers.has_value()) { + transfers.reserve(numTransfers.value()); + for (uint16_t t = 0; t < numTransfers.value(); ++t) { + transfers.emplace_back( + ctx->bv_const(("transfer_" + std::to_string(t)).c_str(), + minBitsToRepresentUInt(numStages))); + } + } else { + transfers.reserve(numStages); + for (uint16_t t = 0; t < numStages; ++t) { + transfers.emplace_back( + ctx->bool_const(("transfer_" + std::to_string(t)).c_str())); + } + } +} + +auto NASolver::getExactNumTransfersConstraints() const -> std::vector { + std::vector constraints; + if (numTransfers.has_value() && numTransfers.value() != 0) { + constraints.reserve(numTransfers.value() + 1); + for (uint16_t t = 1; t < numTransfers.value(); ++t) { + constraints.emplace_back(ult(transfers[t - 1], transfers[t])); + } + constraints.emplace_back( + ult(transfers[numTransfers.value() - 1], + ctx->bv_val(numStages, minBitsToRepresentUInt(numStages)))); + } + return constraints; +} + +auto NASolver::getHaveSamePositionConstraint(const uint16_t q0, + const uint16_t q1, + const uint16_t t) const -> expr { + return stages[t].getQubit(q0).getX() == stages[t].getQubit(q1).getX() && + stages[t].getQubit(q0).getY() == stages[t].getQubit(q1).getY(); +} + +auto NASolver::getHaveDifferentPositionConstraint(const uint16_t q0, + const uint16_t q1, + const uint16_t t) const + -> expr { + return !getHaveSamePositionConstraint(q0, q1, t); +} + +// NOLINTNEXTLINE (bugprone-switch-missing-default-case) +auto NASolver::getAffectedByRydbergBeamConstraint(const uint16_t q, + const uint16_t t) const + -> expr { + switch (storage) { + case Storage::None: + return ctx->bool_val(true); + case Storage::Bottom: + return ule(stages[t].getQubit(q).getY(), + ctx->bv_val(maxEntanglingY, minBitsToRepresentUInt(maxY))); + case Storage::TwoSided: + default: + return ule(minEntanglingY, stages[t].getQubit(q).getY()) && + ule(stages[t].getQubit(q).getY(), + ctx->bv_val(maxEntanglingY, minBitsToRepresentUInt(maxY))); + } +} + +auto NASolver::getShieldedFromRydbergBeamConstraint(const uint16_t q, + const uint16_t t) const + -> expr { + return !getAffectedByRydbergBeamConstraint(q, t); +} + +auto NASolver::getValidRydbergTransitionConstraints(const uint16_t t) const + -> std::vector { + if (t == numStages - 1) { + std::stringstream msg; + msg << "There is no next stage after the last stage " << t; + throw std::invalid_argument(msg.str()); + } + std::vector constraints; + constraints.reserve(3UL * numQubits); + for (uint16_t i = 0; i < numQubits; ++i) { + // For all: AOD/SLM state is preserved + constraints.emplace_back(implies(getRydbergStageConstraint(t), + stages[t].getQubit(i).getA() == + stages[t + 1].getQubit(i).getA())); + // For AOD: column and row are preserved + constraints.emplace_back(implies( + getRydbergStageConstraint(t) && stages[t].getQubit(i).getA(), + stages[t].getQubit(i).getC() == stages[t + 1].getQubit(i).getC() && + stages[t].getQubit(i).getR() == stages[t + 1].getQubit(i).getR())); + // For SLM: stay at the same position + constraints.emplace_back(implies( + getRydbergStageConstraint(t) && !stages[t].getQubit(i).getA(), + stages[t].getQubit(i).getX() == stages[t + 1].getQubit(i).getX() && + stages[t].getQubit(i).getY() == stages[t + 1].getQubit(i).getY())); + } + // we set load and store variables to false because they do not carray any + // meaning in the rydberg stage anyway + for (uint16_t i = 0; i <= static_cast(maxC); ++i) { + constraints.emplace_back( + implies(getRydbergStageConstraint(t), !stages[t].getLoadCol(i))); + constraints.emplace_back( + implies(getRydbergStageConstraint(t), !stages[t].getStoreCol(i))); + } + for (uint16_t i = 0; i <= static_cast(maxR); ++i) { + constraints.emplace_back( + implies(getRydbergStageConstraint(t), !stages[t].getLoadRow(i))); + constraints.emplace_back( + implies(getRydbergStageConstraint(t), !stages[t].getStoreRow(i))); + } + return constraints; +} + +auto NASolver::getValidTransferTransitionConstraints(const uint16_t t) const + -> std::vector { + if (t == numStages - 1) { + std::stringstream msg; + msg << "There is no next stage after the last stage " << t; + throw std::invalid_argument(msg.str()); + } + std::vector constraints; + // TODO: check all reserves for the correct capacity + constraints.reserve(3UL * numQubits); + for (uint16_t i = 0; i < numQubits; ++i) { + // For SLM and Stored: stay at the same position + constraints.emplace_back(implies( + getTransferStageConstraint(t) && !stages[t + 1].getQubit(i).getA(), + stages[t].getQubit(i).getX() == stages[t + 1].getQubit(i).getX() && + stages[t].getQubit(i).getY() == stages[t + 1].getQubit(i).getY())); + // For Stored and Loaded: Must be at zero horizontal and vertical offset + constraints.emplace_back(implies(getTransferStageConstraint(t) && + stages[t].getQubit(i).getA() != + stages[t + 1].getQubit(i).getA(), + 0 == stages[t].getQubit(i).getH() && + 0 == stages[t].getQubit(i).getV())); + // For Loaded: Entire row or column must be loaded + { + expr colClauses = ctx->bool_val(true); + for (uint16_t c = 0; c <= static_cast(maxC); ++c) { + colClauses = colClauses && + implies(stages[t].getQubit(i).getC() == + ctx->bv_val(c, minBitsToRepresentUInt(maxC)), + stages[t].getLoadCol(c)); + } + expr rowClauses = ctx->bool_val(true); + for (uint16_t r = 0; r <= static_cast(maxR); ++r) { + rowClauses = rowClauses && + implies(stages[t].getQubit(i).getR() == + ctx->bv_val(r, minBitsToRepresentUInt(maxR)), + stages[t].getLoadRow(r)); + } + constraints.emplace_back(implies( + getTransferStageConstraint(t) && !stages[t].getQubit(i).getA(), + stages[t + 1].getQubit(i).getA() == (colClauses || rowClauses))); + } + // For Stored: Entire row or column must be stored + { + expr colClauses = ctx->bool_val(true); + for (uint16_t c = 0; c <= static_cast(maxC); ++c) { + colClauses = colClauses && + implies(stages[t].getQubit(i).getC() == + ctx->bv_val(c, minBitsToRepresentUInt(maxC)), + stages[t].getStoreCol(c)); + } + expr rowClauses = ctx->bool_val(true); + for (std::uint16_t r = 0; r <= static_cast(maxR); ++r) { + rowClauses = rowClauses && + implies(stages[t].getQubit(i).getR() == + ctx->bv_val(r, minBitsToRepresentUInt(maxR)), + stages[t].getStoreRow(r)); + } + constraints.emplace_back(implies( + getTransferStageConstraint(t) && stages[t].getQubit(i).getA(), + !stages[t + 1].getQubit(i).getA() == (colClauses || rowClauses))); + } + for (std::uint16_t j = 0; j < numQubits; ++j) { + // For Loaded: Loaded atoms remain in the relative position with respect + // to other AOD/loaded atoms + constraints.emplace_back(implies( + getTransferStageConstraint(t) && stages[t + 1].getQubit(i).getA() && + stages[t + 1].getQubit(j).getA(), + (ult(stages[t].getQubit(i).getX(), stages[t].getQubit(j).getX()) || + ((stages[t].getQubit(i).getX() == stages[t].getQubit(j).getX()) && + slt(stages[t].getQubit(i).getH(), stages[t].getQubit(j).getH()))) == + ult(stages[t + 1].getQubit(i).getC(), + stages[t + 1].getQubit(j).getC()))); + constraints.emplace_back(implies( + getTransferStageConstraint(t) && stages[t + 1].getQubit(i).getA() && + stages[t + 1].getQubit(j).getA(), + (ult(stages[t].getQubit(i).getY(), stages[t].getQubit(j).getY()) || + ((stages[t].getQubit(i).getY() == stages[t].getQubit(j).getY()) && + slt(stages[t].getQubit(i).getV(), stages[t].getQubit(j).getV()))) == + ult(stages[t + 1].getQubit(i).getR(), + stages[t + 1].getQubit(j).getR()))); + } + } + return constraints; +} + +auto NASolver::getCircuitExecutionConstraints( + const std::vector>& ops, + const bool mindOpsOrder, const bool shieldIdleAtoms) -> std::vector { + const auto numGates = ops.size(); + std::unordered_map, std::vector, + qc::PairHash> + pairToGates; + gates.clear(); + gates.reserve(numGates); + std::vector> gatesForQubit(numQubits); + for (std::uint16_t i = 0; static_cast(i) < numGates; ++i) { + gates.emplace_back(ctx->bv_const(("gate_" + std::to_string(i)).c_str(), + minBitsToRepresentUInt(numStages))); + const auto key = std::make_pair(std::min(ops[i].first, ops[i].second), + std::max(ops[i].first, ops[i].second)); + if (pairToGates.find(key) == pairToGates.end()) { + pairToGates.emplace(key, std::vector()); + } + pairToGates.at(key).emplace_back(gates.back()); + gatesForQubit[ops[i].first].emplace_back(gates.back()); + gatesForQubit[ops[i].second].emplace_back(gates.back()); + } + std::vector constraints; + if (mindOpsOrder) { + std::vector> lastGateOnQubit(numQubits, + std::nullopt); + for (std::uint16_t i = 0; i < numGates; ++i) { + const auto& gate = gates[i]; + // if (!lastGateOnQubit[ops[i].first].has_value() && + // !lastGateOnQubit[ops[i]. + // second].has_value()) { + // constraints.emplace_back( + // 0 <= gate); + // } + if (lastGateOnQubit[ops[i].first].has_value()) { + constraints.emplace_back( + // NOLINTNEXTLINE (bugprone-unchecked-optional-access) + ult(lastGateOnQubit[ops[i].first].value(), gate)); + } + if (lastGateOnQubit[ops[i].second].has_value()) { + constraints.emplace_back( + // NOLINTNEXTLINE (bugprone-unchecked-optional-access) + ult(lastGateOnQubit[ops[i].second].value(), gate)); + } + lastGateOnQubit[ops[i].first].emplace(gate); + lastGateOnQubit[ops[i].second].emplace(gate); + } + std::unordered_set lastGates; + for (const auto& lastGate : lastGateOnQubit) { + if (lastGate.has_value()) { + lastGates.emplace(lastGate.value()); + } + } + for (const auto& lastGate : lastGates) { + constraints.emplace_back(ult(lastGate, numStages)); + } + constraints.reserve((3 * numGates) + + (numStages * numQubits * (numQubits - 1) / 2)); + } else { + constraints.reserve((numGates * (numGates - 1) / 2) + 4 * numGates + + (numStages * numQubits * (numQubits - 1) / 2)); + for (std::uint16_t g = 0; static_cast(g) < numGates; ++g) { + constraints.emplace_back( + // 0 <= gates[g] && + ult(gates[g], numStages)); + for (std::uint16_t h = g + 1; static_cast(h) < numGates; ++h) { + if (ops[g].first == ops[h].first || ops[g].first == ops[h].second || + ops[g].second == ops[h].first || ops[g].second == ops[h].second) { + constraints.emplace_back(gates[g] != gates[h]); + } + } + } + } + for (std::uint16_t t = 0; t < numStages; ++t) { + for (std::uint16_t i = 0; i < numQubits; ++i) { + for (std::uint16_t j = i + 1; j < numQubits; ++j) { + if (const auto& it = pairToGates.find({i, j}); + it != pairToGates.end() && !it->second.empty()) { + const auto& gatesForPair = it->second; + for (const auto& gate : gatesForPair) { + auto hDiff = + stages[t].getQubit(j).getH() - stages[t].getQubit(i).getH(); + auto hDiffSign = ashr( + hDiff, + static_cast(minBitsToRepresentInt(maxHOffset)) - 1); + auto absHDiff = (hDiff ^ hDiffSign) - hDiffSign; + auto vDiff = + stages[t].getQubit(j).getV() - stages[t].getQubit(i).getV(); + auto vDiffSign = ashr( + vDiff, + static_cast(minBitsToRepresentInt(maxVOffset)) - 1); + auto absVDiff = (vDiff ^ vDiffSign) - vDiffSign; + + constraints.emplace_back(implies( + gate == ctx->bv_val(t, minBitsToRepresentUInt(numStages)), + getRydbergStageConstraint(t) && + getHaveSamePositionConstraint(i, j, t) && + getAffectedByRydbergBeamConstraint(i, t) && + getAffectedByRydbergBeamConstraint(j, t) && + ult(absHDiff, maxHDist) && + ult(absVDiff, ctx->bv_val(maxVDist, minBitsToRepresentInt( + maxVOffset))))); + } + expr premisses = getRydbergStageConstraint(t) && + getAffectedByRydbergBeamConstraint(i, t) && + getAffectedByRydbergBeamConstraint(j, t); + for (const auto& gate : gatesForPair) { + premisses = + premisses && + gate != ctx->bv_val(t, minBitsToRepresentUInt(numStages)); + } + constraints.emplace_back( + implies(premisses, getHaveDifferentPositionConstraint(i, j, t))); + } else { + constraints.emplace_back( + implies(getRydbergStageConstraint(t) && + getAffectedByRydbergBeamConstraint(i, t) && + getAffectedByRydbergBeamConstraint(j, t), + getHaveDifferentPositionConstraint(i, j, t))); + } + } + if (shieldIdleAtoms) { + if (gatesForQubit[i].empty()) { + constraints.emplace_back( + implies(getRydbergStageConstraint(t), + getShieldedFromRydbergBeamConstraint(i, t))); + } else { + expr premisses = getRydbergStageConstraint(t); + for (const auto& gate : gatesForQubit[i]) { + premisses = + premisses && + gate != ctx->bv_val(t, minBitsToRepresentUInt(numStages)); + } + constraints.emplace_back( + implies(premisses, getShieldedFromRydbergBeamConstraint(i, t))); + } + } + } + } + return constraints; +} + +auto NASolver::getRydbergStageConstraint(const std::uint16_t t) const -> expr { + return !getTransferStageConstraint(t); +} + +auto NASolver::getTransferStageConstraint(const std::uint16_t t) const -> expr { + if (numTransfers.has_value()) { + expr clauses = ctx->bool_val(false); + for (const auto& transfer : transfers) { + clauses = clauses || + transfer == ctx->bv_val(t, minBitsToRepresentUInt(numStages)); + } + return clauses; + } + return transfers[t]; +} + +auto NASolver::getValidStageConstraints(const std::uint16_t t) const + -> std::vector { + std::vector constraints; + constraints.reserve(numQubits * (5UL * numQubits + 3UL)); + for (std::uint16_t i = 0; i < numQubits; ++i) { + // 0 <= x <= maxX + constraints.emplace_back( + // ule(0, stages[t].getQubit(i). + // getX()) && + ule(stages[t].getQubit(i).getX(), + ctx->bv_val(maxX, minBitsToRepresentUInt(maxX)))); + // 0 <= y <= maxY + constraints.emplace_back( + // 0 <= stages[t].getQubit(i). + // getY() && + ule(stages[t].getQubit(i).getY(), + ctx->bv_val(maxY, minBitsToRepresentUInt(maxY)))); + // For AOD: 0 <= c <= maxC + constraints.emplace_back( + implies(stages[t].getQubit(i).getA(), + // 0 <= stages[t].getQubit(i). + // getC() && + ule(stages[t].getQubit(i).getC(), + ctx->bv_val(maxC, minBitsToRepresentUInt(maxC))))); + // For AOD: 0 <= r <= maxR + constraints.emplace_back( + implies(stages[t].getQubit(i).getA(), + // 0 <= stages[t].getQubit(i). + // getR() && + ule(stages[t].getQubit(i).getR(), + ctx->bv_val(maxR, minBitsToRepresentUInt(maxR))))); + // For AOD: - maxHOffset <= h <= maxHOffset + constraints.emplace_back( + implies(stages[t].getQubit(i).getA(), + sle(ctx->bv_val(-static_cast(maxHOffset), + minBitsToRepresentInt(maxHOffset)), + stages[t].getQubit(i).getH()) && + sle(stages[t].getQubit(i).getH(), maxHOffset))); + // For AOD: - maxVOffset <= v <= maxVOffset + constraints.emplace_back( + implies(stages[t].getQubit(i).getA(), + sle(ctx->bv_val(-static_cast(maxVOffset), + minBitsToRepresentInt(maxVOffset)), + stages[t].getQubit(i).getV()) && + sle(stages[t].getQubit(i).getV(), maxVOffset))); + // For SLM: c = 0, r = 0, h = 0, v = 0 + constraints.emplace_back( + implies(!stages[t].getQubit(i).getA(), + stages[t].getQubit(i).getC() == + ctx->bv_val(0, minBitsToRepresentUInt(maxC)) && + stages[t].getQubit(i).getR() == + ctx->bv_val(0, minBitsToRepresentUInt(maxR)) && + stages[t].getQubit(i).getH() == + ctx->bv_val(0, minBitsToRepresentInt(maxHOffset)) && + stages[t].getQubit(i).getV() == + ctx->bv_val(0, minBitsToRepresentInt(maxVOffset)))); + for (std::uint16_t j = 0; j < numQubits; ++j) { + // For AOD: (x < x' ∨ x == x' ∧ v < v') <-> r < r' + constraints.emplace_back(implies( + stages[t].getQubit(i).getA() && stages[t].getQubit(j).getA(), + (ult(stages[t].getQubit(i).getX(), stages[t].getQubit(j).getX()) || + ((stages[t].getQubit(i).getX() == stages[t].getQubit(j).getX()) && + slt(stages[t].getQubit(i).getH(), stages[t].getQubit(j).getH()))) == + ult(stages[t].getQubit(i).getC(), stages[t].getQubit(j).getC()))); + // For AOD: (y < y' ∨ y == y' ∧ h < h') <-> c < c' + constraints.emplace_back(implies( + stages[t].getQubit(i).getA() && stages[t].getQubit(j).getA(), + (ult(stages[t].getQubit(i).getY(), stages[t].getQubit(j).getY()) || + ((stages[t].getQubit(i).getY() == stages[t].getQubit(j).getY()) && + slt(stages[t].getQubit(i).getV(), stages[t].getQubit(j).getV()))) == + ult(stages[t].getQubit(i).getR(), stages[t].getQubit(j).getR()))); + } + for (std::uint16_t j = i + 1; j < numQubits; ++j) { + // For all: h == h' -> v == v' -> x != x' or y != y' + constraints.emplace_back(implies( + stages[t].getQubit(i).getH() == stages[t].getQubit(j).getH() && + stages[t].getQubit(i).getV() == stages[t].getQubit(j).getV(), + getHaveDifferentPositionConstraint(i, j, t))); + } + } + return constraints; +} + +NASolver::NASolver(const std::uint16_t newMaxX, const std::uint16_t newMaxY, + const std::uint16_t newMaxC, const std::uint16_t newMaxR, + const std::uint16_t newMaxHOffset, + const std::uint16_t newMaxVOffset, + const std::uint16_t newMaxHDist, + const std::uint16_t newMaxVDist, + const std::uint16_t newMinEntanglingY, + const std::uint16_t newMaxEntanglingY) + : ctx(std::make_shared()), maxX(newMaxX), maxY(newMaxY), + minEntanglingY(newMinEntanglingY), maxEntanglingY(newMaxEntanglingY), + maxC(newMaxC), maxR(newMaxR), maxHOffset(newMaxHOffset), + maxVOffset(newMaxVOffset), maxHDist(newMaxHDist), maxVDist(newMaxVDist) + +{ + if (minEntanglingY == 0 && maxEntanglingY < maxY) { + storage = Storage::Bottom; + } else if (minEntanglingY > 0 && maxEntanglingY < maxY) { + storage = Storage::TwoSided; + } else if (minEntanglingY == 0 && maxEntanglingY == maxY) { + storage = Storage::None; + } else { + throw std::invalid_argument("One sided storage zone is only supported " + "below the entangling zone (higher Y)."); + } +} + +auto NASolver::solve(const std::vector>& ops, + const std::uint16_t newNumQubits, + const std::uint16_t newNumStages, + const std::optional newNumTransfers, + const bool mindOpsOrder, const bool shieldIdleQubits) + -> Result { + if (shieldIdleQubits) { + if (storage == Storage::None) { + throw std::invalid_argument("No storage zone is available."); + } + } + + numQubits = newNumQubits; + numStages = newNumStages; + numTransfers = newNumTransfers; + + solver solver(*ctx, "QF_BV"); + + initVariables(); + + // Now we assert the constraints to the solver. + if (numTransfers.has_value()) { + for (const auto& c : getExactNumTransfersConstraints()) { + solver.add(c); + } + } + for (const auto& c : + getCircuitExecutionConstraints(ops, mindOpsOrder, shieldIdleQubits)) { + solver.add(c); + } + for (std::uint16_t t = 0; t < numStages; ++t) { + for (const auto& c : getValidStageConstraints(t)) { + solver.add(c); + } + if (t < numStages - 1) { + for (const auto& c : getValidRydbergTransitionConstraints(t)) { + solver.add(c); + } + for (const auto& c : getValidTransferTransitionConstraints(t)) { + solver.add(c); + } + } + } + + // Check satisfiability + if (solver.check() == unsat) { + return Result{false, {}}; + } + const auto model = solver.get_model(); + std::uint16_t nTrans = 0; + std::vector resultStages; + resultStages.reserve(numStages); + for (const auto& stage : stages) { + const bool rydberg = + numTransfers.has_value() + ? (nTrans == numTransfers || + model.eval(transfers[nTrans]).as_uint64() != stage.getT()) + : model.eval(transfers[stage.getT()]).is_false(); + if (numTransfers.has_value() && !rydberg) { + ++nTrans; + } + std::vector resultQubits; + resultQubits.reserve(numQubits); + for (std::uint16_t i = 0; i < numQubits; ++i) { + resultQubits.emplace_back( + {model.eval(stage.getQubit(i).getX()).get_numeral_uint(), + model.eval(stage.getQubit(i).getY()).get_numeral_uint(), + model.eval(stage.getQubit(i).getA()).is_true(), + model.eval(stage.getQubit(i).getC()).get_numeral_uint(), + model.eval(stage.getQubit(i).getR()).get_numeral_uint(), + model.eval(bv2int(stage.getQubit(i).getH(), true)).get_numeral_int(), + model.eval(bv2int(stage.getQubit(i).getV(), true)) + .get_numeral_int()}); + } + std::vector resultGates; + for (std::uint16_t i = 0; i < static_cast(gates.size()); + ++i) { + if (model.eval(gates[i]).as_uint64() == stage.getT()) { + resultGates.emplace_back({stage.getT(), ops[i]}); + } + } + resultStages.emplace_back( + {rydberg, resultQubits, resultGates}); + } + return Result{true, resultStages}; +} + +/// Initialize a Qubit from a YAML string. +// NOLINTNEXTLINE (misc-include-cleaner) +auto NASolver::Result::Qubit::fromYAML(const YAML::Node& yaml) -> Qubit { + Qubit qubit{}; + qubit.a = yaml["a"].as(); + qubit.c = yaml["c"].as(); + qubit.h = yaml["h"].as(); + qubit.r = yaml["r"].as(); + qubit.v = yaml["v"].as(); + qubit.x = yaml["x"].as(); + qubit.y = yaml["y"].as(); + return qubit; +} + +auto NASolver::Result::Qubit::yaml(std::size_t indent, const bool item, + const bool compact) const -> std::string { + std::stringstream ss; + ss << std::boolalpha; + ss << std::string(indent, ' '); + if (item) { + ss << "- "; + indent += 2; + } + if (compact) { + ss << "{x: " << x << ", y: " << y << ", a: " << a << ", c: " << c + << ", r: " << r << ", h: " << h << ", v: " << v << "}\n"; + return ss.str(); + } + ss << "x: " << x << "\n"; + ss << std::string(indent, ' ') << "y: " << y << "\n"; + ss << std::string(indent, ' ') << "a: " << a << "\n"; + ss << std::string(indent, ' ') << "c: " << c << "\n"; + ss << std::string(indent, ' ') << "r: " << r << "\n"; + ss << std::string(indent, ' ') << "h: " << h << "\n"; + ss << std::string(indent, ' ') << "v: " << v << "\n"; + return ss.str(); +} +auto NASolver::Result::Qubit::operator==(const Qubit& other) const -> bool { + return x == other.x && y == other.y && a == other.a && c == other.c && + r == other.r && h == other.h && v == other.v; +} + +auto NASolver::Result::Stage::yaml(std::size_t indent, const bool item, + const bool compact) const -> std::string { + std::stringstream ss; + ss << std::boolalpha; + ss << std::string(indent, ' '); + if (item) { + ss << "- "; + indent += 2; + } + ss << "rydberg: " << rydberg << "\n"; + ss << std::string(indent, ' ') << "gates:\n"; + for (const auto& gate : gates) { + ss << gate.yaml(indent + 2, true, compact); + } + ss << std::string(indent, ' ') << "qubits:\n"; + for (const auto& qubit : qubits) { + ss << qubit.yaml(indent + 2, true, compact); + } + return ss.str(); +} +auto NASolver::Result::Stage::operator==(const Stage& other) const -> bool { + return rydberg == other.rydberg && gates == other.gates && + qubits == other.qubits; +} + +auto NASolver::Result::fromYAML(const YAML::Node& yaml) -> Result { + Result result{}; + result.sat = yaml["sat"].as(); + if (result.sat) { + for (const auto& stage : yaml["stages"]) { + result.stages.emplace_back(Stage::fromYAML(stage)); + } + } + return result; +} + +auto NASolver::Result::Gate::fromYAML(const YAML::Node& yaml) -> Gate { + Gate gate{}; + gate.qubits.first = yaml["qubits"][0].as(); + gate.qubits.second = yaml["qubits"][1].as(); + return gate; +} + +auto NASolver::Result::Gate::yaml(std::size_t indent, const bool item, + const bool compact) const -> std::string { + std::stringstream ss; + ss << std::string(indent, ' '); + if (item) { + ss << "- "; + indent += 2; + } + if (compact) { + ss << "qubits: [" << qubits.first << ", " << qubits.second << "]\n"; + return ss.str(); + } + ss << "qubits:\n"; + ss << std::string(indent, ' ') << "- " << qubits.first << "\n"; + ss << std::string(indent, ' ') << "- " << qubits.second << "\n"; + return ss.str(); +} +auto NASolver::Result::Gate::operator==(const Gate& other) const -> bool { + return qubits == other.qubits; +} + +auto NASolver::Result::Stage::fromYAML(const YAML::Node& yaml) -> Stage { + Stage stage{}; + stage.rydberg = yaml["rydberg"].as(); + for (const auto& gate : yaml["gates"]) { + stage.gates.emplace_back(Gate::fromYAML(gate)); + } + for (const auto& qubit : yaml["qubits"]) { + stage.qubits.emplace_back(Qubit::fromYAML(qubit)); + } + return stage; +} + +auto NASolver::Result::yaml(const std::size_t indent, const bool compact) const + -> std::string { + std::stringstream ss; + ss << std::boolalpha; + ss << std::string(indent, ' ') << "sat: " << sat << "\n"; + if (sat) { + ss << std::string(indent, ' ') << "stages:\n"; + for (const auto& stage : stages) { + ss << stage.yaml(indent + 2, true, compact); + } + } + return ss.str(); +} +auto NASolver::Result::operator==(const Result& other) const -> bool { + return sat == other.sat && stages == other.stages; +} +} // namespace na diff --git a/src/na/nasp/SolverFactory.cpp b/src/na/nasp/SolverFactory.cpp new file mode 100644 index 000000000..d3108fe88 --- /dev/null +++ b/src/na/nasp/SolverFactory.cpp @@ -0,0 +1,115 @@ +#include "na/nasp/SolverFactory.hpp" + +#include "circuit_optimizer/CircuitOptimizer.hpp" +#include "ir/QuantumComputation.hpp" +#include "ir/operations/OpType.hpp" +#include "na/Architecture.hpp" +#include "na/NADefinitions.hpp" +#include "na/nasp/Solver.hpp" + +#include +#include +#include +#include +#include +#include +#include + +namespace na { +auto SolverFactory::create(const Architecture& arch) -> NASolver { + const auto interactionZone = + *arch.getPropertiesOfOperation({qc::Z, 1}).zones.cbegin(); + const auto maxX = + static_cast(arch.getNColsInZone(interactionZone) - 1); + const auto maxEntanglingY = + static_cast(arch.getNrowsInZone(interactionZone) - 1); + const auto maxC = + static_cast(arch.getPropertiesOfShuttlingUnit(0).cols); + const auto maxR = + static_cast(arch.getPropertiesOfShuttlingUnit(0).rows); + const auto storageZone = arch.getInitialZones().front(); + const auto maxY = + static_cast(maxEntanglingY + arch.getNrowsInZone(storageZone)); + // the atoms are located, e.g., in the following manner: + // 0 <-- SLM 0 <-- SLM + // o o o <-- AOD OR o o o o <-- AOD + // o o o <-- AOD o o o o <-- AOD + // The max number of columns and rows that can be stacked at one interaction + // site is derived from the fact that the SLM atom must still be in the + // Rydberg interaction radius of the lower left AOD atom. For that we first + // assume the same value for the vertical and horizontal stacking factor + // and solve the resulting quadratic equation. For the solution we take the + // flooring of the positive solution. If the equation allows the horizontal + // stacking factor to be one larger, we take that value. + const auto minAtomDistance = arch.getMinAtomDistance(); + const auto interactionRadius = arch.getInteractionRadius(); + const auto minAtomDistanceSquared = std::pow(minAtomDistance, 2); + const auto interactionRadiusSquared = std::pow(interactionRadius, 2); + const auto maxVDist = static_cast( + std::min(std::floor(.2 + std::sqrt((.8 * interactionRadiusSquared / + minAtomDistanceSquared) - + .16)), + std::floor((0.7071067811865475244 * + static_cast(interactionRadius) / + static_cast(minAtomDistance)) + + 1.))); + const auto maxHDist = + std::pow(maxVDist * minAtomDistance, 2) + + std::pow(maxVDist / 2 * minAtomDistance, 2) <= + interactionRadiusSquared && + std::pow((maxVDist - 1) * minAtomDistance, 2) + + std::pow(maxVDist * minAtomDistance, 2) <= + interactionRadiusSquared + ? static_cast(maxVDist + 1) + : maxVDist; + const auto noInteractionRadius = arch.getNoInteractionRadius(); + const auto firstSite = arch.getSitesInZone(interactionZone)[0]; + const auto siteRight = + arch.getNearestSiteRight(arch.getPositionOfSite(firstSite), true, true); + const auto siteBelow = + arch.getNearestSiteDown(arch.getPositionOfSite(firstSite), true, true); + if (!siteRight || !siteBelow) { + throw std::invalid_argument( + "Unexpected architecture: There is no site to the right or below the " + "first site in the interaction zone."); + } + const auto maxHOffset = static_cast( + (arch.getPositionOfSite(*siteRight) - arch.getPositionOfSite(firstSite)) + .length() - + (noInteractionRadius / 2 / minAtomDistance)); + const auto maxVOffset = static_cast( + (arch.getPositionOfSite(*siteBelow) - arch.getPositionOfSite(firstSite)) + .length() - + (noInteractionRadius / 2 / minAtomDistance)); + return {maxX, maxY, maxC, maxR, maxHOffset, + maxVOffset, maxHDist, maxVDist, 0, maxEntanglingY}; +} + +auto SolverFactory::getOpsForSolver(const qc::QuantumComputation& circ, + const FullOpType opType, const bool quiet) + -> std::vector> { + auto flattened = circ; + qc::CircuitOptimizer::flattenOperations(flattened); + std::vector> ops; + ops.reserve(flattened.size()); + for (const auto& op : flattened) { + if (op->getType() == opType.type && + op->getNcontrols() == opType.nControls) { + const auto& operands = op->getUsedQubits(); + if (operands.size() != 2) { + std::stringstream ss; + ss << "Operation " << op->getName() << " does not have two operands."; + throw std::invalid_argument(ss.str()); + } + ops.emplace_back(*operands.cbegin(), *operands.rbegin()); + } else if (!quiet) { + std::stringstream ss; + ss << "Operation " << op->getName() << " is not of type " << opType.type + << " or does not have " << opType.nControls << " controls."; + throw std::invalid_argument(ss.str()); + } + } + return ops; +} + +} // namespace na diff --git a/test/na/CMakeLists.txt b/test/na/CMakeLists.txt index 574b9d102..b1267c938 100644 --- a/test/na/CMakeLists.txt +++ b/test/na/CMakeLists.txt @@ -1,4 +1,7 @@ if(TARGET MQT::QMapNA) - file(GLOB_RECURSE NA_TEST_SOURCES *.cpp) + file(GLOB NA_TEST_SOURCES *.cpp) package_add_test(mqt-qmap-na-test MQT::QMapNA ${NA_TEST_SOURCES}) endif() + +add_subdirectory(nalac) +add_subdirectory(nasp) diff --git a/test/na/nalac/CMakeLists.txt b/test/na/nalac/CMakeLists.txt new file mode 100644 index 000000000..d6414b0ab --- /dev/null +++ b/test/na/nalac/CMakeLists.txt @@ -0,0 +1,4 @@ +if(TARGET MQT::NALAC) + file(GLOB NA_TEST_SOURCES *.cpp) + package_add_test(mqt-qmap-nalac-test MQT::NALAC ${NA_TEST_SOURCES}) +endif() diff --git a/test/na/test_nagraphalgorithms.cpp b/test/na/nalac/test_nagraphalgorithms.cpp similarity index 97% rename from test/na/test_nagraphalgorithms.cpp rename to test/na/nalac/test_nagraphalgorithms.cpp index 55bbee33c..fa1e182e2 100644 --- a/test/na/test_nagraphalgorithms.cpp +++ b/test/na/nalac/test_nagraphalgorithms.cpp @@ -1,17 +1,12 @@ -// -// 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 "Definitions.hpp" #include "datastructures/Layer.hpp" #include "ir/QuantumComputation.hpp" #include "ir/operations/OpType.hpp" -#include "na/NAGraphAlgorithms.hpp" +#include "na/nalac/NAGraphAlgorithms.hpp" -#include "gtest/gtest.h" #include #include +#include #include #include #include diff --git a/test/na/test_namapper.cpp b/test/na/nalac/test_namapper.cpp similarity index 89% rename from test/na/test_namapper.cpp rename to test/na/nalac/test_namapper.cpp index 344710bf8..63474d68d 100644 --- a/test/na/test_namapper.cpp +++ b/test/na/nalac/test_namapper.cpp @@ -1,8 +1,3 @@ -// -// 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 "Definitions.hpp" #include "datastructures/Layer.hpp" #include "ir/QuantumComputation.hpp" @@ -12,7 +7,8 @@ #include "na/Architecture.hpp" #include "na/Configuration.hpp" #include "na/NAComputation.hpp" -#include "na/NAMapper.hpp" +#include "na/NADefinitions.hpp" +#include "na/nalac/NAMapper.hpp" #include "na/operations/NAGlobalOperation.hpp" #include "na/operations/NALocalOperation.hpp" #include "na/operations/NAShuttlingOperation.hpp" @@ -29,81 +25,6 @@ #include namespace na { -auto validateAODConstraints(const NAComputation& comp) -> bool { - std::size_t counter = 1; // the first operation is `init at ...;` - for (const auto& naOp : comp) { - ++counter; - 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) { - std::cout << "Error in op number " << counter - << " (two start points identical)\n"; - return false; - } - if (*e1 == *e2) { - std::cout << "Error in op number " << counter - << " (two end points identical)\n"; - return false; - } - if (s1->x == s2->x && e1->x != e2->x) { - std::cout << "Error in op number " << counter - << " (columns not preserved)\n"; - return false; - } - if (s1->y == s2->y && e1->y != e2->y) { - std::cout << "Error in op number " << counter - << " (rows not preserved)\n"; - return false; - } - if (s1->x < s2->x && e1->x >= e2->x) { - std::cout << "Error in op number " << counter - << " (column order not preserved)\n"; - return false; - } - if (s1->y < s2->y && e1->y >= e2->y) { - std::cout << "Error in op number " << counter - << " (row order not preserved)\n"; - return false; - } - if (s1->x > s2->x && e1->x <= e2->x) { - std::cout << "Error in op number " << counter - << " (column order not preserved)\n"; - return false; - } - if (s1->y > s2->y && e1->y <= e2->y) { - std::cout << "Error in op number " << counter - << " (row order not preserved)\n"; - 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) { - std::cout << "Error in op number " << counter - << " (identical positions)\n"; - return false; - } - } - } - } - } - return true; -} auto retrieveQuantumComputation(const NAComputation& nac, const Architecture& arch) @@ -757,7 +678,7 @@ rz(3.9927041) q[7];)"; 1, 1, na::NAMappingMethod::MaximizeParallelismHeuristic)); mapper.map(circ); const auto& result = mapper.getResult(); - EXPECT_TRUE(na::validateAODConstraints(result)); + EXPECT_TRUE(result.validateAODConstraints()); EXPECT_TRUE(na::checkEquivalence(circ, result, arch)); std::ignore = mapper.getStats(); // --------------------------------------------------------------------- @@ -766,13 +687,13 @@ rz(3.9927041) q[7];)"; 3, 3, na::NAMappingMethod::MaximizeParallelismHeuristic)); mapper2.map(circ); const auto& result2 = mapper2.getResult(); - EXPECT_TRUE(na::validateAODConstraints(result2)); + EXPECT_TRUE(result2.validateAODConstraints()); // --------------------------------------------------------------------- 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(result3.validateAODConstraints()); EXPECT_TRUE(na::checkEquivalence(circ, result3, arch)); // --------------------------------------------------------------------- } @@ -1010,7 +931,7 @@ ry(2.2154814) q;)"; 3, 2, na::NAMappingMethod::MaximizeParallelismHeuristic)); mapper.map(circ); std::ignore = mapper.getStats(); - EXPECT_TRUE(na::validateAODConstraints(mapper.getResult())); + EXPECT_TRUE(mapper.getResult().validateAODConstraints()); } TEST(NAMapper, QAOA16NarrowEntangling) { @@ -1246,5 +1167,5 @@ ry(2.2154814) q;)"; 3, 2, na::NAMappingMethod::MaximizeParallelismHeuristic)); mapper.map(circ); std::ignore = mapper.getStats(); - EXPECT_TRUE(na::validateAODConstraints(mapper.getResult())); + EXPECT_TRUE(mapper.getResult().validateAODConstraints()); } diff --git a/test/na/nasp/CMakeLists.txt b/test/na/nasp/CMakeLists.txt new file mode 100644 index 000000000..3e768ab57 --- /dev/null +++ b/test/na/nasp/CMakeLists.txt @@ -0,0 +1,6 @@ +if(TARGET MQT::NASP) + file(GLOB NA_TEST_SOURCES *.cpp) + package_add_test(mqt-qmap-nasp-test MQT::NASP ${NA_TEST_SOURCES}) + target_compile_definitions(mqt-qmap-nasp-test + PRIVATE TEST_CIRCUITS_PATH="${CMAKE_CURRENT_SOURCE_DIR}/circuits") +endif() diff --git a/test/na/nasp/circuits/steane.qasm b/test/na/nasp/circuits/steane.qasm new file mode 100644 index 000000000..03089b389 --- /dev/null +++ b/test/na/nasp/circuits/steane.qasm @@ -0,0 +1,17 @@ +OPENQASM 2.0; +include "qelib1.inc"; +qreg q[7]; +h q; +cz q[0],q[3]; +cz q[0],q[4]; +cz q[1],q[2]; +cz q[1],q[5]; +cz q[1],q[6]; +cz q[2],q[3]; +cz q[2],q[4]; +cz q[3],q[5]; +cz q[4],q[6]; +h q[0]; +h q[2]; +h q[5]; +h q[6]; diff --git a/test/na/nasp/test_codegenerator.cpp b/test/na/nasp/test_codegenerator.cpp new file mode 100644 index 000000000..31c364c45 --- /dev/null +++ b/test/na/nasp/test_codegenerator.cpp @@ -0,0 +1,36 @@ +#include "ir/QuantumComputation.hpp" +#include "ir/operations/OpType.hpp" +#include "na/NAComputation.hpp" +#include "na/nasp/CodeGenerator.hpp" +#include "na/nasp/Solver.hpp" +#include "na/nasp/SolverFactory.hpp" + +#include +#include +#include + +TEST(CodeGenerator, Generate) { + const auto& circ = qc::QuantumComputation(TEST_CIRCUITS_PATH "/steane.qasm"); + // initialize a solver with the following parameters + // - 3 interaction sites in the horizontal direction + // - 7 interaction sites in the vertical direction + // - 2 AOD columns + // - 3 AOD rows + // - 5 rows and columns in every interaction site which corresponds to a + // maximum offset of 2 in both directions + // - qubits can interact with directly or diagonally adjacent qubits, which + // corresponds to a maximum distance of 2 in both directions + // - the entangling zone starts at y = 2 and ends at y = 4 which implies a + // storage zone at the top and at the bottom + na::NASolver solver(3, 7, 2, 3, 2, 2, 2, 2, 2, 4); + // get operations for solver + const auto& pairs = + na::SolverFactory::getOpsForSolver(circ, {qc::Z, 1}, true); + // solve + const auto result = + solver.solve(pairs, static_cast(circ.getNqubits()), 4, + std::nullopt, false, true); + const auto& comp = na::CodeGenerator::generate(circ, result, 2, 2, 2, 4); + const auto valid = comp.validateAODConstraints(); + EXPECT_TRUE(valid); +} diff --git a/test/na/nasp/test_solver.cpp b/test/na/nasp/test_solver.cpp new file mode 100644 index 000000000..f0cbb6a70 --- /dev/null +++ b/test/na/nasp/test_solver.cpp @@ -0,0 +1,135 @@ +#include "ir/QuantumComputation.hpp" +#include "ir/operations/OpType.hpp" +#include "na/nasp/Solver.hpp" +#include "na/nasp/SolverFactory.hpp" + +#include +#include +#include +#include +#include +#include +#include + +TEST(Solver, SteaneDoubleSidedStorage) { + const auto& circ = qc::QuantumComputation(TEST_CIRCUITS_PATH "/steane.qasm"); + // create solver + na::NASolver solver(3, 7, 2, 3, 2, 2, 2, 2, 2, 4); + // get operations for solver + const auto& pairs = + na::SolverFactory::getOpsForSolver(circ, {qc::Z, 1}, true); + // solve + const auto result = + solver.solve(pairs, static_cast(circ.getNqubits()), 4, + std::nullopt, false, true); + EXPECT_TRUE(result.sat); + EXPECT_EQ(result.stages.size(), 4); +} + +TEST(Solver, SteaneBottomStorage) { + const auto& circ = qc::QuantumComputation(TEST_CIRCUITS_PATH "/steane.qasm"); + // create solver + na::NASolver solver(3, 7, 2, 3, 2, 2, 2, 2, 0, 4); + // get operations for solver + const auto& pairs = + na::SolverFactory::getOpsForSolver(circ, {qc::Z, 1}, true); + // solve + const auto resultUnsat = + solver.solve(pairs, static_cast(circ.getNqubits()), 4, + std::nullopt, false, true); + EXPECT_FALSE(resultUnsat.sat); + const auto resultSat = + solver.solve(pairs, static_cast(circ.getNqubits()), 5, + std::nullopt, false, true); + EXPECT_TRUE(resultSat.sat); + EXPECT_TRUE(resultSat.stages.front().rydberg); + for (const auto& q : resultSat.stages.front().qubits) { + EXPECT_GE(q.x, 0); + EXPECT_LE(q.x, 3); + EXPECT_GE(q.y, 0); + EXPECT_LE(q.y, 7); + EXPECT_GE(q.c, 0); + EXPECT_LE(q.c, 2); + EXPECT_GE(q.r, 0); + EXPECT_LE(q.r, 3); + EXPECT_GE(q.h, -2); + EXPECT_LE(q.h, 2); + EXPECT_GE(q.v, -2); + EXPECT_LE(q.v, 2); + EXPECT_NO_THROW(std::ignore = q.a); + } + for (const auto& g : resultSat.stages.front().gates) { + EXPECT_TRUE(std::find(pairs.cbegin(), pairs.cend(), g.qubits) != + pairs.cend()); + } +} + +TEST(Solver, NoShieldingFixedOrder) { + const auto& circ = qc::QuantumComputation(TEST_CIRCUITS_PATH "/steane.qasm"); + // create solver + na::NASolver solver(3, 7, 2, 3, 2, 2, 2, 2, 0, 7); + // get operations for solver + const auto& pairs = + na::SolverFactory::getOpsForSolver(circ, {qc::Z, 1}, true); + // solve + const auto result = + solver.solve(pairs, static_cast(circ.getNqubits()), 3, + std::nullopt, false, false); + EXPECT_TRUE(result.sat); +} + +TEST(Solver, FixedTransfer) { + const auto& circ = qc::QuantumComputation(TEST_CIRCUITS_PATH "/steane.qasm"); + // create solver + na::NASolver solver(3, 7, 2, 3, 2, 2, 2, 2, 2, 4); + // get operations for solver + const auto& pairs = + na::SolverFactory::getOpsForSolver(circ, {qc::Z, 1}, true); + // solve + const auto result = solver.solve( + pairs, static_cast(circ.getNqubits()), 5, 2, false, true); + EXPECT_TRUE(result.sat); +} + +TEST(Solver, Unsat) { + const auto& circ = qc::QuantumComputation(TEST_CIRCUITS_PATH "/steane.qasm"); + // create solver + na::NASolver solver(3, 7, 2, 3, 2, 2, 2, 2, 2, 4); + // get operations for solver + const auto& pairs = + na::SolverFactory::getOpsForSolver(circ, {qc::Z, 1}, true); + // solve + const auto result = + solver.solve(pairs, static_cast(circ.getNqubits()), 3, + std::nullopt, false, true); + EXPECT_FALSE(result.sat); +} + +TEST(Solver, Exceptions) { + // One sided storage zone is only supported below the entangling zone (higher + // Y), i.e., minEntanglingY must be 0 or maxEntanglingY must less than maxY. + EXPECT_THROW(std::ignore = na::NASolver(3, 7, 2, 3, 2, 2, 2, 2, 2, 7), + std::invalid_argument); + na::NASolver solver(3, 7, 2, 3, 2, 2, 2, 2, 0, 7); + // In order to shield qubits, there must be a storage zone, i.e., + // maxEntanglingY must be less than maxY. + EXPECT_THROW(std::ignore = + solver.solve({{0, 1}}, 3, 1, std::nullopt, false, true), + std::invalid_argument); +} + +TEST(Solver, YAMLRoundTrip) { + const auto& circ = qc::QuantumComputation(TEST_CIRCUITS_PATH "/steane.qasm"); + // create solver + na::NASolver solver(3, 7, 2, 3, 2, 2, 2, 2, 2, 4); + // get operations for solver + const auto& pairs = + na::SolverFactory::getOpsForSolver(circ, {qc::Z, 1}, true); + // solve + const auto result = + solver.solve(pairs, static_cast(circ.getNqubits()), 4, + std::nullopt, false, true); + const auto resultRT = na::NASolver::Result::fromYAML( + YAML::Load(result.yaml())); // Round-Tripped result + EXPECT_EQ(resultRT, result); +} diff --git a/test/na/nasp/test_solverfactory.cpp b/test/na/nasp/test_solverfactory.cpp new file mode 100644 index 000000000..7944a4111 --- /dev/null +++ b/test/na/nasp/test_solverfactory.cpp @@ -0,0 +1,300 @@ +#include "ir/QuantumComputation.hpp" +#include "ir/operations/OpType.hpp" +#include "na/Architecture.hpp" +#include "na/nasp/SolverFactory.hpp" + +#include +#include +#include +#include +#include +#include +#include + +TEST(SolverFactory, Create) { + na::Architecture arch; + // write content to a file + std::istringstream archIS(R"({ + "name": "Nature", + "initialZones": [ + "storage" + ], + "zones": [ + { + "name": "entangling", + "xmin": -300, + "xmax": 656, + "ymin": -20, + "ymax": 46, + "fidelity": 0.9959 + }, + { + "name": "storage", + "xmin": -300, + "xmax": 656, + "ymin": 47, + "ymax": 121, + "fidelity": 1 + }, + { + "name": "readout", + "xmin": -300, + "xmax": 656, + "ymin": 122, + "ymax": 156, + "fidelity": 0.99 + } + ], + "operations": [ + { + "name": "rz", + "type": "local", + "zones": [ + "entangling", + "storage", + "readout" + ], + "time": 0.5, + "fidelity": 0.999 + }, + { + "name": "ry", + "type": "global", + "zones": [ + "entangling", + "storage", + "readout" + ], + "time": 0.5, + "fidelity": 0.999 + }, + { + "name": "cz", + "type": "global", + "zones": [ + "entangling" + ], + "time": 0.2, + "fidelity": 0.9959 + }, + { + "name": "measure", + "type": "global", + "zones": [ + "readout" + ], + "time": 0.2, + "fidelity": 0.95 + } + ], + "decoherence": { + "t1": 100000000, + "t2": 1500000 + }, + "interactionRadius": 2, + "noInteractionRadius": 5, + "minAtomDistance": 1, + "shuttling": [ + { + "rows": 5, + "columns": 5, + "xmin": -2.5, + "xmax": 2.5, + "ymin": -2.5, + "ymax": 2.5, + "move": { + "speed": 0.55, + "fidelity": 1 + }, + "load": { + "time": 20, + "fidelity": 1 + }, + "store": { + "time": 20, + "fidelity": 1 + } + } + ] +})"); + std::stringstream gridSS; + gridSS << "x,y\n"; + // entangling zone (4 x 36 = 144 sites) + for (size_t y = 0; y <= 36; y += 12) { + for (size_t x = 3; x <= 353; x += 10) { + gridSS << x << "," << y << "\n"; + } + } + // storage zone (12 x 72 = 864 sites) + for (size_t y = 56; y <= 111; y += 5) { + for (size_t x = 0; x <= 355; x += 5) { + gridSS << x << "," << y << "\n"; + } + } + // readout zone (4 x 72 = 288 sites) + for (size_t y = 131; y <= 146; y += 5) { + for (size_t x = 0; x <= 355; x += 5) { + gridSS << x << "," << y << "\n"; + } + } + // total: 1296 sites + arch.fromFileStream(archIS, gridSS); + // create solver + auto solver = na::SolverFactory::create(arch); + const auto& circ = qc::QuantumComputation(TEST_CIRCUITS_PATH "/steane.qasm"); + // get operations for solver + const auto& pairs = + na::SolverFactory::getOpsForSolver(circ, {qc::Z, 1}, true); + // solve + const auto result = + solver.solve(pairs, static_cast(circ.getNqubits()), 5, + std::nullopt, false, true); + EXPECT_TRUE(result.sat); +} + +TEST(SolverFactory, CreateExceptions) { + na::Architecture arch; + // write content to a file + std::istringstream archIS(R"({ + "name": "Nature", + "initialZones": [ + "storage" + ], + "zones": [ + { + "name": "entangling", + "xmin": -300, + "xmax": 656, + "ymin": -20, + "ymax": 46, + "fidelity": 0.9959 + }, + { + "name": "storage", + "xmin": -300, + "xmax": 656, + "ymin": 47, + "ymax": 121, + "fidelity": 1 + }, + { + "name": "readout", + "xmin": -300, + "xmax": 656, + "ymin": 122, + "ymax": 156, + "fidelity": 0.99 + } + ], + "operations": [ + { + "name": "rz", + "type": "local", + "zones": [ + "entangling", + "storage", + "readout" + ], + "time": 0.5, + "fidelity": 0.999 + }, + { + "name": "ry", + "type": "global", + "zones": [ + "entangling", + "storage", + "readout" + ], + "time": 0.5, + "fidelity": 0.999 + }, + { + "name": "cz", + "type": "global", + "zones": [ + "entangling" + ], + "time": 0.2, + "fidelity": 0.9959 + }, + { + "name": "measure", + "type": "global", + "zones": [ + "readout" + ], + "time": 0.2, + "fidelity": 0.95 + } + ], + "decoherence": { + "t1": 100000000, + "t2": 1500000 + }, + "interactionRadius": 2, + "noInteractionRadius": 5, + "minAtomDistance": 1, + "shuttling": [ + { + "rows": 5, + "columns": 5, + "xmin": -2.5, + "xmax": 2.5, + "ymin": -2.5, + "ymax": 2.5, + "move": { + "speed": 0.55, + "fidelity": 1 + }, + "load": { + "time": 20, + "fidelity": 1 + }, + "store": { + "time": 20, + "fidelity": 1 + } + } + ] +})"); + std::stringstream gridSS; + gridSS << "x,y\n"; + // entangling zone (1 x 36 = 36 sites) + for (size_t y = 0; y <= 0; y += 12) { + for (size_t x = 3; x <= 353; x += 10) { + gridSS << x << "," << y << "\n"; + } + } + // storage zone (12 x 72 = 864 sites) + for (size_t y = 56; y <= 111; y += 5) { + for (size_t x = 0; x <= 355; x += 5) { + gridSS << x << "," << y << "\n"; + } + } + // readout zone (4 x 72 = 288 sites) + for (size_t y = 131; y <= 146; y += 5) { + for (size_t x = 0; x <= 355; x += 5) { + gridSS << x << "," << y << "\n"; + } + } + arch.fromFileStream(archIS, gridSS); + EXPECT_THROW(std::ignore = na::SolverFactory::create(arch), + std::invalid_argument); + + auto circ = qc::QuantumComputation(3); + circ.h(0); + circ.cz(0, 1); + circ.cecr(0, 1, 2); + // get operations for solver + // when the parameter quiet is false and the circuit contains an operation + // that is not of type Z and does not have 1 control, an exception is thrown + EXPECT_THROW(std::ignore = + na::SolverFactory::getOpsForSolver(circ, {qc::Z, 1}, false), + std::invalid_argument); + // At the moment the function can only handle operation types that lead to two + // operands, in this example the operation has three operands. + EXPECT_THROW(std::ignore = + na::SolverFactory::getOpsForSolver(circ, {qc::ECR, 1}, true), + std::invalid_argument); +} diff --git a/test/na/test_architecture.cpp b/test/na/test_architecture.cpp index 2d00c456a..b94ad2b2b 100644 --- a/test/na/test_architecture.cpp +++ b/test/na/test_architecture.cpp @@ -1,8 +1,3 @@ -// -// 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 "ir/operations/OpType.hpp" #include "na/Architecture.hpp" #include "na/Configuration.hpp" @@ -199,6 +194,11 @@ TEST_F(NAArchitecture, GateProperty) { std::invalid_argument); } +TEST_F(NAArchitecture, Getter) { + EXPECT_EQ(arch.getNrowsInZone(0), 4); + EXPECT_EQ(arch.getNColsInZone(0), 36); +} + TEST_F(NAArchitecture, WithConfiguration) { na::Configuration const config(2, 3); const auto modArch = arch.withConfig(config);