GCC Code Coverage Report


Directory: ./
File: Converters/CliffTableauConverters.cpp
Date: 2022-10-15 05:10:18
Warnings: 2 unchecked decisions!
Exec Total Coverage
Lines: 112 116 96.6%
Functions: 2 2 100.0%
Branches: 165 288 57.3%
Decisions: 40 52 76.9%

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 <stdexcept>
16
17 #include "Converters.hpp"
18
19 namespace tket {
20
21 58 CliffTableau circuit_to_tableau(const Circuit &circ) {
22
1/2
✓ Branch 2 taken 58 times.
✗ Branch 3 not taken.
58 CliffTableau tab(circ.all_qubits());
23
6/10
✓ Branch 1 taken 58 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 58 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 257 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 256 times.
✗ Branch 11 not taken.
✓ Branch 13 taken 257 times.
✓ Branch 14 taken 57 times.
0/1
? Decision couldn't be analyzed.
314 for (const Command &com : circ) {
24 257 std::vector<unsigned> qbs;
25
3/4
✓ Branch 1 taken 257 times.
✗ Branch 2 not taken.
✓ Branch 7 taken 353 times.
✓ Branch 8 taken 257 times.
0/1
? Decision couldn't be analyzed.
610 for (const UnitID &qb : com.get_args()) {
26
3/6
✓ Branch 1 taken 353 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 353 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 353 times.
✗ Branch 8 not taken.
353 qbs.push_back(tab.qubits_.left.at(Qubit(qb)));
27 257 }
28
2/2
✓ Branch 4 taken 256 times.
✓ Branch 5 taken 1 times.
258 tab.apply_gate_at_end(com.get_op_ptr()->get_type(), qbs);
29 317 }
30 57 return tab;
31 1 }
32
33 39 Circuit tableau_to_circuit(const CliffTableau &tab) {
34
1/2
✓ Branch 1 taken 39 times.
✗ Branch 2 not taken.
39 CliffTableau tabl(tab);
35 39 unsigned size = tabl.size_;
36 /*
37 * Aaronson-Gottesman: Improved Simulation of Stabilizer Circuits, Theorem 8
38 * Any unitary stabilizer circuit has an equivalent circuit in canonical form
39 * (H-C-P-C-P-C-H-P-C-P-C).
40 * Produces a circuit realising the tableau, but consumes the tableau in the
41 * process.
42 */
43
1/2
✓ Branch 2 taken 39 times.
✗ Branch 3 not taken.
39 Circuit c(size);
44
45 /*
46 * Step 1: Use Hadamards (in our case, Vs) to make C (zpauli_x) have full rank
47 */
48
1/2
✓ Branch 1 taken 39 times.
✗ Branch 2 not taken.
39 MatrixXb echelon = tabl.zpauli_x;
49 39 std::map<unsigned, unsigned> leading_val_to_col;
50
2/2
✓ Branch 0 taken 127 times.
✓ Branch 1 taken 39 times.
2/2
✓ Decision 'true' taken 127 times.
✓ Decision 'false' taken 39 times.
166 for (unsigned i = 0; i < size; i++) {
51
2/2
✓ Branch 0 taken 439 times.
✓ Branch 1 taken 122 times.
2/2
✓ Decision 'true' taken 439 times.
✓ Decision 'false' taken 122 times.
561 for (unsigned j = 0; j < size; j++) {
52
3/4
✓ Branch 1 taken 439 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 6 times.
✓ Branch 4 taken 433 times.
2/2
✓ Decision 'true' taken 6 times.
✓ Decision 'false' taken 433 times.
439 if (echelon(j, i)) {
53
3/4
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 5 times.
✓ Branch 6 taken 1 times.
2/2
✓ Decision 'true' taken 5 times.
✓ Decision 'false' taken 1 times.
6 if (leading_val_to_col.find(j) == leading_val_to_col.end()) {
54
1/2
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
5 leading_val_to_col.insert({j, i});
55 5 break;
56 } else {
57
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 unsigned l = leading_val_to_col.at(j);
58
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 times.
2/2
✓ Decision 'true' taken 3 times.
✓ Decision 'false' taken 1 times.
4 for (unsigned k = 0; k < size; k++) {
59
2/4
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
3 echelon(k, i) ^= echelon(k, l);
60 }
61 }
62 }
63 }
64
2/2
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 122 times.
2/2
✓ Decision 'true' taken 5 times.
✓ Decision 'false' taken 122 times.
127 if (leading_val_to_col.size() > i)
65 5 continue; // Independent of previous cols
66
2/4
✓ Branch 3 taken 122 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 122 times.
✗ Branch 7 not taken.
122 c.add_op<unsigned>(OpType::V, {i});
67
1/2
✓ Branch 1 taken 122 times.
✗ Branch 2 not taken.
122 tabl.apply_V_at_front(i);
68
1/2
✓ Branch 1 taken 122 times.
✗ Branch 2 not taken.
122 tabl.apply_V_at_front(i);
69
1/2
✓ Branch 1 taken 122 times.
✗ Branch 2 not taken.
122 tabl.apply_V_at_front(i);
70
3/6
✓ Branch 1 taken 122 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 122 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 122 times.
✗ Branch 8 not taken.
122 echelon.col(i) = tabl.zpauli_z.col(i);
71
1/2
✓ Branch 0 taken 272 times.
✗ Branch 1 not taken.
1/2
✓ Decision 'true' taken 272 times.
✗ Decision 'false' not taken.
272 for (unsigned j = 0; j < size; j++) {
72
3/4
✓ Branch 1 taken 272 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 122 times.
✓ Branch 4 taken 150 times.
2/2
✓ Decision 'true' taken 122 times.
✓ Decision 'false' taken 150 times.
272 if (echelon(j, i)) {
73
2/4
✓ Branch 2 taken 122 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 122 times.
✗ Branch 6 not taken.
1/2
✓ Decision 'true' taken 122 times.
✗ Decision 'false' not taken.
122 if (leading_val_to_col.find(j) == leading_val_to_col.end()) {
74
1/2
✓ Branch 2 taken 122 times.
✗ Branch 3 not taken.
122 leading_val_to_col.insert({j, i});
75 122 break;
76 } else {
77 unsigned l = leading_val_to_col.at(j);
78
0/2
✗ Decision 'true' not taken.
✗ Decision 'false' not taken.
for (unsigned k = 0; k < size; k++) {
79 echelon(k, i) ^= echelon(k, l);
80 }
81 }
82 }
83 }
84
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 122 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 122 times.
122 if (leading_val_to_col.size() == i)
85 throw std::invalid_argument("Stabilisers are not mutually independent");
86 }
87
88 /*
89 * Step 2: Use CXs to perform Gaussian elimination on C (zpauli_x), producing
90 * / A B \
91 * \ I D /
92 */
93 39 for (const std::pair<unsigned, unsigned> &qbs :
94
3/4
✓ Branch 1 taken 39 times.
✗ Branch 2 not taken.
✓ Branch 8 taken 22 times.
✓ Branch 9 taken 39 times.
100 gaussian_elimination_col_ops(tabl.zpauli_x)) {
95
2/4
✓ Branch 3 taken 22 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 22 times.
✗ Branch 7 not taken.
22 c.add_op<unsigned>(OpType::CX, {qbs.first, qbs.second});
96
1/2
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
22 tabl.apply_CX_at_front(qbs.first, qbs.second);
97 39 }
98
99 /*
100 * Step 3: Commutativity of the stabilizer implies that ID^T is symmetric,
101 * therefore D is symmetric, and we can apply phase (S) gates to add a
102 * diagonal matrix to D and use Lemma 7 to convert D to the form D = MM^T
103 * for some invertible M.
104 */
105 std::pair<MatrixXb, MatrixXb> zp_z_llt =
106
1/2
✓ Branch 1 taken 39 times.
✗ Branch 2 not taken.
39 binary_LLT_decomposition(tabl.zpauli_z);
107
2/2
✓ Branch 0 taken 127 times.
✓ Branch 1 taken 39 times.
2/2
✓ Decision 'true' taken 127 times.
✓ Decision 'false' taken 39 times.
166 for (unsigned i = 0; i < size; i++) {
108
3/4
✓ Branch 1 taken 127 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 125 times.
2/2
✓ Decision 'true' taken 2 times.
✓ Decision 'false' taken 125 times.
127 if (zp_z_llt.second(i, i)) {
109
2/4
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 2 times.
✗ Branch 7 not taken.
2 c.add_op<unsigned>(OpType::S, {i});
110
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 tabl.apply_S_at_front(i);
111
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 tabl.apply_S_at_front(i);
112
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 tabl.apply_S_at_front(i);
113 }
114 }
115
116 /*
117 * Step 4: Use CXs to produce
118 * / A B \
119 * \ M M /
120 * Note that when we map I to IM, we also map D to D(M^T)^{-1} = M.
121 */
122 std::vector<std::pair<unsigned, unsigned>> m_to_i =
123
1/2
✓ Branch 1 taken 39 times.
✗ Branch 2 not taken.
39 gaussian_elimination_col_ops(zp_z_llt.first);
124
1/2
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
1/2
✓ Decision 'true' taken 12 times.
✗ Decision 'false' not taken.
51 for (std::vector<std::pair<unsigned, unsigned>>::reverse_iterator it =
125 39 m_to_i.rbegin();
126
3/4
✓ Branch 2 taken 51 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 12 times.
✓ Branch 5 taken 39 times.
51 it != m_to_i.rend(); it++) {
127
4/8
✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 12 times.
✗ Branch 6 not taken.
✓ Branch 9 taken 12 times.
✗ Branch 10 not taken.
✓ Branch 12 taken 12 times.
✗ Branch 13 not taken.
12 c.add_op<unsigned>(OpType::CX, {it->first, it->second});
128
3/6
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 12 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 12 times.
✗ Branch 8 not taken.
12 tabl.apply_CX_at_front(it->first, it->second);
129 }
130
131 /*
132 * Step 5: Apply phases to all n qubits to obtain
133 * / A B \
134 * \ M 0 /
135 * Since M is full rank, there exists some subset S of qubits such that
136 * applying two phases in succession (Z) to every a \in S will preserve
137 * the tableau, but set r_{n+1} = ... = r_{2n} = 0 (zpauli_phase = 0^n).
138 * Apply two phases (Z) to every a \in S. DELAYED UNTIL END
139 */
140
2/2
✓ Branch 0 taken 127 times.
✓ Branch 1 taken 39 times.
2/2
✓ Decision 'true' taken 127 times.
✓ Decision 'false' taken 39 times.
166 for (unsigned i = 0; i < size; i++) {
141
2/4
✓ Branch 3 taken 127 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 127 times.
✗ Branch 7 not taken.
127 c.add_op<unsigned>(OpType::S, {i});
142
1/2
✓ Branch 1 taken 127 times.
✗ Branch 2 not taken.
127 tabl.apply_S_at_front(i);
143
1/2
✓ Branch 1 taken 127 times.
✗ Branch 2 not taken.
127 tabl.apply_S_at_front(i);
144
1/2
✓ Branch 1 taken 127 times.
✗ Branch 2 not taken.
127 tabl.apply_S_at_front(i);
145 }
146
147 /*
148 * Step 6: Use CXs to perform Gaussian elimination on M, producing
149 * / A B \
150 * \ I 0 /
151 * By commutativity relations, IB^T = A0^T + I, therefore B = I.
152 */
153 39 for (const std::pair<unsigned, unsigned> &qbs :
154
3/4
✓ Branch 1 taken 39 times.
✗ Branch 2 not taken.
✓ Branch 8 taken 12 times.
✓ Branch 9 taken 39 times.
90 gaussian_elimination_col_ops(tabl.zpauli_x)) {
155
2/4
✓ Branch 3 taken 12 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 12 times.
✗ Branch 7 not taken.
12 c.add_op<unsigned>(OpType::CX, {qbs.first, qbs.second});
156
1/2
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
12 tabl.apply_CX_at_front(qbs.first, qbs.second);
157 39 }
158
159 /*
160 * Step 7: Use Hadamards to produce
161 * / I A \
162 * \ 0 I /
163 */
164
2/2
✓ Branch 0 taken 127 times.
✓ Branch 1 taken 39 times.
2/2
✓ Decision 'true' taken 127 times.
✓ Decision 'false' taken 39 times.
166 for (unsigned i = 0; i < size; i++) {
165
2/4
✓ Branch 3 taken 127 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 127 times.
✗ Branch 7 not taken.
127 c.add_op<unsigned>(OpType::H, {i});
166
1/2
✓ Branch 1 taken 127 times.
✗ Branch 2 not taken.
127 tabl.apply_S_at_front(i);
167
1/2
✓ Branch 1 taken 127 times.
✗ Branch 2 not taken.
127 tabl.apply_V_at_front(i);
168
1/2
✓ Branch 1 taken 127 times.
✗ Branch 2 not taken.
127 tabl.apply_S_at_front(i);
169 }
170
171 /*
172 * Step 8: Now commutativity of the destabilizer implies that A is symmetric,
173 * therefore we can again use phase (S) gates and Lemma 7 to make A = NN^T for
174 * some invertible N.
175 */
176 std::pair<MatrixXb, MatrixXb> xp_z_llt =
177
1/2
✓ Branch 1 taken 39 times.
✗ Branch 2 not taken.
39 binary_LLT_decomposition(tabl.xpauli_z);
178
2/2
✓ Branch 0 taken 127 times.
✓ Branch 1 taken 39 times.
2/2
✓ Decision 'true' taken 127 times.
✓ Decision 'false' taken 39 times.
166 for (unsigned i = 0; i < size; i++) {
179
3/4
✓ Branch 1 taken 127 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 8 times.
✓ Branch 4 taken 119 times.
2/2
✓ Decision 'true' taken 8 times.
✓ Decision 'false' taken 119 times.
127 if (xp_z_llt.second(i, i)) {
180
2/4
✓ Branch 3 taken 8 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 8 times.
✗ Branch 7 not taken.
8 c.add_op<unsigned>(OpType::S, {i});
181
1/2
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
8 tabl.apply_S_at_front(i);
182
1/2
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
8 tabl.apply_S_at_front(i);
183
1/2
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
8 tabl.apply_S_at_front(i);
184 }
185 }
186
187 /*
188 * Step 9: Use CXs to produce
189 * / N N \
190 * \ 0 C /
191 */
192 std::vector<std::pair<unsigned, unsigned>> n_to_i =
193
1/2
✓ Branch 1 taken 39 times.
✗ Branch 2 not taken.
39 gaussian_elimination_col_ops(xp_z_llt.first);
194
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
1/2
✓ Decision 'true' taken 6 times.
✗ Decision 'false' not taken.
45 for (std::vector<std::pair<unsigned, unsigned>>::reverse_iterator it =
195 39 n_to_i.rbegin();
196
3/4
✓ Branch 2 taken 45 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 6 times.
✓ Branch 5 taken 39 times.
45 it != n_to_i.rend(); it++) {
197
4/8
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 6 times.
✗ Branch 6 not taken.
✓ Branch 9 taken 6 times.
✗ Branch 10 not taken.
✓ Branch 12 taken 6 times.
✗ Branch 13 not taken.
6 c.add_op<unsigned>(OpType::CX, {it->first, it->second});
198
3/6
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 6 times.
✗ Branch 8 not taken.
6 tabl.apply_CX_at_front(it->first, it->second);
199 }
200
201 /*
202 * Step 10: Use phases (S) to produce
203 * / N 0 \
204 * \ 0 C /
205 * then by commutativity relations NC^T = I. Next apply two phases (Z) each to
206 * some subset of qubits in order to preserve the above tableau, but set
207 * r_1 = ... = r_n = 0 (xpauli_phase = 0^n). DELAYED UNTIL END
208 */
209
2/2
✓ Branch 0 taken 127 times.
✓ Branch 1 taken 39 times.
2/2
✓ Decision 'true' taken 127 times.
✓ Decision 'false' taken 39 times.
166 for (unsigned i = 0; i < size; i++) {
210
2/4
✓ Branch 3 taken 127 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 127 times.
✗ Branch 7 not taken.
127 c.add_op<unsigned>(OpType::S, {i});
211
1/2
✓ Branch 1 taken 127 times.
✗ Branch 2 not taken.
127 tabl.apply_S_at_front(i);
212
1/2
✓ Branch 1 taken 127 times.
✗ Branch 2 not taken.
127 tabl.apply_S_at_front(i);
213
1/2
✓ Branch 1 taken 127 times.
✗ Branch 2 not taken.
127 tabl.apply_S_at_front(i);
214 }
215
216 /*
217 * Step 11: Use CXs to produce
218 * / I 0 \
219 * \ 0 I /
220 */
221 39 for (const std::pair<unsigned, unsigned> &qbs :
222
3/4
✓ Branch 1 taken 39 times.
✗ Branch 2 not taken.
✓ Branch 8 taken 6 times.
✓ Branch 9 taken 39 times.
84 gaussian_elimination_col_ops(tabl.xpauli_x)) {
223
2/4
✓ Branch 3 taken 6 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 6 times.
✗ Branch 7 not taken.
6 c.add_op<unsigned>(OpType::CX, {qbs.first, qbs.second});
224
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 tabl.apply_CX_at_front(qbs.first, qbs.second);
225 39 }
226
227 /*
228 * DELAYED STEPS: Set all phases to 0 by applying Z or X gates
229 */
230
2/2
✓ Branch 0 taken 127 times.
✓ Branch 1 taken 39 times.
2/2
✓ Decision 'true' taken 127 times.
✓ Decision 'false' taken 39 times.
166 for (unsigned i = 0; i < size; i++) {
231
3/4
✓ Branch 1 taken 127 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 6 times.
✓ Branch 4 taken 121 times.
2/2
✓ Decision 'true' taken 6 times.
✓ Decision 'false' taken 121 times.
127 if (tabl.xpauli_phase(i)) {
232
2/4
✓ Branch 3 taken 6 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 6 times.
✗ Branch 7 not taken.
6 c.add_op<unsigned>(OpType::Z, {i});
233
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 tabl.apply_S_at_front(i);
234
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 tabl.apply_S_at_front(i);
235 }
236
3/4
✓ Branch 1 taken 127 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 9 times.
✓ Branch 4 taken 118 times.
2/2
✓ Decision 'true' taken 9 times.
✓ Decision 'false' taken 118 times.
127 if (tabl.zpauli_phase(i)) {
237
2/4
✓ Branch 3 taken 9 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 9 times.
✗ Branch 7 not taken.
9 c.add_op<unsigned>(OpType::X, {i});
238
1/2
✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
9 tabl.apply_V_at_front(i);
239
1/2
✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
9 tabl.apply_V_at_front(i);
240 }
241 }
242
243 /*
244 * Rename qubits
245 */
246 39 unit_map_t rename_map;
247
1/2
✓ Branch 1 taken 39 times.
✗ Branch 2 not taken.
1/2
✓ Decision 'true' taken 39 times.
✗ Decision 'false' not taken.
78 for (boost::bimap<Qubit, unsigned>::iterator iter = tabl.qubits_.begin(),
248
1/2
✓ Branch 1 taken 39 times.
✗ Branch 2 not taken.
39 iend = tabl.qubits_.end();
249
3/4
✓ Branch 1 taken 166 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 127 times.
✓ Branch 4 taken 39 times.
166 iter != iend; ++iter) {
250
6/12
✓ Branch 1 taken 127 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 127 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 127 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 127 times.
✗ Branch 11 not taken.
✓ Branch 14 taken 127 times.
✗ Branch 15 not taken.
✓ Branch 19 taken 127 times.
✗ Branch 20 not taken.
127 rename_map.insert({Qubit(q_default_reg(), iter->right), iter->left});
251 }
252
1/2
✓ Branch 1 taken 39 times.
✗ Branch 2 not taken.
39 c.rename_units(rename_map);
253
254 78 return c;
255 39 }
256
257 } // namespace tket
258