32namespace GreedyPauliSimp {
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);
43static std::vector<TQE> sample_tqes(
44 const std::set<TQE>& tqes,
size_t k,
unsigned seed) {
46 size_t unsampled_sz = tqes.size();
47 auto first = std::begin(tqes);
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);
54 vec.push_back(*first);
61static void apply_rot2q_to_circ(
const Rotation2Q& rot, Circuit& circ) {
63 circ.add_op<
unsigned>(
OpType::H, {rot.a});
65 circ.add_op<
unsigned>(
OpType::V, {rot.a});
68 circ.add_op<
unsigned>(
OpType::H, {rot.b});
70 circ.add_op<
unsigned>(
OpType::V, {rot.b});
74 circ.add_op<
unsigned>(
OpType::H, {rot.a});
79 circ.add_op<
unsigned>(
OpType::H, {rot.b});
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;
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) {
135 for (
const unsigned& index : remaining_indices) {
136 cost += rows[index]->tqe_cost_increase(tqe);
137 if (++count >= max_lookahead)
break;
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);
154 for (
const std::vector<PauliNode_ptr>& rotation_set : rotation_sets) {
157 r_cost += node->tqe_cost_increase(tqe);
158 if (++count >= max_lookahead)
break;
160 exp_cost += weight * r_cost;
161 if (count >= max_lookahead)
break;
165 tab_cost += weight * node->tqe_cost_increase(tqe);
166 if (++count >= max_lookahead)
break;
168 return exp_cost + tab_cost;
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>>>&
180 const std::vector<std::pair<Rotation2Q, std::array<double, n_costs>>>&
182 const std::array<double, n_costs>& weights) {
183 static_assert(n_costs > 0,
"n_costs must be greater than 0");
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];
192 if (pair.second[cost_index] > maxs[cost_index]) {
193 maxs[cost_index] = pair.second[cost_index];
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);
205 if (valid_indices.size() == 0) {
206 std::vector<Rotation2Q> rot2qs;
207 rot2qs.reserve(rot2q_gates_cost.size());
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());
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};
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);
233 std::vector<Rotation2Q> min_rot2qs;
234 for (
auto it2 = rot2q_gates_cost.begin(); it2 != rot2q_gates_cost.end();
236 if (it2->second[valid_indices[0]] < min_cost) {
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);
244 return {min_tqes, min_rot2qs};
247 auto it = tqe_candidates_cost.begin();
249 std::vector<TQE> min_tqes = {it->first};
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]);
258 for (; it != tqe_candidates_cost.end(); it++) {
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]);
265 if (cost < min_cost) {
267 min_tqes = {it->first};
268 }
else if (cost == min_cost) {
269 min_tqes.push_back(it->first);
272 std::vector<Rotation2Q> min_rot2qs;
273 for (
auto it2 = rot2q_gates_cost.begin(); it2 != rot2q_gates_cost.end();
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]);
281 if (cost < min_cost) {
284 min_rot2qs = {it2->first};
285 }
else if (cost == min_cost) {
286 min_rot2qs.push_back(it2->first);
289 return {min_tqes, min_rot2qs};
294 std::vector<unsigned> qubit_depth;
296 DepthTracker(
unsigned n) : qubit_depth(n, 0), max_depth(0) {};
298 unsigned gate_depth(
unsigned a,
unsigned b)
const {
299 return std::max(qubit_depth[a], qubit_depth[b]) + 1;
301 void add_1q_gate(
unsigned a) {
303 if (qubit_depth[a] > max_depth) {
304 max_depth = qubit_depth[a];
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;
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) {
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);
332 while (remaining_indices.size() != 0) {
334 if (stop_flag.get()->load()) {
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;
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());
359 std::vector<TQE> sampled_tqes =
360 sample_tqes(tqe_candidates, max_tqe_candidates, seed);
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(
368 {default_tableau_tqe_cost(
369 rows, remaining_indices, tqe, max_lookahead),
370 static_cast<double>(depth_tracker.gate_depth(tqe.a, tqe.b))}});
372 TKET_ASSERT(tqe_candidates_cost.size() > 0);
375 minmax_selection(tqe_candidates_cost, {}, {1, depth_weight});
376 TQE selected_tqe = sample_random_tqe(min_tqes, seed);
378 apply_tqe_to_circ(selected_tqe, circ);
380 depth_tracker.add_2q_gate(selected_tqe.a, selected_tqe.b);
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);
393 auto [q_index, supp_z, supp_x] = node.first_support();
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);
401 if (!node.z_sign()) {
402 circ.add_op<
unsigned>(
OpType::X, {q_index});
405 if (!node.x_sign()) {
406 circ.add_op<
unsigned>(
OpType::Z, {q_index});
409 if (q_index != node.qubit_index()) {
410 circ.add_op<
unsigned>(
OpType::SWAP, {q_index, node.qubit_index()});
412 node_ptr2->swap(q_index, node.qubit_index());
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()) {
435 std::vector<PauliNode_ptr>& first_set = rotation_sets[0];
436 for (
unsigned i = first_set.size(); i-- > 0;) {
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();
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});
448 if (!node.z_sign()) {
449 circ.add_op<
unsigned>(
OpType::X, {q_index});
451 if (!node.x_sign()) {
452 circ.add_op<
unsigned>(
OpType::Z, {q_index});
455 if (!node.z_sign()) {
456 circ.add_op<
unsigned>(
OpType::X, {q_index});
458 if (!node.x_sign()) {
459 circ.add_op<
unsigned>(
OpType::Z, {q_index});
461 for (
auto it = optype_list.rbegin(); it != optype_list.rend(); ++it) {
462 circ.add_op<
unsigned>(SQ_CLIFF_DAGGER.at(*it), {q_index});
464 first_set.erase(first_set.begin() + i);
468 if (node_ptr->tqe_cost() > 0)
continue;
470 auto [q_index, supp] = node.first_support();
475 circ.add_op<
unsigned>(
OpType::X, {q_index});
477 circ.add_measure(q_index, node.bit());
479 circ.add_op<
unsigned>(
OpType::X, {q_index});
484 circ.add_op<
unsigned>(
OpType::H, {q_index});
486 circ.add_op<
unsigned>(
OpType::X, {q_index});
488 circ.add_measure(q_index, node.bit());
490 circ.add_op<
unsigned>(
OpType::X, {q_index});
492 circ.add_op<
unsigned>(
OpType::H, {q_index});
499 circ.add_op<
unsigned>(
OpType::V, {q_index});
501 circ.add_measure(q_index, node.bit());
503 circ.add_op<
unsigned>(
OpType::V, {q_index});
513 first_set.erase(first_set.begin() + i);
519 circ.add_op<UnitID>(node.op(), node.args());
520 first_set.erase(first_set.begin() + i);
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++) {
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);
541 cond_circ.add_op<
unsigned>(peb_op, qubits);
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(),
550 std::vector<unsigned> args = cond_bits;
551 for (
unsigned i = 0; i < cond_circ.n_qubits(); i++) {
554 circ.add_op<
unsigned>(cond, args);
555 first_set.erase(first_set.begin() + i);
559 if (node_ptr->tqe_cost() > 0)
continue;
561 auto [q_index, supp] = node.first_support();
562 depth_tracker.add_1q_gate(q_index);
581 circ.add_op<
unsigned>(rot_type, node.angle(), {q_index});
582 first_set.erase(first_set.begin() + i);
589 if (first_set.empty()) {
590 rotation_sets.erase(rotation_sets.begin());
591 if (rotation_sets.empty()) {
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) {
611 if (stop_flag.get()->load()) {
616 rotation_sets, circ, depth_tracker, discount_rate, depth_weight);
618 if (rotation_sets.empty())
break;
620 std::vector<PauliNode_ptr>& first_set = rotation_sets[0];
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;
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());
640 std::vector<TQE> sampled_tqes =
641 sample_tqes(tqe_candidates, max_tqe_candidates, seed);
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(
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))}});
652 std::vector<std::pair<Rotation2Q, std::array<double, 2>>> rot2q_gates_cost;
659 for (
unsigned i = 0; i < first_set.size(); i++) {
661 first_set[i]->tqe_cost() == 1) {
664 std::vector<unsigned> supps;
665 std::vector<Pauli> paulis;
666 for (
unsigned j = 0; j < node.string().size(); j++) {
669 paulis.push_back(node.string()[j]);
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]))}});
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);
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) {
689 node->update(selected_tqe);
693 row->update(selected_tqe);
698 min_rot2qs.begin(), min_rot2qs.end(),
699 [](
const Rotation2Q& r1,
const Rotation2Q& r2) {
700 return r1.index > r2.index;
702 for (
const Rotation2Q& rot : min_rot2qs) {
703 apply_rot2q_to_circ(rot, circ);
704 first_set.erase(first_set.begin() + rot.index);
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) {
717 if (max_tqe_candidates == 0) {
721 if (unordered_set.size() == 0) {
724 unsigned n_qubits = unordered_set[0].string.size();
727 std::vector<std::vector<PauliNode_ptr>> rotation_sets{rotation_set};
728 DepthTracker depth_tracker(n_qubits);
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);
736 tableau_row_nodes_synthesis(
737 rows, c, depth_tracker, depth_weight, max_lookahead, max_tqe_candidates,
738 seed, dummy_stop_flag);
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) {
750 if (max_tqe_candidates == 0) {
754 unsigned n_qubits = circ_flat.
n_qubits();
755 unsigned n_bits = circ_flat.
n_bits();
757 Circuit new_circ(n_qubits, n_bits);
758 std::optional<std::string> name = circ_flat.
get_name();
759 if (name != std::nullopt) {
764 for (
const auto& pair : unit_map) {
765 rev_unit_map.insert({pair.second, pair.first});
770 if (stop_flag.get()->load()) {
777 if (stop_flag.get()->load()) {
781 auto [rotation_sets, rows, measures] = gpg.
get_sequence(stop_flag);
783 if (stop_flag.get()->load()) {
787 DepthTracker depth_tracker(n_qubits);
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);
793 if (stop_flag.get()->load()) {
797 tableau_row_nodes_synthesis(
798 rows, new_circ, depth_tracker, depth_weight, max_lookahead,
799 max_tqe_candidates, seed, stop_flag);
801 if (stop_flag.get()->load()) {
805 for (
auto it = measures.begin(); it != measures.end(); ++it) {
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);
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,
833 std::mt19937 seed_gen(seed);
834 std::vector<Circuit> circuits;
835 circuits.reserve(trials);
836 unsigned threads_started = 0;
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(
845 circ, stop_flag, discount_rate, depth_weight, max_lookahead,
846 max_tqe_candidates, seed_gen(), allow_zzphase);
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();
866 if (circuits.empty())
return false;
867 auto min = std::min_element(
868 circuits.begin(), circuits.end(),
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;
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;
882 circ = std::move(*min);
bool rename_units(const std::map< UnitA, UnitB > &qm)
Rename all the units according to the given mapping.
unsigned n_qubits() const
unsigned depth() const
Depth of circuit.
void set_name(const std::string _name)
Set the name of the circuit.
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.
std::optional< std::string > get_name() const
Get the name of the circuit.
Vertex add_measure(const Qubit &qubit, const Bit &bit)
Add a measure operation from a qubit to a bit.
void replace_SWAPs()
Replace all explicit swaps (i.e.
std::vector< Command > get_commands() const
Get the sequence of commands comprising the circuit.
Defines tket::DeviceCharacterisation, used in NoiseAwarePlacement and in commute_SQ_gates_through_SWA...
OpType
Named operation types.
@ 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
std::map< UnitID, UnitID > unit_map_t
PauliTensor< DensePauliMap, Expr > SymPauliTensor