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 "AssertionSynthesis.hpp" | |||
16 | ||||
17 | #include <array> | |||
18 | #include <cmath> | |||
19 | #include <complex> | |||
20 | #include <optional> | |||
21 | #include <stdexcept> | |||
22 | #include <tkassert/Assert.hpp> | |||
23 | ||||
24 | #include "CircUtils.hpp" | |||
25 | #include "Circuit.hpp" | |||
26 | #include "OpType/OpType.hpp" | |||
27 | #include "Utils/Constants.hpp" | |||
28 | #include "Utils/CosSinDecomposition.hpp" | |||
29 | #include "Utils/EigenConfig.hpp" | |||
30 | #include "Utils/MatrixAnalysis.hpp" | |||
31 | #include "Utils/UnitID.hpp" | |||
32 | ||||
33 | namespace tket { | |||
34 | ||||
35 | /** | |||
36 | * Diagonalise a projector matrix P such that P = U*D*U.dag, where | |||
37 | * U is a unitary and D is a diagonal binary matrix. | |||
38 | * The resulting diagonal matrix always have 1s on the left. | |||
39 | * The benefit of permuting 1s to the left is that the | |||
40 | * diagonal matrix can always be factorised into |0><0|, |1><1| or I if the | |||
41 | * projector has a valid rank. However, this is not optimised. | |||
42 | * | |||
43 | * @param P projector matrix | |||
44 | * | |||
45 | * @return D as a boolean vector, U, rank | |||
46 | */ | |||
47 | 9 | static std::tuple<VectorXb, Eigen::MatrixXcd, int> projector_diagonalisation( | ||
48 | const Eigen::MatrixXcd &P) { | |||
49 | // Solve the eigen values | |||
50 |
1/2✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
|
9 | Eigen::SelfAdjointEigenSolver<Eigen::MatrixXcd> eigen_solver(P); | |
51 | // Make a permutation matrix to reorder D and U_dag | |||
52 |
1/2✓ Branch 2 taken 9 times.
✗ Branch 3 not taken.
|
9 | Eigen::PermutationMatrix<Eigen::Dynamic> perm(P.rows()); | |
53 | 9 | Eigen::MatrixXcd P1 = eigen_solver.eigenvectors() * | ||
54 |
3/6✓ Branch 2 taken 9 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 9 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 9 times.
✗ Branch 9 not taken.
|
18 | eigen_solver.eigenvalues().asDiagonal() * | |
55 |
2/4✓ Branch 2 taken 9 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 9 times.
✗ Branch 6 not taken.
|
18 | eigen_solver.eigenvectors().adjoint(); | |
56 | TKET_ASSERT(P1.isApprox(P)); | |||
57 | // Cast eigenvalues to booleans | |||
58 |
1/2✓ Branch 3 taken 9 times.
✗ Branch 4 not taken.
|
9 | VectorXb eigenvalues(eigen_solver.eigenvalues().rows()); | |
59 |
2/2✓ Branch 1 taken 54 times.
✓ Branch 2 taken 9 times.
|
2/2✓ Decision 'true' taken 54 times.
✓ Decision 'false' taken 9 times.
|
63 | for (unsigned i = 0; i < eigenvalues.rows(); i++) { |
60 |
3/4✓ Branch 2 taken 54 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 30 times.
✓ Branch 6 taken 24 times.
|
2/2✓ Decision 'true' taken 30 times.
✓ Decision 'false' taken 24 times.
|
54 | if (std::abs(eigen_solver.eigenvalues()[i]) < EPS) { |
61 |
1/2✓ Branch 1 taken 30 times.
✗ Branch 2 not taken.
|
30 | eigenvalues[i] = 0; | |
62 | } else { | |||
63 |
1/2✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
|
24 | eigenvalues[i] = 1; | |
64 | } | |||
65 | } | |||
66 | // Move 1 to the front | |||
67 | 9 | unsigned j = 0; | ||
68 |
2/2✓ Branch 1 taken 54 times.
✓ Branch 2 taken 9 times.
|
2/2✓ Decision 'true' taken 54 times.
✓ Decision 'false' taken 9 times.
|
63 | for (unsigned i = 0; i < P.rows(); i++) { |
69 |
3/4✓ Branch 1 taken 54 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 24 times.
✓ Branch 4 taken 30 times.
|
2/2✓ Decision 'true' taken 24 times.
✓ Decision 'false' taken 30 times.
|
54 | if (eigenvalues[i] == 1) { |
70 |
1/2✓ Branch 2 taken 24 times.
✗ Branch 3 not taken.
|
24 | perm.indices()[j] = i; | |
71 | 24 | j++; | ||
72 | } | |||
73 | } | |||
74 | // Assign rank | |||
75 | 9 | unsigned rank = j; | ||
76 | // Pad 0 to the end | |||
77 |
2/2✓ Branch 1 taken 54 times.
✓ Branch 2 taken 9 times.
|
2/2✓ Decision 'true' taken 54 times.
✓ Decision 'false' taken 9 times.
|
63 | for (unsigned i = 0; i < P.rows(); i++) { |
78 |
3/4✓ Branch 1 taken 54 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 30 times.
✓ Branch 4 taken 24 times.
|
2/2✓ Decision 'true' taken 30 times.
✓ Decision 'false' taken 24 times.
|
54 | if (eigenvalues[i] == 0) { |
79 |
1/2✓ Branch 2 taken 30 times.
✗ Branch 3 not taken.
|
30 | perm.indices()[j] = i; | |
80 | 30 | j++; | ||
81 | } | |||
82 | } | |||
83 |
3/6✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 9 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 9 times.
✗ Branch 8 not taken.
|
9 | VectorXb D = perm.transpose() * eigenvalues; | |
84 |
2/4✓ Branch 2 taken 9 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 9 times.
✗ Branch 6 not taken.
|
9 | Eigen::MatrixXcd U = eigen_solver.eigenvectors() * perm; | |
85 | TKET_ASSERT((U * D.cast<double>().asDiagonal() * U.adjoint()).isApprox(P)); | |||
86 |
1/2✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
|
18 | return {D, U, rank}; | |
87 | 9 | } | ||
88 | ||||
89 | // Get the n_th bit of an integer | |||
90 | 89 | static bool get_bit(unsigned value, unsigned index) { | ||
91 | 89 | return (value & (1 << index)) >> index; | ||
92 | } | |||
93 | ||||
94 | /** | |||
95 | * Tensor-factorize a binary diagonal matrix with dimension 2^n. | |||
96 | * Assume D has a rank <= 2^(n-1), and the rank is power of 2 | |||
97 | * | |||
98 | * @param D a vector representation of a diagonal binary matrix | |||
99 | * | |||
100 | * @return A list of 2x2 diagonal binary matrices D_0,...,D_m (as vectors) | |||
101 | * such that tensor(D_0,...,D_m) == D | |||
102 | */ | |||
103 | 10 | static std::vector<Vector2b> tensor_factorization(const VectorXb &D) { | ||
104 | 10 | unsigned dim = D.rows(); | ||
105 | 10 | unsigned log_dim = (unsigned)log2(dim); | ||
106 | 10 | std::vector<Vector2b> factorisations; | ||
107 |
2/2✓ Branch 0 taken 26 times.
✓ Branch 1 taken 10 times.
|
2/2✓ Decision 'true' taken 26 times.
✓ Decision 'false' taken 10 times.
|
36 | for (unsigned i = 0; i < log_dim; i++) { |
108 |
3/6✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 26 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 26 times.
✗ Branch 8 not taken.
|
26 | factorisations.push_back(Vector2b::Zero()); | |
109 | } | |||
110 | // The diagonal matrix D can be factorised into a tensorproduct of | |||
111 | // log_dim 2x2 diagonal boolean matrices in the set {\0><0|, |1><1|, I} | |||
112 | // The factorisation can be determined by the diagonals in D | |||
113 | // For example, if D[0,0] == 1, then all the 2x2 matices should have 1 in | |||
114 | // their top left entry. | |||
115 |
2/2✓ Branch 0 taken 66 times.
✓ Branch 1 taken 10 times.
|
2/2✓ Decision 'true' taken 66 times.
✓ Decision 'false' taken 10 times.
|
76 | for (unsigned i = 0; i < dim; i++) { |
116 |
3/4✓ Branch 1 taken 66 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 31 times.
✓ Branch 4 taken 35 times.
|
0/1? Decision couldn't be analyzed.
|
66 | if (D(i) == 1) { |
117 |
2/2✓ Branch 0 taken 89 times.
✓ Branch 1 taken 31 times.
|
2/2✓ Decision 'true' taken 89 times.
✓ Decision 'false' taken 31 times.
|
120 | for (unsigned j = 0; j < log_dim; j++) { |
118 |
2/2✓ Branch 1 taken 28 times.
✓ Branch 2 taken 61 times.
|
2/2✓ Decision 'true' taken 28 times.
✓ Decision 'false' taken 61 times.
|
89 | if (get_bit(i, j)) { |
119 |
1/2✓ Branch 2 taken 28 times.
✗ Branch 3 not taken.
|
28 | factorisations[j](1) = 1; | |
120 | } else { | |||
121 |
1/2✓ Branch 2 taken 61 times.
✗ Branch 3 not taken.
|
61 | factorisations[j](0) = 1; | |
122 | } | |||
123 | } | |||
124 | } | |||
125 | } | |||
126 | 10 | return factorisations; | ||
127 | } | |||
128 | ||||
129 | 20 | static void apply_unitary(Circuit *circ, const Eigen::MatrixXcd &U) { | ||
130 |
2/2✓ Branch 1 taken 2 times.
✓ Branch 2 taken 18 times.
|
2/2✓ Decision 'true' taken 2 times.
✓ Decision 'false' taken 18 times.
|
20 | if (U.rows() == 2) { |
131 | // Add a Unitary1qBox | |||
132 |
2/4✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
|
2 | Unitary1qBox ubox(U); | |
133 |
3/6✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 2 times.
✗ Branch 7 not taken.
✓ Branch 10 taken 2 times.
✗ Branch 11 not taken.
|
2 | circ->add_op<unsigned>(std::make_shared<Unitary1qBox>(ubox), {0}); | |
134 |
2/2✓ Branch 2 taken 4 times.
✓ Branch 3 taken 14 times.
|
2/2✓ Decision 'true' taken 4 times.
✓ Decision 'false' taken 16 times.
|
20 | } else if (U.rows() == 4) { |
135 | // Add a Unitary2qBox | |||
136 |
2/4✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
|
4 | Unitary2qBox ubox(U); | |
137 |
3/6✓ Branch 3 taken 4 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 4 times.
✗ Branch 7 not taken.
✓ Branch 10 taken 4 times.
✗ Branch 11 not taken.
|
4 | circ->add_op<unsigned>(std::make_shared<Unitary2qBox>(ubox), {0, 1}); | |
138 |
1/2✓ Branch 2 taken 14 times.
✗ Branch 3 not taken.
|
2/2✓ Decision 'true' taken 14 times.
✓ Decision 'false' taken 4 times.
|
18 | } else if (U.rows() == 8) { |
139 | // Add a Unitary3qBox | |||
140 |
2/4✓ Branch 1 taken 14 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 14 times.
✗ Branch 5 not taken.
|
14 | Unitary3qBox ubox(U); | |
141 |
3/6✓ Branch 3 taken 14 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 14 times.
✗ Branch 7 not taken.
✓ Branch 10 taken 14 times.
✗ Branch 11 not taken.
|
14 | circ->add_op<unsigned>(std::make_shared<Unitary3qBox>(ubox), {0, 1, 2}); | |
142 | ||||
143 | 14 | } else { | ||
144 | ✗ | throw CircuitInvalidity("Only 2x2, 4x4, and 8x8 projectors are supported"); | ||
145 | } | |||
146 | 20 | } | ||
147 | ||||
148 | /** | |||
149 | * Apply measurements and update the expectations | |||
150 | * | |||
151 | * @param circ the circuit to apply measurements to | |||
152 | * @param z_projectors a list of 2x2 diagonal binary matrices | |||
153 | * @param debug_bit_index the next available index of the default classical | |||
154 | * register | |||
155 | * @param expected_readouts a list of expectations. e.g. | |||
156 | * expected_readouts[i]=True indicates that the ith bit in the default register | |||
157 | * expects the readout to be 1. | |||
158 | * | |||
159 | * @return the next available index of the default classical register | |||
160 | */ | |||
161 | 10 | static unsigned apply_z_measurements( | ||
162 | Circuit &circ, const std::vector<Vector2b> z_projectors, | |||
163 | unsigned debug_bit_index, std::vector<bool> &expected_readouts) { | |||
164 |
2/2✓ Branch 1 taken 26 times.
✓ Branch 2 taken 10 times.
|
2/2✓ Decision 'true' taken 26 times.
✓ Decision 'false' taken 10 times.
|
36 | for (unsigned i = 0; i < z_projectors.size(); i++) { |
165 |
7/10✓ Branch 2 taken 26 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 26 times.
✗ Branch 5 not taken.
✓ Branch 8 taken 26 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 14 times.
✓ Branch 11 taken 12 times.
✓ Branch 12 taken 14 times.
✓ Branch 13 taken 12 times.
|
2/2✓ Decision 'true' taken 14 times.
✓ Decision 'false' taken 12 times.
|
26 | if (z_projectors[i](0) == 1 && z_projectors[i](1) == 1) { |
166 | 14 | continue; | ||
167 | } | |||
168 |
1/2✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
|
12 | Qubit q(i); | |
169 |
1/2✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
|
12 | Bit b(debug_bit_index++); | |
170 |
1/2✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
|
12 | circ.add_bit(b); | |
171 |
4/8✓ Branch 5 taken 12 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 12 times.
✗ Branch 9 not taken.
✓ Branch 12 taken 24 times.
✓ Branch 13 taken 12 times.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
|
36 | circ.add_op<UnitID>(OpType::Measure, {q, b}); | |
172 | const bool is_1_projector = | |||
173 |
2/8✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 12 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
|
12 | z_projectors[i](0) == 0 && z_projectors[i](1) == 1; | |
174 |
1/2✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
|
12 | expected_readouts.push_back(is_1_projector); | |
175 | 12 | } | ||
176 | 10 | return debug_bit_index; | ||
177 | } | |||
178 | ||||
179 | /** | |||
180 | * Split a projector into two 2^(n-1)-ranked projectors that project the same | |||
181 | * subspace when combined. Assume D has 1s on the left. Suppose D.rows() = 16, | |||
182 | * rank = 5, then the diagonal of D is 11111000|00000000. D can't be factorised | |||
183 | * into Z basis projectors, so we create two rank=2^(n-1) projectors that | |||
184 | * project the same subspace when combined (intersection of two subspaces). | |||
185 | * e.g. D1: 11111111|00000000, D2: 11111000|11100000. | |||
186 | * After permuting D2, both diagonal matrices should have diagonal entires: | |||
187 | * 11111111|00000000. | |||
188 | * Similar to the projector_diagonalisation function, the permutation of the | |||
189 | * eigen decomposition is not optimised. | |||
190 | * | |||
191 | * @param D a vector representation of a diagonal binary matrix | |||
192 | * @param U a unitary matrix | |||
193 | * @param rank the rank of D | |||
194 | * | |||
195 | * @return D1, U1, D2, U2 such that the intersection of the subspaces projected | |||
196 | * by P1=U1*D1*U1.dag and P2=U2*D2*U2.dag is the subspace projected by | |||
197 | * P=U*D*U.dag. | |||
198 | */ | |||
199 | static std::tuple<VectorXb, Eigen::MatrixXcd, VectorXb, Eigen::MatrixXcd> | |||
200 | 3 | projector_split( | ||
201 | const VectorXb &D, const Eigen::MatrixXcd &U, const unsigned &rank) { | |||
202 |
1/2✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
|
3 | VectorXb D_new(D); | |
203 |
2/2✓ Branch 1 taken 3 times.
✓ Branch 2 taken 3 times.
|
2/2✓ Decision 'true' taken 3 times.
✓ Decision 'false' taken 3 times.
|
6 | for (unsigned i = rank; i < D_new.rows() / 2; i++) { |
204 |
1/2✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
|
3 | D_new(i) = 1; | |
205 | } | |||
206 | // U1 | |||
207 |
1/2✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
|
3 | Eigen::MatrixXcd U1(U); | |
208 | // U2 | |||
209 |
1/2✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
|
3 | Eigen::PermutationMatrix<Eigen::Dynamic> perm(D_new.rows()); | |
210 |
2/2✓ Branch 1 taken 24 times.
✓ Branch 2 taken 3 times.
|
2/2✓ Decision 'true' taken 24 times.
✓ Decision 'false' taken 3 times.
|
27 | for (unsigned i = 0; i < D_new.rows(); i++) { |
211 |
6/6✓ Branch 0 taken 15 times.
✓ Branch 1 taken 9 times.
✓ Branch 3 taken 3 times.
✓ Branch 4 taken 12 times.
✓ Branch 5 taken 3 times.
✓ Branch 6 taken 21 times.
|
2/2✓ Decision 'true' taken 3 times.
✓ Decision 'false' taken 21 times.
|
24 | if (i >= rank && i < D_new.rows() / 2) { |
212 |
1/2✓ Branch 3 taken 3 times.
✗ Branch 4 not taken.
|
3 | perm.indices()[i] = i + D_new.rows() / 2 - rank; | |
213 |
6/6✓ Branch 1 taken 12 times.
✓ Branch 2 taken 9 times.
✓ Branch 4 taken 3 times.
✓ Branch 5 taken 9 times.
✓ Branch 6 taken 3 times.
✓ Branch 7 taken 18 times.
|
2/2✓ Decision 'true' taken 3 times.
✓ Decision 'false' taken 18 times.
|
21 | } else if (i >= D_new.rows() / 2 && i < D_new.rows() - rank) { |
214 |
1/2✓ Branch 3 taken 3 times.
✗ Branch 4 not taken.
|
3 | perm.indices()[i] = i - (D_new.rows() / 2 - rank); | |
215 | } else { | |||
216 |
1/2✓ Branch 2 taken 18 times.
✗ Branch 3 not taken.
|
18 | perm.indices()[i] = i; | |
217 | } | |||
218 | } | |||
219 |
2/4✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
|
3 | Eigen::MatrixXcd U2 = U * perm; | |
220 |
1/2✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
|
6 | return {D_new, U1, D_new, U2}; | |
221 | 3 | } | ||
222 | ||||
223 | 8 | std::tuple<Circuit, std::vector<bool>> projector_assertion_synthesis( | ||
224 | const Eigen::MatrixXcd &P) { | |||
225 | // Diagonoalise the projector P | |||
226 |
1/2✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
|
8 | Eigen::MatrixXcd projector(P); | |
227 | 8 | unsigned n_qubits = (unsigned)log2(projector.rows()); | ||
228 |
1/2✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
|
8 | VectorXb D; | |
229 |
1/2✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
|
8 | Eigen::MatrixXcd U; | |
230 | unsigned rank; | |||
231 |
1/2✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
|
8 | std::tie(D, U, rank) = projector_diagonalisation(projector); | |
232 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 8 times.
|
8 | if (rank == 0) { |
233 | ✗ | throw CircuitInvalidity("The projector must have none-zero rank"); | ||
234 | } | |||
235 | 8 | std::vector<bool> expected_readouts; | ||
236 |
1/2✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
|
8 | Circuit circ(n_qubits); | |
237 |
2/2✓ Branch 1 taken 2 times.
✓ Branch 2 taken 6 times.
|
2/2✓ Decision 'true' taken 2 times.
✓ Decision 'false' taken 6 times.
|
8 | if (rank > projector.rows() / 2) { |
238 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
|
2/2✓ Decision 'true' taken 1 times.
✓ Decision 'false' taken 1 times.
|
2 | if (n_qubits >= 3) { |
239 |
2/4✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 1 times.
✗ Branch 6 not taken.
|
1 | throw CircuitInvalidity( | |
240 | 2 | "8x8 projector that requires an ancilla is not supported"); | ||
241 | } | |||
242 | // Add an auxilary qubit to the end | |||
243 |
1/2✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
|
1 | Qubit ancilla(n_qubits); | |
244 |
1/2✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
|
1 | circ.add_qubit(ancilla, false); | |
245 |
4/8✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 1 times.
✗ Branch 8 not taken.
✓ Branch 11 taken 1 times.
✓ Branch 12 taken 1 times.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
|
2 | circ.add_op<Qubit>(OpType::Reset, {ancilla}); | |
246 |
2/4✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
|
1 | Eigen::MatrixXcd zero_projector = Eigen::MatrixXcd::Zero(2, 2); | |
247 |
1/2✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
|
1 | zero_projector(0, 0) = 1; | |
248 | Eigen::MatrixXcd new_projector = | |||
249 |
2/4✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
|
1 | Eigen::KroneckerProduct(projector, zero_projector); | |
250 |
1/2✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
|
1 | std::tie(D, U, rank) = projector_diagonalisation(new_projector); | |
251 | 1 | } | ||
252 | ||||
253 | // Initialise debug bit index | |||
254 | 7 | unsigned debug_bit_index = 0; | ||
255 | // Check if the rank is power of 2 | |||
256 | // If a number is power of 2 then the count of binary 1s will be 1. | |||
257 |
3/4✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 3 times.
|
2/2✓ Decision 'true' taken 4 times.
✓ Decision 'false' taken 3 times.
|
7 | if (rank != 0 && (rank & (rank - 1)) == 0) { |
258 | // Implement the projection | |||
259 | // Apply the transformation before the measurements | |||
260 |
1/2✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
|
4 | std::vector<Vector2b> tensors = tensor_factorization(D); | |
261 |
3/6✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 4 times.
✗ Branch 8 not taken.
|
4 | apply_unitary(&circ, U.adjoint()); | |
262 |
2/4✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
|
4 | apply_z_measurements(circ, tensors, debug_bit_index, expected_readouts); | |
263 |
1/2✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
|
4 | apply_unitary(&circ, U); | |
264 | 4 | } | ||
265 | // Transform this into two projectors | |||
266 | else { | |||
267 |
1/2✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
|
3 | auto [D1, U1, D2, U2] = projector_split(D, U, rank); | |
268 |
1/2✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
|
3 | std::vector<Vector2b> tensors1 = tensor_factorization(D1); | |
269 |
3/6✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 3 times.
✗ Branch 8 not taken.
|
3 | apply_unitary(&circ, U1.adjoint()); | |
270 |
2/4✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
|
3 | debug_bit_index = apply_z_measurements( | |
271 | circ, tensors1, debug_bit_index, expected_readouts); | |||
272 |
1/2✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
|
3 | apply_unitary(&circ, U1); | |
273 | ||||
274 |
1/2✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
|
3 | std::vector<Vector2b> tensors2 = tensor_factorization(D2); | |
275 |
3/6✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 3 times.
✗ Branch 8 not taken.
|
3 | apply_unitary(&circ, U2.adjoint()); | |
276 |
2/4✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
|
3 | apply_z_measurements(circ, tensors2, debug_bit_index, expected_readouts); | |
277 |
1/2✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
|
3 | apply_unitary(&circ, U2); | |
278 | 3 | } | ||
279 |
1/2✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
|
14 | return {circ, expected_readouts}; | |
280 | 12 | } | ||
281 | ||||
282 | 5 | static unsigned get_n_qubits_from_stabilisers( | ||
283 | const PauliStabiliserList &paulis) { | |||
284 |
2/2✓ Branch 1 taken 1 times.
✓ Branch 2 taken 4 times.
|
2/2✓ Decision 'true' taken 1 times.
✓ Decision 'false' taken 4 times.
|
5 | if (paulis.size() == 0) { |
285 |
2/4✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 1 times.
✗ Branch 7 not taken.
|
1 | throw CircuitInvalidity("Stabilisers cannot be empty"); | |
286 | } | |||
287 | 4 | unsigned stabiliser_len = paulis[0].string.size(); | ||
288 |
2/2✓ Branch 1 taken 5 times.
✓ Branch 2 taken 3 times.
|
2/2✓ Decision 'true' taken 5 times.
✓ Decision 'false' taken 3 times.
|
8 | for (unsigned i = 1; i < paulis.size(); i++) { |
289 |
2/2✓ Branch 2 taken 1 times.
✓ Branch 3 taken 4 times.
|
2/2✓ Decision 'true' taken 1 times.
✓ Decision 'false' taken 4 times.
|
5 | if (paulis[i].string.size() != stabiliser_len) { |
290 |
2/4✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 1 times.
✗ Branch 7 not taken.
|
1 | throw CircuitInvalidity("Stabilisers have unequal lengths"); | |
291 | } | |||
292 | } | |||
293 | 3 | return stabiliser_len; | ||
294 | } | |||
295 | ||||
296 | /** | |||
297 | * Apply assertion operators and update the expectations | |||
298 | * | |||
299 | * @param circ the circuit to apply assertion operators to | |||
300 | * @param pauli a Pauli stabiliser | |||
301 | * @param debug_bit_index the next available index of the default classical | |||
302 | * register | |||
303 | * @param expected_readouts a list of expectations. e.g. | |||
304 | * expected_readouts[i]=True indicates that the ith bit in the default register | |||
305 | * expects the readout to be 1. | |||
306 | * | |||
307 | * @return the next available index of the default classical register | |||
308 | */ | |||
309 | 7 | static unsigned add_assertion_operator( | ||
310 | Circuit &circ, const PauliStabiliser &pauli, unsigned debug_bit_index, | |||
311 | const Qubit &ancilla, std::vector<bool> &expected_readouts) { | |||
312 |
4/8✓ Branch 4 taken 7 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 7 times.
✗ Branch 8 not taken.
✓ Branch 11 taken 7 times.
✓ Branch 12 taken 7 times.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
|
14 | circ.add_op<Qubit>(OpType::Reset, {ancilla}); | |
313 |
4/8✓ Branch 4 taken 7 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 7 times.
✗ Branch 8 not taken.
✓ Branch 11 taken 7 times.
✓ Branch 12 taken 7 times.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
|
14 | circ.add_op<Qubit>(OpType::H, {ancilla}); | |
314 |
2/2✓ Branch 1 taken 14 times.
✓ Branch 2 taken 7 times.
|
2/2✓ Decision 'true' taken 14 times.
✓ Decision 'false' taken 7 times.
|
21 | for (unsigned i = 0; i < pauli.string.size(); i++) { |
315 |
2/5✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 8 times.
✗ Branch 5 not taken.
|
14 | switch (pauli.string[i]) { | |
316 |
0/1✗ Decision 'true' not taken.
|
✗ | case Pauli::I: | |
317 | ✗ | break; | ||
318 |
1/1✓ Decision 'true' taken 18 times.
|
6 | case Pauli::X: | |
319 |
5/10✓ Branch 3 taken 6 times.
✗ Branch 4 not taken.
✓ Branch 7 taken 6 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 6 times.
✗ Branch 11 not taken.
✓ Branch 14 taken 12 times.
✓ Branch 15 taken 6 times.
✗ Branch 20 not taken.
✗ Branch 21 not taken.
|
18 | circ.add_op<Qubit>(OpType::CX, {ancilla, Qubit(i)}); | |
320 | 6 | break; | ||
321 |
0/1✗ Decision 'true' not taken.
|
✗ | case Pauli::Y: | |
322 | ✗ | circ.add_op<Qubit>(OpType::CY, {ancilla, Qubit(i)}); | ||
323 | ✗ | break; | ||
324 |
1/1✓ Decision 'true' taken 24 times.
|
8 | case Pauli::Z: | |
325 |
5/10✓ Branch 3 taken 8 times.
✗ Branch 4 not taken.
✓ Branch 7 taken 8 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 8 times.
✗ Branch 11 not taken.
✓ Branch 14 taken 16 times.
✓ Branch 15 taken 8 times.
✗ Branch 20 not taken.
✗ Branch 21 not taken.
|
24 | circ.add_op<Qubit>(OpType::CZ, {ancilla, Qubit(i)}); | |
326 | 8 | break; | ||
327 | } | |||
328 | } | |||
329 |
4/8✓ Branch 4 taken 7 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 7 times.
✗ Branch 8 not taken.
✓ Branch 11 taken 7 times.
✓ Branch 12 taken 7 times.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
|
14 | circ.add_op<Qubit>(OpType::H, {ancilla}); | |
330 |
1/2✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
|
7 | Bit b(debug_bit_index++); | |
331 |
1/2✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
|
7 | circ.add_bit(b); | |
332 |
4/8✓ Branch 5 taken 7 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 7 times.
✗ Branch 9 not taken.
✓ Branch 12 taken 14 times.
✓ Branch 13 taken 7 times.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
|
21 | circ.add_op<UnitID>(OpType::Measure, {ancilla, b}); | |
333 |
1/2✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
|
7 | expected_readouts.push_back(!pauli.coeff); | |
334 | 14 | return debug_bit_index; | ||
335 | } | |||
336 | ||||
337 | // Assume all Pauli stabilisers have equal lengths and no identity | |||
338 | 5 | std::tuple<Circuit, std::vector<bool>> stabiliser_assertion_synthesis( | ||
339 | const PauliStabiliserList &paulis) { | |||
340 |
2/2✓ Branch 1 taken 3 times.
✓ Branch 2 taken 2 times.
|
5 | unsigned n_qubits = get_n_qubits_from_stabilisers(paulis); | |
341 | 3 | std::vector<bool> expected_readouts; | ||
342 |
1/2✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
|
3 | Circuit circ(n_qubits); | |
343 | ||||
344 | // Initialise debug bit index | |||
345 | 3 | unsigned debug_bit_index = 0; | ||
346 | // Add an ancilla | |||
347 |
1/2✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
|
3 | Qubit ancilla(n_qubits); | |
348 |
1/2✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
|
3 | circ.add_qubit(ancilla, false); | |
349 | ||||
350 |
2/2✓ Branch 4 taken 7 times.
✓ Branch 5 taken 3 times.
|
2/2✓ Decision 'true' taken 7 times.
✓ Decision 'false' taken 3 times.
|
10 | for (auto &pauli : paulis) { |
351 |
1/2✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
|
7 | debug_bit_index = add_assertion_operator( | |
352 | circ, pauli, debug_bit_index, ancilla, expected_readouts); | |||
353 | } | |||
354 | ||||
355 |
1/2✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
|
6 | return {circ, expected_readouts}; | |
356 | 3 | } | ||
357 | ||||
358 | } // namespace tket | |||
359 |