Line | Branch | Decision | Exec | Source |
---|---|---|---|---|
1 | // Copyright 2019-2022 Cambridge Quantum Computing | |||
2 | // | |||
3 | // Licensed under the Apache License, Version 2.0 (the "License"); | |||
4 | // you may not use this file except in compliance with the License. | |||
5 | // You may obtain a copy of the License at | |||
6 | // | |||
7 | // http://www.apache.org/licenses/LICENSE-2.0 | |||
8 | // | |||
9 | // Unless required by applicable law or agreed to in writing, software | |||
10 | // distributed under the License is distributed on an "AS IS" BASIS, | |||
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
12 | // See the License for the specific language governing permissions and | |||
13 | // limitations under the License. | |||
14 | ||||
15 | #include "ContextualReduction.hpp" | |||
16 | ||||
17 | #include <algorithm> | |||
18 | #include <optional> | |||
19 | #include <sstream> | |||
20 | #include <stdexcept> | |||
21 | #include <tkassert/Assert.hpp> | |||
22 | #include <tklog/TketLog.hpp> | |||
23 | ||||
24 | #include "Circuit/Circuit.hpp" | |||
25 | #include "Circuit/DAGDefs.hpp" | |||
26 | #include "Eigen/src/Core/Matrix.h" | |||
27 | #include "OpType/OpType.hpp" | |||
28 | #include "OpType/OpTypeInfo.hpp" | |||
29 | #include "Ops/ClassicalOps.hpp" | |||
30 | #include "Ops/OpPtr.hpp" | |||
31 | #include "Transform.hpp" | |||
32 | #include "Utils/HelperFunctions.hpp" | |||
33 | #include "Utils/UnitID.hpp" | |||
34 | ||||
35 | namespace tket { | |||
36 | ||||
37 | namespace Transforms { | |||
38 | ||||
39 | 2 | Transform remove_discarded_ops() { | ||
40 | 8 | return Transform([](Circuit &circ) { | ||
41 | // We want to keep all vertices that have an Output or ClOutput in their | |||
42 | // causal future. Start by constructing this set, then remove the | |||
43 | // remainder. | |||
44 | 8 | VertexSet keep; | ||
45 |
3/4✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
✓ Branch 8 taken 37 times.
✓ Branch 9 taken 8 times.
|
0/1? Decision couldn't be analyzed.
|
45 | for (auto v_end : circ.all_outputs()) { |
46 |
3/4✓ Branch 1 taken 37 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 30 times.
✓ Branch 4 taken 7 times.
|
2/2✓ Decision 'true' taken 30 times.
✓ Decision 'false' taken 7 times.
|
37 | if (circ.get_OpType_from_Vertex(v_end) != OpType::Discard) { |
47 | // Trace back from v, adding to keep-set. We can ignore vertices | |||
48 | // that are already in the keep-set. | |||
49 | 30 | VertexSet v_frontier; | ||
50 |
1/2✓ Branch 1 taken 30 times.
✗ Branch 2 not taken.
|
30 | keep.insert(v_end); | |
51 |
1/2✓ Branch 1 taken 30 times.
✗ Branch 2 not taken.
|
30 | v_frontier.insert(v_end); | |
52 |
2/2✓ Branch 1 taken 124 times.
✓ Branch 2 taken 30 times.
|
2/2✓ Decision 'true' taken 124 times.
✓ Decision 'false' taken 30 times.
|
154 | while (!v_frontier.empty()) { |
53 | 124 | VertexSet new_v_frontier; | ||
54 |
2/2✓ Branch 5 taken 208 times.
✓ Branch 6 taken 124 times.
|
2/2✓ Decision 'true' taken 208 times.
✓ Decision 'false' taken 124 times.
|
332 | for (auto v : v_frontier) { |
55 |
3/4✓ Branch 1 taken 208 times.
✗ Branch 2 not taken.
✓ Branch 8 taken 234 times.
✓ Branch 9 taken 208 times.
|
0/1? Decision couldn't be analyzed.
|
442 | for (const Vertex &v0 : circ.get_predecessors(v)) { |
56 |
3/4✓ Branch 2 taken 234 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 178 times.
✓ Branch 6 taken 56 times.
|
2/2✓ Decision 'true' taken 178 times.
✓ Decision 'false' taken 56 times.
|
234 | if (keep.find(v0) == keep.end()) { |
57 |
1/2✓ Branch 1 taken 178 times.
✗ Branch 2 not taken.
|
178 | keep.insert(v0); | |
58 |
1/2✓ Branch 1 taken 178 times.
✗ Branch 2 not taken.
|
178 | new_v_frontier.insert(v0); | |
59 | } | |||
60 | 208 | } | ||
61 | } | |||
62 | 124 | v_frontier = std::move(new_v_frontier); | ||
63 | 124 | } | ||
64 | 30 | } | ||
65 | 8 | } | ||
66 | // Now remove all operations not in the keep-set. | |||
67 | 8 | VertexList to_remove; | ||
68 |
7/8✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 218 times.
✓ Branch 6 taken 8 times.
✓ Branch 8 taken 218 times.
✓ Branch 9 taken 8 times.
✓ Branch 11 taken 8 times.
✓ Branch 12 taken 8 times.
|
234 | BGL_FORALL_VERTICES(v, circ.dag, DAG) { | |
69 |
3/4✓ Branch 2 taken 218 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 10 times.
✓ Branch 6 taken 208 times.
|
2/2✓ Decision 'true' taken 10 times.
✓ Decision 'false' taken 208 times.
|
218 | if (keep.find(v) == keep.end()) { |
70 |
1/2✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
|
10 | OpType optype = circ.get_OpType_from_Vertex(v); | |
71 |
7/10✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 8 times.
✓ Branch 4 taken 2 times.
✓ Branch 6 taken 8 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 8 times.
✓ Branch 10 taken 2 times.
✓ Branch 11 taken 8 times.
|
2/2✓ Decision 'true' taken 2 times.
✓ Decision 'false' taken 8 times.
|
10 | if (is_gate_type(optype) || is_box_type(optype)) { |
72 |
1/2✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
|
2 | to_remove.push_back(v); | |
73 | } | |||
74 | } | |||
75 | } | |||
76 |
1/2✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
|
8 | circ.remove_vertices( | |
77 | to_remove, Circuit::GraphRewiring::Yes, Circuit::VertexDeletion::Yes); | |||
78 | 16 | return !to_remove.empty(); | ||
79 |
1/2✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
10 | }); | |
80 | } | |||
81 | ||||
82 | 99 | static std::optional<Eigen::MatrixXcd> op_unitary(Op_ptr op) { | ||
83 | try { | |||
84 |
2/2✓ Branch 2 taken 66 times.
✓ Branch 3 taken 33 times.
|
99 | return op->get_unitary(); | |
85 |
1/3✗ Branch 0 not taken.
✓ Branch 1 taken 33 times.
✗ Branch 2 not taken.
|
33 | } catch (BadOpType &) { | |
86 |
1/2✓ Branch 1 taken 33 times.
✗ Branch 2 not taken.
|
33 | std::stringstream ss; | |
87 |
3/6✓ Branch 1 taken 33 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 33 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 33 times.
✗ Branch 9 not taken.
|
0/1? Decision couldn't be analyzed.
|
33 | ss << "Attempting to compute unitary for invalid type: " << op->get_name(); |
88 |
3/6✓ Branch 1 taken 33 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 33 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 33 times.
✗ Branch 9 not taken.
|
33 | tket_log()->warn(ss.str()); | |
89 | 33 | return std::nullopt; | ||
90 | 33 | } catch (SymbolsNotSupported &) { | ||
91 | ✗ | std::stringstream ss; | ||
92 | ss << "Attempting to compute unitary for symbolic operation: " | |||
93 | ✗ | << op->get_name(); | ||
94 | ✗ | tket_log()->warn(ss.str()); | ||
95 | ✗ | return std::nullopt; | ||
96 | } | |||
97 | // Any other exception is unexpected. | |||
98 | } | |||
99 | ||||
100 | /** | |||
101 | * Return i s.t. |U[i,j]| = 1, if it exists. U is unitary, j a column index. | |||
102 | */ | |||
103 | 77 | static std::optional<unsigned> unique_unit_row( | ||
104 | const Eigen::MatrixXcd U, unsigned j) { | |||
105 | 77 | unsigned n = U.rows(); | ||
106 |
1/2✓ Branch 0 taken 123 times.
✗ Branch 1 not taken.
|
1/2✓ Decision 'true' taken 123 times.
✗ Decision 'false' not taken.
|
123 | for (unsigned i = 0; i < n; i++) { |
107 |
1/2✓ Branch 1 taken 123 times.
✗ Branch 2 not taken.
|
123 | double a = std::abs(U(i, j)); | |
108 |
2/2✓ Branch 1 taken 45 times.
✓ Branch 2 taken 78 times.
|
2/2✓ Decision 'true' taken 45 times.
✓ Decision 'false' taken 78 times.
|
123 | if (std::abs(a - 1) < EPS) { |
109 | 45 | return i; | ||
110 |
2/2✓ Branch 0 taken 32 times.
✓ Branch 1 taken 46 times.
|
2/2✓ Decision 'true' taken 32 times.
✓ Decision 'false' taken 46 times.
|
78 | } else if (a >= EPS) { |
111 | 32 | return std::nullopt; | ||
112 | } | |||
113 | } | |||
114 | // If we get here there is a logical error or numerical instability. | |||
115 | ✗ | std::stringstream ss; | ||
116 | ✗ | ss << "Not unitary:" << std::endl << U; | ||
117 | ✗ | throw std::logic_error(ss.str()); | ||
118 | } | |||
119 | ||||
120 | /** | |||
121 | * Return the set of v in F all of whose quantum in-edges are in qvals. | |||
122 | */ | |||
123 | 50 | static VertexSet known_inputs_only( | ||
124 | const Circuit &circ, const VertexSet &F, | |||
125 | const std::map<Edge, bool> &qvals) { | |||
126 | 50 | VertexSet v_frontier; | ||
127 |
2/2✓ Branch 5 taken 75 times.
✓ Branch 6 taken 50 times.
|
2/2✓ Decision 'true' taken 75 times.
✓ Decision 'false' taken 50 times.
|
125 | for (const Vertex &v : F) { |
128 |
1/2✓ Branch 1 taken 75 times.
✗ Branch 2 not taken.
|
75 | EdgeVec v_q_inedges = circ.get_in_edges_of_type(v, EdgeType::Quantum); | |
129 |
3/4✓ Branch 3 taken 75 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 66 times.
✓ Branch 6 taken 9 times.
|
2/2✓ Decision 'true' taken 66 times.
✓ Decision 'false' taken 9 times.
|
75 | if (std::all_of( |
130 | v_q_inedges.begin(), v_q_inedges.end(), | |||
131 |
1/2✓ Branch 2 taken 96 times.
✗ Branch 3 not taken.
|
96 | [&qvals](const Edge &e) { return qvals.find(e) != qvals.end(); })) { | |
132 |
1/2✓ Branch 1 taken 66 times.
✗ Branch 2 not taken.
|
66 | v_frontier.insert(v); | |
133 | } | |||
134 | 75 | } | ||
135 | 50 | return v_frontier; | ||
136 | } | |||
137 | ||||
138 | 13 | Transform simplify_initial( | ||
139 | AllowClassical allow_classical, CreateAllQubits create_all_qubits, | |||
140 | std::shared_ptr<const Circuit> xcirc) { | |||
141 | 20 | return Transform([allow_classical, create_all_qubits, xcirc](Circuit &circ) { | ||
142 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 10 times.
|
2/2✓ Decision 'true' taken 4 times.
✓ Decision 'false' taken 10 times.
|
14 | if (create_all_qubits == CreateAllQubits::Yes) { |
143 |
1/2✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
|
4 | circ.qubit_create_all(); | |
144 | } | |||
145 | ||||
146 | // Find all Create and Reset vertices. | |||
147 | 14 | VertexSet zeroing_vertices; | ||
148 |
7/8✓ Branch 1 taken 14 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 289 times.
✓ Branch 6 taken 14 times.
✓ Branch 8 taken 289 times.
✓ Branch 9 taken 14 times.
✓ Branch 11 taken 14 times.
✓ Branch 12 taken 14 times.
|
317 | BGL_FORALL_VERTICES(v, circ.dag, DAG) { | |
149 |
1/2✓ Branch 1 taken 289 times.
✗ Branch 2 not taken.
|
289 | OpType optype = circ.get_OpType_from_Vertex(v); | |
150 |
4/4✓ Branch 0 taken 254 times.
✓ Branch 1 taken 35 times.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 251 times.
|
2/2✓ Decision 'true' taken 38 times.
✓ Decision 'false' taken 251 times.
|
289 | if (optype == OpType::Create || optype == OpType::Reset) { |
151 |
1/2✓ Branch 1 taken 38 times.
✗ Branch 2 not taken.
|
38 | zeroing_vertices.insert(v); | |
152 | } | |||
153 | } | |||
154 | ||||
155 | // Partial map from quantum edges to values. | |||
156 | 14 | std::map<Edge, bool> qvals; | ||
157 | ||||
158 | // Assign 0 values to all edges coming out of zeroing vertices, and | |||
159 | // construct the set of their target vertices. | |||
160 | 14 | VertexSet F; | ||
161 |
2/2✓ Branch 5 taken 38 times.
✓ Branch 6 taken 14 times.
|
2/2✓ Decision 'true' taken 38 times.
✓ Decision 'false' taken 14 times.
|
52 | for (auto z : zeroing_vertices) { |
162 |
1/2✓ Branch 1 taken 38 times.
✗ Branch 2 not taken.
|
38 | EdgeVec z_outedges = circ.get_all_out_edges(z); | |
163 | TKET_ASSERT(z_outedges.size() == 1); | |||
164 | 38 | Edge z_out = z_outedges[0]; | ||
165 | TKET_ASSERT(circ.get_edgetype(z_out) == EdgeType::Quantum); | |||
166 |
1/2✓ Branch 1 taken 38 times.
✗ Branch 2 not taken.
|
38 | qvals[z_out] = false; | |
167 |
2/4✓ Branch 1 taken 38 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 38 times.
✗ Branch 5 not taken.
|
38 | F.insert(circ.target(z_out)); | |
168 | 38 | } | ||
169 | ||||
170 | // Construct frontier of vertices with all-known input values. | |||
171 |
1/2✓ Branch 1 taken 14 times.
✗ Branch 2 not taken.
|
14 | VertexSet v_frontier = known_inputs_only(circ, F, qvals); | |
172 | ||||
173 | // Partial map from vertices to sequences of X gates to replace them. | |||
174 | 14 | std::map<Vertex, std::vector<bool>> reductions; | ||
175 | ||||
176 | // Partial map from Measure vertices to bits to set after measure. | |||
177 | 14 | std::map<Vertex, bool> measurebits; | ||
178 | ||||
179 |
2/2✓ Branch 1 taken 36 times.
✓ Branch 2 taken 14 times.
|
2/2✓ Decision 'true' taken 36 times.
✓ Decision 'false' taken 14 times.
|
50 | while (!v_frontier.empty()) { |
180 | // Simplify vertices in the frontier that we can, and move it on. | |||
181 | 36 | F.clear(); | ||
182 |
2/2✓ Branch 5 taken 66 times.
✓ Branch 6 taken 36 times.
|
2/2✓ Decision 'true' taken 66 times.
✓ Decision 'false' taken 36 times.
|
102 | for (const Vertex &v : v_frontier) { |
183 | // If there are any Boolean inputs to v, skip it. | |||
184 |
2/4✓ Branch 1 taken 66 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 66 times.
|
2/2✓ Decision 'true' taken 34 times.
✓ Decision 'false' taken 32 times.
|
66 | if (circ.n_in_edges_of_type(v, EdgeType::Boolean) != 0) { |
185 | 34 | continue; | ||
186 | } | |||
187 | ||||
188 |
1/2✓ Branch 1 taken 66 times.
✗ Branch 2 not taken.
|
66 | Op_ptr op = circ.get_Op_ptr_from_Vertex(v); | |
189 |
1/2✓ Branch 2 taken 66 times.
✗ Branch 3 not taken.
|
66 | std::optional<Eigen::MatrixXcd> U = op_unitary(op); | |
190 | 66 | bool is_measure = (op->get_type() == OpType::Measure); | ||
191 | ||||
192 |
7/8✓ Branch 1 taken 13 times.
✓ Branch 2 taken 53 times.
✓ Branch 3 taken 3 times.
✓ Branch 4 taken 10 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 3 times.
✓ Branch 7 taken 10 times.
✓ Branch 8 taken 56 times.
|
2/2✓ Decision 'true' taken 10 times.
✓ Decision 'false' taken 56 times.
|
66 | if (!U && (!is_measure || allow_classical == AllowClassical::No)) { |
193 | 10 | continue; | ||
194 | } | |||
195 | ||||
196 | // Compute input state. | |||
197 |
1/2✓ Branch 1 taken 56 times.
✗ Branch 2 not taken.
|
56 | EdgeVec v_q_inedges = circ.get_in_edges_of_type(v, EdgeType::Quantum); | |
198 | 56 | unsigned n_q = v_q_inedges.size(); | ||
199 |
1/2✓ Branch 2 taken 56 times.
✗ Branch 3 not taken.
|
56 | std::vector<bool> v_invals(n_q); | |
200 |
2/2✓ Branch 0 taken 69 times.
✓ Branch 1 taken 56 times.
|
2/2✓ Decision 'true' taken 69 times.
✓ Decision 'false' taken 56 times.
|
125 | for (unsigned i = 0; i < n_q; i++) { |
201 |
2/4✓ Branch 2 taken 69 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 69 times.
✗ Branch 6 not taken.
|
69 | v_invals[i] = qvals[v_q_inedges[i]]; | |
202 | } | |||
203 | ||||
204 |
2/2✓ Branch 1 taken 53 times.
✓ Branch 2 taken 3 times.
|
2/2✓ Decision 'true' taken 53 times.
✓ Decision 'false' taken 3 times.
|
56 | if (U) { |
205 | // Compute relevant column J of U. | |||
206 | 53 | unsigned J = 0, pow2 = 1u << n_q; | ||
207 |
2/2✓ Branch 0 taken 66 times.
✓ Branch 1 taken 53 times.
|
2/2✓ Decision 'true' taken 66 times.
✓ Decision 'false' taken 53 times.
|
119 | for (unsigned i = 0; i < n_q; i++) { |
208 | 66 | pow2 >>= 1; | ||
209 |
3/4✓ Branch 1 taken 66 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 13 times.
✓ Branch 5 taken 53 times.
|
2/2✓ Decision 'true' taken 13 times.
✓ Decision 'false' taken 53 times.
|
66 | if (v_invals[i]) { |
210 | 13 | J |= pow2; | ||
211 | } | |||
212 | } | |||
213 | ||||
214 | // Check if there is a unique I s.t. |U(I,J)| == 1. | |||
215 |
2/4✓ Branch 2 taken 53 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 53 times.
✗ Branch 6 not taken.
|
53 | std::optional<unsigned> I = unique_unit_row(*U, J); | |
216 |
2/2✓ Branch 1 taken 24 times.
✓ Branch 2 taken 29 times.
|
2/2✓ Decision 'true' taken 29 times.
✓ Decision 'false' taken 24 times.
|
53 | if (!I) continue; |
217 | ||||
218 | // Convert I to a vector of bool | |||
219 |
1/2✓ Branch 2 taken 29 times.
✗ Branch 3 not taken.
|
29 | std::vector<bool> v_outvals(n_q); | |
220 |
2/2✓ Branch 0 taken 42 times.
✓ Branch 1 taken 29 times.
|
2/2✓ Decision 'true' taken 42 times.
✓ Decision 'false' taken 29 times.
|
71 | for (unsigned i = 0; i < n_q; i++) { |
221 |
1/2✓ Branch 2 taken 42 times.
✗ Branch 3 not taken.
|
42 | v_outvals[i] = (*I >> (n_q - 1 - i)) & 1; | |
222 | } | |||
223 | ||||
224 | // Label out-edges of v; construct equivalent X-gate rep. | |||
225 |
1/2✓ Branch 1 taken 29 times.
✗ Branch 2 not taken.
|
29 | EdgeVec v_outedges = circ.get_all_out_edges(v); | |
226 | TKET_ASSERT(v_outedges.size() == n_q); | |||
227 |
1/2✓ Branch 2 taken 29 times.
✗ Branch 3 not taken.
|
29 | std::vector<bool> x_gates(n_q); | |
228 |
2/2✓ Branch 0 taken 42 times.
✓ Branch 1 taken 29 times.
|
2/2✓ Decision 'true' taken 42 times.
✓ Decision 'false' taken 29 times.
|
71 | for (unsigned i = 0; i < n_q; i++) { |
229 |
1/2✓ Branch 1 taken 42 times.
✗ Branch 2 not taken.
|
42 | bool outval = v_outvals[i]; | |
230 |
1/2✓ Branch 2 taken 42 times.
✗ Branch 3 not taken.
|
42 | qvals[v_outedges[i]] = outval; | |
231 |
2/4✓ Branch 1 taken 42 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 42 times.
✗ Branch 6 not taken.
|
42 | x_gates[i] = v_invals[i] ^ outval; | |
232 | } | |||
233 | ||||
234 | // Record vertex for later replacement with X-gates. | |||
235 |
2/4✓ Branch 1 taken 29 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 29 times.
✗ Branch 5 not taken.
|
29 | reductions[v] = x_gates; | |
236 | ||||
237 | // Add all successors of v to potential next frontier F. | |||
238 |
2/2✓ Branch 4 taken 42 times.
✓ Branch 5 taken 29 times.
|
2/2✓ Decision 'true' taken 42 times.
✓ Decision 'false' taken 29 times.
|
71 | for (const Edge &e : v_outedges) { |
239 |
2/4✓ Branch 1 taken 42 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 42 times.
✗ Branch 5 not taken.
|
42 | F.insert(circ.target(e)); | |
240 | } | |||
241 | 29 | } else { | ||
242 | TKET_ASSERT(allow_classical == AllowClassical::Yes); | |||
243 | TKET_ASSERT(n_q == 1); | |||
244 |
2/4✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 3 times.
✗ Branch 6 not taken.
|
3 | measurebits[v] = v_invals[0]; | |
245 | } | |||
246 |
8/8✓ Branch 1 taken 32 times.
✓ Branch 2 taken 24 times.
✓ Branch 4 taken 32 times.
✓ Branch 5 taken 24 times.
✓ Branch 7 taken 32 times.
✓ Branch 8 taken 34 times.
✓ Branch 10 taken 32 times.
✓ Branch 11 taken 34 times.
|
148 | } | |
247 | ||||
248 | // Replace v_frontier with vertices from F having all-known inputs. | |||
249 |
1/2✓ Branch 1 taken 36 times.
✗ Branch 2 not taken.
|
36 | v_frontier = known_inputs_only(circ, F, qvals); | |
250 | } | |||
251 | ||||
252 | // Perform substitutions. | |||
253 | 14 | VertexList bin; | ||
254 |
2/2✓ Branch 7 taken 29 times.
✓ Branch 8 taken 14 times.
|
2/2✓ Decision 'true' taken 29 times.
✓ Decision 'false' taken 14 times.
|
43 | for (const auto &[v, x_gates] : reductions) { |
255 | 29 | unsigned n_q = x_gates.size(); | ||
256 |
1/2✓ Branch 2 taken 29 times.
✗ Branch 3 not taken.
|
29 | Circuit xs_circ(n_q); | |
257 |
2/2✓ Branch 0 taken 42 times.
✓ Branch 1 taken 29 times.
|
2/2✓ Decision 'true' taken 42 times.
✓ Decision 'false' taken 29 times.
|
71 | for (unsigned i = 0; i < n_q; i++) { |
258 |
3/4✓ Branch 1 taken 42 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 17 times.
✓ Branch 4 taken 25 times.
|
2/2✓ Decision 'true' taken 17 times.
✓ Decision 'false' taken 25 times.
|
42 | if (x_gates[i]) { |
259 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 17 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 17 times.
|
17 | if (xcirc) { |
260 | ✗ | unit_map_t qm; | ||
261 | ✗ | qm.insert({Qubit(i), Qubit(0)}); | ||
262 | ✗ | xs_circ.append_with_map(*xcirc, qm); | ||
263 | ✗ | } else { | ||
264 |
2/4✓ Branch 3 taken 17 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 17 times.
✗ Branch 7 not taken.
|
17 | xs_circ.add_op<unsigned>(OpType::X, {i}); | |
265 | } | |||
266 | } | |||
267 | } | |||
268 |
1/2✓ Branch 1 taken 29 times.
✗ Branch 2 not taken.
|
29 | EdgeVec v_in_edges = circ.get_in_edges(v); // all Quantum | |
269 |
1/2✓ Branch 1 taken 29 times.
✗ Branch 2 not taken.
|
29 | EdgeVec v_out_edges = circ.get_all_out_edges(v); // all Quantum | |
270 | TKET_ASSERT(v_in_edges.size() == n_q); | |||
271 | TKET_ASSERT(v_out_edges.size() == n_q); | |||
272 |
2/4✓ Branch 2 taken 29 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 29 times.
✗ Branch 6 not taken.
|
58 | Subcircuit subc{v_in_edges, v_out_edges, {v}}; | |
273 |
1/2✓ Branch 1 taken 29 times.
✗ Branch 2 not taken.
|
29 | circ.substitute(xs_circ, subc, Circuit::VertexDeletion::No); | |
274 |
1/2✓ Branch 1 taken 29 times.
✗ Branch 2 not taken.
|
29 | bin.push_back(v); | |
275 | 29 | } | ||
276 |
2/2✓ Branch 7 taken 3 times.
✓ Branch 8 taken 14 times.
|
2/2✓ Decision 'true' taken 3 times.
✓ Decision 'false' taken 14 times.
|
17 | for (const auto &[v, bitval] : measurebits) { |
277 |
1/2✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
|
3 | Circuit newc(1, 1); | |
278 | // Replace the measure with a set-bit. | |||
279 |
1/2✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
|
3 | std::vector<bool> values = {bitval}; | |
280 |
1/2✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
|
3 | Op_ptr setbitop = std::make_shared<SetBitsOp>(values); | |
281 |
5/10✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 3 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 3 times.
✗ Branch 10 not taken.
✓ Branch 13 taken 3 times.
✓ Branch 14 taken 3 times.
✗ Branch 19 not taken.
✗ Branch 20 not taken.
|
6 | newc.add_op<Bit>(setbitop, {Bit(0)}); | |
282 |
1/2✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
|
3 | EdgeVec q_in_edges = circ.get_in_edges_of_type(v, EdgeType::Quantum); | |
283 | TKET_ASSERT(q_in_edges.size() == 1); | |||
284 |
1/2✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
|
3 | EdgeVec q_out_edges = circ.get_out_edges_of_type(v, EdgeType::Quantum); | |
285 | TKET_ASSERT(q_out_edges.size() == 1); | |||
286 |
1/2✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
|
3 | EdgeVec c_in_edges = circ.get_in_edges_of_type(v, EdgeType::Classical); | |
287 | TKET_ASSERT(c_in_edges.size() == 1); | |||
288 |
1/2✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
|
3 | EdgeVec c_out_edges = circ.get_out_edges_of_type(v, EdgeType::Classical); | |
289 | TKET_ASSERT(c_out_edges.size() == 1); | |||
290 |
1/2✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
|
3 | EdgeVec b_out_edges = circ.get_out_edges_of_type(v, EdgeType::Boolean); | |
291 | Subcircuit subc{q_in_edges, q_out_edges, c_in_edges, | |||
292 |
2/4✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 3 times.
✗ Branch 6 not taken.
|
6 | c_out_edges, b_out_edges, {v}}; | |
293 |
1/2✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
|
3 | circ.substitute(newc, subc, Circuit::VertexDeletion::No); | |
294 |
1/2✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
|
3 | bin.push_back(v); | |
295 | 3 | } | ||
296 | 14 | bool changed = !bin.empty(); | ||
297 |
1/2✓ Branch 1 taken 14 times.
✗ Branch 2 not taken.
|
14 | circ.remove_vertices( | |
298 | bin, Circuit::GraphRewiring::No, Circuit::VertexDeletion::Yes); | |||
299 | ||||
300 | 14 | return changed; | ||
301 |
2/4✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 13 times.
✗ Branch 6 not taken.
|
27 | }); | |
302 | } | |||
303 | ||||
304 | 33 | std::optional<std::shared_ptr<const ClassicalTransformOp>> classical_transform( | ||
305 | Op_ptr op) { | |||
306 |
1/2✓ Branch 2 taken 33 times.
✗ Branch 3 not taken.
|
33 | std::optional<Eigen::MatrixXcd> U = op_unitary(op); | |
307 |
2/2✓ Branch 1 taken 20 times.
✓ Branch 2 taken 13 times.
|
2/2✓ Decision 'true' taken 13 times.
✓ Decision 'false' taken 20 times.
|
33 | if (!U) return std::nullopt; |
308 |
3/6✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 13 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 13 times.
✗ Branch 9 not taken.
|
13 | unsigned n = op->get_desc().n_qubits().value(); | |
309 | 13 | unsigned pow2n = 1u << n; | ||
310 | TKET_ASSERT(U->cols() == pow2n); | |||
311 |
1/2✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
|
13 | std::vector<uint32_t> values(pow2n); | |
312 |
2/2✓ Branch 0 taken 24 times.
✓ Branch 1 taken 5 times.
|
2/2✓ Decision 'true' taken 24 times.
✓ Decision 'false' taken 5 times.
|
29 | for (uint32_t J = 0; J < pow2n; J++) { |
313 | // Look at the column U[*,J]. Is there a unique nonzero element? | |||
314 |
2/4✓ Branch 2 taken 24 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 24 times.
✗ Branch 6 not taken.
|
24 | std::optional<unsigned> I = unique_unit_row(*U, J); | |
315 |
2/2✓ Branch 1 taken 8 times.
✓ Branch 2 taken 16 times.
|
2/2✓ Decision 'true' taken 16 times.
✓ Decision 'false' taken 8 times.
|
24 | if (!I) return std::nullopt; |
316 |
2/4✓ Branch 2 taken 16 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 16 times.
✗ Branch 6 not taken.
|
16 | values[reverse_bits(J, n)] = reverse_bits(*I, n); | |
317 | } | |||
318 |
1/2✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
|
5 | return std::make_shared<ClassicalTransformOp>(n, values); | |
319 | 33 | } | ||
320 | ||||
321 | 4 | Transform simplify_measured() { | ||
322 | 10 | return Transform([](Circuit &circ) { | ||
323 | // First construct the set of all Measure vertices that are followed by | |||
324 | // Discard vertices (and no Boolean out-edges). | |||
325 | 10 | VertexSet M; | ||
326 |
7/8✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 233 times.
✓ Branch 6 taken 10 times.
✓ Branch 8 taken 233 times.
✓ Branch 9 taken 10 times.
✓ Branch 11 taken 10 times.
✓ Branch 12 taken 10 times.
|
253 | BGL_FORALL_VERTICES(v, circ.dag, DAG) { | |
327 |
3/4✓ Branch 1 taken 233 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 13 times.
✓ Branch 4 taken 220 times.
|
2/2✓ Decision 'true' taken 13 times.
✓ Decision 'false' taken 220 times.
|
233 | if (circ.get_OpType_from_Vertex(v) == OpType::Measure) { |
328 |
2/4✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 13 times.
✗ Branch 4 not taken.
|
1/2✓ Decision 'true' taken 13 times.
✗ Decision 'false' not taken.
|
13 | if (circ.n_out_edges_of_type(v, EdgeType::Boolean) == 0) { |
329 |
1/2✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
|
13 | EdgeVec m_q_outs = circ.get_out_edges_of_type(v, EdgeType::Quantum); | |
330 | TKET_ASSERT(m_q_outs.size() == 1); | |||
331 | 13 | Edge m_q_out = m_q_outs[0]; | ||
332 |
1/2✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
|
13 | Vertex v1 = circ.target(m_q_out); | |
333 |
3/4✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 9 times.
✓ Branch 4 taken 4 times.
|
2/2✓ Decision 'true' taken 9 times.
✓ Decision 'false' taken 4 times.
|
13 | if (circ.get_OpType_from_Vertex(v1) == OpType::Discard) { |
334 |
1/2✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
|
9 | M.insert(v); | |
335 | } | |||
336 | 13 | } | ||
337 | } | |||
338 | } | |||
339 | 10 | bool changed = false; | ||
340 | bool carry_on; | |||
341 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 10 times.
|
14 | do { | |
342 | 14 | carry_on = false; | ||
343 | 14 | VertexList bin; | ||
344 | // Find all classical maps all of whose successors are in M | |||
345 |
2/2✓ Branch 5 taken 17 times.
✓ Branch 6 taken 14 times.
|
2/2✓ Decision 'true' taken 17 times.
✓ Decision 'false' taken 14 times.
|
31 | for (const Vertex &v : M) { |
346 |
1/2✓ Branch 1 taken 17 times.
✗ Branch 2 not taken.
|
17 | VertexVec preds = circ.get_predecessors(v); | |
347 |
2/2✓ Branch 5 taken 34 times.
✓ Branch 6 taken 17 times.
|
2/2✓ Decision 'true' taken 34 times.
✓ Decision 'false' taken 17 times.
|
51 | for (const Vertex &v0 : preds) { |
348 | // Any Boolean inputs? | |||
349 |
2/4✓ Branch 1 taken 34 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 34 times.
|
2/2✓ Decision 'true' taken 29 times.
✓ Decision 'false' taken 5 times.
|
34 | if (circ.n_in_edges_of_type(v0, EdgeType::Boolean) != 0) { |
350 | 29 | continue; | ||
351 | } | |||
352 | // No. Are all successors in M? | |||
353 |
1/2✓ Branch 1 taken 34 times.
✗ Branch 2 not taken.
|
34 | VertexVec succs = circ.get_successors(v0); | |
354 |
3/4✓ Branch 3 taken 34 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 33 times.
|
34 | if (std::any_of(succs.begin(), succs.end(), [&](const Vertex &u) { | |
355 |
1/2✓ Branch 2 taken 38 times.
✗ Branch 3 not taken.
|
38 | return M.find(u) == M.end(); | |
356 | })) { | |||
357 | 1 | continue; | ||
358 | } | |||
359 | // Yes. Is it a classical map? | |||
360 |
1/2✓ Branch 1 taken 33 times.
✗ Branch 2 not taken.
|
33 | Op_ptr op = circ.get_Op_ptr_from_Vertex(v0); | |
361 | std::optional<std::shared_ptr<const ClassicalTransformOp>> cm = | |||
362 |
1/2✓ Branch 2 taken 33 times.
✗ Branch 3 not taken.
|
33 | classical_transform(op); | |
363 |
2/2✓ Branch 1 taken 28 times.
✓ Branch 2 taken 5 times.
|
33 | if (!cm) continue; | |
364 | // Yes. Remove v0. | |||
365 | 5 | unsigned n_qb = succs.size(); | ||
366 |
1/2✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
|
5 | circ.remove_vertex( | |
367 | v0, Circuit::GraphRewiring::Yes, Circuit::VertexDeletion::No); | |||
368 |
1/2✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
|
5 | bin.push_back(v0); | |
369 | // Insert *cm on the classical target wires. | |||
370 |
1/2✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
|
5 | EdgeVec cl_edges(n_qb); | |
371 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 5 times.
|
13 | for (unsigned i = 0; i < n_qb; i++) { | |
372 | EdgeVec m_c_outs = | |||
373 |
1/2✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
|
8 | circ.get_out_edges_of_type(succs[i], EdgeType::Classical); | |
374 | TKET_ASSERT(m_c_outs.size() == 1); | |||
375 | 8 | cl_edges[i] = m_c_outs[0]; | ||
376 | 8 | } | ||
377 |
1/2✓ Branch 5 taken 5 times.
✗ Branch 6 not taken.
|
10 | Subcircuit cl_subc{{}, {}, cl_edges, cl_edges, {}, {}}; | |
378 |
1/2✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
|
5 | Circuit cl_circ(0, n_qb); | |
379 |
1/2✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
|
5 | std::vector<unsigned> args(n_qb); | |
380 | 5 | std::iota(args.begin(), args.end(), 0); | ||
381 |
1/2✓ Branch 4 taken 5 times.
✗ Branch 5 not taken.
|
5 | cl_circ.add_op<unsigned>(*cm, args); | |
382 |
1/2✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
|
5 | circ.substitute(cl_circ, cl_subc, Circuit::VertexDeletion::No); | |
383 | 5 | changed = true; | ||
384 | 5 | carry_on = true; | ||
385 |
6/6✓ Branch 5 taken 5 times.
✓ Branch 6 taken 28 times.
✓ Branch 8 taken 5 times.
✓ Branch 9 taken 28 times.
✓ Branch 11 taken 5 times.
✓ Branch 12 taken 29 times.
|
90 | } | |
386 | 17 | } | ||
387 |
1/2✓ Branch 1 taken 14 times.
✗ Branch 2 not taken.
|
14 | circ.remove_vertices( | |
388 | bin, Circuit::GraphRewiring::No, Circuit::VertexDeletion::Yes); | |||
389 | 14 | } while (carry_on); | ||
390 | 10 | return changed; | ||
391 |
1/2✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
|
14 | }); | |
392 | } | |||
393 | ||||
394 | 2 | std::pair<Circuit, Circuit> separate_classical(const Circuit &circ) { | ||
395 | // Initialize the two circuits to return. | |||
396 |
1/2✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
|
2 | qubit_vector_t qubits = circ.all_qubits(); | |
397 |
1/2✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
|
2 | bit_vector_t bits = circ.all_bits(); | |
398 |
2/4✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
|
2 | Circuit c0, c1; | |
399 |
2/2✓ Branch 5 taken 5 times.
✓ Branch 6 taken 2 times.
|
7 | for (const Qubit &qb : qubits) { | |
400 |
1/2✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
|
5 | c0.add_qubit(qb); | |
401 | } | |||
402 |
2/2✓ Branch 5 taken 5 times.
✓ Branch 6 taken 2 times.
|
7 | for (const Bit &b : bits) { | |
403 |
1/2✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
|
5 | c0.add_bit(b); | |
404 |
1/2✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
|
5 | c1.add_bit(b); | |
405 | } | |||
406 | ||||
407 | // Get the command list. | |||
408 |
1/2✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
|
2 | std::vector<Command> cmds = circ.get_commands(); | |
409 | ||||
410 | // Find the final Measure (or ClInput if no Measure) on each bit. | |||
411 |
1/2✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
|
2 | VertexVec c_in = circ.c_inputs(); | |
412 | 2 | unsigned n_bits = bits.size(); | ||
413 | TKET_ASSERT(n_bits == c_in.size()); | |||
414 | 2 | std::map<Bit, Vertex> final_vert_on_bit; | ||
415 |
2/2✓ Branch 0 taken 5 times.
✓ Branch 1 taken 2 times.
|
7 | for (unsigned i = 0; i < n_bits; i++) { | |
416 |
1/2✓ Branch 3 taken 5 times.
✗ Branch 4 not taken.
|
5 | final_vert_on_bit[bits[i]] = c_in[i]; | |
417 | } | |||
418 |
2/2✓ Branch 5 taken 11 times.
✓ Branch 6 taken 2 times.
|
13 | for (const Command &cmd : cmds) { | |
419 | 11 | OpType optype = cmd.get_op_ptr()->get_type(); | ||
420 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 8 times.
|
11 | if (optype == OpType::Measure) { | |
421 |
1/2✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
|
3 | bit_vector_t cmd_bits = cmd.get_bits(); | |
422 | TKET_ASSERT(cmd_bits.size() == 1); | |||
423 |
1/2✓ Branch 3 taken 3 times.
✗ Branch 4 not taken.
|
3 | final_vert_on_bit[cmd_bits[0]] = cmd.get_vertex(); | |
424 | 3 | } | ||
425 | } | |||
426 | ||||
427 | // Construct the set of final vertices. | |||
428 | 2 | VertexSet final_verts; | ||
429 |
2/2✓ Branch 5 taken 5 times.
✓ Branch 6 taken 2 times.
|
7 | for (const Bit &b : bits) { | |
430 |
2/4✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 5 times.
✗ Branch 5 not taken.
|
5 | final_verts.insert(final_vert_on_bit[b]); | |
431 | } | |||
432 | ||||
433 | // Construct the set of vertices to go in c1. | |||
434 | // Initially include final_verts; later exclude them. | |||
435 |
1/2✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
|
2 | VertexSet c1_verts(final_verts); | |
436 |
2/2✓ Branch 5 taken 11 times.
✓ Branch 6 taken 2 times.
|
13 | for (const Command &cmd : cmds) { | |
437 | 11 | Vertex v = cmd.get_vertex(); | ||
438 |
1/2✓ Branch 1 taken 11 times.
✗ Branch 2 not taken.
|
11 | VertexVec preds = circ.get_predecessors(v); | |
439 |
3/4✓ Branch 3 taken 11 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 5 times.
✓ Branch 6 taken 6 times.
|
11 | if (std::all_of(preds.begin(), preds.end(), [&c1_verts](const Vertex &v) { | |
440 |
1/2✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
|
13 | return c1_verts.find(v) != c1_verts.end(); | |
441 | })) { | |||
442 |
1/2✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
|
5 | c1_verts.insert(v); | |
443 | } | |||
444 | 11 | } | ||
445 |
2/2✓ Branch 5 taken 5 times.
✓ Branch 6 taken 2 times.
|
7 | for (const Vertex &v : final_verts) { | |
446 |
1/2✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
|
5 | c1_verts.erase(v); | |
447 | } | |||
448 | ||||
449 | // Step through the circuit, filling in c0 and c1. | |||
450 |
2/2✓ Branch 5 taken 11 times.
✓ Branch 6 taken 2 times.
|
13 | for (const Command &cmd : cmds) { | |
451 | 11 | Op_ptr op = cmd.get_op_ptr(); | ||
452 |
1/2✓ Branch 1 taken 11 times.
✗ Branch 2 not taken.
|
11 | unit_vector_t args = cmd.get_args(); | |
453 |
3/4✓ Branch 3 taken 11 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 6 times.
✓ Branch 7 taken 5 times.
|
11 | if (c1_verts.find(cmd.get_vertex()) == c1_verts.end()) { | |
454 |
1/2✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
|
6 | c0.add_op(op, args); | |
455 | } else { | |||
456 |
1/2✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
|
5 | c1.add_op(op, args); | |
457 | } | |||
458 | 11 | } | ||
459 | ||||
460 |
1/2✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
|
4 | return {c0, c1}; | |
461 | 2 | } | ||
462 | ||||
463 | } // namespace Transforms | |||
464 | ||||
465 | } // namespace tket | |||
466 |