| 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 |