Skip to content

Commit ee328e7

Browse files
Split StatePreparation into hpp, cpp file
1 parent 6918a79 commit ee328e7

File tree

2 files changed

+287
-0
lines changed

2 files changed

+287
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#pragma once
2+
3+
#include "CircuitOptimizer.hpp"
4+
#include "QuantumComputation.hpp"
5+
6+
#include <cmath>
7+
#include <complex>
8+
#include <utility>
9+
10+
namespace qc {
11+
/**
12+
* Class to prepare a generic Quantum State from a list of normalized complex
13+
amplitudes
14+
* Adapted implementation of Qiskit State Preparation:
15+
*
16+
https://github.com/Qiskit/qiskit/blob/main/qiskit/circuit/library/data_preparation/state_preparation.py#
17+
* based on paper:
18+
* Shende, Bullock, Markov. Synthesis of Quantum Logic Circuits (2004)
19+
[`https://ieeexplore.ieee.org/document/1629135`]
20+
* */
21+
class StatePreparation : public QuantumComputation {
22+
23+
public:
24+
explicit StatePreparation(const std::vector<std::complex<double>>& amplitudes);
25+
26+
private:
27+
template <typename T> static bool isNormalized(std::vector<T> vec);
28+
template <typename T> static double twoNorm(std::vector<T> vec);
29+
static std::vector<std::vector<double>> kroneckerProduct(std::vector<std::vector<double>> matrixA, std::vector<std::vector<double>> matrixB);
30+
static std::vector<std::vector<double>> createIdentity(size_t size);
31+
static std::vector<double> matrixVectorProd(const std::vector<std::vector<double>>& matrix,
32+
std::vector<double> vector);
33+
static qc::QuantumComputation
34+
gatesToUncompute(std::vector<std::complex<double>> amplitudes,
35+
size_t numQubits);
36+
static std::tuple<std::vector<std::complex<double>>, std::vector<double>,
37+
std::vector<double>>
38+
rotationsToDisentangle(std::vector<std::complex<double>> amplitudes);
39+
static std::tuple<std::complex<double>, double, double>
40+
blochAngles(std::complex<double> const complexA,
41+
std::complex<double> const complexB);
42+
static qc::QuantumComputation
43+
multiplex(qc::OpType targetGate, std::vector<double> angles, bool lastCnot);
44+
};
45+
} // namespace qc

src/algorithms/StatePreparation.cpp

+242
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
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

Comments
 (0)