Repository: cda-tum/mqt-qmap (language: python)

Issue:
Clifford synthesis
Adding the functionality to synthesize clifford circuits from tableaus and unoptimized input circuits, with some structural changes.

First, Adding LogicBlocks as a dependency and the logging functionality from there.
Introduction of Cliffordresults class to contain all Tableaus and Resulting Circuits.
Introduction of Tableaus as data structure to represent Clifford circuits.

ToDos:

- [x] Check integration of Logicblocks for sneaky errors 
- [x] Check functionality of Cliffordsynthesiszer
- [x] Write Tests for new Classes
- [x] Add ability to load a tableau from a tableau string as in qiskit (https://qiskit.org/documentation/stubs/qiskit.quantum_info.Clifford.html)
- [x] Add Python bindings
🔧 Add `clang-tidy` to the project
This PR adds a project wide `clang-tidy` configuration to QMAP for ensuring the code quality of the C++ part of the project. 
Additionally, it sets up a CI job (using `cpp-linter`) that checks compliance on every push/pull request.


Discussion / hints:

Fix patch:
diff --git a/include/Architecture.hpp b/include/Architecture.hpp
index d89fa3c04..424cb373c 100644
--- a/include/Architecture.hpp
+++ b/include/Architecture.hpp
@@ -17,10 +17,10 @@
 #include <unordered_map>
 #include <vector>
 
-constexpr unsigned short GATES_OF_BIDIRECTIONAL_SWAP  = 3U;
-constexpr unsigned short GATES_OF_UNIDIRECTIONAL_SWAP = 7U;
-constexpr unsigned short GATES_OF_DIRECTION_REVERSE   = 4U;
-constexpr unsigned short GATES_OF_TELEPORTATION       = 7U;
+constexpr std::uint8_t GATES_OF_BIDIRECTIONAL_SWAP  = 3U;
+constexpr std::uint8_t GATES_OF_UNIDIRECTIONAL_SWAP = 7U;
+constexpr std::uint8_t GATES_OF_DIRECTION_REVERSE   = 4U;
+constexpr std::uint8_t GATES_OF_TELEPORTATION       = 7U;
 
 constexpr int COST_SINGLE_QUBIT_GATE = 1;
 constexpr int COST_CNOT_GATE         = 10;
@@ -66,35 +66,35 @@ class Architecture {
     [[nodiscard]] std::string getName() const { return name; }
     void setName(const std::string& propertiesName) { name = propertiesName; }
 
-    [[nodiscard]] unsigned short getNqubits() const { return nq; }
-    void                         setNqubits(unsigned short nqs) { nq = nqs; }
+    [[nodiscard]] std::uint16_t getNqubits() const { return nq; }
+    void                        setNqubits(std::uint16_t nqs) { nq = nqs; }
 
-    Property<unsigned short, Property<qc::OpType, double>>
+    Property<std::uint16_t, Property<qc::OpType, double>>
         singleQubitErrorRate{};
-    Property<unsigned short,
-             Property<unsigned short, Property<qc::OpType, double>>>
-                                          twoQubitErrorRate{};
-    Property<unsigned short, double>      readoutErrorRate{};
-    Property<unsigned short, double>      t1Time{};
-    Property<unsigned short, double>      t2Time{};
-    Property<unsigned short, double>      qubitFrequency{};
-    Property<unsigned short, std::string> calibrationDate{};
+    Property<std::uint16_t,
+             Property<std::uint16_t, Property<qc::OpType, double>>>
+                                         twoQubitErrorRate{};
+    Property<std::uint16_t, double>      readoutErrorRate{};
+    Property<std::uint16_t, double>      t1Time{};
+    Property<std::uint16_t, double>      t2Time{};
+    Property<std::uint16_t, double>      qubitFrequency{};
+    Property<std::uint16_t, std::string> calibrationDate{};
 
     // convenience functions
-    void setSingleQubitErrorRate(unsigned short     qubit,
+    void setSingleQubitErrorRate(std::uint16_t      qubit,
                                  const std::string& operation,
                                  double             errorRate) {
       singleQubitErrorRate.get(qubit).set(qc::opTypeFromString(operation),
                                           errorRate);
     }
     [[nodiscard]] double
-    getSingleQubitErrorRate(unsigned short     qubit,
+    getSingleQubitErrorRate(std::uint16_t      qubit,
                             const std::string& operation) const {
       return singleQubitErrorRate.get(qubit).get(
           qc::opTypeFromString(operation));
     }
     [[nodiscard]] double
-    getAverageSingleQubitErrorRate(const unsigned short qubit) const {
+    getAverageSingleQubitErrorRate(const std::uint16_t qubit) const {
       double avgErrorRate = 0.0;
       for (const auto& [opType, error] :
            singleQubitErrorRate.get(qubit).get()) {
@@ -104,20 +104,20 @@ class Architecture {
              static_cast<double>(singleQubitErrorRate.get(qubit).get().size());
     }
 
-    void setTwoQubitErrorRate(unsigned short qubit1, unsigned short qubit2,
+    void setTwoQubitErrorRate(std::uint16_t qubit1, std::uint16_t qubit2,
                               double             errorRate,
                               const std::string& operation = "cx") {
       twoQubitErrorRate.get(qubit1).get(qubit2).set(
           qc::opTypeFromString(operation), errorRate);
     }
     [[nodiscard]] double
-    getTwoQubitErrorRate(unsigned short qubit1, unsigned short qubit2,
+    getTwoQubitErrorRate(std::uint16_t qubit1, std::uint16_t qubit2,
                          const std::string& operation = "cx") const {
       return twoQubitErrorRate.get(qubit1).get(qubit2).get(
           qc::opTypeFromString(operation));
     }
     [[nodiscard]] bool
-    twoQubitErrorRateAvailable(unsigned short qubit1, unsigned short qubit2,
+    twoQubitErrorRateAvailable(std::uint16_t qubit1, std::uint16_t qubit2,
                                const std::string& operation = "cx") const {
       return twoQubitErrorRate.available(qubit1) &&
              twoQubitErrorRate.get(qubit1).available(qubit2) &&
@@ -149,7 +149,7 @@ class Architecture {
 
       json["name"]   = name;
       json["qubits"] = {};
-      for (unsigned short i = 0; i < nq; ++i) {
+      for (std::uint16_t i = 0U; i < nq; ++i) {
         auto& qubitProperties = json["qubits"][std::to_string(i)];
 
         if (singleQubitErrorRate.available(i)) {
@@ -196,14 +196,14 @@ class Architecture {
     [[nodiscard]] std::string toString() const { return json().dump(2); }
 
   protected:
-    std::string    name{};
-    unsigned short nq{};
+    std::string   name{};
+    std::uint16_t nq{};
   };
 
   void loadCouplingMap(std::istream& is);
   void loadCouplingMap(std::istream&& is);
   void loadCouplingMap(const std::string& filename);
-  void loadCouplingMap(unsigned short nQ, const CouplingMap& cm);
+  void loadCouplingMap(std::uint16_t nQ, const CouplingMap& cm);
   void loadCouplingMap(AvailableArchitecture architecture);
   void loadProperties(std::istream& is);
   void loadProperties(std::istream&& is);
@@ -220,12 +220,12 @@ class Architecture {
     loadProperties(props_filename);
   }
 
-  Architecture(unsigned short nQ, const CouplingMap& couplingMap);
-  Architecture(unsigned short nQ, const CouplingMap& couplingMap,
+  Architecture(std::uint16_t nQ, const CouplingMap& couplingMap);
+  Architecture(std::uint16_t nQ, const CouplingMap& couplingMap,
                const Properties& properties);
 
-  [[nodiscard]] unsigned short getNqubits() const { return nqubits; }
-  void                         setNqubits(unsigned short nQ) { nqubits = nQ; }
+  [[nodiscard]] std::uint16_t getNqubits() const { return nqubits; }
+  void                        setNqubits(std::uint16_t nQ) { nqubits = nQ; }
 
   [[nodiscard]] const std::string& getName() const { return name; }
   void setName(const std::string& architectureName) { name = architectureName; }
@@ -240,7 +240,7 @@ class Architecture {
   }
 
   CouplingMap& getCurrentTeleportations() { return current_teleportations; }
-  std::vector<std::pair<short, short>>& getTeleportationQubits() {
+  std::vector<std::pair<std::int16_t, std::int16_t>>& getTeleportationQubits() {
     return teleportationQubits;
   }
 
@@ -261,9 +261,12 @@ class Architecture {
 
   [[nodiscard]] bool bidirectional() const { return isBidirectional; }
 
-  [[nodiscard]] bool isArchitectureAvailable() {
+  [[nodiscard]] bool isArchitectureAvailable() const {
     return !(name.empty()) && nqubits != 0;
   }
+  [[nodiscard]] bool isCalibrationDataAvailable() const {
+    return !(name.empty()) && !properties.empty();
+  }
 
   void reset() {
     name    = "";
@@ -276,8 +279,8 @@ class Architecture {
     singleQubitFidelities.clear();
   }
 
-  [[nodiscard]] double distance(unsigned short control,
-                                unsigned short target) const {
+  [[nodiscard]] double distance(std::uint16_t control,
+                                std::uint16_t target) const {
     if (current_teleportations.empty()) {
       return distanceTable.at(control).at(target);
     } else {
@@ -285,8 +288,8 @@ class Architecture {
     }
   }
 
-  [[nodiscard]] std::set<unsigned short> getQubitSet() const {
-    std::set<unsigned short> result{};
+  [[nodiscard]] std::set<std::uint16_t> getQubitSet() const {
+    std::set<std::uint16_t> result{};
     for (int i = 0; i < nqubits; ++i) {
       result.insert(result.end(),
                     i); // should be constant with gcc, or at most O(nqubits)
@@ -294,16 +297,15 @@ class Architecture {
     return result;
   }
 
-  unsigned long minimumNumberOfSwaps(std::vector<unsigned short>& permutation,
-                                     long                         limit = -1);
-  void          minimumNumberOfSwaps(
-               std::vector<unsigned short>&                            permutation,
-               std::vector<std::pair<unsigned short, unsigned short>>& swaps);
+  std::uint64_t minimumNumberOfSwaps(std::vector<std::uint16_t>& permutation,
+                                     std::int64_t                limit = -1);
+  void          minimumNumberOfSwaps(std::vector<std::uint16_t>& permutation,
+                                     std::vector<Edge>&          swaps);
 
   struct Node {
-    unsigned long                                          nswaps = 0;
-    std::vector<std::pair<unsigned short, unsigned short>> swaps{};
-    std::unordered_map<unsigned short, unsigned short>     permutation{};
+    std::uint64_t                                    nswaps = 0U;
+    std::vector<Edge>                                swaps{};
+    std::unordered_map<std::uint16_t, std::uint16_t> permutation{};
 
     void print(std::ostream& out) {
       out << swaps.size() << ": ";
@@ -320,39 +322,44 @@ class Architecture {
 
   [[nodiscard]] std::size_t getCouplingLimit() const;
   [[nodiscard]] std::size_t
-  getCouplingLimit(const std::set<unsigned short>& qubitChoice) const;
-
-  void getHighestFidelityCouplingMap(unsigned short subsetSize,
-                                     CouplingMap&   couplingMap);
-  [[nodiscard]] std::vector<std::set<unsigned short>>
-       getAllConnectedSubsets(unsigned short subsetSize);
-  void getReducedCouplingMaps(unsigned short            subsetSize,
-                              std::vector<CouplingMap>& couplingMaps);
-  void getReducedCouplingMap(const std::set<unsigned short>& qubitChoice,
-                             CouplingMap&                    couplingMap);
+  getCouplingLimit(const std::set<std::uint16_t>& qubitChoice) const;
+
+  void getHighestFidelityCouplingMap(std::uint16_t subsetSize,
+                                     CouplingMap&  couplingMap) const;
+  [[nodiscard]] std::vector<QubitSubset>
+       getAllConnectedSubsets(std::uint16_t subsetSize) const;
+  void getReducedCouplingMaps(std::uint16_t             subsetSize,
+                              std::vector<CouplingMap>& couplingMaps) const;
+  void getReducedCouplingMap(const QubitSubset& qubitChoice,
+                             CouplingMap&       couplingMap) const;
   [[nodiscard]] static double
-  getAverageArchitectureFidelity(const CouplingMap&              couplingMap,
-                                 const std::set<unsigned short>& qubitChoice,
-                                 const Properties&               props);
+  getAverageArchitectureFidelity(const CouplingMap& cm,
+                                 const QubitSubset& qubitChoice,
+                                 const Properties&  props);
+
+  [[nodiscard]] static QubitSubset getQubitSet(const CouplingMap& cm);
+  [[nodiscard]] static std::vector<std::uint16_t>
+  getQubitList(const CouplingMap& cm) {
+    const auto qubitSet = getQubitSet(cm);
+    return {qubitSet.begin(), qubitSet.end()};
+  }
 
-  [[nodiscard]] static std::vector<unsigned short>
-  getQubitList(const CouplingMap& couplingMap);
+  static bool isConnected(const QubitSubset& qubitChoice,
+                          const CouplingMap& reducedCouplingMap);
 
-  static bool isConnected(const std::set<unsigned short>& qubitChoice,
-                          const CouplingMap&              reducedCouplingMap);
+  static void printCouplingMap(const CouplingMap& cm, std::ostream& os);
 
 protected:
-  std::string                          name;
-  unsigned short                       nqubits                = 0;
-  CouplingMap                          couplingMap            = {};
-  CouplingMap                          current_teleportations = {};
-  bool                                 isBidirectional        = true;
-  Matrix                               distanceTable          = {};
-  std::vector<std::pair<short, short>> teleportationQubits{};
-
-  Properties          properties            = {};
-  Matrix              fidelityTable         = {};
-  std::vector<double> singleQubitFidelities = {};
+  std::string   name;
+  std::uint16_t nqubits                = 0;
+  CouplingMap   couplingMap            = {};
+  CouplingMap   current_teleportations = {};
+  bool          isBidirectional        = true;
+  Matrix        distanceTable          = {};
+  std::vector<std::pair<std::int16_t, std::int16_t>> teleportationQubits{};
+  Properties                                         properties            = {};
+  Matrix                                             fidelityTable         = {};
+  std::vector<double>                                singleQubitFidelities = {};
 
   void createDistanceTable();
   void createFidelityTable();
@@ -380,15 +387,15 @@ class Architecture {
   static bool contains(const std::vector<int>& v, const int e) {
     return std::find(v.begin(), v.end(), e) != v.end();
   }
-  [[nodiscard]] unsigned long bfs(unsigned short start, unsigned short goal,
+  [[nodiscard]] std::uint64_t bfs(std::uint16_t start, std::uint16_t goal,
                                   const std::set<Edge>& teleportations) const;
 
   static std::size_t findCouplingLimit(const CouplingMap& cm, int nQubits);
   static std::size_t
   findCouplingLimit(const CouplingMap& cm, int nQubits,
-                    const std::set<unsigned short>& qubitChoice);
+                    const std::set<std::uint16_t>& qubitChoice);
   static void
-  findCouplingLimit(unsigned short node, int curSum,
-                    const std::vector<std::vector<unsigned short>>& connections,
+  findCouplingLimit(std::uint16_t node, int curSum,
+                    const std::vector<std::vector<std::uint16_t>>& connections,
                     std::vector<int>& d, std::vector<bool>& visited);
 };
diff --git a/include/utils.hpp b/include/utils.hpp
index e4e9ad9e3..5eb440163 100644
--- a/include/utils.hpp
+++ b/include/utils.hpp
@@ -19,8 +19,9 @@
 #include <vector>
 
 using Matrix      = std::vector<std::vector<double>>;
-using Edge        = std::pair<unsigned short, unsigned short>;
+using Edge        = std::pair<std::uint16_t, std::uint16_t>;
 using CouplingMap = std::set<Edge>;
+using QubitSubset = std::set<std::uint16_t>;
 
 struct Exchange {
   Exchange(unsigned short first, unsigned short second, qc::OpType op)
@@ -69,7 +70,7 @@ class Dijkstra {
     double cost                  = -1.;
   };
 
-  static void build_table(unsigned short n, const std::set<Edge>& graph,
+  static void build_table(unsigned short n, const CouplingMap& graph,
                           Matrix& distanceTable,
                           const std::function<double(const Node&)>& cost);
 
@@ -138,17 +139,15 @@ std::string printPi(std::vector<unsigned short>& pi);
 /// \param current index of current qubit
 /// \param visited visited qubits
 /// \param cm coupling map of architecture
-void dfs(unsigned short current, std::set<unsigned short>& visited,
+void dfs(unsigned short current, std::set<std::uint16_t>& visited,
          const CouplingMap& rcm);
 
-using filter_function = std::function<bool(const std::set<unsigned short>&)>;
-std::vector<std::set<unsigned short>>
-subsets(const std::set<unsigned short>& input, int size,
-        filter_function filter = nullptr);
-
-void parse_line(const std::string& line, char separator,
-                const std::set<char>&     escape_chars,
-                const std::set<char>&     ignored_chars,
-                std::vector<std::string>& result);
-std::set<std::pair<unsigned short, unsigned short>>
-getFullyConnectedMap(unsigned short nQubits);
+using filter_function = std::function<bool(const QubitSubset&)>;
+std::vector<QubitSubset> subsets(const QubitSubset& input, int size,
+                                 const filter_function& filter = nullptr);
+
+void        parse_line(const std::string& line, char separator,
+                       const std::set<char>&     escape_chars,
+                       const std::set<char>&     ignored_chars,
+                       std::vector<std::string>& result);
+CouplingMap getFullyConnectedMap(std::uint16_t nQubits);
diff --git a/mqt/qmap/bindings.cpp b/mqt/qmap/bindings.cpp
index 42250396b..37a36cb3e 100644
--- a/mqt/qmap/bindings.cpp
+++ b/mqt/qmap/bindings.cpp
@@ -3,9 +3,7 @@
 // See README.md or go to https://github.com/cda-tum/qmap for more information.
 //
 
-#ifdef Z3_FOUND
 #include "exact/ExactMapper.hpp"
-#endif
 #include "heuristic/HeuristicMapper.hpp"
 #include "nlohmann/json.hpp"
 #include "pybind11/pybind11.h"
@@ -17,13 +15,9 @@
 #define MACRO_STRINGIFY(x) STRINGIFY(x)
 
 namespace py = pybind11;
-namespace nl = nlohmann;
 using namespace pybind11::literals;
 
-// c++ binding function
-MappingResults map(const py::object& circ, Architecture& arch,
-                   Configuration& config) {
-  qc::QuantumComputation qc{};
+void loadQC(qc::QuantumComputation& qc, const py::object& circ) {
   try {
     if (py::isinstance<py::str>(circ)) {
       auto&& file = circ.cast<std::string>();
@@ -36,6 +30,14 @@ MappingResults map(const py::object& circ, Architecture& arch,
     ss << "Could not import circuit: " << e.what();
     throw std::invalid_argument(ss.str());
   }
+}
+
+// c++ binding function
+MappingResults map(const py::object& circ, Architecture& arch,
+                   Configuration& config) {
+  qc::QuantumComputation qc{};
+
+  loadQC(qc, circ);
 
   if (config.useTeleportation) {
     config.teleportationQubits =
@@ -47,13 +49,7 @@ MappingResults map(const py::object& circ, Architecture& arch,
     if (config.method == Method::Heuristic) {
       mapper = std::make_unique<HeuristicMapper>(qc, arch);
     } else if (config.method == Method::Exact) {
-#ifdef Z3_FOUND
       mapper = std::make_unique<ExactMapper>(qc, arch);
-#else
-      std::stringstream ss{};
-      ss << toString(config.method) << " (Z3 support not enabled)";
-      throw std::invalid_argument(ss.str());
-#endif
     }
   } catch (std::exception const& e) {
     std::stringstream ss{};
@@ -275,59 +271,59 @@ PYBIND11_MODULE(pyqmap, m) {
            "target"_a, "error_rate"_a, "operation"_a = "cx")
       .def(
           "get_readout_error",
-          [](const Architecture::Properties& props, unsigned short qubit) {
+          [](const Architecture::Properties& props, std::uint16_t qubit) {
             return props.readoutErrorRate.get(qubit);
           },
           "qubit"_a)
       .def(
           "set_readout_error",
-          [](Architecture::Properties& props, unsigned short qubit,
+          [](Architecture::Properties& props, std::uint16_t qubit,
              double rate) { props.readoutErrorRate.set(qubit, rate); },
           "qubit"_a, "readout_error_rate"_a)
       .def(
           "get_t1",
-          [](const Architecture::Properties& props, unsigned short qubit) {
+          [](const Architecture::Properties& props, std::uint16_t qubit) {
             return props.t1Time.get(qubit);
           },
           "qubit"_a)
       .def(
           "set_t1",
-          [](Architecture::Properties& props, unsigned short qubit, double t1) {
+          [](Architecture::Properties& props, std::uint16_t qubit, double t1) {
             props.t1Time.set(qubit, t1);
           },
           "qubit"_a, "t1"_a)
       .def(
           "get_t2",
-          [](const Architecture::Properties& props, unsigned short qubit) {
+          [](const Architecture::Properties& props, std::uint16_t qubit) {
             return props.t2Time.get(qubit);
           },
           "qubit"_a)
       .def(
           "set_t2",
-          [](Architecture::Properties& props, unsigned short qubit, double t2) {
+          [](Architecture::Properties& props, std::uint16_t qubit, double t2) {
             props.t2Time.set(qubit, t2);
           },
           "qubit"_a, "t2"_a)
       .def(
           "get_frequency",
-          [](const Architecture::Properties& props, unsigned short qubit) {
+          [](const Architecture::Properties& props, std::uint16_t qubit) {
             return props.qubitFrequency.get(qubit);
           },
           "qubit"_a)
       .def(
           "set_frequency",
-          [](Architecture::Properties& props, unsigned short qubit,
+          [](Architecture::Properties& props, std::uint16_t qubit,
              double freq) { props.qubitFrequency.set(qubit, freq); },
           "qubit"_a, "qubit_frequency"_a)
       .def(
           "get_calibration_date",
-          [](const Architecture::Properties& props, unsigned short qubit) {
+          [](const Architecture::Properties& props, std::uint16_t qubit) {
             return props.calibrationDate.get(qubit);
           },
           "qubit"_a)
       .def(
           "set_calibration_date",
-          [](Architecture::Properties& props, unsigned short qubit,
+          [](Architecture::Properties& props, std::uint16_t qubit,
              const std::string& date) {
             props.calibrationDate.set(qubit, date);
           },
@@ -341,9 +337,9 @@ PYBIND11_MODULE(pyqmap, m) {
 
   // Interface to the QMAP internal architecture class
   arch.def(py::init<>())
-      .def(py::init<unsigned short, const CouplingMap&>(), "num_qubits"_a,
+      .def(py::init<std::uint16_t, const CouplingMap&>(), "num_qubits"_a,
            "coupling_map"_a)
-      .def(py::init<unsigned short, const CouplingMap&,
+      .def(py::init<std::uint16_t, const CouplingMap&,
                     const Architecture::Properties&>(),
            "num_qubits"_a, "coupling_map"_a, "properties"_a)
       .def_property("name", &Architecture::getName, &Architecture::setName)
diff --git a/mqt/qmap/compile.py b/mqt/qmap/compile.py
index da5240b48..a4013dd8f 100644
--- a/mqt/qmap/compile.py
+++ b/mqt/qmap/compile.py
@@ -24,6 +24,9 @@
 from qiskit.transpiler import Layout
 from qiskit.transpiler.target import Target
 
+from .load_architecture import load_architecture
+from .load_calibration import load_calibration
+
 
 def extract_initial_layout_from_qasm(qasm: str, qregs: list[QuantumRegister]) -> Layout:
     """
@@ -75,9 +78,9 @@ def compile(
 
     :param circ: Qiskit QuantumCircuit object or path to circuit file
     :type circ: QuantumCircuit | str
-    :param arch: Architecture to map to. Either a path to a file with architecture information, one of the available architectures (:py:mod:`mqt.qmap.Arch`), Architecture, or `qiskit.providers.backend` (if Qiskit is installed)
+    :param arch: Architecture to map to. Either a path to a file with architecture information, one of the available architectures (:py:mod:`mqt.qmap.Arch`), Architecture, or `qiskit.providers.backend`
     :type arch: str | Arch | Architecture | Backend | None
-    :param calibration: Path to file containing calibration information, `qiskit.providers.models.BackendProperties` object (if Qiskit is installed), or `qiskit.transpiler.target.Target` object (if Qiskit is installed)
+    :param calibration: Path to file containing calibration information, `qiskit.providers.models.BackendProperties` object (if Qiskit is installed), or `qiskit.transpiler.target.Target` object
     :type calibration: str | BackendProperties | Target | None
     :param method: Mapping technique to use (*heuristic* | exact)
     :type method: str | Method
@@ -120,40 +123,11 @@ def compile(
     if subgraph is None:
         subgraph = set()
 
-    architecture = Architecture()
     if arch is None and calibration is None:
         raise ValueError("Either arch or calibration must be specified")
 
-    if arch is not None:
-        if isinstance(arch, str):
-            try:
-                architecture.load_coupling_map(Arch(arch))
-            except ValueError:
-                architecture.load_coupling_map(arch)
-        elif isinstance(arch, Arch):
-            architecture.load_coupling_map(arch)
-        elif isinstance(arch, Architecture):
-            architecture = arch
-        elif isinstance(arch, Backend):
-            from mqt.qmap.qiskit.backend import import_backend
-
-            architecture = import_backend(arch)
-        else:
-            raise ValueError("No compatible type for architecture:", type(arch))
-
-    if calibration is not None:
-        if isinstance(calibration, str):
-            architecture.load_properties(calibration)
-        elif isinstance(calibration, BackendProperties):
-            from mqt.qmap.qiskit.backend import import_backend_properties
-
-            architecture.load_properties(import_backend_properties(calibration))
-        elif isinstance(calibration, Target):
-            from mqt.qmap.qiskit.backend import import_target
-
-            architecture.load_properties(import_target(calibration))
-        else:
-            raise ValueError("No compatible type for calibration:", type(calibration))
+    architecture = load_architecture(arch)
+    load_calibration(architecture, calibration)
 
     config = Configuration()
     config.method = Method(method)
diff --git a/mqt/qmap/load_architecture.py b/mqt/qmap/load_architecture.py
new file mode 100644
index 000000000..f6e02cc92
--- /dev/null
+++ b/mqt/qmap/load_architecture.py
@@ -0,0 +1,36 @@
+from __future__ import annotations
+
+from mqt.qmap.pyqmap import Arch, Architecture
+
+from qiskit.providers import Backend
+
+
+def load_architecture(arch: str | Arch | Architecture | Backend | None = None) -> Architecture:
+    """
+    Load an architecture from a string, Arch, Architecture, or Backend. If None is passed, no architecture is loaded.
+    :param arch: Architecture to map to. Either a path to a file with architecture information, one of the available architectures (:py:mod:`mqt.qmap.Arch`), Architecture, or `qiskit.providers.backend`
+    :type arch: str | Arch | Architecture | Backend | None
+
+    :return: Architecture
+    :rtype: Architecture
+    """
+    architecture = Architecture()
+
+    if arch is not None:
+        if isinstance(arch, str):
+            try:
+                architecture.load_coupling_map(Arch(arch))
+            except ValueError:
+                architecture.load_coupling_map(arch)
+        elif isinstance(arch, Arch):
+            architecture.load_coupling_map(arch)
+        elif isinstance(arch, Architecture):
+            architecture = arch
+        elif isinstance(arch, Backend):
+            from mqt.qmap.qiskit.backend import import_backend
+
+            architecture = import_backend(arch)
+        else:
+            raise ValueError("No compatible type for architecture:", type(arch))
+
+    return architecture
diff --git a/mqt/qmap/load_calibration.py b/mqt/qmap/load_calibration.py
new file mode 100644
index 000000000..261bb5f26
--- /dev/null
+++ b/mqt/qmap/load_calibration.py
@@ -0,0 +1,31 @@
+from __future__ import annotations
+
+from mqt.qmap.pyqmap import Architecture
+
+from qiskit.providers.models import BackendProperties
+from qiskit.transpiler.target import Target
+
+
+def load_calibration(architecture: Architecture, calibration: str | BackendProperties | Target | None = None) -> None:
+    """
+    Load a calibration from a string, BackendProperties, or Target.
+    :param architecture: Architecture to load the calibration for
+    :type architecture: Architecture
+    :param calibration: Path to file containing calibration information, `qiskit.providers.models.BackendProperties` object, or `qiskit.transpiler.target.Target` object
+    :type calibration: str | BackendProperties | Target | None
+    """
+    if calibration is None:
+        return
+
+    if isinstance(calibration, str):
+        architecture.load_properties(calibration)
+    elif isinstance(calibration, BackendProperties):
+        from mqt.qmap.qiskit.backend import import_backend_properties
+
+        architecture.load_properties(import_backend_properties(calibration))
+    elif isinstance(calibration, Target):
+        from mqt.qmap.qiskit.backend import import_target
+
+        architecture.load_properties(import_target(calibration))
+    else:
+        raise ValueError("No compatible type for calibration:", type(calibration))
diff --git a/src/Architecture.cpp b/src/Architecture.cpp
index f9163429d..48dfa2679 100644
--- a/src/Architecture.cpp
+++ b/src/Architecture.cpp
@@ -20,14 +20,15 @@ void Architecture::loadCouplingMap(std::istream& is) {
 }
 
 void Architecture::loadCouplingMap(const std::string& filename) {
-  size_t slash = filename.find_last_of('/');
-  size_t dot   = filename.find_last_of('.');
-  name         = filename.substr(slash + 1, dot - slash - 1);
-  auto ifs     = std::ifstream(filename);
-  if (ifs.good())
+  const std::size_t slash = filename.find_last_of('/');
+  const std::size_t dot   = filename.find_last_of('.');
+  name                    = filename.substr(slash + 1, dot - slash - 1);
+  auto ifs                = std::ifstream(filename);
+  if (ifs.good()) {
     this->loadCouplingMap(ifs);
-  else
+  } else {
     throw QMAPException("Error opening coupling map file.");
+  }
 }
 
 void Architecture::loadCouplingMap(std::istream&& is) {
@@ -35,14 +36,14 @@ void Architecture::loadCouplingMap(std::istream&& is) {
   properties.clear();
   std::string line;
 
-  std::regex  r_nqubits = std::regex("([0-9]+)");
-  std::regex  r_edge    = std::regex("([0-9]+) ([0-9]+)");
+  const auto  rNqubits = std::regex("([0-9]+)");
+  const auto  rEdge    = std::regex("([0-9]+) ([0-9]+)");
   std::smatch m;
 
   // get number of qubits
   if (std::getline(is, line)) {
-    if (std::regex_search(line, m, r_nqubits)) {
-      nqubits = static_cast<unsigned short>(std::stoul(m.str(1)));
+    if (std::regex_search(line, m, rNqubits)) {
+      nqubits = static_cast<std::uint16_t>(std::stoul(m.str(1)));
     } else {
       throw QMAPException("No qubit count found in coupling map file: " + line);
     }
@@ -51,9 +52,9 @@ void Architecture::loadCouplingMap(std::istream&& is) {
   }
   // load edges
   while (std::getline(is, line)) {
-    if (std::regex_search(line, m, r_edge)) {
-      auto v1 = static_cast<unsigned short>(std::stoul(m.str(1)));
-      auto v2 = static_cast<unsigned short>(std::stoul(m.str(2)));
+    if (std::regex_search(line, m, rEdge)) {
+      auto v1 = static_cast<std::uint16_t>(std::stoul(m.str(1)));
+      auto v2 = static_cast<std::uint16_t>(std::stoul(m.str(2)));
       couplingMap.emplace(v1, v2);
     } else {
       throw QMAPException("Could not identify edge in coupling map file: " +
@@ -72,7 +73,7 @@ void Architecture::loadCouplingMap(std::istream&& is) {
   createDistanceTable();
 }
 
-void Architecture::loadCouplingMap(unsigned short nQ, const CouplingMap& cm) {
+void Architecture::loadCouplingMap(std::uint16_t nQ, const CouplingMap& cm) {
   nqubits     = nQ;
   couplingMap = cm;
   properties.clear();
@@ -98,8 +99,8 @@ void Architecture::loadProperties(const std::string& filename) {
 }
 
 void Architecture::loadProperties(std::istream&& is) {
-  static const auto SingleQubitGates = {"id", "u1", "u2", "u3",
-                                        "rz", "sx", "x"};
+  static const auto SINGLE_QUBIT_GATES = {"id", "u1", "u2", "u3",
+                                          "rz", "sx", "x"};
 
   properties.clear();
 
@@ -113,26 +114,26 @@ void Architecture::loadProperties(std::istream&& is) {
   std::smatch sMatch;
   std::getline(is, line); // skip first line
   // load edges
-  int qubitNumber = 0;
+  std::uint16_t qubitNumber = 0U;
   while (std::getline(is, line)) {
     std::stringstream        ss(line);
     std::vector<std::string> data{};
     parse_line(line, ',', {'\"'}, {'\\'}, data);
-    properties.t1Time.set(qubitNumber, std::stod(data.at(1)));
-    properties.t2Time.set(qubitNumber, std::stod(data.at(2)));
-    properties.qubitFrequency.set(qubitNumber, std::stod(data.at(3)));
-    properties.readoutErrorRate.set(qubitNumber, std::stod(data.at(4)));
+    properties.t1Time.set(qubitNumber, std::stod(data.at(1U)));
+    properties.t2Time.set(qubitNumber, std::stod(data.at(2U)));
+    properties.qubitFrequency.set(qubitNumber, std::stod(data.at(3U)));
+    properties.readoutErrorRate.set(qubitNumber, std::stod(data.at(4U)));
     // csv file reports average single qubit fidelities
-    for (const auto& operation : SingleQubitGates) {
+    for (const auto& operation : SINGLE_QUBIT_GATES) {
       properties.setSingleQubitErrorRate(qubitNumber, operation,
                                          std::stod(data.at(5)));
     }
     // only try to parse CNOT fidelities if there are any
     if (data.size() >= 7U) {
-      std::string s = data[6];
+      std::string s = data[6U];
       while (std::regex_search(s, sMatch, regexDoubleFidelity)) {
-        auto a = static_cast<unsigned short>(std::stoul(sMatch.str(2U)));
-        auto b = static_cast<unsigned short>(std::stoul(sMatch.str(3U)));
+        auto a = static_cast<std::uint16_t>(std::stoul(sMatch.str(2U)));
+        auto b = static_cast<std::uint16_t>(std::stoul(sMatch.str(3U)));
         if (!isArchitectureAvailable()) {
           couplingMap.emplace(a, b);
         }
@@ -145,7 +146,7 @@ void Architecture::loadProperties(std::istream&& is) {
       }
     }
     if (data.size() == 8U) {
-      properties.calibrationDate.set(qubitNumber, data[7]);
+      properties.calibrationDate.set(qubitNumber, data[7U]);
     }
     ++qubitNumber;
   }
@@ -160,7 +161,7 @@ void Architecture::loadProperties(std::istream&& is) {
   }
   properties.setNqubits(qubitNumber);
   if (!isArchitectureAvailable()) {
-    nqubits = static_cast<unsigned short>(qubitNumber);
+    nqubits = qubitNumber;
     createDistanceTable();
   }
 
@@ -182,11 +183,11 @@ void Architecture::loadProperties(const Properties& props) {
   createFidelityTable();
 }
 
-Architecture::Architecture(unsigned short nQ, const CouplingMap& couplingMap) {
+Architecture::Architecture(std::uint16_t nQ, const CouplingMap& couplingMap) {
   loadCouplingMap(nQ, couplingMap);
 }
 
-Architecture::Architecture(unsigned short nQ, const CouplingMap& couplingMap,
+Architecture::Architecture(std::uint16_t nQ, const CouplingMap& couplingMap,
                            const Properties& props)
     : Architecture(nQ, couplingMap) {
   loadProperties(props);
@@ -218,7 +219,7 @@ void Architecture::createFidelityTable() {
   fidelityTable.clear();
   fidelityTable.resize(nqubits, std::vector<double>(nqubits, 0.0));
 
-  singleQubitFidelities.resize(nqubits, 0.0);
+  singleQubitFidelities.resize(nqubits, 1.0);
 
   for (const auto& [first, second] : couplingMap) {
     if (properties.twoQubitErrorRateAvailable(first, second)) {
@@ -228,26 +229,27 @@ void Architecture::createFidelityTable() {
   }
 
   for (const auto& [qubit, operationProps] :
-       properties.singleQubitErrorRate.get())
+       properties.singleQubitErrorRate.get()) {
     singleQubitFidelities[qubit] =
         1.0 - properties.getAverageSingleQubitErrorRate(qubit);
+  }
 }
 
-unsigned long
-Architecture::minimumNumberOfSwaps(std::vector<unsigned short>& permutation,
-                                   long                         limit) {
+std::uint64_t
+Architecture::minimumNumberOfSwaps(std::vector<std::uint16_t>& permutation,
+                                   std::int64_t                limit) {
   bool tryToAbortEarly = (limit != -1);
 
   // consolidate used qubits
-  std::set<unsigned short> qubits{};
+  QubitSubset qubits{};
   for (const auto& q : permutation) {
     qubits.insert(q);
   }
 
   // create map for goal permutation
-  std::unordered_map<unsigned short, unsigned short> goalPermutation{};
-  unsigned short                                     count    = 0;
-  bool                                               identity = true;
+  std::unordered_map<std::uint16_t, std::uint16_t> goalPermutation{};
+  std::uint16_t                                    count    = 0U;
+  bool                                             identity = true;
   for (const auto q : qubits) {
     goalPermutation.emplace(q, permutation.at(count));
     if (q != permutation.at(count)) {
@@ -257,11 +259,11 @@ Architecture::minimumNumberOfSwaps(std::vector<unsigned short>& permutation,
   }
 
   if (identity) {
-    return 0;
+    return 0U;
   }
 
   // create selection of swap possibilities
-  std::set<std::pair<unsigned short, unsigned short>> possibleSwaps{};
+  std::set<Edge> possibleSwaps{};
   for (const auto& edge : couplingMap) {
     // only use SWAPs between qubits that are currently being considered
     if (qubits.count(edge.first) == 0 || qubits.count(edge.second) == 0) {
@@ -277,7 +279,7 @@ Architecture::minimumNumberOfSwaps(std::vector<unsigned short>& permutation,
 
   Node start{};
   // start with identity permutation
-  for (unsigned short i = 0; i < nqubits; ++i) {
+  for (std::uint16_t i = 0U; i < nqubits; ++i) {
     start.permutation.emplace(i, i);
   }
 
@@ -295,7 +297,7 @@ Architecture::minimumNumberOfSwaps(std::vector<unsigned short>& permutation,
     // in case no solution has been found using less than `limit` swaps, search
     // can be aborted
     if (tryToAbortEarly &&
-        current.nswaps >= static_cast<unsigned long>(limit)) {
+        current.nswaps >= static_cast<std::uint64_t>(limit)) {
       return limit + 1U;
     }
 
@@ -324,11 +326,10 @@ Architecture::minimumNumberOfSwaps(std::vector<unsigned short>& permutation,
   return start.nswaps;
 }
 
-void Architecture::minimumNumberOfSwaps(
-    std::vector<unsigned short>&                            permutation,
-    std::vector<std::pair<unsigned short, unsigned short>>& swaps) {
+void Architecture::minimumNumberOfSwaps(std::vector<std::uint16_t>& permutation,
+                                        std::vector<Edge>&          swaps) {
   // consolidate used qubits
-  std::set<unsigned short> qubits{};
+  QubitSubset qubits{};
   for (const auto& q : permutation) {
     qubits.insert(q);
   }
@@ -339,9 +340,9 @@ void Architecture::minimumNumberOfSwaps(
   }
 
   // create map for goal permutation
-  std::unordered_map<unsigned short, unsigned short> goalPermutation{};
-  unsigned short                                     count    = 0;
-  bool                                               identity = true;
+  std::unordered_map<std::uint16_t, std::uint16_t> goalPermutation{};
+  std::uint16_t                                    count    = 0;
+  bool                                             identity = true;
   for (const auto q : qubits) {
     goalPermutation.emplace(q, permutation.at(count));
     if (q != permutation.at(count)) {
@@ -355,7 +356,7 @@ void Architecture::minimumNumberOfSwaps(
   }
 
   // create selection of swap possibilities
-  std::set<std::pair<unsigned short, unsigned short>> possibleSwaps{};
+  std::set<Edge> possibleSwaps{};
   for (const auto& edge : couplingMap) {
     // only use SWAPs between qubits that are currently being considered
     if (qubits.count(edge.first) == 0 || qubits.count(edge.second) == 0) {
@@ -373,7 +374,7 @@ void Architecture::minimumNumberOfSwaps(
   Node start{};
 
   // start with identity permutation
-  for (unsigned short i = 0; i < nqubits; ++i) {
+  for (std::uint16_t i = 0U; i < nqubits; ++i) {
     start.permutation.emplace(i, i);
   }
 
@@ -391,8 +392,9 @@ void Architecture::minimumNumberOfSwaps(
     for (const auto& swap : possibleSwaps) {
       Node next = current;
       // continue if the same swap was applied earlier
-      if (!next.swaps.empty() && next.swaps.back() == swap)
+      if (!next.swaps.empty() && next.swaps.back() == swap) {
         continue;
+      }
 
       // apply and insert swap
       std::swap(next.permutation.at(swap.first),
@@ -421,12 +423,12 @@ std::size_t Architecture::getCouplingLimit() const {
   return findCouplingLimit(getCouplingMap(), getNqubits());
 }
 
-std::size_t Architecture::getCouplingLimit(
-    const std::set<unsigned short>& qubitChoice) const {
+std::size_t
+Architecture::getCouplingLimit(const QubitSubset& qubitChoice) const {
   return findCouplingLimit(getCouplingMap(), getNqubits(), qubitChoice);
 }
 
-unsigned long Architecture::bfs(unsigned short start, unsigned short goal,
+std::uint64_t Architecture::bfs(std::uint16_t start, std::uint16_t goal,
                                 const std::set<Edge>& teleportations) const {
   std::queue<std::vector<int>> queue;
   std::vector<int>             v;
@@ -434,41 +436,40 @@ unsigned long Architecture::bfs(unsigned short start, unsigned short goal,
   queue.push(v);
   std::vector<std::vector<int>> solutions;
 
-  unsigned long length = 0;
+  std::uint64_t length = 0U;
   std::set<int> successors;
   while (!queue.empty()) {
     v = queue.front();
     queue.pop();
-    int current = v[v.size() - 1];
+    const int current = v[v.size() - 1];
     if (current == goal) {
       length = v.size();
       solutions.push_back(v);
       break;
-    } else {
-      successors.clear();
-      for (const auto& edge : getCouplingMap()) {
-        if (edge.first == current && !contains(v, edge.second)) {
-          successors.insert(edge.second);
-        }
-        if (edge.second == current && !contains(v, edge.first)) {
-          successors.insert(edge.first);
-        }
+    }
+    successors.clear();
+    for (const auto& edge : getCouplingMap()) {
+      if (edge.first == current && !contains(v, edge.second)) {
+        successors.insert(edge.second);
       }
-      for (const auto& edge : teleportations) {
-        if (edge.first == current && !contains(v, edge.second)) {
-          successors.insert(edge.second);
-        }
-        if (edge.second == current && !contains(v, edge.first)) {
-          successors.insert(edge.first); // was v2 but this is probably wrong
-        }
+      if (edge.second == current && !contains(v, edge.first)) {
+        successors.insert(edge.first);
       }
-
-      for (int successor : successors) {
-        std::vector<int> v2 = v;
-        v2.push_back(successor);
-        queue.push(v2);
+    }
+    for (const auto& edge : teleportations) {
+      if (edge.first == current && !contains(v, edge.second)) {
+        successors.insert(edge.second);
+      }
+      if (edge.second == current && !contains(v, edge.first)) {
+        successors.insert(edge.first); // was v2 but this is probably wrong
       }
     }
+
+    for (const auto successor : successors) {
+      auto v2 = v;
+      v2.push_back(successor);
+      queue.push(v2);
+    }
   }
   while (!queue.empty() && queue.front().size() == length) {
     if (queue.front()[queue.front().size() - 1] == goal) {
@@ -498,12 +499,12 @@ unsigned long Architecture::bfs(unsigned short start, unsigned short goal,
 
 std::size_t Architecture::findCouplingLimit(const CouplingMap& cm,
                                             int                nQubits) {
-  std::vector<std::vector<unsigned short>> connections;
-  std::vector<int>                         d;
-  std::vector<bool>                        visited;
+  std::vector<std::vector<std::uint16_t>> connections;
+  std::vector<int>                        d;
+  std::vector<bool>                       visited;
   connections.resize(nQubits);
   int maxSum = -1;
-  for (auto edge : cm) {
+  for (const auto& edge : cm) {
     connections.at(edge.first).emplace_back(edge.second);
   }
   for (int q = 0; q < nQubits; ++q) {
@@ -515,27 +516,30 @@ std::size_t Architecture::findCouplingLimit(const CouplingMap& cm,
     std::fill(visited.begin(), visited.end(), false);
     findCouplingLimit(q, 0, connections, d, visited);
     auto it = std::max_element(d.begin(), d.end());
-    if ((*it) > maxSum)
+    if ((*it) > maxSum) {
       maxSum = (*it);
+    }
   }
   return maxSum;
 }
 
-std::size_t
-Architecture::findCouplingLimit(const CouplingMap& cm, int nQubits,
-                                const std::set<unsigned short>& qubitChoice) {
-  std::vector<std::vector<unsigned short>> connections;
-  std::vector<int>                         d;
-  std::vector<bool>                        visited;
+std::size_t Architecture::findCouplingLimit(const CouplingMap& cm, int nQubits,
+                                            const QubitSubset& qubitChoice) {
+  std::vector<std::vector<std::uint16_t>> connections;
+  std::vector<int>                        d;
+  std::vector<bool>                       visited;
   connections.resize(nQubits);
   int maxSum = -1;
-  for (auto edge : cm) {
-    if (qubitChoice.count(edge.first) && qubitChoice.count(edge.second))
+  for (const auto& edge : cm) {
+    if ((qubitChoice.count(edge.first) != 0U) &&
+        (qubitChoice.count(edge.second) != 0U)) {
       connections.at(edge.first).emplace_back(edge.second);
+    }
   }
   for (int q = 0; q < nQubits; ++q) {
-    if (connections.at(q).empty())
+    if (connections.at(q).empty()) {
       continue;
+    }
     d.clear();
     d.resize(nQubits);
     std::fill(d.begin(), d.end(), 0);
@@ -544,22 +548,25 @@ Architecture::findCouplingLimit(const CouplingMap& cm, int nQubits,
     std::fill(visited.begin(), visited.end(), false);
     findCouplingLimit(q, 0, connections, d, visited);
     auto it = std::max_element(d.begin(), d.end());
-    if ((*it) > maxSum)
+    if ((*it) > maxSum) {
       maxSum = (*it);
+    }
   }
   return maxSum;
 }
 
 void Architecture::findCouplingLimit(
-    unsigned short node, int curSum,
-    const std::vector<std::vector<unsigned short>>& connections,
+    std::uint16_t node, int curSum,
+    const std::vector<std::vector<std::uint16_t>>& connections,
     std::vector<int>& d, std::vector<bool>& visited) {
-  if (visited.at(node))
+  if (visited.at(node)) {
     return;
+  }
   visited[node] = true;
 
-  if (d.at(node) < curSum)
+  if (d.at(node) < curSum) {
     d[node] = curSum;
+  }
   if (connections.at(node).empty()) {
     visited[node] = false;
     return;
@@ -572,37 +579,42 @@ void Architecture::findCouplingLimit(
   visited[node] = false;
 }
 
-void Architecture::getHighestFidelityCouplingMap(unsigned short subsetSize,
-                                                 CouplingMap&   reducedMap) {
-  if (!isArchitectureAvailable() || nqubits == subsetSize ||
-      properties.empty()) {
+void Architecture::getHighestFidelityCouplingMap(
+    std::uint16_t subsetSize, CouplingMap& reducedMap) const {
+  if (!isArchitectureAvailable()) {
+    reducedMap = getFullyConnectedMap(subsetSize);
+    return;
+  }
+
+  if (nqubits == subsetSize) {
     reducedMap = couplingMap;
-  } else {
-    double bestFidelity        = 0.0;
-    auto   allConnectedSubsets = getAllConnectedSubsets(subsetSize);
-
-    for (const auto& qubitChoice : allConnectedSubsets) {
-      double      currentFidelity{};
-      CouplingMap map{};
-      getReducedCouplingMap(qubitChoice, map);
-      currentFidelity =
-          getAverageArchitectureFidelity(map, qubitChoice, properties);
-      if (currentFidelity > bestFidelity) {
-        reducedMap   = map;
-        bestFidelity = currentFidelity;
-      }
+    return;
+  }
+
+  double bestFidelity        = std::numeric_limits<double>::lowest();
+  auto   allConnectedSubsets = getAllConnectedSubsets(subsetSize);
+
+  for (const auto& qubitChoice : allConnectedSubsets) {
+    double      currentFidelity{};
+    CouplingMap map{};
+    getReducedCouplingMap(qubitChoice, map);
+    currentFidelity =
+        getAverageArchitectureFidelity(map, qubitChoice, properties);
+    if (currentFidelity > bestFidelity) {
+      reducedMap   = map;
+      bestFidelity = currentFidelity;
     }
   }
 }
-std::vector<std::set<unsigned short>>
-Architecture::getAllConnectedSubsets(unsigned short subsetSize) {
-  std::vector<std::set<unsigned short>> result{};
+std::vector<QubitSubset>
+Architecture::getAllConnectedSubsets(std::uint16_t subsetSize) const {
+  std::vector<QubitSubset> result{};
   if (!isArchitectureAvailable() || nqubits == subsetSize) {
     result.emplace_back(getQubitSet());
   } else if (nqubits < subsetSize) {
     throw QMAPException("Architecture too small!");
   } else {
-    auto filter = [&](const std::set<unsigned short>& subset) {
+    auto filter = [&](const QubitSubset& subset) {
       CouplingMap cm = {};
       Architecture::getReducedCouplingMap(subset, cm);
       return isConnected(subset, cm);
@@ -614,7 +626,7 @@ Architecture::getAllConnectedSubsets(unsigned short subsetSize) {
   return result;
 }
 void Architecture::getReducedCouplingMaps(
-    unsigned short subsetSize, std::vector<CouplingMap>& couplingMaps) {
+    std::uint16_t subsetSize, std::vector<CouplingMap>& couplingMaps) const {
   couplingMaps.clear();
   if (!isArchitectureAvailable()) {
     couplingMaps.emplace_back(getFullyConnectedMap(subsetSize));
@@ -625,8 +637,8 @@ void Architecture::getReducedCouplingMaps(
     }
   }
 }
-void Architecture::getReducedCouplingMap(
-    const std::set<unsigned short>& qubitChoice, CouplingMap& reducedMap) {
+void Architecture::getReducedCouplingMap(const QubitSubset& qubitChoice,
+                                         CouplingMap&       reducedMap) const {
   reducedMap.clear();
   if (!isArchitectureAvailable()) {
     reducedMap = getFullyConnectedMap(qubitChoice.size());
@@ -640,9 +652,10 @@ void Architecture::getReducedCouplingMap(
   }
 }
 
-double Architecture::getAverageArchitectureFidelity(
-    const CouplingMap& cm, const std::set<unsigned short>& qubitChoice,
-    const Properties& props) {
+double
+Architecture::getAverageArchitectureFidelity(const CouplingMap& cm,
+                                             const QubitSubset& qubitChoice,
+                                             const Properties&  props) {
   if (props.empty()) {
     return 0.0;
   }
@@ -661,20 +674,27 @@ double Architecture::getAverageArchitectureFidelity(
   return result;
 }
 
-std::vector<unsigned short>
-Architecture::getQubitList(const CouplingMap& couplingMap) {
-  std::set<unsigned short> result{};
-  for (const auto& edge : couplingMap) {
-    result.emplace(edge.first);
-    result.emplace(edge.second);
+QubitSubset Architecture::getQubitSet(const CouplingMap& cm) {
+  QubitSubset result{};
+  for (const auto& [q0, q1] : cm) {
+    result.emplace(q0);
+    result.emplace(q1);
   }
-  return {result.begin(), result.end()};
+  return result;
 }
 
-bool Architecture::isConnected(const std::set<unsigned short>& qubitChoice,
+bool Architecture::isConnected(const QubitSubset& qubitChoice,
                                const CouplingMap& reducedCouplingMap) {
-  std::set<unsigned short> reachedQubits{};
-  reachedQubits.insert(*(qubitChoice.begin()));
+  QubitSubset reachedQubits{};
+  reachedQubits.emplace(*(qubitChoice.begin()));
   dfs(*(qubitChoice.begin()), reachedQubits, reducedCouplingMap);
   return (reachedQubits == qubitChoice);
 }
+
+void Architecture::printCouplingMap(const CouplingMap& cm, std::ostream& os) {
+  os << "{ ";
+  for (const auto& edge : cm) {
+    os << "(" << edge.first << " " << edge.second << ") ";
+  }
+  os << "}" << std::endl;
+}
diff --git a/src/utils.cpp b/src/utils.cpp
index 0cb3805f6..6c52d00ca 100644
--- a/src/utils.cpp
+++ b/src/utils.cpp
@@ -5,15 +5,15 @@
 
 #include <utils.hpp>
 
-void Dijkstra::build_table(unsigned short n, const std::set<Edge>& couplingMap,
+void Dijkstra::build_table(std::uint16_t n, const CouplingMap& couplingMap,
                            Matrix& distanceTable,
                            const std::function<double(const Node&)>& cost) {
   distanceTable.clear();
   distanceTable.resize(n, std::vector<double>(n, -1.));
 
-  for (unsigned short i = 0; i < n; ++i) {
+  for (std::uint16_t i = 0; i < n; ++i) {
     std::vector<Dijkstra::Node> nodes(n);
-    for (unsigned short j = 0; j < n; ++j) {
+    for (std::uint16_t j = 0; j < n; ++j) {
       nodes.at(j).contains_correct_edge = false;
       nodes.at(j).visited               = false;
       nodes.at(j).pos                   = j;
@@ -42,11 +42,11 @@ void Dijkstra::build_table(unsigned short n, const std::set<Edge>& couplingMap,
 }
 
 void Dijkstra::dijkstra(const CouplingMap& couplingMap,
-                        std::vector<Node>& nodes, unsigned short start) {
+                        std::vector<Node>& nodes, std::uint16_t start) {
   std::priority_queue<Node*> queue{};
   queue.push(&nodes.at(start));
   while (!queue.empty()) {
-    auto current     = queue.top();
+    auto* current    = queue.top();
     current->visited = true;
     queue.pop();
     auto pos = current->pos;
@@ -61,15 +61,16 @@ void Dijkstra::dijkstra(const CouplingMap& couplingMap,
         to = edge.first;
       }
       if (to != -1) {
-        if (nodes.at(to).visited)
+        if (nodes.at(to).visited) {
           continue;
+        }
 
-        Node new_node;
-        new_node.cost                  = current->cost + 1.0;
-        new_node.pos                   = to;
-        new_node.contains_correct_edge = correctEdge;
-        if (nodes.at(to).cost < 0 || new_node < nodes.at(to)) {
-          nodes.at(to) = new_node;
+        Node newNode;
+        newNode.cost                  = current->cost + 1.0;
+        newNode.pos                   = to;
+        newNode.contains_correct_edge = correctEdge;
+        if (nodes.at(to).cost < 0 || newNode < nodes.at(to)) {
+          nodes.at(to) = newNode;
           queue.push(&nodes.at(to));
         }
       }
@@ -80,14 +81,14 @@ void Dijkstra::dijkstra(const CouplingMap& couplingMap,
 /// Create a string representation of a given permutation
 /// \param pi permutation
 /// \return string representation of pi
-std::string printPi(std::vector<unsigned short>& pi) {
+std::string printPi(std::vector<std::uint16_t>& pi) {
   if (std::is_sorted(pi.begin(), pi.end())) {
     return "( )";
   }
 
   std::stringstream perm{};
   perm << '(';
-  for (unsigned long i = 0; i < pi.size() - 1; i++) {
+  for (std::uint64_t i = 0; i < pi.size() - 1; i++) {
     perm << pi[i] << ',';
   }
   perm << pi[pi.size() - 1] << ')';
@@ -99,16 +100,16 @@ std::string printPi(std::vector<unsigned short>& pi) {
 /// subset of qubits is connected on the given architecture \param current index
 /// of current qubit \param visited visited qubits \param cm coupling map of
 /// architecture
-void dfs(unsigned short current, std::set<unsigned short>& visited,
+void dfs(std::uint16_t current, std::set<std::uint16_t>& visited,
          const CouplingMap& rcm) {
   for (auto edge : rcm) {
     if (edge.first == current) {
-      if (!visited.count(edge.second)) {
+      if (visited.count(edge.second) == 0U) {
         visited.insert(edge.second);
         dfs(edge.second, visited, rcm);
       }
     } else if (edge.second == current) {
-      if (!visited.count(edge.first)) {
+      if (visited.count(edge.first) == 0U) {
         visited.insert(edge.first);
         dfs(edge.first, visited, rcm);
       }
@@ -116,11 +117,14 @@ void dfs(unsigned short current, std::set<unsigned short>& visited,
   }
 }
 
-std::vector<std::set<unsigned short>>
-subsets(const std::set<unsigned short>& input, int length,
-        filter_function filter) {
-  std::size_t                           n = input.size();
-  std::vector<std::set<unsigned short>> result;
+std::vector<QubitSubset> subsets(const QubitSubset& input, int length,
+                                 const filter_function& filter) {
+  std::size_t              n = input.size();
+  std::vector<QubitSubset> result{};
+
+  if (length == 0) {
+    throw std::invalid_argument("Length of subset must be greater than 0");
+  }
 
   if (length == 1) {
     for (const auto& item : input) {
@@ -130,12 +134,12 @@ subsets(const std::set<unsigned short>& input, int length,
   } else {
     std::size_t i = (1U << length) - 1U;
 
-    while (!(i >> n)) {
-      std::set<unsigned short> v{};
-      auto                     it = input.begin();
+    while ((i >> n) == 0U) {
+      std::set<std::uint16_t> v{};
+      auto                    it = input.begin();
 
       for (std::size_t j = 0U; j < n; j++, ++it) {
-        if (i & (1U << j)) {
+        if ((i & (1U << j)) != 0U) {
           v.emplace(*it);
         }
       }
@@ -161,24 +165,25 @@ subsets(const std::set<unsigned short>& input, int length,
 }
 
 void parse_line(const std::string& line, char separator,
-                const std::set<char>&     escape_chars,
-                const std::set<char>&     ignored_chars,
+                const std::set<char>&     escapeChars,
+                const std::set<char>&     ignoredChars,
                 std::vector<std::string>& result) {
+  result.clear();
   std::string word;
-  bool        in_escape = false;
-  for (char c : line) {
-    if (ignored_chars.find(c) != ignored_chars.end()) {
+  bool        inEscape = false;
+  for (const char c : line) {
+    if (ignoredChars.find(c) != ignoredChars.end()) {
       continue;
     }
-    if (in_escape) {
-      if (escape_chars.find(c) != escape_chars.end()) {
-        in_escape = false;
+    if (inEscape) {
+      if (escapeChars.find(c) != escapeChars.end()) {
+        inEscape = false;
       } else {
         word += c;
       }
     } else {
-      if (escape_chars.find(c) != escape_chars.end()) {
-        in_escape = true;
+      if (escapeChars.find(c) != escapeChars.end()) {
+        inEscape = true;
       } else if (c == separator) {
         result.push_back(word);
         word = "";
@@ -190,9 +195,8 @@ void parse_line(const std::string& line, char separator,
   result.push_back(word);
 }
 
-std::set<std::pair<unsigned short, unsigned short>>
-getFullyConnectedMap(unsigned short nQubits) {
-  std::set<std::pair<unsigned short, unsigned short>> result{};
+CouplingMap getFullyConnectedMap(std::uint16_t nQubits) {
+  CouplingMap result{};
   for (int q = 0; q < nQubits; ++q) {
     for (int p = q + 1; p < nQubits; ++p) {
       result.emplace(q, p);


Test patch:
diff --git a/test/test_architecture.cpp b/test/test_architecture.cpp
index 360322b91..74d939b44 100644
--- a/test/test_architecture.cpp
+++ b/test/test_architecture.cpp
@@ -64,18 +64,10 @@ TEST_P(TestArchitecture, GetHighestFidelity) {
   CouplingMap cm{};
 
   arch.getHighestFidelityCouplingMap(arch.getNqubits(), cm);
-
   EXPECT_EQ(cm, arch.getCouplingMap());
 
-  arch.getHighestFidelityCouplingMap(1, cm);
-
-  CouplingMap expected{};
-
-  if (arch_name.find(".csv") == std::string::npos) {
-    EXPECT_EQ(cm, arch.getCouplingMap());
-  } else {
-    EXPECT_NE(cm, arch.getCouplingMap());
-  }
+  arch.getHighestFidelityCouplingMap(1U, cm);
+  EXPECT_TRUE(cm.empty());
 }
 TEST_P(TestArchitecture, ReducedMaps) {
   auto&             arch_name = GetParam();
@@ -106,6 +98,8 @@ TEST(TestArchitecture, ConnectedTest) {
   cm.emplace(std::make_pair(3, 4));
   cm.emplace(std::make_pair(4, 0));
 
+  Architecture::printCouplingMap(cm, std::cout);
+
   architecture.loadCouplingMap(5, cm);
 
   std::vector<CouplingMap> cms;
@@ -140,22 +134,23 @@ TEST(TestArchitecture, FidelityTest) {
   architecture.loadProperties(props);
   architecture.getHighestFidelityCouplingMap(2, cm);
 
-  std::vector<unsigned short> highestFidelity{2, 3};
-  auto                        qubitList = Architecture::getQubitList(cm);
+  const std::vector<std::uint16_t> highestFidelity{2, 3};
+  auto                             qubitList = Architecture::getQubitList(cm);
 
   EXPECT_EQ(qubitList, highestFidelity);
 }
 
 TEST(TestArchitecture, FullyConnectedTest) {
-  CouplingMap cm = getFullyConnectedMap(3);
+  const auto cm = getFullyConnectedMap(3);
 
   ASSERT_TRUE(cm.size() == 3 * 2);
 }
 
 TEST(TestArchitecture, MinimumNumberOfSwapsError) {
-  Architecture                architecture{};
-  std::vector<unsigned short> permutation{1, 1, 2, 3, 4};
-  std::vector<std::pair<unsigned short, unsigned short>> swaps{};
+  Architecture               architecture{};
+  std::vector<std::uint16_t> permutation{1, 1, 2, 3, 4};
+  printPi(permutation);
+  std::vector<Edge> swaps{};
   EXPECT_THROW(architecture.minimumNumberOfSwaps(permutation, swaps),
                std::runtime_error);
 }