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 "PauliExpBoxUnitaryCalculator.hpp" | |||
16 | ||||
17 | #include <algorithm> | |||
18 | #include <tkassert/Assert.hpp> | |||
19 | ||||
20 | #include "Circuit/Boxes.hpp" | |||
21 | #include "Utils/PauliStrings.hpp" | |||
22 | ||||
23 | namespace tket { | |||
24 | namespace tket_sim { | |||
25 | namespace internal { | |||
26 | ||||
27 | // A nonzero entry in the matrix containing only 1,0,-1 values, | |||
28 | // which we will build up for the tensor product | |||
29 | // (taking out a factor of i from Y). | |||
30 | typedef Eigen::Triplet<int> Entry; | |||
31 | ||||
32 | 1 | static std::map<Pauli, std::array<Entry, 2>> get_pauli_map() { | ||
33 | 1 | std::map<Pauli, std::array<Entry, 2>> result; | ||
34 | { | |||
35 |
1/2✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
|
1 | auto& i_matr = result[Pauli::I]; | |
36 | 1 | i_matr[0] = Entry(0, 0, 1); | ||
37 | 1 | i_matr[1] = Entry(1, 1, 1); | ||
38 | } | |||
39 | { | |||
40 |
1/2✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
|
1 | auto& x_matr = result[Pauli::X]; | |
41 | 1 | x_matr[0] = Entry(0, 1, 1); | ||
42 | 1 | x_matr[1] = Entry(1, 0, 1); | ||
43 | } | |||
44 | { | |||
45 | // Remove a factor of i. | |||
46 |
1/2✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
|
1 | auto& y_matr = result[Pauli::Y]; | |
47 | 1 | y_matr[0] = Entry(0, 1, -1); | ||
48 | 1 | y_matr[1] = Entry(1, 0, 1); | ||
49 | } | |||
50 | { | |||
51 |
1/2✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
|
1 | auto& z_matr = result[Pauli::Z]; | |
52 | 1 | z_matr[0] = Entry(0, 0, 1); | ||
53 | 1 | z_matr[1] = Entry(1, 1, -1); | ||
54 | } | |||
55 | 1 | return result; | ||
56 | } | |||
57 | ||||
58 | // The tensor product of the left and right entries. | |||
59 | 45660 | static Entry get_combined_entry(const Entry& left, const Entry& right) { | ||
60 | 91320 | return Entry( | ||
61 | 45660 | 2 * left.row() + right.row(), 2 * left.col() + right.col(), | ||
62 | 91320 | left.value() * right.value()); | ||
63 | } | |||
64 | ||||
65 | namespace { | |||
66 | // To save repeated reallocation, build the data up inside here. | |||
67 | struct PauliExpBoxUnitaryCalculator { | |||
68 | // The two nonzero entries of the 2x2 matrix. | |||
69 | const std::map<Pauli, std::array<Entry, 2>> pauli_map; | |||
70 | ||||
71 | // The tensor product matrix, all factors of i removed. | |||
72 | // Although QubitPauliString and QubitPauliTensor could probably | |||
73 | // be made to do the work for us, they are more complicated | |||
74 | // than we need. | |||
75 | std::vector<Entry> sparse_matrix; | |||
76 | ||||
77 | // The number of Y which occurred. | |||
78 | unsigned power_of_i; | |||
79 | ||||
80 | // A work vector to avoid repeated reallocation. | |||
81 | std::vector<TripletCd> triplets; | |||
82 | ||||
83 | // We need to remember which diagonal entries were filled, | |||
84 | // to construct the final triplets. | |||
85 | std::vector<unsigned> set_diagonals; | |||
86 | ||||
87 | // In the tensor product of the given entry in "sparse_matrix" | |||
88 | // with the new 2x2 Pauli matrix on the right, | |||
89 | // add the new entries to "sparse_matrix" | |||
90 | // (including changing the existing entry). | |||
91 | // I.e., each entry in "sparse_matrix" is a 1 or -1, | |||
92 | // which adds two more +/-1 values to the list of nonzero entries | |||
93 | // (whilst being removed itself), | |||
94 | // since we take out a factor of i from Y. | |||
95 | void add_entries(unsigned sparse_matrix_index, Pauli pauli); | |||
96 | ||||
97 | // Call this to begin entering a new Pauli string "manually". | |||
98 | void clear(); | |||
99 | ||||
100 | // Add a single Pauli to the RIGHT of the current tensor product. | |||
101 | void append(Pauli pauli); | |||
102 | ||||
103 | void fill_triplets(double phase); | |||
104 | ||||
105 | PauliExpBoxUnitaryCalculator(); | |||
106 | ||||
107 | static PauliExpBoxUnitaryCalculator& get(); | |||
108 | }; | |||
109 | ||||
110 | 1845 | PauliExpBoxUnitaryCalculator& PauliExpBoxUnitaryCalculator::get() { | ||
111 |
4/8✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1844 times.
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 1 times.
✗ Branch 7 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
|
1845 | static PauliExpBoxUnitaryCalculator calculator; | |
112 | 1845 | return calculator; | ||
113 | } | |||
114 | ||||
115 | 1 | PauliExpBoxUnitaryCalculator::PauliExpBoxUnitaryCalculator() | ||
116 | 1 | : pauli_map(get_pauli_map()) {} | ||
117 | ||||
118 | 1845 | void PauliExpBoxUnitaryCalculator::clear() { | ||
119 | 1845 | sparse_matrix.resize(1); | ||
120 | 1845 | sparse_matrix[0] = Entry(0, 0, 1); | ||
121 | 1845 | power_of_i = 0; | ||
122 | 1845 | triplets.clear(); | ||
123 | 1845 | } | ||
124 | ||||
125 | 22830 | void PauliExpBoxUnitaryCalculator::add_entries( | ||
126 | unsigned sparse_matrix_index, Pauli pauli) { | |||
127 | TKET_ASSERT(sparse_matrix_index < sparse_matrix.size()); | |||
128 | 22830 | const auto& single_pauli = pauli_map.at(pauli); | ||
129 |
1/2✓ Branch 1 taken 22830 times.
✗ Branch 2 not taken.
|
22830 | sparse_matrix.push_back( | |
130 | 22830 | get_combined_entry(sparse_matrix[sparse_matrix_index], single_pauli[0])); | ||
131 | // Overwrite existing entry. | |||
132 | // The reference is valid until after the new entry returns! | |||
133 | 22830 | auto& existing_entry = sparse_matrix[sparse_matrix_index]; | ||
134 | 22830 | existing_entry = get_combined_entry(existing_entry, single_pauli[1]); | ||
135 | 22830 | } | ||
136 | ||||
137 | 6668 | void PauliExpBoxUnitaryCalculator::append(Pauli pauli) { | ||
138 | 6668 | const auto current_size = sparse_matrix.size(); | ||
139 |
2/2✓ Branch 0 taken 1662 times.
✓ Branch 1 taken 5006 times.
|
2/2✓ Decision 'true' taken 1662 times.
✓ Decision 'false' taken 5006 times.
|
6668 | if (pauli == Pauli::Y) { |
140 | 1662 | ++power_of_i; | ||
141 | } | |||
142 |
2/2✓ Branch 0 taken 22830 times.
✓ Branch 1 taken 6668 times.
|
2/2✓ Decision 'true' taken 22830 times.
✓ Decision 'false' taken 6668 times.
|
29498 | for (unsigned ii = 0; ii < current_size; ++ii) { |
143 | 22830 | add_entries(ii, pauli); | ||
144 | } | |||
145 | 6668 | } | ||
146 | ||||
147 | 1845 | void PauliExpBoxUnitaryCalculator::fill_triplets(double phase) { | ||
148 |
1/2✓ Branch 2 taken 1845 times.
✗ Branch 3 not taken.
|
1845 | triplets.reserve(sparse_matrix.size()); | |
149 | 1845 | triplets.clear(); | ||
150 | ||||
151 | // If M^2 = I then exp(itM) = cos(t)I + i.sin(t)M. | |||
152 | 1845 | const double angle = -0.5 * PI * phase; | ||
153 | 1845 | const double cc = std::cos(angle); | ||
154 | 1845 | const double ss = std::sin(angle); | ||
155 | ||||
156 | // TODO: efficiency: (i) check if cos(t) or sin(t) is nearly zero; | |||
157 | // (ii): fill the I coefficients FIRST, so we don't have to bother with | |||
158 | // "set_diagonals" (as we can overwrite diagonal entries directly). | |||
159 | 1845 | const std::complex<double> identity_coefficient(cc); | ||
160 | ||||
161 | // We took out factors of i from M (coming from Pauli Y), | |||
162 | // so have to put them back in. | |||
163 | 1845 | power_of_i = power_of_i % 4; | ||
164 | const auto matrix_coefficient = | |||
165 | 1845 | std::polar(1.0, 0.5 * (power_of_i + 1) * PI) * ss; | ||
166 | ||||
167 | // When we form the sparse representation of the matrix exp(itM), | |||
168 | // we must check which diagonal entries were missed out, | |||
169 | // coming from the cos(t)I term. | |||
170 | 1845 | set_diagonals.clear(); | ||
171 | ||||
172 |
2/2✓ Branch 5 taken 24675 times.
✓ Branch 6 taken 1845 times.
|
2/2✓ Decision 'true' taken 24675 times.
✓ Decision 'false' taken 1845 times.
|
26520 | for (const auto& entry : sparse_matrix) { |
173 | // In Utils\PauliStrings.hpp, there is | |||
174 | // QubitPauliTensor operator*(Complex a, const QubitPauliTensor &qpt), | |||
175 | // which messes up ordinary std statements like std::complex * int. | |||
176 | // Not really a problem, but can be unexpected. | |||
177 | std::complex<double> value = | |||
178 |
1/2✓ Branch 3 taken 24675 times.
✗ Branch 4 not taken.
|
24675 | matrix_coefficient * std::complex<double>(entry.value()); | |
179 | ||||
180 |
2/2✓ Branch 2 taken 1779 times.
✓ Branch 3 taken 22896 times.
|
2/2✓ Decision 'true' taken 1779 times.
✓ Decision 'false' taken 22896 times.
|
24675 | if (entry.row() == entry.col()) { |
181 |
1/2✓ Branch 2 taken 1779 times.
✗ Branch 3 not taken.
|
1779 | set_diagonals.push_back(entry.row()); | |
182 | 1779 | value += identity_coefficient; | ||
183 | } | |||
184 |
1/2✓ Branch 3 taken 24675 times.
✗ Branch 4 not taken.
|
24675 | triplets.emplace_back(entry.row(), entry.col(), value); | |
185 | } | |||
186 | // Complexity: let there be k diagonals set, out of N, and you want | |||
187 | // the other N-k (remembering that N = 2^n can be large). | |||
188 | // Here, we storing the diagonals we DON'T want in a std::vector, sort them, | |||
189 | // then select the OTHER elements. This takes time | |||
190 | // O(k.log k) + O(N.log k) time (with pretty small O constants, | |||
191 | // since sorting a std::vector is fast). | |||
192 | // | |||
193 | // If instead we use std::set to store all diagonals, | |||
194 | // remove each as it is set, and finally iterate through the | |||
195 | // REMAINING elements, it would be | |||
196 | // O(N.log N) + O((N-k).log N) + O(k.log k) time. | |||
197 | // This is probably a bit worse. | |||
198 | // | |||
199 | // If we really wanted to speed it up, we'd use bit operations. | |||
200 |
1/2✓ Branch 3 taken 1845 times.
✗ Branch 4 not taken.
|
1845 | std::sort(set_diagonals.begin(), set_diagonals.end()); | |
201 |
2/2✓ Branch 1 taken 24675 times.
✓ Branch 2 taken 1845 times.
|
2/2✓ Decision 'true' taken 24675 times.
✓ Decision 'false' taken 1845 times.
|
26520 | for (unsigned ii = 0; ii < sparse_matrix.size(); ++ii) { |
202 |
3/4✓ Branch 3 taken 24675 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 22896 times.
✓ Branch 6 taken 1779 times.
|
2/2✓ Decision 'true' taken 22896 times.
✓ Decision 'false' taken 1779 times.
|
24675 | if (!std::binary_search(set_diagonals.cbegin(), set_diagonals.cend(), ii)) { |
203 |
1/2✓ Branch 1 taken 22896 times.
✗ Branch 2 not taken.
|
22896 | triplets.emplace_back(ii, ii, identity_coefficient); | |
204 | } | |||
205 | } | |||
206 | 1845 | } | ||
207 | } // namespace | |||
208 | ||||
209 | 1845 | std::vector<TripletCd> get_triplets(const PauliExpBox& box) { | ||
210 |
2/4✓ Branch 1 taken 1845 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1845 times.
✗ Branch 5 not taken.
|
1845 | const auto phase_optional = eval_expr(box.get_phase()); | |
211 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 1845 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 1845 times.
|
1845 | if (!phase_optional) { |
212 | ✗ | throw SymbolsNotSupported( | ||
213 | "PauliExpBoxUnitaryCalculator called " | |||
214 | ✗ | "with symbolic phase parameter"); | ||
215 | } | |||
216 |
1/2✓ Branch 1 taken 1845 times.
✗ Branch 2 not taken.
|
1845 | const auto pauli_string = box.get_paulis(); | |
217 |
1/2✓ Branch 1 taken 1845 times.
✗ Branch 2 not taken.
|
1845 | auto& calculator = PauliExpBoxUnitaryCalculator::get(); | |
218 |
1/2✓ Branch 1 taken 1845 times.
✗ Branch 2 not taken.
|
1845 | calculator.clear(); | |
219 |
2/2✓ Branch 5 taken 6668 times.
✓ Branch 6 taken 1845 times.
|
2/2✓ Decision 'true' taken 6668 times.
✓ Decision 'false' taken 1845 times.
|
8513 | for (auto pauli : pauli_string) { |
220 |
1/2✓ Branch 1 taken 6668 times.
✗ Branch 2 not taken.
|
6668 | calculator.append(pauli); | |
221 | } | |||
222 |
2/4✓ Branch 1 taken 1845 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1845 times.
✗ Branch 5 not taken.
|
1845 | calculator.fill_triplets(phase_optional.value()); | |
223 |
1/2✓ Branch 1 taken 1845 times.
✗ Branch 2 not taken.
|
3690 | return calculator.triplets; | |
224 | 1845 | } | ||
225 | ||||
226 | } // namespace internal | |||
227 | } // namespace tket_sim | |||
228 | } // namespace tket | |||
229 |