Skip to content

Commit 84b5041

Browse files
Refactored StatePreparation files to match the other algorithms
1 parent d7e69f4 commit 84b5041

File tree

2 files changed

+83
-80
lines changed

2 files changed

+83
-80
lines changed

include/mqt-core/algorithms/StatePreparation.hpp

+9-34
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,11 @@
99

1010
#pragma once
1111

12-
#include "CircuitOptimizer.hpp"
1312
#include "QuantumComputation.hpp"
14-
#include "ir/operations/StandardOperation.hpp"
15-
16-
#include <cmath>
17-
#include <complex>
18-
#include <utility>
1913

2014
namespace qc {
2115
/**
22-
* Class to prepare a generic Quantum State from a list of normalized complex
16+
* Prepares a generic Quantum State from a list of normalized complex
2317
amplitudes
2418
* Adapted implementation of Qiskit State Preparation:
2519
*
@@ -28,32 +22,13 @@ namespace qc {
2822
* Shende, Bullock, Markov. Synthesis of Quantum Logic Circuits (2004)
2923
[`https://ieeexplore.ieee.org/document/1629135`]
3024
* */
31-
class StatePreparation : public QuantumComputation {
32-
33-
public:
34-
explicit StatePreparation(
35-
const std::vector<std::complex<double>>& amplitudes);
3625

37-
private:
38-
template <typename T> static bool isNormalized(std::vector<T> vec);
39-
template <typename T> static double twoNorm(std::vector<T> vec);
40-
static std::vector<std::vector<double>>
41-
kroneckerProduct(std::vector<std::vector<double>> matrixA,
42-
std::vector<std::vector<double>> matrixB);
43-
static std::vector<std::vector<double>> createIdentity(size_t size);
44-
static std::vector<double>
45-
matrixVectorProd(const std::vector<std::vector<double>>& matrix,
46-
std::vector<double> vector);
47-
static qc::QuantumComputation
48-
gatesToUncompute(std::vector<std::complex<double>> amplitudes,
49-
size_t numQubits);
50-
static std::tuple<std::vector<std::complex<double>>, std::vector<double>,
51-
std::vector<double>>
52-
rotationsToDisentangle(std::vector<std::complex<double>> amplitudes);
53-
static std::tuple<std::complex<double>, double, double>
54-
blochAngles(std::complex<double> const complexA,
55-
std::complex<double> const complexB);
56-
static qc::QuantumComputation
57-
multiplex(qc::OpType targetGate, std::vector<double> angles, bool lastCnot);
58-
};
26+
/**
27+
* @throws invalid_argument when amplitudes are not normalized or length not
28+
* power of 2
29+
* @param list of complex amplitudes to initialize to
30+
* @return MQT Circuit that initializes a state
31+
* */
32+
[[nodiscard]] auto createStatePreparationCircuit(
33+
const std::vector<std::complex<double>>& amplitudes) -> QuantumComputation;
5934
} // namespace qc

src/algorithms/StatePreparation.cpp

+74-46
Original file line numberDiff line numberDiff line change
@@ -6,30 +6,60 @@
66
*
77
* Licensed under the MIT License
88
*/
9-
109
#include "algorithms/StatePreparation.hpp"
1110

11+
#include "CircuitOptimizer.hpp"
12+
#include "ir/operations/StandardOperation.hpp"
13+
14+
#include <cmath>
15+
#include <complex>
16+
#include <utility>
17+
1218
static const double EPS = 1e-10;
1319

1420
namespace qc {
1521
using Matrix = std::vector<std::vector<double>>;
1622

17-
StatePreparation::StatePreparation(
18-
const std::vector<std::complex<double>>& amplitudes) {}
23+
auto createStatePreparationCircuit(
24+
const std::vector<std::complex<double>>& amplitudes) -> QuantumComputation {
25+
26+
if (!isNormalized(amplitudes)) {
27+
throw std::invalid_argument{
28+
"Using State Preparation with Amplitudes that are not normalized"};
29+
}
30+
31+
// get number of qubits needed
32+
double const numQubits = std::log2(amplitudes.size());
33+
34+
if (numQubits == 0 || std::floor(numQubits) != numQubits) {
35+
throw std::invalid_argument{
36+
"Using State Preparation with vector size that is not a power of 2"};
37+
}
38+
39+
QuantumComputation toZeroCircuit =
40+
gatesToUncompute(amplitudes, static_cast<size_t>(numQubits));
41+
42+
// invert circuit
43+
CircuitOptimizer::flattenOperations(toZeroCircuit);
44+
toZeroCircuit.invert();
45+
46+
return toZeroCircuit;
47+
}
1948

20-
template <typename T> bool StatePreparation::isNormalized(std::vector<T> vec) {
49+
template <typename T>
50+
[[noexcept]] auto isNormalized(std::vector<T> vec) -> bool {
2151
return std::abs(1 - twoNorm(vec)) < EPS;
2252
}
2353

24-
template <typename T> double StatePreparation::twoNorm(std::vector<T> vec) {
54+
template <typename T>[[noexcept]] auto twoNorm(std::vector<T> vec) -> double {
2555
double norm = 0;
2656
for (auto elem : vec) {
2757
norm += std::norm(elem);
2858
}
2959
return sqrt(norm);
3060
}
3161

32-
Matrix StatePreparation::kroneckerProduct(Matrix matrixA, Matrix matrixB) {
62+
[[noexcept]] auto kroneckerProduct(Matrix matrixA, Matrix matrixB) -> Matrix {
3363
size_t const rowA = matrixA.size();
3464
size_t const rowB = matrixB.size();
3565
size_t const colA = matrixA[0].size();
@@ -55,7 +85,7 @@ Matrix StatePreparation::kroneckerProduct(Matrix matrixA, Matrix matrixB) {
5585
return newMatrix;
5686
}
5787

58-
Matrix StatePreparation::createIdentity(size_t size) {
88+
[[noexcept]] auto createIdentity(size_t size) -> Matrix {
5989
Matrix identity{
6090
std::vector<std::vector<double>>(size, std::vector<double>(size, 0))};
6191
for (size_t i = 0; i < size; ++i) {
@@ -64,9 +94,9 @@ Matrix StatePreparation::createIdentity(size_t size) {
6494
return identity;
6595
}
6696

67-
std::vector<double>
68-
StatePreparation::matrixVectorProd(const Matrix& matrix,
69-
std::vector<double> vector) {
97+
[[noexcept]] auto
98+
matrixVectorProd(const Matrix& matrix,
99+
std::vector<double> vector) -> std::vector<double> {
70100
std::vector<double> result;
71101
for (const auto& matrixVec : matrix) {
72102
double sum{0};
@@ -79,10 +109,9 @@ StatePreparation::matrixVectorProd(const Matrix& matrix,
79109
}
80110

81111
// creates circuit that takes desired vector to zero
82-
qc::QuantumComputation
83-
StatePreparation::gatesToUncompute(std::vector<std::complex<double>> amplitudes,
84-
size_t numQubits) {
85-
qc::QuantumComputation disentangler{numQubits};
112+
[[noexcept]] auto gatesToUncompute(std::vector<std::complex<double>> amplitudes,
113+
size_t numQubits) -> QuantumComputation {
114+
QuantumComputation disentangler{numQubits};
86115
for (size_t i = 0; i < numQubits; ++i) {
87116
// rotations to disentangle LSB
88117
auto [remainingParams, thetas, phis] = rotationsToDisentangle(amplitudes);
@@ -96,8 +125,8 @@ StatePreparation::gatesToUncompute(std::vector<std::complex<double>> amplitudes,
96125
}
97126
if (phisNorm != 0) {
98127
// call multiplex with RZGate
99-
qc::QuantumComputation rzMultiplexer =
100-
multiplex(qc::OpType{qc::RZ}, phis, addLastCnot);
128+
QuantumComputation rzMultiplexer =
129+
multiplex(OpType{RZ}, phis, addLastCnot);
101130
// append rzMultiplexer to disentangler, but it should only attach on
102131
// qubits i-numQubits, thus "i" is added to the local qubit indices
103132
for (auto& op : rzMultiplexer) {
@@ -107,16 +136,16 @@ StatePreparation::gatesToUncompute(std::vector<std::complex<double>> amplitudes,
107136
for (auto control : op->getControls()) {
108137
// there were some errors when accessing the qubit directly and
109138
// adding to it
110-
op->setControls(qc::Controls{
111-
qc::Control{control.qubit + static_cast<unsigned int>(i)}});
139+
op->setControls(
140+
Controls{Control{control.qubit + static_cast<unsigned int>(i)}});
112141
}
113142
}
114-
disentangler.emplace_back<qc::Operation>(rzMultiplexer.asOperation());
143+
disentangler.emplace_back<Operation>(rzMultiplexer.asOperation());
115144
}
116145
if (thetasNorm != 0) {
117146
// call multiplex with RYGate
118-
qc::QuantumComputation ryMultiplexer =
119-
multiplex(qc::OpType{qc::RY}, thetas, addLastCnot);
147+
QuantumComputation ryMultiplexer =
148+
multiplex(OpType{RY}, thetas, addLastCnot);
120149
// append reversed ry_multiplexer to disentangler, but it should only
121150
// attach on qubits i-numQubits, thus "i" is added to the local qubit
122151
// indices
@@ -128,11 +157,11 @@ StatePreparation::gatesToUncompute(std::vector<std::complex<double>> amplitudes,
128157
for (auto control : op->getControls()) {
129158
// there were some errors when accessing the qubit directly and
130159
// adding to it
131-
op->setControls(qc::Controls{
132-
qc::Control{control.qubit + static_cast<unsigned int>(i)}});
160+
op->setControls(
161+
Controls{Control{control.qubit + static_cast<unsigned int>(i)}});
133162
}
134163
}
135-
disentangler.emplace_back<qc::Operation>(ryMultiplexer.asOperation());
164+
disentangler.emplace_back<Operation>(ryMultiplexer.asOperation());
136165
}
137166
}
138167
// adjust global phase according to the last e^(it)
@@ -146,10 +175,10 @@ StatePreparation::gatesToUncompute(std::vector<std::complex<double>> amplitudes,
146175

147176
// works out Ry and Rz rotation angles used to disentangle LSB qubit
148177
// rotations make up block diagonal matrix U
149-
std::tuple<std::vector<std::complex<double>>, std::vector<double>,
150-
std::vector<double>>
151-
StatePreparation::rotationsToDisentangle(
152-
std::vector<std::complex<double>> amplitudes) {
178+
[[noexcept]] auto
179+
rotationsToDisentangle(std::vector<std::complex<double>> amplitudes)
180+
-> std::tuple<std::vector<std::complex<double>>, std::vector<double>,
181+
std::vector<double>> {
153182
std::vector<std::complex<double>> remainingVector;
154183
std::vector<double> thetas;
155184
std::vector<double> phis;
@@ -164,9 +193,9 @@ StatePreparation::rotationsToDisentangle(
164193
return {remainingVector, thetas, phis};
165194
}
166195

167-
std::tuple<std::complex<double>, double, double>
168-
StatePreparation::blochAngles(std::complex<double> const complexA,
169-
std::complex<double> const complexB) {
196+
[[noexcept]] auto blochAngles(std::complex<double> const complexA,
197+
std::complex<double> const complexB)
198+
-> std::tuple<std::complex<double>, double, double> {
170199
double theta{0};
171200
double phi{0};
172201
double finalT{0};
@@ -191,18 +220,17 @@ StatePreparation::blochAngles(std::complex<double> const complexA,
191220
* @param lastCnot : add last cnot if true
192221
* @return multiplexer circuit as QuantumComputation
193222
*/
194-
qc::QuantumComputation StatePreparation::multiplex(qc::OpType targetGate,
195-
std::vector<double> angles,
196-
bool lastCnot) {
223+
[[noexcept]] auto multiplex(OpType targetGate, std::vector<double> angles,
224+
bool lastCnot) -> QuantumComputation {
197225
size_t const listLen = angles.size();
198226
double const localNumQubits =
199227
std::floor(std::log2(static_cast<double>(listLen))) + 1;
200-
qc::QuantumComputation multiplexer{static_cast<size_t>(localNumQubits)};
228+
QuantumComputation multiplexer{static_cast<size_t>(localNumQubits)};
201229
// recursion base case
202230
if (localNumQubits == 1) {
203-
multiplexer.emplace_back<qc::StandardOperation>(
204-
multiplexer.getNqubits(), qc::Controls{}, 0, targetGate,
205-
std::vector{angles[0]});
231+
multiplexer.emplace_back<StandardOperation>(multiplexer.getNqubits(),
232+
Controls{}, 0, targetGate,
233+
std::vector{angles[0]});
206234
return multiplexer;
207235
}
208236

@@ -218,31 +246,31 @@ qc::QuantumComputation StatePreparation::multiplex(qc::OpType targetGate,
218246
std::make_move_iterator(angles.begin()),
219247
std::make_move_iterator(angles.begin() +
220248
static_cast<int64_t>(listLen) / 2)};
221-
qc::QuantumComputation multiplex1 = multiplex(targetGate, angles1, false);
249+
QuantumComputation multiplex1 = multiplex(targetGate, angles1, false);
222250

223251
// append multiplex1 to multiplexer
224-
multiplexer.emplace_back<qc::Operation>(multiplex1.asOperation());
252+
multiplexer.emplace_back<Operation>(multiplex1.asOperation());
225253
// flips the LSB qubit, control on MSB
226-
multiplexer.cx(0, static_cast<qc::Qubit>(localNumQubits - 1));
254+
multiplexer.cx(0, static_cast<Qubit>(localNumQubits - 1));
227255

228256
std::vector<double> const angles2{std::make_move_iterator(angles.begin()) +
229257
static_cast<int64_t>(listLen) / 2,
230258
std::make_move_iterator(angles.end())};
231-
qc::QuantumComputation multiplex2 = multiplex(targetGate, angles2, false);
259+
QuantumComputation multiplex2 = multiplex(targetGate, angles2, false);
232260

233261
// extra efficiency by reversing (!= inverting) second multiplex
234262
if (listLen > 1) {
235263
multiplex2.reverse();
236-
multiplexer.emplace_back<qc::Operation>(multiplex2.asOperation());
264+
multiplexer.emplace_back<Operation>(multiplex2.asOperation());
237265
} else {
238-
multiplexer.emplace_back<qc::Operation>(multiplex2.asOperation());
266+
multiplexer.emplace_back<Operation>(multiplex2.asOperation());
239267
}
240268

241269
if (lastCnot) {
242-
multiplexer.cx(0, static_cast<qc::Qubit>(localNumQubits - 1));
270+
multiplexer.cx(0, static_cast<Qubit>(localNumQubits - 1));
243271
}
244272

245-
qc::CircuitOptimizer::flattenOperations(multiplexer);
273+
CircuitOptimizer::flattenOperations(multiplexer);
246274
return multiplexer;
247275
}
248276

0 commit comments

Comments
 (0)