-
Notifications
You must be signed in to change notification settings - Fork 47
/
passes.cpp
1148 lines (1099 loc) · 52.6 KB
/
passes.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Copyright 2019-2024 Cambridge Quantum Computing
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <pybind11/functional.h>
#include <optional>
#include <tklog/TketLog.hpp>
#include "binder_json.hpp"
#include "tket/Mapping/LexiLabelling.hpp"
#include "tket/Mapping/LexiRouteRoutingMethod.hpp"
#include "tket/Mapping/RoutingMethod.hpp"
#include "tket/Predicates/CompilerPass.hpp"
#include "tket/Predicates/PassGenerators.hpp"
#include "tket/Predicates/PassLibrary.hpp"
#include "tket/Transformations/ContextualReduction.hpp"
#include "tket/Transformations/PauliOptimisation.hpp"
#include "tket/Transformations/Transform.hpp"
#include "typecast.hpp"
namespace py = pybind11;
using json = nlohmann::json;
namespace tket {
// using py::object and converting internally to json creates better stubs,
// hence this wrapper
typedef std::function<void(const CompilationUnit &, const py::object &)>
PyPassCallback;
PassCallback from_py_pass_callback(const PyPassCallback &py_pass_callback) {
return [py_pass_callback](
const CompilationUnit &compilationUnit, const json &j) {
return py_pass_callback(compilationUnit, py::object(j));
};
}
// given keyword arguments for DecomposeTK2, return a TwoQbFidelities struct
Transforms::TwoQbFidelities get_fidelities(const py::kwargs &kwargs) {
Transforms::TwoQbFidelities fid;
for (const auto &kwarg : kwargs) {
const std::string kwargstr = py::cast<std::string>(kwarg.first);
using Func = std::function<double(double)>;
if (kwargstr == "CX_fidelity") {
fid.CX_fidelity = py::cast<double>(kwarg.second);
} else if (kwargstr == "ZZMax_fidelity") {
fid.ZZMax_fidelity = py::cast<double>(kwarg.second);
} else if (kwargstr == "ZZPhase_fidelity") {
fid.ZZPhase_fidelity = py::cast<std::variant<double, Func>>(kwarg.second);
} else {
throw py::type_error(
"got an unexpected keyword argument '" + kwargstr + "'");
}
}
return fid;
}
static PassPtr gen_cx_mapping_pass_kwargs(
const Architecture &arc, const Placement::Ptr &placer, py::kwargs kwargs) {
std::vector<RoutingMethodPtr> config = {
std::make_shared<LexiLabellingMethod>(),
std::make_shared<LexiRouteRoutingMethod>()};
if (kwargs.contains("config")) {
config = py::cast<std::vector<RoutingMethodPtr>>(kwargs["config"]);
}
bool directed_cx = false;
if (kwargs.contains("directed_cx")) {
directed_cx = py::cast<bool>(kwargs["directed_cx"]);
}
bool delay_measures = true;
if (kwargs.contains("delay_measures")) {
delay_measures = py::cast<bool>(kwargs["delay_measures"]);
}
return gen_cx_mapping_pass(arc, placer, config, directed_cx, delay_measures);
}
static PassPtr gen_default_routing_pass(const Architecture &arc) {
return gen_routing_pass(
arc, {std::make_shared<LexiLabellingMethod>(),
std::make_shared<LexiRouteRoutingMethod>()});
}
static PassPtr gen_default_aas_routing_pass(
const Architecture &arc, py::kwargs kwargs) {
unsigned lookahead = 1;
aas::CNotSynthType cnotsynthtype = aas::CNotSynthType::Rec;
if (kwargs.contains("lookahead"))
lookahead = py::cast<unsigned>(kwargs["lookahead"]);
if (kwargs.contains("cnotsynthtype"))
cnotsynthtype = py::cast<aas::CNotSynthType>(kwargs["cnotsynthtype"]);
if (lookahead == 0) {
throw std::invalid_argument(
"[AAS]: invalid input, the lookahead must be > 0");
}
return gen_full_mapping_pass_phase_poly(arc, lookahead, cnotsynthtype);
}
const PassPtr &DecomposeClassicalExp() {
// a special box decomposer for Circuits containing
// ClassicalExpBox<py::object> and ClExprOp
static const PassPtr pp([]() {
Transform t = Transform([](Circuit &circ) {
py::module decomposer =
py::module::import("pytket.circuit.decompose_classical");
const py::tuple result = decomposer.attr("_decompose_expressions")(circ);
const bool success = result[1].cast<bool>();
if (success) {
circ = result[0].cast<Circuit>();
}
return success;
});
PredicatePtrMap s_ps;
/**
* Preserves Max2QubitGatesPredicate since any box with >2 qubits is
* already invalid.
* Preserves ConnectivityPredicate (and DirectednessPredicate) since the
* verification looks inside CircBoxes and any other boxes with >2
* qubits are already invalid.
* Most others are preserved since the predicates look within CircBoxes.
*
* Invalidates GateSetPredicate because it adds Classical OpTypes
*/
PredicateClassGuarantees g_postcons = {
{typeid(GateSetPredicate), Guarantee::Clear},
};
PostConditions postcon{s_ps, g_postcons, Guarantee::Preserve};
json j;
j["name"] = "DecomposeClassicalExp";
return std::make_shared<StandardPass>(s_ps, t, postcon, j);
}());
return pp;
}
std::optional<OpTypeSet> get_gate_set(const BasePass &base_pass) {
std::optional<OpTypeSet> allowed_ops;
for (const std::pair<const std::type_index, std::shared_ptr<tket::Predicate>>
&p : base_pass.get_conditions().first) {
std::shared_ptr<GateSetPredicate> gsp_ptr =
std::dynamic_pointer_cast<GateSetPredicate>(p.second);
if (!gsp_ptr) {
continue;
}
OpTypeSet candidate_allowed_ops = gsp_ptr->get_allowed_types();
if (!allowed_ops) {
allowed_ops = candidate_allowed_ops;
} else {
OpTypeSet intersection;
std::set_intersection(
candidate_allowed_ops.begin(), candidate_allowed_ops.end(),
allowed_ops->begin(), allowed_ops->end(),
std::inserter(intersection, intersection.begin()));
allowed_ops = intersection;
}
}
return allowed_ops;
}
PYBIND11_MODULE(passes, m) {
py::module_::import("pytket._tket.predicates");
m.def(
"_sympy_import", []() { return Expr(); },
"This function only exists so that sympy gets imported in the resulting "
".pyi file. "
"It's needed due to a bug in pybind11-stubgen's translation for "
"Callables most likely.");
py::enum_<SafetyMode>(m, "SafetyMode")
.value(
"Audit", SafetyMode::Audit,
"Checks which predicates a circuit satisfies after the "
"application of each base pass")
.value(
"Default", SafetyMode::Default,
"Only check that a circuit satisfies the preconditions of "
"the overall pass at the start and the postconditions at "
"the end")
// .value("Off", SafetyMode::Off) // not currently supported
.export_values();
py::enum_<aas::CNotSynthType>(m, "CNotSynthType")
.value(
"SWAP", aas::CNotSynthType::SWAP,
"swap-based algorithm for CNOT synthesis")
.value(
"HamPath", aas::CNotSynthType::HamPath,
"Hamilton-path-based method for CNOT synthesis; this method will "
"fail if there is no Hamilton path in the given architecture")
.value(
"Rec", aas::CNotSynthType::Rec,
"recursive Steiner--Gauss method for CNOT synthesis")
.export_values();
/* Compiler passes */
class PyBasePass : public BasePass {
public:
using BasePass::BasePass;
/* Trampolines (need one for each virtual function */
virtual bool apply(
CompilationUnit &c_unit, SafetyMode safe_mode = SafetyMode::Default,
const PassCallback &before_apply = trivial_callback,
const PassCallback &after_apply = trivial_callback) const override {
PYBIND11_OVERLOAD_PURE(
bool, /* Return type */
BasePass, /* Parent class */
apply, /* Name of function in C++ (must match Python name) */
c_unit, before_apply, after_apply, safe_mode /* Argument(s) */
);
}
virtual std::string to_string() const override {
PYBIND11_OVERLOAD_PURE(
std::string, /* Return type */
BasePass, /* Parent class */
to_string /* Name of function in C++ (must match Python name) */
);
}
virtual json get_config() const override {
PYBIND11_OVERLOAD_PURE(
json, /* Return type */
BasePass, /* Parent class */
get_config /* Name of function in C++ (must match Python name) */
);
}
};
py::class_<BasePass, PassPtr, PyBasePass>(
m, "BasePass", "Base class for passes.")
.def(
"apply",
[](const BasePass &pass, CompilationUnit &cu,
SafetyMode safety_mode) { return pass.apply(cu, safety_mode); },
"Apply to a :py:class:`CompilationUnit`.\n\n"
":return: True if the pass modified the circuit. Note that in some "
"cases the method may return True even when the circuit is "
"unmodified (but a return value of False definitely implies no "
"modification).",
py::arg("compilation_unit"),
py::arg("safety_mode") = SafetyMode::Default)
.def(
"apply",
[](const BasePass &pass, Circuit &circ) {
CompilationUnit cu(circ);
bool applied = pass.apply(cu);
circ = cu.get_circ_ref();
return applied;
},
"Apply to a :py:class:`Circuit` in-place.\n\n"
":return: True if pass modified the circuit, else False",
py::arg("circuit"))
.def(
"apply",
[](const BasePass &pass, Circuit &circ,
const PyPassCallback &before_apply,
const PyPassCallback &after_apply) {
CompilationUnit cu(circ);
bool applied = pass.apply(
cu, SafetyMode::Default, from_py_pass_callback(before_apply),
from_py_pass_callback(after_apply));
circ = cu.get_circ_ref();
return applied;
},
"Apply to a :py:class:`Circuit` in-place and invoke "
"callbacks "
"for all nested passes.\n\n"
"\n:param before_apply: Invoked before a pass is applied. "
"The CompilationUnit and a summary of the pass "
"configuration are passed into the callback."
"\n:param after_apply: Invoked after a pass is applied. "
"The CompilationUnit and a summary of the pass "
"configuration are passed into the callback."
"\n:return: True if pass modified the circuit, else False",
py::arg("circuit"), py::arg("before_apply"), py::arg("after_apply"))
.def("__str__", [](const BasePass &) { return "<tket::BasePass>"; })
.def("__repr__", &BasePass::to_string)
.def(
"to_dict",
[](const BasePass &base_pass) {
return py::cast(serialise(base_pass));
},
":return: A JSON serializable dictionary representation of the Pass.")
.def(
"get_preconditions",
[](const BasePass &base_pass) {
std::vector<PredicatePtr> pre_conditions;
for (const std::pair<
const std::type_index, std::shared_ptr<tket::Predicate>>
&p : base_pass.get_conditions().first) {
pre_conditions.push_back(p.second);
}
return pre_conditions;
},
"Returns the precondition Predicates for the given pass."
"\n:return: A list of Predicate")
.def(
"get_postconditions",
[](const BasePass &base_pass) {
std::vector<PredicatePtr> post_conditions;
for (const std::pair<
const std::type_index, std::shared_ptr<tket::Predicate>> &
p : base_pass.get_conditions().second.specific_postcons_) {
post_conditions.push_back(p.second);
}
return post_conditions;
},
"Returns the postcondition Predicates for the given pass."
"\n\n:return: A list of :py:class:`Predicate`")
.def(
"get_gate_set", &get_gate_set,
"Returns the intersection of all set of OpType for all "
"GateSetPredicate in the `BasePass` preconditions, or `None` "
"if there are no gate-set predicates.",
"\n\n:return: A set of allowed OpType")
.def_static(
"from_dict",
[](const py::dict &base_pass_dict,
std::map<std::string, std::function<Circuit(const Circuit &)>>
&custom_deserialisation) {
return deserialise(base_pass_dict, custom_deserialisation);
},
"Construct a new Pass instance from a JSON serializable dictionary "
"representation. `custom_deserialisation` is a map between "
"`CustomPass` "
"label attributes and a Circuit to Circuit function matching the "
"`CustomPass` `transform` argument. This allows the construction of "
"some `CustomPass` from JSON. `CustomPass` without a matching entry "
"in "
"`custom_deserialisation` will be rejected.",
py::arg("base_pass_dict"),
py::arg("custom_deserialisation") =
std::map<std::string, std::function<Circuit(const Circuit &)>>{})
.def(py::pickle(
[](py::object self) { // __getstate__
return py::make_tuple(self.attr("to_dict")());
},
[](const py::tuple &t) { // __setstate__
const json j = t[0].cast<json>();
return deserialise(j);
}));
py::class_<SequencePass, std::shared_ptr<SequencePass>, BasePass>(
m, "SequencePass", "A sequence of compilation passes.")
.def(
py::init<const py::tket_custom::SequenceVec<PassPtr> &, bool>(),
"Construct from a list of compilation passes arranged in order of "
"application."
"\n\n:param pass_list: sequence of passes"
"\n:param strict: if True (the default), check that all "
"postconditions and preconditions of the passes in the sequence are "
"compatible and raise an exception if not."
"\n:return: a pass that applies the sequence",
py::arg("pass_list"), py::arg("strict") = true)
.def("__str__", [](const BasePass &) { return "<tket::SequencePass>"; })
.def(
"to_dict",
[](const SequencePass &seq_pass) {
return py::cast(
serialise(std::make_shared<SequencePass>(seq_pass)));
},
":return: A JSON serializable dictionary representation of the "
"SequencePass.")
.def(
"get_sequence", &SequencePass::get_sequence,
":return: The underlying sequence of passes.");
py::class_<RepeatPass, std::shared_ptr<RepeatPass>, BasePass>(
m, "RepeatPass",
"Repeat a pass until its `apply()` method returns False, or if "
"`strict_check` is True until it stops modifying the circuit.")
.def(
py::init<const PassPtr &, bool>(),
"Construct from a compilation pass.", py::arg("compilation_pass"),
py::arg("strict_check") = false)
.def("__str__", [](const RepeatPass &) { return "<tket::BasePass>"; })
.def(
"get_pass", &RepeatPass::get_pass,
":return: The underlying compilation pass.");
py::class_<
RepeatWithMetricPass, std::shared_ptr<RepeatWithMetricPass>, BasePass>(
m, "RepeatWithMetricPass",
"Repeat a compilation pass until the given metric stops "
"decreasing.")
.def(
py::init<const PassPtr &, const Transform::Metric &>(),
"Construct from a compilation pass and a metric function.",
py::arg("compilation_pass"), py::arg("metric"))
.def(
"__str__",
[](const BasePass &) { return "<tket::RepeatWithMetricPass>"; })
.def(
"get_pass", &RepeatWithMetricPass::get_pass,
":return: The underlying compilation pass.")
.def(
"get_metric", &RepeatWithMetricPass::get_metric,
":return: The underlying metric.");
py::class_<
RepeatUntilSatisfiedPass, std::shared_ptr<RepeatUntilSatisfiedPass>,
BasePass>(
m, "RepeatUntilSatisfiedPass",
"Repeat a compilation pass until a predicate on the circuit is "
"satisfied.")
.def(
py::init<const PassPtr &, const PredicatePtr &>(),
"Construct from a compilation pass and a predicate.",
py::arg("compilation_pass"), py::arg("predicate"))
.def(
py::init<
const PassPtr &, const std::function<bool(const Circuit &)> &>(),
"Construct from a compilation pass and a user-defined "
"function from :py:class:`Circuit` to `bool`.",
py::arg("compilation_pass"), py::arg("check_function"))
.def(
"__str__",
[](const BasePass &) { return "<tket::RepeatUntilSatisfiedPass>"; })
.def(
"get_pass", &RepeatUntilSatisfiedPass::get_pass,
":return: The underlying compilation pass.")
.def(
"get_predicate", &RepeatUntilSatisfiedPass::get_predicate,
":return: The underlying predicate.");
/* Pass library */
m.def(
"KAKDecomposition", &KAKDecomposition,
"Squash sequences of two-qubit operations into minimal form.\n\n"
"Pass to squash together sequences of single- and two-qubit gates "
"into minimal form. Can decompose to TK2 or CX gates.\n\n"
"Two-qubit operations can always be expressed in a minimal form of "
"maximum three CXs, or as a single TK2 gate (a result also known "
"as the KAK or Cartan decomposition).\n\n"
"It is in general recommended to squash to TK2 gates, and to then use"
" the `DecomposeTK2` pass for noise-aware decompositions to other "
"gatesets. For backward compatibility, decompositions to CX are also "
"supported. In this case, `cx_fidelity` can be provided to perform "
"approximate decompositions to CX gates.\n\n"
"When decomposing to TK2 gates, any sequence of two or more two-qubit"
" gates on the same set of qubits are replaced by a single TK2 gate. "
"When decomposing to CX, the substitution is only performed if it "
"results in a reduction of the number of CX gates, or if at least "
"one of the two-qubit gates is not a CX.\n\n"
"Using the `allow_swaps=True` (default) option, qubits will be "
"swapped when convenient to further reduce the two-qubit gate count "
"(only applicable when decomposing to CX gates).\n\n"
"Note that gates containing symbolic parameters are not squashed.\n\n"
":param target_2qb_gate: OpType to decompose to. Either TK2 or CX.\n"
":param cx_fidelity: Estimated CX gate fidelity, used when "
"target_2qb_gate=CX.\n"
":param allow_swaps: Whether to allow implicit wire swaps.",
py::arg("target_2qb_gate") = OpType::CX, py::arg("cx_fidelity") = 1.,
py::arg("allow_swaps") = true);
m.def(
"KAKDecomposition",
[](double cx_fidelity) {
return KAKDecomposition(OpType::CX, cx_fidelity);
},
py::arg("cx_fidelity"));
m.def(
"DecomposeTK2",
[](bool allow_swaps, const py::kwargs &kwargs) {
return DecomposeTK2(get_fidelities(kwargs), allow_swaps);
},
"Decompose each TK2 gate into two-qubit gates."
"\n\nGate fidelities can be passed as keyword arguments to perform "
"noise-aware decompositions. If the fidelities of several gate types "
"are provided, the best will be chosen.\n\n"
"We currently support `CX_fidelity`, `ZZMax_fidelity` and "
"`ZZPhase_fidelity`. If provided, the `CX` and `ZZMax` fidelities "
"must be given by a single floating point fidelity. The `ZZPhase` "
"fidelity is given as a lambda float -> float, mapping a ZZPhase "
"angle parameter to its fidelity, or by a single float. These parameters "
"will be used to return the optimal decomposition of each TK2 gate, "
"taking noise into consideration.\n\n"
"If no fidelities are provided, the TK2 gates will be decomposed "
"exactly using CX gates. For equal fidelities, ZZPhase will be prefered "
"over ZZMax and CX if the decomposition results in fewer two-qubit "
"gates.\n\n"
"All TK2 gate parameters must be normalised, i.e. they must satisfy "
"`NormalisedTK2Predicate`. (This can be achieved by applying the "
":py:meth:`NormaliseTK2` pass beforehand.)\n\n"
"Using the `allow_swaps=True` (default) option, qubits will be swapped "
"when convenient to reduce the two-qubit gate count of the decomposed "
"TK2.\n\n"
"If the TK2 angles are symbolic values, the decomposition will "
"be exact (i.e. not noise-aware). It is not possible in general "
"to obtain optimal decompositions for arbitrary symbolic parameters, "
"so consider substituting for concrete values if possible."
"\n\n:param allow_swaps: Whether to allow implicit wire swaps.",
py::arg("allow_swaps") = true);
m.def(
"NormaliseTK2", &NormaliseTK2,
"Normalises all TK2 gates.\n\n"
"TK2 gates have three angles in the interval [0, 4], but these can always"
" be normalised to be within the so-called Weyl chamber by adding "
"single-qubit gates.\n\n"
"More precisely, the three angles a, b, c of TK2(a, b, c) are normalised "
"exactly when the two following conditions are met:\n"
" - numerical values must be in the Weyl chamber, "
"ie `1/2 >= a >= b >= |c|`,\n"
" - symbolic values must come before any numerical value in the array."
"\n\nAfter this pass, all TK2 angles will be normalised and the circuit "
"will satisfy `NormalisedTK2Predicate`.");
m.def(
"ThreeQubitSquash", &ThreeQubitSquash,
"Squash three-qubit subcircuits into subcircuits having fewer CX gates, "
"when possible, and apply Clifford simplification."
"\n\nThe circuit to which this is applied must consist of single-qubit, "
"pure-classical and CX gates, and Measure, Collapse, Reset, Phase and "
"conditional gates."
"\n\n:param allow_swaps: whether to allow implicit wire swaps",
py::arg("allow_swaps") = true);
m.def(
"CommuteThroughMultis", &CommuteThroughMultis,
"Moves single-qubit operations past multi-qubit operations that they "
"commute with, towards the front of the circuit.");
m.def(
"DecomposeArbitrarilyControlledGates",
&DecomposeArbitrarilyControlledGates,
"Decomposes CCX, CnX, CnY, CnZ, CnRy, CnRz and CnRx gates into "
"CX and single-qubit gates.");
m.def(
"DecomposeBoxes", &DecomposeBoxes,
"Recursively replaces all boxes by their decomposition into circuits."
"\n\n:param excluded_types: box `OpType`s excluded from decomposition"
"\n:param excluded_opgroups: opgroups excluded from decomposition",
py::arg("excluded_types") = std::unordered_set<OpType>(),
py::arg("excluded_opgroups") = std::unordered_set<std::string>());
m.def(
"DecomposeClassicalExp", &DecomposeClassicalExp,
"Replaces each :py:class:`ClassicalExpBox` and `ClExprOp` by a sequence "
"of classical gates.");
m.def(
"DecomposeMultiQubitsCX", &DecomposeMultiQubitsCX,
"Converts all multi-qubit gates into CX and single-qubit gates.");
m.def(
"GlobalisePhasedX",
[](bool squash) {
PyErr_WarnEx(
PyExc_DeprecationWarning,
"The GlobalisePhasedX pass is unreliable and deprecated. It will "
"be removed no earlier that three months after the pytket 1.35 "
"release.",
1);
return GlobalisePhasedX(squash);
},
"Turns all PhasedX and NPhasedX gates into global gates\n\n"
"Replaces any PhasedX gates with global NPhasedX gates. "
"By default, this transform will squash all single-qubit gates "
"to PhasedX and Rz gates before proceeding further. "
"Existing non-global NPhasedX will not be preserved. "
"This is the recommended setting for best "
"performance. If squashing is disabled, each non-global PhasedX gate "
"will be replaced with two global NPhasedX, but any other gates will "
"be left untouched."
"\n\nDEPRECATED: This pass will be removed no earlier than three months "
"after the pytket 1.35 release."
"\n\n:param squash: Whether to squash the circuit in pre-processing "
"(default: true)."
"\n\nIf squash=true (default), the `GlobalisePhasedX` transform's "
"`apply` method "
"will always return true. "
"For squash=false, `apply()` will return true if the circuit was "
"changed and false otherwise.\n\n"
"It is not recommended to use this pass with symbolic expressions, as"
" in certain cases a blow-up in symbolic expression sizes may occur.",
py::arg("squash") = true);
m.def(
"DecomposeSingleQubitsTK1", &DecomposeSingleQubitsTK1,
"Converts all single-qubit gates into TK1 gates.");
m.def(
"PeepholeOptimise2Q", &PeepholeOptimise2Q,
"Performs peephole optimisation including resynthesis of 2-qubit "
"gate sequences, and converts to a circuit containing only CX and TK1 "
"gates."
"\n\n:param allow_swaps: whether to allow implicit wire swaps",
py::arg("allow_swaps") = true);
m.def(
"FullPeepholeOptimise", &FullPeepholeOptimise,
"Performs peephole optimisation including resynthesis of 2- and 3-qubit "
"gate sequences, and converts to a circuit containing only the given "
"2-qubit gate (which may be CX or TK2) and TK1 gates."
"\n\n:param allow_swaps: whether to allow implicit wire swaps",
py::arg("allow_swaps") = true, py::arg("target_2qb_gate") = OpType::CX);
m.def(
"RebaseTket", &RebaseTket,
"Converts all gates to CX, TK1 and Phase. "
"(Any Measure and Reset operations are left untouched; "
"Conditional gates are also allowed.)");
m.def(
"RemoveRedundancies", &RemoveRedundancies,
"Removes gate-inverse pairs, merges rotations, removes identity "
"rotations, and removes redundant gates before measurement. Does not "
"add any new gate types.\n\n"
"When merging rotations with the same op group name, the merged "
"operation keeps the same name.");
m.def(
"SynthesiseTK", &SynthesiseTK,
"Optimises and converts all gates to TK2, TK1 and Phase gates.");
m.def(
"SynthesiseTket", &SynthesiseTket,
"Optimises and converts all gates to CX, TK1 and Phase gates.");
m.def(
"SynthesiseUMD", &SynthesiseUMD,
"Optimises and converts all gates to XXPhase, PhasedX, Rz and Phase. "
"DEPRECATED: will be removed after pytket 1.32.");
m.def(
"SquashTK1", &SquashTK1,
"Squash sequences of single-qubit gates to TK1 gates.");
m.def(
"SquashRzPhasedX", &SquashRzPhasedX,
"Squash single qubit gates into PhasedX and Rz gates. Also remove "
"identity gates. Commute Rz gates to the back if possible.");
m.def(
"FlattenRegisters", &FlattenRegisters,
"Merges all quantum and classical registers into their "
"respective "
"default registers with contiguous indexing.");
m.def(
"SquashCustom", &gen_squash_pass,
"Squash sequences of single qubit gates from the target gate set "
"into an optimal form given by `tk1_replacement`."
"\n\n:param singleqs: The types of single qubit gates in the target "
"gate set. This pass will only affect sequences of gates that are "
"already in this set."
"\n:param tk1_replacement: A function which, given the parameters of "
"an Rz(a)Rx(b)Rz(c) triple, returns an equivalent circuit in the "
"desired basis."
"\n:param always_squash_symbols: If true, always squash symbolic gates "
"regardless of the blow-up in complexity. Default is false, meaning that "
"symbolic gates are only squashed if doing so reduces the overall "
"symbolic complexity.",
py::arg("singleqs"), py::arg("tk1_replacement"),
py::arg("always_squash_symbols") = false);
m.def(
"AutoSquash", &gen_auto_squash_pass,
"Attempt to generate a squash pass automatically for the given target "
"single qubit gateset.\n"
"Raises an error if no known TK1 decomposition can be found based on the "
"given gateset, in which case try using :py:class:`SquashCustom` with "
"your own decomposition."
"\n\n:param singleqs: The types of single qubit gates in the target "
"gate set. This pass will only affect sequences of gates that are "
"already in this set.",
py::arg("singleqs"));
m.def(
"DelayMeasures", &DelayMeasures,
"Commutes Measure operations to the end of the circuit. Throws an "
"exception when this is not possible because of gates following the "
"measure which are dependent on either the resulting quantum state "
"or classical values."
"\n\n:param allow_partial: Whether to allow measurements that cannot be "
"commuted to "
"the end, and delay them as much as possible instead. If false, the pass "
"includes a :py:class:`CommutableMeasuresPredicate` precondition.",
py::arg("allow_partial") = true);
m.def(
"RemoveDiscarded", &RemoveDiscarded,
"A pass to remove all operations that have no ``OpType.Output`` or "
"``OpType.ClOutput`` in their causal future (in other words, all "
"operations whose causal future is discarded).");
m.def(
"SimplifyMeasured", &SimplifyMeasured,
"A pass to replace all 'classical maps' followed by measure "
"operations whose quantum output is discarded with classical "
"operations following the measure. (A 'classical map' is a quantum "
"operation that acts as a permutation of the computational basis "
"states followed by a diagonal operation.)");
m.def(
"RemoveBarriers", &RemoveBarriers,
"A pass to remove all barrier instructions from the circuit.");
m.def(
"ZXGraphlikeOptimisation", &ZXGraphlikeOptimisation,
"Attempt to optimise the circuit by simplifying in ZX calculus and "
"extracting a circuit back out. Due to limitations in extraction, may "
"not work if the circuit contains created or discarded qubits. As a "
"resynthesis pass, this will ignore almost all optimisations achieved "
"beforehand and may increase the cost of the circuit.");
/* Pass generators */
m.def(
"RebaseCustom", &gen_rebase_pass,
"Construct a custom rebase pass, given user-defined rebases for TK1 and "
"CX. This pass:"
"\n\n"
"1. decomposes multi-qubit gates not in the set of gate types `gateset` "
"to CX gates;\n"
"2. if CX is not in `gateset`, replaces CX gates with `cx_replacement`;\n"
"3. converts any single-qubit gates not in the gate type set to the form "
":math:`\\mathrm{Rz}(a)\\mathrm{Rx}(b)\\mathrm{Rz}(c)` (in "
"matrix-multiplication order, i.e. reverse order in the "
"circuit);\n"
"4. applies the `tk1_replacement` function to each of these triples "
":math:`(a,b,c)` to generate replacement circuits."
"\n\n"
":param gateset: the allowed operations in the rebased circuit "
"(in addition, Measure and Reset operations are always allowed "
"and are left alone; conditional operations may be present; and Phase "
"gates may also be introduced by the rebase)"
"\n:param cx_replacement: the equivalent circuit to replace a CX gate "
"using two qubit gates from the desired basis (can use any single qubit "
"OpTypes)"
"\n:param tk1_replacement: a function which, given the parameters of an "
"Rz(a)Rx(b)Rz(c) triple, returns an equivalent circuit in the desired "
"basis"
"\n:return: a pass that rebases to the given gate set (possibly "
"including conditional and phase operations, and Measure and Reset",
py::arg("gateset"), py::arg("cx_replacement"),
py::arg("tk1_replacement"));
m.def(
"RebaseCustom", &gen_rebase_pass_via_tk2,
"Construct a custom rebase pass, given user-defined rebases for TK1 and "
"TK2. This pass:"
"\n\n"
"1. decomposes multi-qubit gates not in the set of gate types `gateset` "
"to TK2 gates;\n"
"2. if TK2 is not in `gateset`, replaces TK2(a,b,c) gates via the "
"`tk2_replacement` function;\n"
"3. converts any single-qubit gates not in the gate type set to TK1;\n"
"4. if TK2 is not in `gateset`. applies the `tk1_replacement` function "
"to each TK1(a,b,c)."
"\n\n"
":param gateset: the allowed operations in the rebased circuit "
"(in addition, Measure and Reset always allowed "
"and are left alone; conditional operations may be present; and Phase "
"gates may also be introduced by the rebase)\n"
":param tk2_replacement: a function which, given the parameters (a,b,c) "
"of an XXPhase(a)YYPhase(b)ZZPhase(c) triple, returns an equivalent "
"circuit in the desired basis\n"
":param tk1_replacement: a function which, given the parameters (a,b,c) "
"of an Rz(a)Rx(b)Rz(c) triple, returns an equivalent circuit in the "
"desired basis\n"
":return: a pass that rebases to the given gate set (possibly including "
"conditional and phase operations, and Measure and Reset)",
py::arg("gateset"), py::arg("tk2_replacement"),
py::arg("tk1_replacement"));
m.def(
"AutoRebase", &gen_auto_rebase_pass,
"Attempt to generate a rebase pass automatically for the given target "
"gateset. Checks if there are known existing decompositions "
"to target gateset and TK1 to target gateset and uses those to construct "
"a custom rebase.\n"
"Raises an error if no known decompositions can be found, in which case "
"try using :py:class:`RebaseCustom` with your own decompositions.\n\n"
":param gateset: Set of supported OpTypes, target gate set. "
"(in addition, Measure and Reset operations are always allowed "
"and are left alone; conditional operations may be present; and Phase "
"gates may also be introduced by the rebase)\n"
":param allow_swaps: Whether to allow implicit wire swaps. Default to "
"False.",
py::arg("gateset"), py::arg("allow_swaps") = false);
m.def(
"EulerAngleReduction", &gen_euler_pass,
"Uses Euler angle decompositions to squash all chains of P and Q "
"rotations, "
"where P,Q ∈ {Rx,Ry,Rz}. "
"By default (`strict=False`), this pass will try to decompose the chains "
"into pairs of -P-Q- or -Q-P- rotations, commuting any third rotation "
"past multi-qubit gates. "
"If `strict=True`, all chains will be decomposed to P-Q-P triples "
"and no further optimisation is performed."
"\n\n:param q: The type of the Q rotation (Q ∈ {Rx,Ry,Rz})."
"\n:param p: The type of the P rotation (P ∈ {Rx,Ry,Rz}, P ≠ Q)."
"\n:param strict: Optionally performs strict P-Q-P Euler decomposition"
"\n:return: a pass that squashes chains of P and Q rotations",
py::arg("q"), py::arg("p"), py::arg("strict") = false);
m.def(
"CustomRoutingPass",
[](const Architecture &arc,
const py::tket_custom::SequenceVec<RoutingMethodPtr> &config) {
return gen_routing_pass(arc, config);
},
"Construct a pass to route to the connectivity graph of an "
":py:class:`Architecture`. Edge direction is ignored. "
"\n\n"
":return: a pass that routes to the given device architecture ",
py::arg("arc"), py::arg("config"));
m.def(
"RoutingPass", &gen_default_routing_pass,
"Construct a pass to route to the connectivity graph of an "
":py:class:`Architecture`. Edge direction is ignored. "
"Uses :py:class:`LexiLabellingMethod` and "
":py:class:`LexiRouteRoutingMethod`."
"\n\n"
":return: a pass that routes to the given device architecture",
py::arg("arc"));
m.def(
"PlacementPass", &gen_placement_pass,
":param placer: The Placement used for relabelling."
"\n:return: a pass to relabel :py:class:`Circuit` Qubits to "
":py:class:`Architecture` Nodes",
py::arg("placer"));
m.def(
"NaivePlacementPass", &gen_naive_placement_pass,
":param architecture: The Architecture used for relabelling."
"\n:return: a pass to relabel :py:class:`Circuit` Qubits to "
":py:class:`Architecture` Nodes",
py::arg("architecture"));
m.def(
"FlattenRelabelRegistersPass", &gen_flatten_relabel_registers_pass,
"Removes empty Quantum wires from the Circuit and relabels all Qubit to "
"a register from passed name. \n\n:param label: Name to relabel "
"remaining Qubit to, default 'q'.\n:param relabel_classical_expressions: "
"Whether to relabel arguments of expressions held in `ClassicalExpBox`. "
"\n:return: A pass that removes empty "
"wires and relabels.",
py::arg("label") = q_default_reg(),
py::arg("relabel_classical_expressions") = true);
m.def(
"RenameQubitsPass", &gen_rename_qubits_pass,
"Rename some or all qubits. "
"\n\n"
":param qubit_map: map from old to new qubit names ",
py::arg("qubit_map"));
m.def(
"FullMappingPass",
[](const Architecture &arc, const Placement::Ptr &placement_ptr,
const py::tket_custom::SequenceVec<RoutingMethodPtr> &config) {
return gen_full_mapping_pass(arc, placement_ptr, config);
},
"Construct a pass to relabel :py:class:`Circuit` Qubits to "
":py:class:`Architecture` Nodes, and then route to the connectivity "
"graph "
"of an :py:class:`Architecture`. Edge direction is ignored."
"\n\n:param arc: The architecture to use for connectivity information. "
"\n:param placer: The Placement used for relabelling."
"\n:param config: Parameters for routing, a list of RoutingMethod, each "
"method is checked and run if applicable in turn."
"\n:return: a pass to perform the remapping",
py::arg("arc"), py::arg("placer"), py::arg("config"));
m.def(
"DefaultMappingPass", &gen_default_mapping_pass,
"Construct a pass to relabel :py:class:`Circuit` Qubits to "
":py:class:`Architecture` Nodes, and then route to the connectivity "
"graph "
"of the given :py:class:`Architecture`. Edge direction is ignored. "
"Placement used "
"is GraphPlacement."
"\n\n:param arc: The Architecture used for connectivity information."
"\n:param delay_measures: Whether to commute measurements to the end "
"of the circuit, defaulting to true."
"\n:return: a pass to perform the remapping",
py::arg("arc"), py::arg("delay_measures") = true);
m.def(
"AASRouting", &gen_default_aas_routing_pass,
"Construct a pass to relabel :py:class:`Circuit` Qubits to "
":py:class:`Device` Nodes, and then use architecture-aware "
"synthesis to route the circuit. In the steps of the pass the circuit "
"will be converted to CX, Rz, H gateset. The limited connectivity of the "
":py:class:`Architecture` is used for the routing. "
"The direction of the edges is ignored. The placement used "
"is GraphPlacement. This pass can take a few parameters for the "
"routing, described below:"
"\n\n- (unsigned) lookahead=1: parameter for the recursive iteration"
"\n- (CNotSynthType) cnotsynthtype=CNotSynthType.Rec: CNOT synthesis type"
"\n\nNB: The circuit needs to have at most as "
"many qubits as the architecture has nodes. The resulting circuit will "
"always have the same number of qubits as the architecture has nodes, "
"even if the input circuit had fewer."
"\n\n:param arc: target architecture"
"\n:param \\**kwargs: parameters for routing (described above)"
"\n:return: a pass to perform the remapping",
py::arg("arc"));
m.def(
"ComposePhasePolyBoxes", &ComposePhasePolyBoxes,
"Pass to convert a given :py:class:`Circuit` to the CX, Rz, H gateset "
"and compose "
"phase polynomial boxes from the groups of the CX+Rz gates."
"\n\n- (unsigned) min_size=0: minimal number of CX gates in each phase "
"polynominal box: groups with a smaller number of CX gates are not "
"affected by this transformation\n"
"\n:param \\**kwargs: parameters for composition (described above)"
"\n:return: a pass to perform the composition",
py::arg("min_size") = 0);
m.def(
"CXMappingPass", &gen_cx_mapping_pass_kwargs,
"Construct a pass to convert all gates to CX, relabel "
":py:class:`Circuit` Qubits to :py:class:`Architecture` Nodes, route to "
"the "
"connectivty graph of a :py:class:`Architecture` and decompose "
"additional "
"routing gates (SWAP and BRIDGE) to CX gates."
"\n\n:param arc: The Architecture used for connectivity information."
"\n:param placer: The placement used for relabelling."
"\n:param \\**kwargs: Parameters for routing: "
"(bool)directed_cx=false, (bool)delay_measures=true"
"\n:return: a pass to perform the remapping",
py::arg("arc"), py::arg("placer"));
m.def(
"CliffordSimp", &gen_clifford_simp_pass,
"An optimisation pass that performs a number of rewrite rules for "
"simplifying Clifford gate sequences, similar to Duncan & Fagan "
"(https://arxiv.org/abs/1901.10114). "
"Given a circuit with CXs and any single-qubit gates, produces a "
"circuit with TK1, CX gates."
"\n\n:param allow_swaps: dictates whether the rewriting will "
"disregard CX placement or orientation and introduce wire swaps."
"\n:return: a pass to perform the rewriting",
py::arg("allow_swaps") = true);
m.def(
"CliffordResynthesis", &gen_clifford_resynthesis_pass,
"An optimisation pass that resynthesises Clifford subcircuits, trying "
"to reduce the 2-qubit gate count as much as possible."
"\n\n:param transform: optional user-provided resynthesis method to "
"apply to all Clifford subcircuits (a function taking a Clifford "
"circuit as an argument and returning an equivalent circuit); if not "
"provided, a default resynthesis method is applied"
"\n:param allow_swaps: whether the rewriting may introduce wire swaps "
"(only relevant to the default resynthesis method used when the "
"`transform` argument is not provided)"
"\n:return: a pass to perform the rewriting",
py::arg("transform") = std::nullopt, py::arg("allow_swaps") = true);
m.def(
"CliffordPushThroughMeasures", &gen_clifford_push_through_pass,
"An optimisation pass that resynthesise a Clifford subcircuit "
"before end of circuit Measurement operations by implementing "
"the action of the Clifford as a mutual diagonalisation circuit "
"and a permutation on output measurements realised as a series "
"of classical operations."
"\n: return: a pass to simplify end of circuit Clifford gates.");
m.def(
"DecomposeSwapsToCXs", &gen_decompose_routing_gates_to_cxs_pass,
"Construct a pass to decompose SWAP and BRIDGE gates to CX gates, "
"constraining connectivity to an :py:class:`Architecture`, optionally "
"taking the directedness of the connectivity graph into account."
"\n\n:param arc: The architecture to use for connectivity information."
"\n:param respect_direction: Optionally takes the directedness of "
"the connectivity graph into account."
"\n:return: a pass to perform the decomposition",
py::arg("arc"), py::arg("respect_direction") = false);
m.def(
"DecomposeSwapsToCircuit", &gen_user_defined_swap_decomp_pass,
":param replacement_circuit: An equivalent circuit to replace a "
"SWAP gate with in the desired basis."
"\n:return: a pass to replace all SWAP gates with the given circuit",
py::arg("replacement_circuit"));
m.def(
"OptimisePhaseGadgets", &gen_optimise_phase_gadgets,
"Construct a pass that synthesises phase gadgets and converts to a "
"circuit containing only CX, TK1 and Phase gates."
"\n\n:param cx_config: A configuration of CXs to convert phase "
"gadgets into."
"\n:return: a pass to perform the synthesis",
py::arg("cx_config") = CXConfigType::Snake);
m.def(
"PauliExponentials", &gen_pauli_exponentials,
"Construct a pass that converts a circuit into a graph of Pauli "
"exponential boxes, with information"
"\n\n:param strat: A synthesis strategy for the Pauli graph."
"\n:param cx_config: A configuration of CXs to convert Pauli gadgets "
"into."
"\n:return: a pass to perform the simplification",
py::arg("strat") = Transforms::PauliSynthStrat::Sets,
py::arg("cx_config") = CXConfigType::Snake);
m.def(
"PauliSimp", &gen_synthesise_pauli_graph,
"Construct a pass that converts a circuit into a graph of Pauli "
"gadgets to account for commutation and phase folding, and "
"resynthesises them as either individual gagdets, pairwise "
"constructions, or by diagonalising sets of commuting gadgets.\n\n"
"This pass will not preserve the global phase of the circuit."
"\n\n:param strat: A synthesis strategy for the Pauli graph."
"\n:param cx_config: A configuration of CXs to convert Pauli gadgets "
"into."
"\n:return: a pass to perform the simplification",
py::arg("strat") = Transforms::PauliSynthStrat::Sets,
py::arg("cx_config") = CXConfigType::Snake);
m.def(
"GuidedPauliSimp", &gen_special_UCC_synthesis,
"Applies the ``PauliSimp`` optimisation pass to any region of the "
"circuit contained within a :py:class:`CircBox`. This can be useful "
"to focus the synthesis to target specific sets of commuting "