tket
Loading...
Searching...
No Matches
GreedyPauliOptimisation.cpp
Go to the documentation of this file.
1// Copyright Quantinuum
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
16
17#include <algorithm>
18#include <chrono>
19#include <future>
20#include <queue>
21#include <random>
22
27
28namespace tket {
29
30namespace Transforms {
31
32namespace GreedyPauliSimp {
33
34static TQE sample_random_tqe(const std::vector<TQE>& vec, unsigned seed) {
35 std::mt19937 rng(seed);
36 std::uniform_int_distribution<size_t> dist(0, vec.size() - 1);
37 size_t random_index = dist(rng);
38 auto it = vec.begin();
39 std::advance(it, random_index);
40 return *it;
41}
42
43static std::vector<TQE> sample_tqes(
44 const std::set<TQE>& tqes, size_t k, unsigned seed) {
45 // https://stackoverflow.com/a/59090754
46 size_t unsampled_sz = tqes.size();
47 auto first = std::begin(tqes);
48 std::vector<TQE> vec;
49 std::mt19937 rng(seed);
50 vec.reserve(std::min(k, unsampled_sz));
51 for (k = std::min(k, unsampled_sz); k != 0; ++first) {
52 auto r = std::uniform_int_distribution<std::size_t>(0, --unsampled_sz)(rng);
53 if (r < k) {
54 vec.push_back(*first);
55 --k;
56 }
57 }
58 return vec;
59}
60
61static void apply_rot2q_to_circ(const Rotation2Q& rot, Circuit& circ) {
62 if (rot.p_a == Pauli::X) {
63 circ.add_op<unsigned>(OpType::H, {rot.a});
64 } else if (rot.p_a == Pauli::Y) {
65 circ.add_op<unsigned>(OpType::V, {rot.a});
66 }
67 if (rot.p_b == Pauli::X) {
68 circ.add_op<unsigned>(OpType::H, {rot.b});
69 } else if (rot.p_b == Pauli::Y) {
70 circ.add_op<unsigned>(OpType::V, {rot.b});
71 }
72 circ.add_op<unsigned>(OpType::ZZPhase, rot.angle, {rot.a, rot.b});
73 if (rot.p_a == Pauli::X) {
74 circ.add_op<unsigned>(OpType::H, {rot.a});
75 } else if (rot.p_a == Pauli::Y) {
76 circ.add_op<unsigned>(OpType::Vdg, {rot.a});
77 }
78 if (rot.p_b == Pauli::X) {
79 circ.add_op<unsigned>(OpType::H, {rot.b});
80 } else if (rot.p_b == Pauli::Y) {
81 circ.add_op<unsigned>(OpType::Vdg, {rot.b});
82 }
83}
84
85static void apply_tqe_to_circ(const TQE& tqe, Circuit& circ) {
86 const TQEType& gate_type = tqe.type;
87 const unsigned& a = tqe.a;
88 const unsigned& b = tqe.b;
89 switch (gate_type) {
90 case TQEType::XX:
91 circ.add_op<unsigned>(OpType::H, {a});
92 circ.add_op<unsigned>(OpType::CX, {a, b});
93 circ.add_op<unsigned>(OpType::H, {a});
94 break;
95 case TQEType::XY:
96 circ.add_op<unsigned>(OpType::H, {a});
97 circ.add_op<unsigned>(OpType::CY, {a, b});
98 circ.add_op<unsigned>(OpType::H, {a});
99 break;
100 case TQEType::XZ:
101 circ.add_op<unsigned>(OpType::CX, {b, a});
102 break;
103 case TQEType::YX:
104 circ.add_op<unsigned>(OpType::H, {b});
105 circ.add_op<unsigned>(OpType::CY, {b, a});
106 circ.add_op<unsigned>(OpType::H, {b});
107 break;
108 case TQEType::YY:
109 circ.add_op<unsigned>(OpType::V, {a});
110 circ.add_op<unsigned>(OpType::CY, {a, b});
111 circ.add_op<unsigned>(OpType::Vdg, {a});
112 break;
113 case TQEType::YZ:
114 circ.add_op<unsigned>(OpType::CY, {b, a});
115 break;
116 case TQEType::ZX:
117 circ.add_op<unsigned>(OpType::CX, {a, b});
118 break;
119 case TQEType::ZY:
120 circ.add_op<unsigned>(OpType::CY, {a, b});
121 break;
122 case TQEType::ZZ:
123 circ.add_op<unsigned>(OpType::CZ, {a, b});
124 break;
125 }
126}
127
128// return the sum of the cost increases on remaining tableau nodes
129static double default_tableau_tqe_cost(
130 const std::vector<PauliNode_ptr>& rows,
131 const std::vector<unsigned>& remaining_indices, const TQE& tqe,
132 const unsigned& max_lookahead) {
133 double cost = 0;
134 unsigned count = 0;
135 for (const unsigned& index : remaining_indices) {
136 cost += rows[index]->tqe_cost_increase(tqe);
137 if (++count >= max_lookahead) break;
138 }
139 return cost;
140}
141
142// return the weighted sum of the cost increases on remaining nodes
143// we discount the weight after each set
144static double default_pauliexp_tqe_cost(
145 const double discount_rate,
146 const std::vector<std::vector<PauliNode_ptr>>& rotation_sets,
147 const std::vector<PauliNode_ptr>& rows, const TQE& tqe,
148 const unsigned& max_lookahead) {
149 double discount = 1 / (1 + discount_rate);
150 double weight = 1;
151 double exp_cost = 0;
152 double tab_cost = 0;
153 unsigned count = 0;
154 for (const std::vector<PauliNode_ptr>& rotation_set : rotation_sets) {
155 int r_cost = 0;
156 for (const PauliNode_ptr& node : rotation_set) {
157 r_cost += node->tqe_cost_increase(tqe);
158 if (++count >= max_lookahead) break;
159 }
160 exp_cost += weight * r_cost;
161 if (count >= max_lookahead) break;
162 weight *= discount;
163 }
164 for (const PauliNode_ptr& node : rows) {
165 tab_cost += weight * node->tqe_cost_increase(tqe);
166 if (++count >= max_lookahead) break;
167 }
168 return exp_cost + tab_cost;
169}
170
171// given a map from TQE to an array of costs, and an optional map
172// specifying the costs for implementing some 2q rotations directly
173// as ZZPhase gates. Select the TQEs and 2q rotations with the minimum
174// weighted sum of minmax-normalised costs. Cost arrays must all be the same
175// length (enforced by the template parameter).
176template <size_t n_costs>
177static std::pair<std::vector<TQE>, std::vector<Rotation2Q>> minmax_selection(
178 const std::vector<std::pair<TQE, std::array<double, n_costs>>>&
179 tqe_candidates_cost,
180 const std::vector<std::pair<Rotation2Q, std::array<double, n_costs>>>&
181 rot2q_gates_cost,
182 const std::array<double, n_costs>& weights) {
183 static_assert(n_costs > 0, "n_costs must be greater than 0");
184 // for each cost type, store its min and max
185 std::array<double, n_costs> mins = tqe_candidates_cost.begin()->second;
186 std::array<double, n_costs> maxs = tqe_candidates_cost.begin()->second;
187 for (const auto& pair : tqe_candidates_cost) {
188 for (unsigned cost_index = 0; cost_index < n_costs; cost_index++) {
189 if (pair.second[cost_index] < mins[cost_index]) {
190 mins[cost_index] = pair.second[cost_index];
191 }
192 if (pair.second[cost_index] > maxs[cost_index]) {
193 maxs[cost_index] = pair.second[cost_index];
194 }
195 }
196 }
197 // valid_indices stores the indices of the costs where min!=max
198 std::vector<unsigned> valid_indices;
199 for (unsigned cost_index = 0; cost_index < n_costs; cost_index++) {
200 if (mins[cost_index] != maxs[cost_index]) {
201 valid_indices.push_back(cost_index);
202 }
203 }
204 // if all have the same cost, return the first one
205 if (valid_indices.size() == 0) {
206 std::vector<Rotation2Q> rot2qs;
207 rot2qs.reserve(rot2q_gates_cost.size());
208 std::transform(
209 rot2q_gates_cost.begin(), rot2q_gates_cost.end(),
210 std::back_inserter(rot2qs),
211 [](const auto& pair) { return pair.first; });
212 std::vector<TQE> selected_tqes;
213 selected_tqes.reserve(tqe_candidates_cost.size());
214 std::transform(
215 tqe_candidates_cost.begin(), tqe_candidates_cost.end(),
216 std::back_inserter(selected_tqes),
217 [](const auto& pair) { return pair.first; });
218 return {selected_tqes, rot2qs};
219 }
220 // if only one cost variable, no need to normalise
221 if (valid_indices.size() == 1) {
222 auto it = tqe_candidates_cost.begin();
223 double min_cost = it->second[valid_indices[0]];
224 std::vector<TQE> min_tqes = {it->first};
225 for (; it != tqe_candidates_cost.end(); it++) {
226 if (it->second[valid_indices[0]] < min_cost) {
227 min_tqes = {it->first};
228 min_cost = it->second[valid_indices[0]];
229 } else if (it->second[valid_indices[0]] == min_cost) {
230 min_tqes.push_back(it->first);
231 }
232 }
233 std::vector<Rotation2Q> min_rot2qs;
234 for (auto it2 = rot2q_gates_cost.begin(); it2 != rot2q_gates_cost.end();
235 it2++) {
236 if (it2->second[valid_indices[0]] < min_cost) {
237 min_tqes.clear();
238 min_cost = it2->second[valid_indices[0]];
239 min_rot2qs = {it2->first};
240 } else if (it2->second[valid_indices[0]] == min_cost) {
241 min_rot2qs.push_back(it2->first);
242 }
243 }
244 return {min_tqes, min_rot2qs};
245 }
246 // find the tqe with the minimum normalised cost
247 auto it = tqe_candidates_cost.begin();
248 double min_cost = 0;
249 std::vector<TQE> min_tqes = {it->first};
250 // initialise min_cost
251 for (const auto& cost_index : valid_indices) {
252 min_cost += weights[cost_index] *
253 (it->second[cost_index] - mins[cost_index]) /
254 (maxs[cost_index] - mins[cost_index]);
255 }
256 it++;
257 // iterate all tqes
258 for (; it != tqe_candidates_cost.end(); it++) {
259 double cost = 0;
260 for (const auto& cost_index : valid_indices) {
261 cost += weights[cost_index] *
262 (it->second[cost_index] - mins[cost_index]) /
263 (maxs[cost_index] - mins[cost_index]);
264 }
265 if (cost < min_cost) {
266 min_cost = cost;
267 min_tqes = {it->first};
268 } else if (cost == min_cost) {
269 min_tqes.push_back(it->first);
270 }
271 }
272 std::vector<Rotation2Q> min_rot2qs;
273 for (auto it2 = rot2q_gates_cost.begin(); it2 != rot2q_gates_cost.end();
274 it2++) {
275 double cost = 0;
276 for (const auto& cost_index : valid_indices) {
277 cost += weights[cost_index] *
278 (it2->second[cost_index] - mins[cost_index]) /
279 (maxs[cost_index] - mins[cost_index]);
280 }
281 if (cost < min_cost) {
282 min_cost = cost;
283 min_tqes.clear();
284 min_rot2qs = {it2->first};
285 } else if (cost == min_cost) {
286 min_rot2qs.push_back(it2->first);
287 }
288 }
289 return {min_tqes, min_rot2qs};
290}
291
292// simple struct that tracks the depth on each qubit
293struct DepthTracker {
294 std::vector<unsigned> qubit_depth;
295 unsigned max_depth;
296 DepthTracker(unsigned n) : qubit_depth(n, 0), max_depth(0) {};
297
298 unsigned gate_depth(unsigned a, unsigned b) const {
299 return std::max(qubit_depth[a], qubit_depth[b]) + 1;
300 };
301 void add_1q_gate(unsigned a) {
302 qubit_depth[a]++;
303 if (qubit_depth[a] > max_depth) {
304 max_depth = qubit_depth[a];
305 }
306 };
307 void add_2q_gate(unsigned a, unsigned b) {
308 unsigned new_gate_depth = gate_depth(a, b);
309 qubit_depth[a] = new_gate_depth;
310 qubit_depth[b] = new_gate_depth;
311 if (new_gate_depth > max_depth) {
312 max_depth = new_gate_depth;
313 }
314 };
315};
316
320static void tableau_row_nodes_synthesis(
321 std::vector<PauliNode_ptr>& rows, Circuit& circ,
322 DepthTracker& depth_tracker, double depth_weight, unsigned max_lookahead,
323 unsigned max_tqe_candidates, unsigned seed,
324 std::shared_ptr<std::atomic<bool>> stop_flag) {
325 // only consider nodes with a non-zero cost
326 std::vector<unsigned> remaining_indices;
327 for (unsigned i = 0; i < rows.size(); i++) {
328 if (rows[i]->tqe_cost() > 0) {
329 remaining_indices.push_back(i);
330 }
331 }
332 while (remaining_indices.size() != 0) {
333 // check early termination
334 if (stop_flag.get()->load()) {
335 return;
336 };
337 // get nodes with min cost
338 std::vector<unsigned> min_nodes_indices = {remaining_indices[0]};
339 unsigned min_cost = rows[remaining_indices[0]]->tqe_cost();
340 for (unsigned i = 1; i < remaining_indices.size(); i++) {
341 unsigned node_cost = rows[remaining_indices[i]]->tqe_cost();
342 if (node_cost == min_cost) {
343 min_nodes_indices.push_back(remaining_indices[i]);
344 } else if (node_cost < min_cost) {
345 min_nodes_indices = {remaining_indices[i]};
346 min_cost = node_cost;
347 }
348 }
349 // for each node with min cost, find the list of tqe gates that can reduce
350 // its cost
351 std::set<TQE> tqe_candidates;
352 TKET_ASSERT(min_nodes_indices.size() > 0);
353 for (const unsigned& index : min_nodes_indices) {
354 std::vector<TQE> node_reducing_tqes = rows[index]->reduction_tqes();
355 tqe_candidates.insert(
356 node_reducing_tqes.begin(), node_reducing_tqes.end());
357 }
358 // sample
359 std::vector<TQE> sampled_tqes =
360 sample_tqes(tqe_candidates, max_tqe_candidates, seed);
361 // for each tqe we compute a vector of cost factors which will
362 // be combined to make the final decision.
363 // we currently only consider tqe_cost and gate_depth.
364 std::vector<std::pair<TQE, std::array<double, 2>>> tqe_candidates_cost;
365 for (const TQE& tqe : sampled_tqes) {
366 tqe_candidates_cost.push_back(
367 {tqe,
368 {default_tableau_tqe_cost(
369 rows, remaining_indices, tqe, max_lookahead),
370 static_cast<double>(depth_tracker.gate_depth(tqe.a, tqe.b))}});
371 }
372 TKET_ASSERT(tqe_candidates_cost.size() > 0);
373 // select the best one
374 auto [min_tqes, _] =
375 minmax_selection(tqe_candidates_cost, {}, {1, depth_weight});
376 TQE selected_tqe = sample_random_tqe(min_tqes, seed);
377 // apply TQE
378 apply_tqe_to_circ(selected_tqe, circ);
379 // update depth tracker
380 depth_tracker.add_2q_gate(selected_tqe.a, selected_tqe.b);
381 // remove finished nodes
382 for (unsigned i = remaining_indices.size(); i-- > 0;) {
383 unsigned node_index = remaining_indices[i];
384 rows[node_index]->update(selected_tqe);
385 if (rows[node_index]->tqe_cost() == 0) {
386 remaining_indices.erase(remaining_indices.begin() + i);
387 }
388 }
389 }
390 // apply local Cliffords
391 for (PauliNode_ptr& node_ptr : rows) {
392 PauliPropagation& node = dynamic_cast<PauliPropagation&>(*node_ptr);
393 auto [q_index, supp_z, supp_x] = node.first_support();
394 // transform supp_z,supp_x to Z,X
395 std::vector<OpType> optype_list = AA_TO_ZX.at({supp_z, supp_x});
396 for (auto it = optype_list.rbegin(); it != optype_list.rend(); ++it) {
397 circ.add_op<unsigned>(SQ_CLIFF_DAGGER.at(*it), {q_index});
398 node.update(*it, q_index);
399 }
400 // remove signs
401 if (!node.z_sign()) {
402 circ.add_op<unsigned>(OpType::X, {q_index});
403 node.update(OpType::X, q_index);
404 }
405 if (!node.x_sign()) {
406 circ.add_op<unsigned>(OpType::Z, {q_index});
407 node.update(OpType::Z, q_index);
408 }
409 if (q_index != node.qubit_index()) {
410 circ.add_op<unsigned>(OpType::SWAP, {q_index, node.qubit_index()});
411 for (PauliNode_ptr& node_ptr2 : rows) {
412 node_ptr2->swap(q_index, node.qubit_index());
413 }
414 }
415 }
416}
417
428static void consume_nodes(
429 std::vector<std::vector<PauliNode_ptr>>& rotation_sets, Circuit& circ,
430 DepthTracker& depth_tracker, double discount_rate, double depth_weight) {
431 if (rotation_sets.empty()) {
432 return;
433 }
434 while (true) {
435 std::vector<PauliNode_ptr>& first_set = rotation_sets[0];
436 for (unsigned i = first_set.size(); i-- > 0;) {
437 PauliNode_ptr& node_ptr = first_set[i];
438 switch (node_ptr->get_type()) {
440 if (node_ptr->tqe_cost() > 0) continue;
441 Reset& node = dynamic_cast<Reset&>(*node_ptr);
442 auto [q_index, supp_z, supp_x] = node.first_support();
443 // conjugate the pair to +Z/X
444 std::vector<OpType> optype_list = AA_TO_ZX.at({supp_z, supp_x});
445 for (auto it = optype_list.begin(); it != optype_list.end(); ++it) {
446 circ.add_op<unsigned>(*it, {q_index});
447 }
448 if (!node.z_sign()) {
449 circ.add_op<unsigned>(OpType::X, {q_index});
450 }
451 if (!node.x_sign()) {
452 circ.add_op<unsigned>(OpType::Z, {q_index});
453 }
454 circ.add_op<unsigned>(OpType::Reset, {q_index});
455 if (!node.z_sign()) {
456 circ.add_op<unsigned>(OpType::X, {q_index});
457 }
458 if (!node.x_sign()) {
459 circ.add_op<unsigned>(OpType::Z, {q_index});
460 }
461 for (auto it = optype_list.rbegin(); it != optype_list.rend(); ++it) {
462 circ.add_op<unsigned>(SQ_CLIFF_DAGGER.at(*it), {q_index});
463 }
464 first_set.erase(first_set.begin() + i);
465 break;
466 }
468 if (node_ptr->tqe_cost() > 0) continue;
469 MidMeasure& node = dynamic_cast<MidMeasure&>(*node_ptr);
470 auto [q_index, supp] = node.first_support();
471 // Conjugate the Pauli to +Z
472 switch (supp) {
473 case Pauli::Z: {
474 if (!node.sign()) {
475 circ.add_op<unsigned>(OpType::X, {q_index});
476 }
477 circ.add_measure(q_index, node.bit());
478 if (!node.sign()) {
479 circ.add_op<unsigned>(OpType::X, {q_index});
480 }
481 break;
482 }
483 case Pauli::X: {
484 circ.add_op<unsigned>(OpType::H, {q_index});
485 if (!node.sign()) {
486 circ.add_op<unsigned>(OpType::X, {q_index});
487 }
488 circ.add_measure(q_index, node.bit());
489 if (!node.sign()) {
490 circ.add_op<unsigned>(OpType::X, {q_index});
491 }
492 circ.add_op<unsigned>(OpType::H, {q_index});
493 break;
494 }
495 case Pauli::Y: {
496 if (!node.sign()) {
497 circ.add_op<unsigned>(OpType::Vdg, {q_index});
498 } else {
499 circ.add_op<unsigned>(OpType::V, {q_index});
500 }
501 circ.add_measure(q_index, node.bit());
502 if (!node.sign()) {
503 circ.add_op<unsigned>(OpType::V, {q_index});
504 } else {
505 circ.add_op<unsigned>(OpType::Vdg, {q_index});
506 }
507 break;
508 }
509 default: {
510 TKET_ASSERT(false);
511 }
512 }
513 first_set.erase(first_set.begin() + i);
514 break;
515 }
517 // always implement Classical nodes
518 ClassicalNode& node = dynamic_cast<ClassicalNode&>(*node_ptr);
519 circ.add_op<UnitID>(node.op(), node.args());
520 first_set.erase(first_set.begin() + i);
521 break;
522 }
524 // conditionals are implemented as a conditioned sequence of
525 // PauliExpBoxes and subsequently optimised by recursively calling
526 // greedy_pauli_optimisation
527 ConditionalBlock& node = dynamic_cast<ConditionalBlock&>(*node_ptr);
528 const std::vector<unsigned> cond_bits = node.cond_bits();
529 const unsigned cond_value = node.cond_value();
530 std::vector<unsigned> qubits;
531 for (unsigned i = 0; i < circ.n_qubits(); i++) {
532 qubits.push_back(i);
533 }
534 Circuit cond_circ(circ.n_qubits());
535 for (const auto& t : node.rotations()) {
536 const std::vector<Pauli>& string = std::get<0>(t);
537 bool sign = std::get<1>(t);
538 Expr angle = sign ? std::get<2>(t) : -std::get<2>(t);
539 Op_ptr peb_op =
540 std::make_shared<PauliExpBox>(SymPauliTensor(string, angle));
541 cond_circ.add_op<unsigned>(peb_op, qubits);
542 }
543 greedy_pauli_optimisation(discount_rate, depth_weight)
544 .apply(cond_circ);
545 // replace implicit wire swaps
546 cond_circ.replace_all_implicit_wire_swaps();
547 Op_ptr cond = std::make_shared<Conditional>(
548 std::make_shared<CircBox>(cond_circ), (unsigned)cond_bits.size(),
549 cond_value);
550 std::vector<unsigned> args = cond_bits;
551 for (unsigned i = 0; i < cond_circ.n_qubits(); i++) {
552 args.push_back(i);
553 }
554 circ.add_op<unsigned>(cond, args);
555 first_set.erase(first_set.begin() + i);
556 break;
557 }
559 if (node_ptr->tqe_cost() > 0) continue;
560 PauliRotation& node = dynamic_cast<PauliRotation&>(*node_ptr);
561 auto [q_index, supp] = node.first_support();
562 depth_tracker.add_1q_gate(q_index);
563 OpType rot_type;
564 switch (supp) {
565 case Pauli::Y: {
566 rot_type = OpType::Ry;
567 break;
568 }
569 case Pauli::Z: {
570 rot_type = OpType::Rz;
571 break;
572 }
573 case Pauli::X: {
574 rot_type = OpType::Rx;
575 break;
576 }
577 default:
578 // support can't be Pauli::I
579 TKET_ASSERT(false);
580 }
581 circ.add_op<unsigned>(rot_type, node.angle(), {q_index});
582 first_set.erase(first_set.begin() + i);
583 break;
584 }
585 default:
586 TKET_ASSERT(false);
587 }
588 }
589 if (first_set.empty()) {
590 rotation_sets.erase(rotation_sets.begin());
591 if (rotation_sets.empty()) {
592 return;
593 }
594 } else {
595 return;
596 }
597 }
598}
599
603static void pauli_exps_synthesis(
604 std::vector<std::vector<PauliNode_ptr>>& rotation_sets,
605 std::vector<PauliNode_ptr>& rows, Circuit& circ,
606 DepthTracker& depth_tracker, double discount_rate, double depth_weight,
607 unsigned max_lookahead, unsigned max_tqe_candidates, unsigned seed,
608 bool allow_zzphase, std::shared_ptr<std::atomic<bool>> stop_flag) {
609 while (true) {
610 // check timeout
611 if (stop_flag.get()->load()) {
612 return;
613 };
614
615 consume_nodes(
616 rotation_sets, circ, depth_tracker, discount_rate, depth_weight);
617
618 if (rotation_sets.empty()) break;
619
620 std::vector<PauliNode_ptr>& first_set = rotation_sets[0];
621 // get nodes with min cost
622 std::vector<unsigned> min_nodes_indices = {0};
623 unsigned min_cost = first_set[0]->tqe_cost();
624 for (unsigned i = 1; i < first_set.size(); i++) {
625 unsigned node_cost = first_set[i]->tqe_cost();
626 if (node_cost == min_cost) {
627 min_nodes_indices.push_back(i);
628 } else if (node_cost < min_cost) {
629 min_nodes_indices = {i};
630 min_cost = node_cost;
631 }
632 }
633 std::set<TQE> tqe_candidates;
634 for (const unsigned& index : min_nodes_indices) {
635 std::vector<TQE> node_reducing_tqes = first_set[index]->reduction_tqes();
636 tqe_candidates.insert(
637 node_reducing_tqes.begin(), node_reducing_tqes.end());
638 }
639 // sample
640 std::vector<TQE> sampled_tqes =
641 sample_tqes(tqe_candidates, max_tqe_candidates, seed);
642
643 // for each tqe we compute costs which might subject to normalisation
644 std::vector<std::pair<TQE, std::array<double, 2>>> tqe_candidates_cost;
645 for (const TQE& tqe : sampled_tqes) {
646 tqe_candidates_cost.push_back(
647 {tqe,
648 {default_pauliexp_tqe_cost(
649 discount_rate, rotation_sets, rows, tqe, max_lookahead),
650 static_cast<double>(depth_tracker.gate_depth(tqe.a, tqe.b))}});
651 }
652 std::vector<std::pair<Rotation2Q, std::array<double, 2>>> rot2q_gates_cost;
653 if (allow_zzphase) {
654 // implementing a 2q rotation directly will result in a
655 // -1 tqe cost change in the first rotation set and 0 elsewhere.
656 // If multiple 2q rotations are worth implementing directly, we
657 // implement all of them to avoid doing the same cost calculation
658 // in the next rounds.
659 for (unsigned i = 0; i < first_set.size(); i++) {
660 if (first_set[i]->get_type() == PauliNodeType::PauliRotation &&
661 first_set[i]->tqe_cost() == 1) {
662 const PauliRotation& node =
663 dynamic_cast<const PauliRotation&>(*first_set[i]);
664 std::vector<unsigned> supps;
665 std::vector<Pauli> paulis;
666 for (unsigned j = 0; j < node.string().size(); j++) {
667 if (node.string()[j] != Pauli::I) {
668 supps.push_back(j);
669 paulis.push_back(node.string()[j]);
670 }
671 }
672 rot2q_gates_cost.push_back(
673 {{paulis[0], paulis[1], supps[0], supps[1], node.angle(), i},
674 {-1, static_cast<double>(
675 depth_tracker.gate_depth(supps[0], supps[1]))}});
676 }
677 }
678 }
679 // select the best one
680 auto [min_tqes, min_rot2qs] = minmax_selection(
681 tqe_candidates_cost, rot2q_gates_cost, {1, depth_weight});
682 if (min_rot2qs.empty()) {
683 TQE selected_tqe = sample_random_tqe(min_tqes, seed);
684 // apply TQE
685 apply_tqe_to_circ(selected_tqe, circ);
686 depth_tracker.add_2q_gate(selected_tqe.a, selected_tqe.b);
687 for (std::vector<PauliNode_ptr>& rotation_set : rotation_sets) {
688 for (PauliNode_ptr& node : rotation_set) {
689 node->update(selected_tqe);
690 }
691 }
692 for (PauliNode_ptr& row : rows) {
693 row->update(selected_tqe);
694 }
695 } else {
696 // apply 2q rotations directly
697 std::sort(
698 min_rot2qs.begin(), min_rot2qs.end(),
699 [](const Rotation2Q& r1, const Rotation2Q& r2) {
700 return r1.index > r2.index;
701 });
702 for (const Rotation2Q& rot : min_rot2qs) {
703 apply_rot2q_to_circ(rot, circ);
704 first_set.erase(first_set.begin() + rot.index);
705 }
706 }
707 }
708}
709
711 const std::vector<SymPauliTensor>& unordered_set, double depth_weight,
712 unsigned max_lookahead, unsigned max_tqe_candidates, unsigned seed,
713 bool allow_zzphase) {
714 if (max_lookahead == 0) {
715 throw GreedyPauliSimpError("max_lookahead must be greater than 0.");
716 }
717 if (max_tqe_candidates == 0) {
718 throw GreedyPauliSimpError("max_tqe_candidates must be greater than 0.");
719 }
720
721 if (unordered_set.size() == 0) {
722 return Circuit();
723 }
724 unsigned n_qubits = unordered_set[0].string.size();
725 Circuit c(n_qubits);
726 auto [rotation_set, rows] = gpg_from_unordered_set(unordered_set);
727 std::vector<std::vector<PauliNode_ptr>> rotation_sets{rotation_set};
728 DepthTracker depth_tracker(n_qubits);
729 // synthesise Pauli exps
730 std::shared_ptr<std::atomic<bool>> dummy_stop_flag =
731 std::make_shared<std::atomic<bool>>(false);
732 pauli_exps_synthesis(
733 rotation_sets, rows, c, depth_tracker, 0, depth_weight, max_lookahead,
734 max_tqe_candidates, seed, allow_zzphase, dummy_stop_flag);
735 // synthesise the tableau
736 tableau_row_nodes_synthesis(
737 rows, c, depth_tracker, depth_weight, max_lookahead, max_tqe_candidates,
738 seed, dummy_stop_flag);
739 c.replace_SWAPs();
740 return c;
741}
742
744 const Circuit& circ, std::shared_ptr<std::atomic<bool>> stop_flag,
745 double discount_rate, double depth_weight, unsigned max_lookahead,
746 unsigned max_tqe_candidates, unsigned seed, bool allow_zzphase) {
747 if (max_lookahead == 0) {
748 throw GreedyPauliSimpError("max_lookahead must be greater than 0.");
749 }
750 if (max_tqe_candidates == 0) {
751 throw GreedyPauliSimpError("max_tqe_candidates must be greater than 0.");
752 }
753 Circuit circ_flat(circ);
754 unsigned n_qubits = circ_flat.n_qubits();
755 unsigned n_bits = circ_flat.n_bits();
756 // empty circuit
757 Circuit new_circ(n_qubits, n_bits);
758 std::optional<std::string> name = circ_flat.get_name();
759 if (name != std::nullopt) {
760 new_circ.set_name(name.value());
761 }
762 unit_map_t unit_map = circ_flat.flatten_registers();
763 unit_map_t rev_unit_map;
764 for (const auto& pair : unit_map) {
765 rev_unit_map.insert({pair.second, pair.first});
766 }
767
768 GPGraph gpg(circ_flat.all_qubits(), circ_flat.all_bits());
769 for (const Command& cmd : circ_flat.get_commands()) {
770 if (stop_flag.get()->load()) {
771 return Circuit();
772 }
773 gpg.apply_gate_at_end(cmd);
774 }
775
776 // We regularly check whether the timeout has ocurred
777 if (stop_flag.get()->load()) {
778 return Circuit();
779 }
780
781 auto [rotation_sets, rows, measures] = gpg.get_sequence(stop_flag);
782
783 if (stop_flag.get()->load()) {
784 return Circuit();
785 }
786
787 DepthTracker depth_tracker(n_qubits);
788 // synthesise Pauli exps
789 pauli_exps_synthesis(
790 rotation_sets, rows, new_circ, depth_tracker, discount_rate, depth_weight,
791 max_lookahead, max_tqe_candidates, seed, allow_zzphase, stop_flag);
792
793 if (stop_flag.get()->load()) {
794 return Circuit();
795 }
796 // synthesise the tableau
797 tableau_row_nodes_synthesis(
798 rows, new_circ, depth_tracker, depth_weight, max_lookahead,
799 max_tqe_candidates, seed, stop_flag);
800
801 if (stop_flag.get()->load()) {
802 return Circuit();
803 }
804
805 for (auto it = measures.begin(); it != measures.end(); ++it) {
806 new_circ.add_measure(it->left, it->right);
807 }
808 new_circ.rename_units(rev_unit_map);
809 new_circ.replace_SWAPs();
810 return new_circ;
811}
812
814 const Circuit& circ, double discount_rate, double depth_weight,
815 unsigned max_lookahead, unsigned max_tqe_candidates, unsigned seed,
816 bool allow_zzphase) {
817 std::shared_ptr<std::atomic<bool>> dummy_stop_flag =
818 std::make_shared<std::atomic<bool>>(false);
820 circ, dummy_stop_flag, discount_rate, depth_weight, max_lookahead,
821 max_tqe_candidates, seed, allow_zzphase);
822}
823
824} // namespace GreedyPauliSimp
825
827 double discount_rate, double depth_weight, unsigned max_lookahead,
828 unsigned max_tqe_candidates, unsigned seed, bool allow_zzphase,
829 unsigned thread_timeout, unsigned trials) {
830 return Transform([discount_rate, depth_weight, max_lookahead,
831 max_tqe_candidates, seed, allow_zzphase, thread_timeout,
832 trials](Circuit& circ) {
833 std::mt19937 seed_gen(seed);
834 std::vector<Circuit> circuits;
835 circuits.reserve(trials);
836 unsigned threads_started = 0;
837
838 while (threads_started < trials) {
839 std::shared_ptr<std::atomic<bool>> stop_flag =
840 std::make_shared<std::atomic<bool>>(false);
841 std::future<Circuit> future = std::async(
842 std::launch::async,
843 [&, stop_flag]() { // Capture `stop_flag` explicitly in the lambda
845 circ, stop_flag, discount_rate, depth_weight, max_lookahead,
846 max_tqe_candidates, seed_gen(), allow_zzphase);
847 });
848 threads_started++;
849
850 if (future.wait_for(std::chrono::seconds(thread_timeout)) ==
851 std::future_status::ready) {
852 circuits.emplace_back();
853 circuits.back() = future.get();
854 circuits.back().decompose_boxes_recursively();
855 } else {
856 // If the thread isn't complete within time, prompt cancelling the
857 // optimisation and break from while loop
858 *stop_flag = true;
859 break;
860 }
861 }
862
863 // Return the smallest circuit if any were found within the single
864 // thread_timeout
865 // If none are found then return false
866 if (circuits.empty()) return false;
867 auto min = std::min_element(
868 circuits.begin(), circuits.end(),
869 [](const Circuit& a, const Circuit& b) {
870 const auto two_qubit_gates_a = a.count_n_qubit_gates(2);
871 const auto two_qubit_gates_b = b.count_n_qubit_gates(2);
872 if (two_qubit_gates_a != two_qubit_gates_b) {
873 return two_qubit_gates_a < two_qubit_gates_b;
874 }
875 const auto n_gates_a = a.n_gates();
876 const auto n_gates_b = b.n_gates();
877 if (n_gates_a != n_gates_b) {
878 return n_gates_a < n_gates_b;
879 }
880 return a.depth() < b.depth();
881 });
882 circ = std::move(*min);
883 return true;
884 });
885}
886
887} // namespace Transforms
888
889} // namespace tket
A circuit.
Definition Circuit.hpp:212
bool rename_units(const std::map< UnitA, UnitB > &qm)
Rename all the units according to the given mapping.
Definition Circuit.hpp:1643
unsigned n_qubits() const
unsigned depth() const
Depth of circuit.
void set_name(const std::string _name)
Set the name of the circuit.
Definition Circuit.hpp:1597
qubit_vector_t all_qubits() const
bit_vector_t all_bits() const
unit_map_t flatten_registers()
Convert all quantum and classical bits to use default registers.
unsigned n_gates() const
std::optional< std::string > get_name() const
Get the name of the circuit.
Definition Circuit.hpp:1590
unsigned n_bits() const
Vertex add_measure(const Qubit &qubit, const Bit &bit)
Add a measure operation from a qubit to a bit.
Definition Circuit.hpp:767
void replace_SWAPs()
Replace all explicit swaps (i.e.
std::vector< Command > get_commands() const
Get the sequence of commands comprising the circuit.
Definition Circuit.cpp:105
A transformation of a circuit that preserves its semantics.
Definition Transform.hpp:27
bool apply(Circuit &circ) const
Apply the transform to a circuit.
Definition Transform.hpp:69
Pauli graph structure for GreedyPauliSimp.
void apply_gate_at_end(const Command &cmd, bool conditional=false, std::vector< unsigned > cond_bits={}, unsigned cond_value=0)
Applies the given gate to the end of the graph.
std::tuple< std::vector< std::vector< PauliNode_ptr > >, std::vector< PauliNode_ptr >, boost::bimap< unsigned, unsigned > > get_sequence(std::shared_ptr< std::atomic< bool > > stop_flag)
STL namespace.
Circuit greedy_pauli_set_synthesis(const std::vector< SymPauliTensor > &unordered_set, double depth_weight, unsigned max_lookahead, unsigned max_tqe_candidates, unsigned seed, bool allow_zzphase)
Synthesise a set of unordered Pauli exponentials by applying Clifford gates and single qubit rotation...
Circuit greedy_pauli_graph_synthesis(const Circuit &circ, double discount_rate, double depth_weight, unsigned max_lookahead, unsigned max_tqe_candidates, unsigned seed, bool allow_zzphase)
Converts the given circuit into a GPGraph and conjugates each node by greedily applying 2-qubit Cliff...
TQEType
Types of 2-qubit entangled Clifford gates.
std::tuple< std::vector< PauliNode_ptr >, std::vector< PauliNode_ptr > > gpg_from_unordered_set(const std::vector< SymPauliTensor > &unordered_set)
Convert a unordered set of SymPauliTensor into a set of PauliRotations followed by a set of PauliProp...
std::shared_ptr< PauliNode > PauliNode_ptr
Circuit greedy_pauli_graph_synthesis_flag(const Circuit &circ, std::shared_ptr< std::atomic< bool > > stop_flag, double discount_rate, double depth_weight, unsigned max_lookahead, unsigned max_tqe_candidates, unsigned seed, bool allow_zzphase)
Transform greedy_pauli_optimisation(double discount_rate, double depth_weight, unsigned max_lookahead, unsigned max_tqe_candidates, unsigned seed, bool allow_zzphase, unsigned thread_timeout, unsigned trials)
Defines tket::DeviceCharacterisation, used in NoiseAwarePlacement and in commute_SQ_gates_through_SWA...
Definition Path.cpp:22
OpType
Named operation types.
Definition OpType.hpp:29
@ SWAP
Swap two qubits.
@ Reset
Reset a qubit to the zero state.
@ CX
Controlled OpType::X.
@ CY
Controlled OpType::Y.
@ CZ
Controlled OpType::Z.
SymEngine::Expression Expr
Representation of a phase as a multiple of .
std::shared_ptr< const Op > Op_ptr
Definition OpPtr.hpp:24
std::map< UnitID, UnitID > unit_map_t
Definition UnitID.hpp:312
PauliTensor< DensePauliMap, Expr > SymPauliTensor