1
+ #include " algorithms/StatePreparation.hpp"
2
+
3
+ static const double EPS = 1e-10 ;
4
+
5
+ namespace qc {
6
+ using Matrix = std::vector<std::vector<double >>;
7
+
8
+ StatePreparation::StatePreparation (
9
+ const std::vector<std::complex<double >>& amplitudes) {
10
+
11
+ }
12
+
13
+ template <typename T> bool StatePreparation::isNormalized (std::vector<T> vec) {
14
+ return std::abs (1 - twoNorm (vec)) < EPS;
15
+ }
16
+
17
+ template <typename T> double StatePreparation::twoNorm (std::vector<T> vec) {
18
+ double norm = 0 ;
19
+ for (auto elem : vec) {
20
+ norm += std::norm (elem);
21
+ }
22
+ return sqrt (norm);
23
+ }
24
+
25
+ Matrix StatePreparation::kroneckerProduct (Matrix matrixA, Matrix matrixB) {
26
+ size_t const rowA = matrixA.size ();
27
+ size_t const rowB = matrixB.size ();
28
+ size_t const colA = matrixA[0 ].size ();
29
+ size_t const colB = matrixB[0 ].size ();
30
+ // initialize size
31
+ Matrix newMatrix{(rowA * rowB), std::vector<double >(colA * colB, 0 )};
32
+ // code taken from RosettaCode slightly adapted
33
+ for (size_t i = 0 ; i < rowA; ++i) {
34
+ // k loops till rowB
35
+ for (size_t j = 0 ; j < colA; ++j) {
36
+ // j loops till colA
37
+ for (size_t k = 0 ; k < rowB; ++k) {
38
+ // l loops till colB
39
+ for (size_t l = 0 ; l < colB; ++l) {
40
+ // Each element of matrix A is
41
+ // multiplied by whole Matrix B
42
+ // resp and stored as Matrix C
43
+ newMatrix[i * rowB + k][j * colB + l] = matrixA[i][j] * matrixB[k][l];
44
+ }
45
+ }
46
+ }
47
+ }
48
+ return newMatrix;
49
+ }
50
+
51
+ Matrix StatePreparation::createIdentity (size_t size) {
52
+ Matrix identity{
53
+ std::vector<std::vector<double >>(size, std::vector<double >(size, 0 ))};
54
+ for (size_t i = 0 ; i < size; ++i) {
55
+ identity[i][i] = 1 ;
56
+ }
57
+ return identity;
58
+ }
59
+
60
+ std::vector<double >
61
+ StatePreparation::matrixVectorProd (const Matrix& matrix,
62
+ std::vector<double > vector) {
63
+ std::vector<double > result;
64
+ for (const auto & matrixVec : matrix) {
65
+ double sum{0 };
66
+ for (size_t i = 0 ; i < matrixVec.size (); ++i) {
67
+ sum += matrixVec[i] * vector[i];
68
+ }
69
+ result.push_back (sum);
70
+ }
71
+ return result;
72
+ }
73
+
74
+ // creates circuit that takes desired vector to zero
75
+ qc::QuantumComputation
76
+ StatePreparation::gatesToUncompute (std::vector<std::complex<double >> amplitudes,
77
+ size_t numQubits) {
78
+ qc::QuantumComputation disentangler{numQubits};
79
+ for (size_t i = 0 ; i < numQubits; ++i) {
80
+ // rotations to disentangle LSB
81
+ auto [remainingParams, thetas, phis] = rotationsToDisentangle (amplitudes);
82
+ amplitudes = remainingParams;
83
+ // perform required rotations
84
+ bool addLastCnot = true ;
85
+ double const phisNorm = twoNorm (phis);
86
+ double const thetasNorm = twoNorm (thetas);
87
+ if (phisNorm != 0 && thetasNorm != 0 ) {
88
+ addLastCnot = false ;
89
+ }
90
+ if (phisNorm != 0 ) {
91
+ // call multiplex with RZGate
92
+ qc::QuantumComputation rzMultiplexer =
93
+ multiplex (qc::OpType{qc::RZ}, phis, addLastCnot);
94
+ // append rzMultiplexer to disentangler, but it should only attach on
95
+ // qubits i-numQubits, thus "i" is added to the local qubit indices
96
+ for (auto & op : rzMultiplexer) {
97
+ for (auto & target : op->getTargets ()) {
98
+ target += static_cast <unsigned int >(i);
99
+ }
100
+ for (auto control : op->getControls ()) {
101
+ // there were some errors when accessing the qubit directly and
102
+ // adding to it
103
+ op->setControls (qc::Controls{
104
+ qc::Control{control.qubit + static_cast <unsigned int >(i)}});
105
+ }
106
+ }
107
+ disentangler.emplace_back <qc::Operation>(rzMultiplexer.asOperation ());
108
+ }
109
+ if (thetasNorm != 0 ) {
110
+ // call multiplex with RYGate
111
+ qc::QuantumComputation ryMultiplexer =
112
+ multiplex (qc::OpType{qc::RY}, thetas, addLastCnot);
113
+ // append reversed ry_multiplexer to disentangler, but it should only
114
+ // attach on qubits i-numQubits, thus "i" is added to the local qubit
115
+ // indices
116
+ std::reverse (ryMultiplexer.begin (), ryMultiplexer.end ());
117
+ for (auto & op : ryMultiplexer) {
118
+ for (auto & target : op->getTargets ()) {
119
+ target += static_cast <unsigned int >(i);
120
+ }
121
+ for (auto control : op->getControls ()) {
122
+ // there were some errors when accessing the qubit directly and
123
+ // adding to it
124
+ op->setControls (qc::Controls{
125
+ qc::Control{control.qubit + static_cast <unsigned int >(i)}});
126
+ }
127
+ }
128
+ disentangler.emplace_back <qc::Operation>(ryMultiplexer.asOperation ());
129
+ }
130
+ }
131
+ // adjust global phase according to the last e^(it)
132
+ double const arg = -std::arg (std::accumulate (
133
+ amplitudes.begin (), amplitudes.end (), std::complex<double >(0 , 0 )));
134
+ if (arg != 0 ) {
135
+ disentangler.gphase (arg);
136
+ }
137
+ return disentangler;
138
+ }
139
+
140
+ // works out Ry and Rz rotation angles used to disentangle LSB qubit
141
+ // rotations make up block diagonal matrix U
142
+ std::tuple<std::vector<std::complex<double >>, std::vector<double >,
143
+ std::vector<double >>
144
+ StatePreparation::rotationsToDisentangle (
145
+ std::vector<std::complex<double >> amplitudes) {
146
+ std::vector<std::complex<double >> remainingVector;
147
+ std::vector<double > thetas;
148
+ std::vector<double > phis;
149
+ for (size_t i = 0 ; i < (amplitudes.size () / 2 ); ++i) {
150
+ auto [remains, theta, phi] =
151
+ blochAngles (amplitudes[2 * i], amplitudes[2 * i + 1 ]);
152
+ remainingVector.push_back (remains);
153
+ // minus sign because we move it to zero
154
+ thetas.push_back (-theta);
155
+ phis.push_back (-phi);
156
+ }
157
+ return {remainingVector, thetas, phis};
158
+ }
159
+
160
+ std::tuple<std::complex<double >, double , double >
161
+ StatePreparation::blochAngles (std::complex<double > const complexA,
162
+ std::complex<double > const complexB) {
163
+ double theta{0 };
164
+ double phi{0 };
165
+ double finalT{0 };
166
+ double const magA = std::abs (complexA);
167
+ double const magB = std::abs (complexB);
168
+ double const finalR = sqrt (pow (magA, 2 ) + pow (magB, 2 ));
169
+ if (finalR > EPS) {
170
+ theta = 2 * acos (magA / finalR);
171
+ double const aAngle = std::arg (complexA);
172
+ double const bAngle = std::arg (complexB);
173
+ finalT = aAngle + bAngle;
174
+ phi = bAngle - aAngle;
175
+ }
176
+ return {finalR * exp (std::complex<double >{0 , 1 } * finalT / 2 .), theta, phi};
177
+ }
178
+
179
+ // recursive implementation that returns multiplexer circuit
180
+ /* *
181
+ * @param target_gate : Ry or Rz gate to apply to target qubit, multiplexed
182
+ * over all other "select" qubits
183
+ * @param angles : list of rotation angles to apply Ry and Rz
184
+ * @param lastCnot : add last cnot if true
185
+ * @return multiplexer circuit as QuantumComputation
186
+ */
187
+ qc::QuantumComputation StatePreparation::multiplex (qc::OpType targetGate,
188
+ std::vector<double > angles,
189
+ bool lastCnot) {
190
+ size_t const listLen = angles.size ();
191
+ double const localNumQubits =
192
+ std::floor (std::log2 (static_cast <double >(listLen))) + 1 ;
193
+ qc::QuantumComputation multiplexer{static_cast <size_t >(localNumQubits)};
194
+ // recursion base case
195
+ if (localNumQubits == 1 ) {
196
+ multiplexer.emplace_back <qc::StandardOperation>(
197
+ multiplexer.getNqubits (), qc::Controls{}, 0 , targetGate,
198
+ std::vector{angles[0 ]});
199
+ return multiplexer;
200
+ }
201
+
202
+ Matrix const matrix{std::vector<double >{0.5 , 0.5 },
203
+ std::vector<double >{0.5 , -0.5 }};
204
+ Matrix const identity =
205
+ createIdentity (static_cast <size_t >(pow (2 ., localNumQubits - 2 .)));
206
+ Matrix const angleWeights = kroneckerProduct (matrix, identity);
207
+
208
+ angles = matrixVectorProd (angleWeights, angles);
209
+
210
+ std::vector<double > const angles1{
211
+ std::make_move_iterator (angles.begin ()),
212
+ std::make_move_iterator (angles.begin () +
213
+ static_cast <int64_t >(listLen) / 2 )};
214
+ qc::QuantumComputation multiplex1 = multiplex (targetGate, angles1, false );
215
+
216
+ // append multiplex1 to multiplexer
217
+ multiplexer.emplace_back <qc::Operation>(multiplex1.asOperation ());
218
+ // flips the LSB qubit, control on MSB
219
+ multiplexer.cx (0 , static_cast <qc::Qubit>(localNumQubits - 1 ));
220
+
221
+ std::vector<double > const angles2{std::make_move_iterator (angles.begin ()) +
222
+ static_cast <int64_t >(listLen) / 2 ,
223
+ std::make_move_iterator (angles.end ())};
224
+ qc::QuantumComputation multiplex2 = multiplex (targetGate, angles2, false );
225
+
226
+ // extra efficiency by reversing (!= inverting) second multiplex
227
+ if (listLen > 1 ) {
228
+ multiplex2.reverse ();
229
+ multiplexer.emplace_back <qc::Operation>(multiplex2.asOperation ());
230
+ } else {
231
+ multiplexer.emplace_back <qc::Operation>(multiplex2.asOperation ());
232
+ }
233
+
234
+ if (lastCnot) {
235
+ multiplexer.cx (0 , static_cast <qc::Qubit>(localNumQubits - 1 ));
236
+ }
237
+
238
+ qc::CircuitOptimizer::flattenOperations (multiplexer);
239
+ return multiplexer;
240
+ }
241
+
242
+ } // namespace qc
0 commit comments