Line | Branch | Decision | Exec | Source |
---|---|---|---|---|
1 | // Copyright 2019-2022 Cambridge Quantum Computing | |||
2 | // | |||
3 | // Licensed under the Apache License, Version 2.0 (the "License"); | |||
4 | // you may not use this file except in compliance with the License. | |||
5 | // You may obtain a copy of the License at | |||
6 | // | |||
7 | // http://www.apache.org/licenses/LICENSE-2.0 | |||
8 | // | |||
9 | // Unless required by applicable law or agreed to in writing, software | |||
10 | // distributed under the License is distributed on an "AS IS" BASIS, | |||
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
12 | // See the License for the specific language governing permissions and | |||
13 | // limitations under the License. | |||
14 | ||||
15 | #pragma once | |||
16 | ||||
17 | /** | |||
18 | * @file | |||
19 | * @brief Named registers of arrays of (quantum or classical) nodes | |||
20 | */ | |||
21 | ||||
22 | #include <boost/functional/hash.hpp> | |||
23 | #include <map> | |||
24 | #include <memory> | |||
25 | #include <optional> | |||
26 | #include <regex> | |||
27 | #include <set> | |||
28 | #include <sstream> | |||
29 | #include <string> | |||
30 | #include <tklog/TketLog.hpp> | |||
31 | ||||
32 | #include "BiMapHeaders.hpp" | |||
33 | #include "Json.hpp" | |||
34 | ||||
35 | namespace tket { | |||
36 | ||||
37 | /** Type of information held */ | |||
38 | enum class UnitType { Qubit, Bit }; | |||
39 | ||||
40 | /** The type and dimension of a register */ | |||
41 | typedef std::pair<UnitType, unsigned> register_info_t; | |||
42 | ||||
43 | typedef std::optional<register_info_t> opt_reg_info_t; | |||
44 | ||||
45 | const std::string &q_default_reg(); | |||
46 | const std::string &c_default_reg(); | |||
47 | const std::string &node_default_reg(); | |||
48 | const std::string &c_debug_zero_prefix(); | |||
49 | const std::string &c_debug_one_prefix(); | |||
50 | const std::string &c_debug_default_name(); | |||
51 | ||||
52 | /** Conversion invalid */ | |||
53 | class InvalidUnitConversion : public std::logic_error { | |||
54 | public: | |||
55 | ✗ | InvalidUnitConversion(const std::string &name, const std::string &new_type) | ||
56 | ✗ | : std::logic_error("Cannot convert " + name + " to " + new_type) {} | ||
57 | }; | |||
58 | ||||
59 | /** | |||
60 | * Location holding a bit or qubit of information | |||
61 | * | |||
62 | * Each location has a name (signifying the 'register' to which it belongs) and | |||
63 | * an index within that register (which may be multi-dimensional). | |||
64 | */ | |||
65 | class UnitID { | |||
66 | public: | |||
67 | ✗ | UnitID() : data_(std::make_shared<UnitData>()) {} | ||
68 | ||||
69 | /** String representation including name and index */ | |||
70 | std::string repr() const; | |||
71 | ||||
72 | /** Register name */ | |||
73 | ✗ | std::string reg_name() const { return data_->name_; } | ||
74 | ||||
75 | /** Index dimension */ | |||
76 | ✗ | unsigned reg_dim() const { return data_->index_.size(); } | ||
77 | ||||
78 | /** Index */ | |||
79 | ✗ | std::vector<unsigned> index() const { return data_->index_; } | ||
80 | ||||
81 | /** Unit type */ | |||
82 | ✗ | UnitType type() const { return data_->type_; } | ||
83 | ||||
84 | /** Register dimension and type */ | |||
85 | 808337 | register_info_t reg_info() const { return {type(), reg_dim()}; } | ||
86 | ||||
87 | ✗ | bool operator<(const UnitID &other) const { | ||
88 | ✗ | int n = data_->name_.compare(other.data_->name_); | ||
89 |
0/2✗ Decision 'true' not taken.
✗ Decision 'false' not taken.
|
✗ | if (n > 0) return false; | |
90 |
0/2✗ Decision 'true' not taken.
✗ Decision 'false' not taken.
|
✗ | if (n < 0) return true; | |
91 | ✗ | return data_->index_ < other.data_->index_; | ||
92 | } | |||
93 | 203 | bool operator>(const UnitID &other) const { return other < *this; } | ||
94 | ✗ | bool operator==(const UnitID &other) const { | ||
95 | ✗ | return (this->data_->name_ == other.data_->name_) && | ||
96 | ✗ | (this->data_->index_ == other.data_->index_); | ||
97 | } | |||
98 | ✗ | bool operator!=(const UnitID &other) const { return !(*this == other); } | ||
99 | ||||
100 | 494 | friend std::size_t hash_value(UnitID const &unitid) { | ||
101 | 494 | size_t seed = 0; | ||
102 |
1/2✓ Branch 2 taken 494 times.
✗ Branch 3 not taken.
|
494 | boost::hash_combine(seed, unitid.data_->name_); | |
103 |
1/2✓ Branch 2 taken 494 times.
✗ Branch 3 not taken.
|
494 | boost::hash_combine(seed, unitid.data_->index_); | |
104 |
1/2✓ Branch 2 taken 494 times.
✗ Branch 3 not taken.
|
494 | boost::hash_combine(seed, unitid.data_->type_); | |
105 | 494 | return seed; | ||
106 | } | |||
107 | ||||
108 | protected: | |||
109 | ✗ | UnitID( | ||
110 | const std::string &name, const std::vector<unsigned> &index, | |||
111 | UnitType type) | |||
112 | ✗ | : data_(std::make_shared<UnitData>(name, index, type)) {} | ||
113 | ||||
114 | private: | |||
115 | struct UnitData { | |||
116 | std::string name_; | |||
117 | std::vector<unsigned> index_; | |||
118 | UnitType type_; | |||
119 | ||||
120 | ✗ | UnitData() : name_(), index_(), type_(UnitType::Qubit) {} | ||
121 | ✗ | UnitData( | ||
122 | const std::string &name, const std::vector<unsigned> &index, | |||
123 | UnitType type) | |||
124 | ✗ | : name_(name), index_(index), type_(type) { | ||
125 | ✗ | static const std::string id_regex_str = "[a-z][A-Za-z0-9_]*"; | ||
126 | ✗ | static const std::regex id_regex(id_regex_str); | ||
127 |
0/2✗ Decision 'true' not taken.
✗ Decision 'false' not taken.
|
✗ | if (!name.empty() && !std::regex_match(name, id_regex)) { | |
128 | ✗ | std::stringstream msg; | ||
129 | msg << "UnitID name '" << name << "' does not match '" << id_regex_str | |||
130 |
0/1? Decision couldn't be analyzed.
|
✗ | << "', as required for QASM conversion."; | |
131 | ✗ | tket_log()->warn(msg.str()); | ||
132 | } | |||
133 | } | |||
134 | }; | |||
135 | std::shared_ptr<UnitData> data_; | |||
136 | }; | |||
137 | ||||
138 | template <class Unit_T> | |||
139 | 14298 | void unitid_to_json(nlohmann::json &j, const Unit_T &unit) { | ||
140 | static_assert(std::is_base_of<UnitID, Unit_T>::value); | |||
141 |
2/4✓ Branch 2 taken 7149 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 7149 times.
✗ Branch 6 not taken.
|
14298 | j.push_back(unit.reg_name()); | |
142 |
2/4✓ Branch 2 taken 7149 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 7149 times.
✗ Branch 6 not taken.
|
14298 | j.push_back(unit.index()); | |
143 | 14298 | } | ||
144 | ||||
145 | template <class T> | |||
146 | 8520 | void json_to_unitid(const nlohmann::json &j, T &unit) { | ||
147 |
3/6✓ Branch 3 taken 4260 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 4260 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 4260 times.
✗ Branch 10 not taken.
|
8520 | unit = T(j.at(0).get<std::string>(), j.at(1).get<std::vector<unsigned>>()); | |
148 | 8520 | } | ||
149 | ||||
150 | /** Location holding a qubit */ | |||
151 | class Qubit : public UnitID { | |||
152 | public: | |||
153 | ✗ | Qubit() : UnitID("", {}, UnitType::Qubit) {} | ||
154 | ||||
155 | /** Qubit in default register */ | |||
156 | ✗ | explicit Qubit(unsigned index) | ||
157 | ✗ | : UnitID(q_default_reg(), {index}, UnitType::Qubit) {} | ||
158 | ||||
159 | /** Named register with no index */ | |||
160 | explicit Qubit(const std::string &name) : UnitID(name, {}, UnitType::Qubit) {} | |||
161 | ||||
162 | /** Named register with a one-dimensional index */ | |||
163 | ✗ | Qubit(const std::string &name, unsigned index) | ||
164 | ✗ | : UnitID(name, {index}, UnitType::Qubit) {} | ||
165 | ||||
166 | /** Named register with a two-dimensional index */ | |||
167 | Qubit(const std::string &name, unsigned row, unsigned col) | |||
168 | : UnitID(name, {row, col}, UnitType::Qubit) {} | |||
169 | ||||
170 | /** Named register with a three-dimensional index */ | |||
171 | 4822 | Qubit(const std::string &name, unsigned row, unsigned col, unsigned layer) | ||
172 |
2/4✓ Branch 2 taken 4822 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 4822 times.
✗ Branch 6 not taken.
|
4822 | : UnitID(name, {row, col, layer}, UnitType::Qubit) {} | |
173 | ||||
174 | /** Named register with a multi-dimensional index */ | |||
175 | ✗ | Qubit(const std::string &name, std::vector<unsigned> index) | ||
176 | ✗ | : UnitID(name, index, UnitType::Qubit) {} | ||
177 | ||||
178 | /** Copy constructor */ | |||
179 | ✗ | explicit Qubit(const UnitID &other) : UnitID(other) { | ||
180 |
0/2✗ Decision 'true' not taken.
✗ Decision 'false' not taken.
|
✗ | if (other.type() != UnitType::Qubit) { | |
181 | ✗ | throw InvalidUnitConversion(other.repr(), "Qubit"); | ||
182 | } | |||
183 | } | |||
184 | }; | |||
185 | ||||
186 | JSON_DECL(Qubit) | |||
187 | ||||
188 | /** Location holding a bit */ | |||
189 | class Bit : public UnitID { | |||
190 | public: | |||
191 | ✗ | Bit() : UnitID("", {}, UnitType::Bit) {} | ||
192 | ||||
193 | /** Bit in default register */ | |||
194 | ✗ | explicit Bit(unsigned index) | ||
195 | ✗ | : UnitID(c_default_reg(), {index}, UnitType::Bit) {} | ||
196 | ||||
197 | /** Named register with no index */ | |||
198 | explicit Bit(const std::string &name) : UnitID(name, {}, UnitType::Bit) {} | |||
199 | ||||
200 | /** Named register with a one-dimensional index */ | |||
201 | ✗ | Bit(const std::string &name, unsigned index) | ||
202 | ✗ | : UnitID(name, {index}, UnitType::Bit) {} | ||
203 | ||||
204 | /** Named register with a two-dimensional index */ | |||
205 | Bit(const std::string &name, unsigned row, unsigned col) | |||
206 | : UnitID(name, {row, col}, UnitType::Bit) {} | |||
207 | ||||
208 | /** Named register with a three-dimensional index */ | |||
209 | Bit(const std::string &name, unsigned row, unsigned col, unsigned layer) | |||
210 | : UnitID(name, {row, col, layer}, UnitType::Bit) {} | |||
211 | ||||
212 | /** Named register with a multi-dimensional index */ | |||
213 | ✗ | Bit(const std::string &name, std::vector<unsigned> index) | ||
214 | ✗ | : UnitID(name, index, UnitType::Bit) {} | ||
215 | ||||
216 | ✗ | explicit Bit(const UnitID &other) : UnitID(other) { | ||
217 |
0/2✗ Decision 'true' not taken.
✗ Decision 'false' not taken.
|
✗ | if (other.type() != UnitType::Bit) { | |
218 | ✗ | throw InvalidUnitConversion(other.repr(), "Bit"); | ||
219 | } | |||
220 | } | |||
221 | }; | |||
222 | ||||
223 | JSON_DECL(Bit) | |||
224 | ||||
225 | /** Architectural qubit location */ | |||
226 | class Node : public Qubit { | |||
227 | public: | |||
228 | ✗ | Node() : Qubit() {} | ||
229 | ||||
230 | /** Qubit in default register */ | |||
231 | ✗ | explicit Node(unsigned index) : Qubit(node_default_reg(), index) {} | ||
232 | ||||
233 | /** Named register with a one-dimensional index */ | |||
234 | ✗ | Node(const std::string &name, unsigned index) : Qubit(name, index) {} | ||
235 | ||||
236 | /** Named register with a two-dimensional index */ | |||
237 | Node(const std::string &name, unsigned row, unsigned col) | |||
238 | : Qubit(name, row, col) {} | |||
239 | ||||
240 | /** Named register with a three-dimensional index */ | |||
241 | 4822 | Node(const std::string &name, unsigned row, unsigned col, unsigned layer) | ||
242 | 4822 | : Qubit(name, row, col, layer) {} | ||
243 | ||||
244 | /** Named register with a multi-dimensional index */ | |||
245 | 866 | Node(const std::string &name, std::vector<unsigned> index) | ||
246 |
1/2✓ Branch 2 taken 866 times.
✗ Branch 3 not taken.
|
866 | : Qubit(name, index) {} | |
247 | ||||
248 | ✗ | explicit Node(const UnitID &other) : Qubit(other) {} | ||
249 | }; | |||
250 | ||||
251 | JSON_DECL(Node) | |||
252 | ||||
253 | /** A correspondence between two sets of unit IDs */ | |||
254 | typedef boost::bimap<UnitID, UnitID> unit_bimap_t; | |||
255 | ||||
256 | /** A pair of ("initial" and "final") correspondences between unit IDs */ | |||
257 | typedef struct { | |||
258 | unit_bimap_t initial; | |||
259 | unit_bimap_t final; | |||
260 | } unit_bimaps_t; | |||
261 | ||||
262 | typedef std::vector<UnitID> unit_vector_t; | |||
263 | typedef std::map<UnitID, UnitID> unit_map_t; | |||
264 | typedef std::set<UnitID> unit_set_t; | |||
265 | ||||
266 | typedef std::vector<Qubit> qubit_vector_t; | |||
267 | typedef std::map<Qubit, Qubit> qubit_map_t; | |||
268 | JSON_DECL(qubit_map_t) | |||
269 | ||||
270 | typedef std::vector<Bit> bit_vector_t; | |||
271 | typedef std::map<Bit, Bit> bit_map_t; | |||
272 | ||||
273 | typedef std::set<Node> node_set_t; | |||
274 | typedef std::vector<Node> node_vector_t; | |||
275 | ||||
276 | /** A register of locations sharing the same name */ | |||
277 | typedef std::map<unsigned, UnitID> register_t; | |||
278 | ||||
279 | template <typename UnitA, typename UnitB> | |||
280 | 10234 | static bool update_map(unit_bimap_t &m, const std::map<UnitA, UnitB> &um) { | ||
281 | 10234 | unit_map_t new_m; | ||
282 | 10234 | bool changed = false; | ||
283 |
2/2✓ Branch 5 taken 7086 times.
✓ Branch 6 taken 5140 times.
|
2/2✓ Decision 'true' taken 7086 times.
✓ Decision 'false' taken 5140 times.
|
24350 | for (const std::pair<const UnitA, UnitB> &pair : um) { |
284 |
1/2✓ Branch 1 taken 7086 times.
✗ Branch 2 not taken.
|
14116 | const auto &it = m.right.find(pair.first); | |
285 |
4/6✓ Branch 1 taken 7086 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 7086 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 54 times.
✓ Branch 7 taken 7032 times.
|
2/2✓ Decision 'true' taken 108 times.
✓ Decision 'false' taken 14008 times.
|
14116 | if (it == m.right.end()) { |
286 | 108 | continue; | ||
287 | } | |||
288 |
2/4✓ Branch 1 taken 7032 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 7032 times.
✗ Branch 6 not taken.
|
14008 | new_m.insert({it->second, pair.second}); | |
289 |
1/2✓ Branch 1 taken 7032 times.
✗ Branch 2 not taken.
|
14008 | changed |= (m.right.erase(pair.first) > 0); | |
290 | } | |||
291 |
2/2✓ Branch 4 taken 7032 times.
✓ Branch 5 taken 5140 times.
|
2/2✓ Decision 'true' taken 7032 times.
✓ Decision 'false' taken 5140 times.
|
24242 | for (const std::pair<const UnitID, UnitID> &pair : new_m) { |
292 |
2/4✓ Branch 1 taken 7032 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 7032 times.
✗ Branch 5 not taken.
|
14008 | changed |= m.left.insert(pair).second; | |
293 | } | |||
294 | 10234 | return changed; | ||
295 | 10234 | } | ||
296 | ||||
297 | /** | |||
298 | * Update a pair of "initial" and "final" correspondences. | |||
299 | * | |||
300 | * If \p maps is null then the function does nothing and returns false. | |||
301 | * | |||
302 | * @param[in,out] maps maps to be updated | |||
303 | * @param[in] um_initial new correspondences added to initial map | |||
304 | * @param[in] um_final new correspondences added to final map | |||
305 | * | |||
306 | * @tparam UnitA first unit type | |||
307 | * @tparam UnitB second unit type | |||
308 | * | |||
309 | * @return whether any changes were made to the maps | |||
310 | */ | |||
311 | template <typename UnitA, typename UnitB> | |||
312 | 2580 | bool update_maps( | ||
313 | std::shared_ptr<unit_bimaps_t> maps, | |||
314 | const std::map<UnitA, UnitB> &um_initial, | |||
315 | const std::map<UnitA, UnitB> &um_final) { | |||
316 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 23 times.
|
1/2✓ Decision 'true' taken 23 times.
✗ Decision 'false' not taken.
|
2580 | if (!maps) return false; |
317 | // Can only work for Unit classes | |||
318 | static_assert(std::is_base_of<UnitID, UnitA>::value); | |||
319 | static_assert(std::is_base_of<UnitID, UnitB>::value); | |||
320 | // Unit types must be related, so cannot rename e.g. Bits to Qubits | |||
321 | static_assert( | |||
322 | std::is_base_of<UnitA, UnitB>::value || | |||
323 | std::is_base_of<UnitB, UnitA>::value); | |||
324 | ||||
325 | 2570 | bool changed = false; | ||
326 | 2570 | changed |= update_map(maps->initial, um_initial); | ||
327 | 2570 | changed |= update_map(maps->final, um_final); | ||
328 | 2570 | return changed; | ||
329 | } | |||
330 | ||||
331 | } // namespace tket | |||
332 |