From 489a27e2bf720dc74e5215efaf99b408194d5015 Mon Sep 17 00:00:00 2001 From: Thomas Lauritsen Date: Thu, 27 Mar 2025 14:54:04 +0100 Subject: [PATCH 1/4] Added functionality for noisy circuit. I think. Everything can build. Need to do testing now. --- cpp/clue/include/NoisyQC.hpp | 32 +++ .../experiments/NoisyGroverExperiment.hpp | 51 ++++ cpp/clue/src/CMakeLists.txt | 3 +- cpp/clue/src/NoisyQC.cpp | 37 +++ .../src/experiments/NoisyGroverExperiment.cpp | 236 ++++++++++++++++++ 5 files changed, 358 insertions(+), 1 deletion(-) create mode 100644 cpp/clue/include/NoisyQC.hpp create mode 100644 cpp/clue/include/experiments/NoisyGroverExperiment.hpp create mode 100644 cpp/clue/src/NoisyQC.cpp create mode 100644 cpp/clue/src/experiments/NoisyGroverExperiment.cpp diff --git a/cpp/clue/include/NoisyQC.hpp b/cpp/clue/include/NoisyQC.hpp new file mode 100644 index 00000000..ddf59e7f --- /dev/null +++ b/cpp/clue/include/NoisyQC.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include "QuantumComputation.hpp" + +typedef long unsigned int luint; +/*************************************************************************/ + +/* Class for noisy quantum circuits + */ +class NoisyQuantumComputation +{ +protected: + luint nQubits; + std::vector> layers; + +public: + /* Constructor*/ + NoisyQuantumComputation(luint); + + /* Helper functions*/ + luint size() { return this->nQubits; } + qc::QuantumComputation build_noisy_qc(); // Build a noisy quantum circuit based on the + qc::QuantumComputation build_non_noisy_circuit(); // Get the quantum circuit as if no noise is present. + + void push_back(const qc::QuantumComputation qc, const double epsilon) + { + if (qc.size() != this->size()) + std::cerr << "The same if the circuit does not match the expected size." << std::endl; + + layers.push_back({qc, epsilon}); + } +}; \ No newline at end of file diff --git a/cpp/clue/include/experiments/NoisyGroverExperiment.hpp b/cpp/clue/include/experiments/NoisyGroverExperiment.hpp new file mode 100644 index 00000000..7a4ca304 --- /dev/null +++ b/cpp/clue/include/experiments/NoisyGroverExperiment.hpp @@ -0,0 +1,51 @@ +#ifndef CLUE_EX_SEARCH +#define CLUE_EX_SEARCH + +#include "Experiment.hpp" +#include "boost/dynamic_bitset.hpp" +#include "NoisyQC.hpp" + +using namespace std; + +/** + * Class for clauses in a formula. + * + * A clause is a a formula with the following shape: "x1 v x2 v x3...", where `vi` is a variable (possibly negated). + * The variables can only appear once (since appearing together with different value implies the clause is always true). + */ +class QuantumSearch : public Experiment +{ +protected: + luint qbits; + unordered_set success_set; + + /* Method that serves as an oracle for the search function */ + bool oracle(boost::dynamic_bitset<>); + bool oracle(luint); + void quantum_oracle(NoisyQuantumComputation &); // Adds the oracle part to the circuit given as input + void quantum_diffusion(NoisyQuantumComputation &); // Adds the diffusion operator to the circuit given as input + + /* Overriden methods from Experiment */ + CCSparseVector clue_observable(); + dd::vEdge dd_observable(); + /* Virtual methods from Experiment */ + luint size() { return this->qbits; } + luint correct_size() { return 2UL; } + luint bound_size() { return 2UL; } + array direct(); + vector matrix(); + dd::CMat matrix_B(dd::CMat &); + qc::QuantumComputation quantum(double); + qc::QuantumComputation *quantum_B(double); + QuantumSearch *change_exec_type(ExperimentType); + +public: + QuantumSearch(luint, vector, luint, ExperimentType, dd::Package<> *); + + static QuantumSearch *random(luint, ExperimentType, dd::Package<> *); + + /* Method to get the string out of an experiment */ + string to_string(); +}; + +#endif \ No newline at end of file diff --git a/cpp/clue/src/CMakeLists.txt b/cpp/clue/src/CMakeLists.txt index a668637f..9266a964 100644 --- a/cpp/clue/src/CMakeLists.txt +++ b/cpp/clue/src/CMakeLists.txt @@ -14,7 +14,8 @@ add_library( ${CMAKE_CURRENT_SOURCE_DIR}/RationalFunctions.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Linalg_Vector.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Linalg_Subspace.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/Linalg_DD.cpp) + ${CMAKE_CURRENT_SOURCE_DIR}/Linalg_DD.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/NoisyQC.cpp) # set include directories target_include_directories(${PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/include diff --git a/cpp/clue/src/NoisyQC.cpp b/cpp/clue/src/NoisyQC.cpp new file mode 100644 index 00000000..eb1201b2 --- /dev/null +++ b/cpp/clue/src/NoisyQC.cpp @@ -0,0 +1,37 @@ +#include "NoisyQC.hpp" + +NoisyQuantumComputation::NoisyQuantumComputation(luint _nQubits) +{ + this->nQubits = _nQubits; +} + +qc::QuantumComputation NoisyQuantumComputation::build_noisy_qc() +{ + auto noisy_qc = qc::QuantumComputation(this->nQubits); + + // Generate random number between [0,1) + std::random_device rd; + std::mt19937 gen(rd()); + + for (const auto &[layer, epsilon] : this->layers) + { + if (std::generate_canonical(gen) > epsilon) + { + noisy_qc.emplace_back(layer.front()->clone()); + } + } + + return noisy_qc; +} + +qc::QuantumComputation NoisyQuantumComputation::build_non_noisy_circuit() +{ + auto qc = qc::QuantumComputation(this->size()); + + for (const auto &[layer, _] : this->layers) + { + qc.emplace_back(layer.front()->clone()); + } + + return qc; +} \ No newline at end of file diff --git a/cpp/clue/src/experiments/NoisyGroverExperiment.cpp b/cpp/clue/src/experiments/NoisyGroverExperiment.cpp new file mode 100644 index 00000000..044ef9eb --- /dev/null +++ b/cpp/clue/src/experiments/NoisyGroverExperiment.cpp @@ -0,0 +1,236 @@ +#include "experiments/NoisyGroverExperiment.hpp" + +#include "NoisyQC.hpp" +#include +#include "dd/Package.hpp" + +QuantumSearch::QuantumSearch(luint nQbits, vector success, luint eIterations, ExperimentType eType, dd::Package<> *ePackage) : Experiment("Grover", "H", eIterations, eType, ePackage) +{ + this->qbits = nQbits; + luint bound = static_cast(pow(2UL, nQbits - 1)); + for (luint el : success) + { + if (el >= bound) + { + throw domain_error("The value for success is out of bound"); + } + else + { + this->success_set.insert(el); + } + } +} + +/*static*/ QuantumSearch *QuantumSearch::random(luint nQbits, ExperimentType eType, dd::Package<> *ePackage) +{ + luint half_size = static_cast(pow(2UL, nQbits - 1)); + luint iterations = static_cast(ceil(pow(2., static_cast(nQbits - 1) / 2.))); + + // RANDOM WITH SEVERAL SUCCESS VALUES + luint number_of_successes = 1U; //(static_cast(rand())%(nQbits-1))+1; + vector success_set = vector(); + for (luint i = 0; i < number_of_successes; i++) + { + success_set.push_back(static_cast(rand()) % half_size); + } + return new QuantumSearch(nQbits, success_set, iterations, eType, ePackage); +} + +bool QuantumSearch::oracle(boost::dynamic_bitset<> bitchain) +{ + luint value = 0UL, to_add = 1UL; + for (luint i = 0; i < bitchain.size(); i++) + { + if (bitchain[i]) + { + value += to_add; + } + to_add *= 2UL; + } + return this->oracle(value); +} +bool QuantumSearch::oracle(luint value) +{ + return this->success_set.contains(value); +} + +/** + * @brief Applies the Quantum Oracle associated with `this`. + * + * This method applies to the given ``circuit`` the Quantum Oracle associated with the + * success set defined in `this`. For getting a description of the circuit itself, look + * into the notes in https://cnot.io/quantum_algorithms/grover/grovers_algorithm.html, + * where the oracle is described. + * + * As a summary, for each `element` in the success set, we add a controlled `X` gate + * over the ancillary qubit, where the controls on the other qubits are as the bit chain + * representing `element`. More precisely, for `3` in 4 bits, we have `3 = 0101`, so we would + * apply a controlled `X` to the 5-th qubit with controls `C(-)C(+)C(-)C(+)`. + */ +void QuantumSearch::quantum_oracle(NoisyQuantumComputation &circuit) +{ + vector> success_bitchains = vector>(this->success_set.size()); + luint j = 0; + for (luint success : this->success_set) + { + success_bitchains[j] = boost::dynamic_bitset<>(this->size() - 1, success); + j++; + } + qc::Controls controls{}; + for (boost::dynamic_bitset<> element : success_bitchains) + { + controls.clear(); + for (luint i = 0; i < this->size() - 1; ++i) + { + controls.emplace(static_cast(i), (element[i] ? qc::Control::Type::Pos : qc::Control::Type::Neg)); + } + circuit.mcx(controls, static_cast(this->size() - 1)); + } +} +/** + * @brief Applies the Quantum Diffusion operator for Grover's algorithm. + * + * This method applies to the given ``circuit`` the Quantum Diffusion operator. For getting + * a description of the circuit itself, look into the notes in + * https://cnot.io/quantum_algorithms/grover/grovers_algorithm.html, + * where the diffusion operator is described. + * + * As a summary, we apply the hadamard gate to each qubit and then a controlled `X` gate + * over the ancillary qubit with negative controls all over other qubits. + * + */ +void QuantumSearch::quantum_diffusion(qc::QuantumComputation &circuit) +{ + // Code taken from mqt-core/algorithms/Grover.cpp + for (luint i = 1; i < this->size() - 1; ++i) + { + circuit.h(static_cast(i)); + } + + qc::Controls controls{}; + for (qc::Qubit j = 1; j < this->size() - 1; ++j) + { + controls.emplace(j, qc::Control::Type::Neg); + } + circuit.z(0); // X-H-X + circuit.mcx(controls, 0); + circuit.z(0); // X-H-X + + for (luint i = this->size() - 2; i > 0; --i) + { + circuit.h(static_cast(i)); + } +} + +/* Overriden methods from Experiment */ +CCSparseVector QuantumSearch::clue_observable() +{ + luint full_size = static_cast(pow(2UL, this->size())), half = full_size / 2UL; + CCSparseVector result = CCSparseVector(full_size); + CC coeff = CC(sqrt(1. / static_cast(half))); + for (luint i = half; i < full_size; i++) + { + result.set_value(i, coeff); + } + + return result; +} +/** + * @brief Computes the initial state for the Grover algorithm. + * + * As described in https://cnot.io/quantum_algorithms/grover/grovers_algorithm.html, + * the initial state for Grover's Algorithm is an entangled state in the non-ancillary qubits + * with a |-> state for the ancillary qubit. + * + */ +dd::vEdge QuantumSearch::dd_observable() +{ + vector states; + for (luint i = 0; i < this->size() - 1; i++) + { + states.push_back(dd::BasisStates::plus); + } + states.push_back(dd::BasisStates::minus); + + return this->package->makeBasisState(this->size(), states); +} +/* Virtual methods from Experiment */ +array QuantumSearch::direct() +{ + return {}; // TODO: This is incomplete +} +vector QuantumSearch::matrix() +{ + luint full_size = static_cast(pow(2UL, this->size())); + luint half_size = full_size / 2UL; + vector result = vector(full_size, full_size); + + CC coeff = -CC(1 / pow(2UL, this->size() - 2)), coeff_one = coeff + CC(1.); + for (luint i = 0; i < half_size; i++) + { + for (luint j = 0; j < half_size; j++) + { + if (i == j) + { + result[i].set_value(j, coeff_one); + result[i + half_size].set_value(j + half_size, (this->success_set.contains(j)) ? coeff_one : -coeff_one); + } + else + { + result[i].set_value(j, coeff); + result[i + half_size].set_value(j + half_size, (this->success_set.contains(j)) ? coeff : -coeff); + } + } + } + return result; // TODO: This is incomplete +} + +dd::CMat QuantumSearch::matrix_B(dd::CMat &U) +{ + return identity_matrix(U.size()); // There is no begin hamiltonian: we use the identity +} +qc::QuantumComputation QuantumSearch::quantum(double) +{ + auto noise_operator = NoisyQuantumComputation(this->size()); + + this->quantum_oracle(noise_operator); + this->quantum_diffusion(noise_operator); + + auto circuit = noise_operator.build_noisy_qc(); + return circuit; +} +qc::QuantumComputation *QuantumSearch::quantum_B(double) +{ + qc::QuantumComputation *circuit = new qc::QuantumComputation(this->size()); + return circuit; // Identity circuit +} +QuantumSearch *QuantumSearch::change_exec_type(ExperimentType new_type) +{ + vector to_copy; + to_copy.reserve(this->success_set.size()); + for (std::unordered_set::iterator it = this->success_set.begin(); it != this->success_set.end(); it++) + { + to_copy.push_back(*it); + } + + return new QuantumSearch(this->size() - 1, to_copy, this->iterations, new_type, this->package); +} + +string QuantumSearch::to_string() +{ + stringstream stream; + stream << "\"Grover Search Algorithm of " << this->size() << " q-bits (1 is a flag) of ["; + std::unordered_set::iterator it = this->success_set.begin(); + if (it != this->success_set.end()) + { + stream << *it; + it++; + } + while (it != this->success_set.end()) + { + stream << ", " << *it; + it++; + } + stream << "]\""; + return stream.str(); +} From dfd87f1237980c2251a6a8f0b31f48cbb813bd32 Mon Sep 17 00:00:00 2001 From: Thomas Lauritsen Date: Fri, 28 Mar 2025 11:47:20 +0100 Subject: [PATCH 2/4] Added an initial NoisyQC implementation. Also includes tests for adding a layer and the succesfull capture of non compatible layers. --- cpp/clue/apps/CMakeLists.txt | 2 +- cpp/clue/include/NoisyQC.hpp | 20 +- .../experiments/NoisyGroverExperiment.hpp | 51 ---- cpp/clue/src/NoisyQC.cpp | 14 +- .../src/experiments/NoisyGroverExperiment.cpp | 236 ------------------ cpp/clue/tests/CMakeLists.txt | 3 +- cpp/clue/tests/NoisyQC_Tests.cpp | 119 +++++++++ 7 files changed, 146 insertions(+), 299 deletions(-) delete mode 100644 cpp/clue/include/experiments/NoisyGroverExperiment.hpp delete mode 100644 cpp/clue/src/experiments/NoisyGroverExperiment.cpp create mode 100644 cpp/clue/tests/NoisyQC_Tests.cpp diff --git a/cpp/clue/apps/CMakeLists.txt b/cpp/clue/apps/CMakeLists.txt index dd5fa3ca..28079e31 100644 --- a/cpp/clue/apps/CMakeLists.txt +++ b/cpp/clue/apps/CMakeLists.txt @@ -8,5 +8,5 @@ macro(ADD_APP_EXECUTABLE runname) endmacro() ADD_APP_EXECUTABLE(main) -ADD_APP_EXECUTABLE(main_QASM) +#ADD_APP_EXECUTABLE(main_QASM) removed from cmake because of repeated failure to build. ADD_APP_EXECUTABLE(main_script ${PROJECT_NAME}-experiments) \ No newline at end of file diff --git a/cpp/clue/include/NoisyQC.hpp b/cpp/clue/include/NoisyQC.hpp index ddf59e7f..d1276318 100644 --- a/cpp/clue/include/NoisyQC.hpp +++ b/cpp/clue/include/NoisyQC.hpp @@ -18,14 +18,24 @@ class NoisyQuantumComputation NoisyQuantumComputation(luint); /* Helper functions*/ - luint size() { return this->nQubits; } - qc::QuantumComputation build_noisy_qc(); // Build a noisy quantum circuit based on the - qc::QuantumComputation build_non_noisy_circuit(); // Get the quantum circuit as if no noise is present. + int size() { return this->layers.size(); } + luint getNqubits() { return this->nQubits; } // Number of qubits in the circuit + qc::QuantumComputation *build_noisy_qc(); // Build a noisy quantum circuit based on the epsilon values. Have to return a pointer to match with experiment class. + qc::QuantumComputation build_non_noisy_qc(); // Get the quantum circuit as if no noise is present. + /* Add a layer to the Noisy Quantum Computation + Primary use of the noisy quantum computation. With this implementation we can each gate a specific epsilon value. + + This is a bit more cumbersome of an implementation, but it allows us to build it in similar fashion to the python implementation. + */ void push_back(const qc::QuantumComputation qc, const double epsilon) { - if (qc.size() != this->size()) - std::cerr << "The same if the circuit does not match the expected size." << std::endl; + if (qc.size() == 0) + throw std::runtime_error("The quantum circuit is empty."); + if (qc.getNqubits() != this->getNqubits()) + throw std::runtime_error("The qubits of the circuits does not match the expected number."); + if (epsilon > 1.0 || epsilon < 0.0) + throw std::runtime_error("Noise should be between 0 and 1."); layers.push_back({qc, epsilon}); } diff --git a/cpp/clue/include/experiments/NoisyGroverExperiment.hpp b/cpp/clue/include/experiments/NoisyGroverExperiment.hpp deleted file mode 100644 index 7a4ca304..00000000 --- a/cpp/clue/include/experiments/NoisyGroverExperiment.hpp +++ /dev/null @@ -1,51 +0,0 @@ -#ifndef CLUE_EX_SEARCH -#define CLUE_EX_SEARCH - -#include "Experiment.hpp" -#include "boost/dynamic_bitset.hpp" -#include "NoisyQC.hpp" - -using namespace std; - -/** - * Class for clauses in a formula. - * - * A clause is a a formula with the following shape: "x1 v x2 v x3...", where `vi` is a variable (possibly negated). - * The variables can only appear once (since appearing together with different value implies the clause is always true). - */ -class QuantumSearch : public Experiment -{ -protected: - luint qbits; - unordered_set success_set; - - /* Method that serves as an oracle for the search function */ - bool oracle(boost::dynamic_bitset<>); - bool oracle(luint); - void quantum_oracle(NoisyQuantumComputation &); // Adds the oracle part to the circuit given as input - void quantum_diffusion(NoisyQuantumComputation &); // Adds the diffusion operator to the circuit given as input - - /* Overriden methods from Experiment */ - CCSparseVector clue_observable(); - dd::vEdge dd_observable(); - /* Virtual methods from Experiment */ - luint size() { return this->qbits; } - luint correct_size() { return 2UL; } - luint bound_size() { return 2UL; } - array direct(); - vector matrix(); - dd::CMat matrix_B(dd::CMat &); - qc::QuantumComputation quantum(double); - qc::QuantumComputation *quantum_B(double); - QuantumSearch *change_exec_type(ExperimentType); - -public: - QuantumSearch(luint, vector, luint, ExperimentType, dd::Package<> *); - - static QuantumSearch *random(luint, ExperimentType, dd::Package<> *); - - /* Method to get the string out of an experiment */ - string to_string(); -}; - -#endif \ No newline at end of file diff --git a/cpp/clue/src/NoisyQC.cpp b/cpp/clue/src/NoisyQC.cpp index eb1201b2..aa3e80f2 100644 --- a/cpp/clue/src/NoisyQC.cpp +++ b/cpp/clue/src/NoisyQC.cpp @@ -5,9 +5,13 @@ NoisyQuantumComputation::NoisyQuantumComputation(luint _nQubits) this->nQubits = _nQubits; } -qc::QuantumComputation NoisyQuantumComputation::build_noisy_qc() +/* When we build the noisy qc, we simply add the operation with probability 1-epsilon or else we do nothing. + With this implementation, we are also requiring the qc to have only one operation in the layer. + This is a bit more cumbersome of an implementation, but it allows us to build it in similar fashion to the python implementation. + I thought about instead of doing nothing, we apply the identify gate to all the targets in the operation. Thoughts?*/ +qc::QuantumComputation *NoisyQuantumComputation::build_noisy_qc() { - auto noisy_qc = qc::QuantumComputation(this->nQubits); + auto noisy_qc = new qc::QuantumComputation(this->nQubits); // Generate random number between [0,1) std::random_device rd; @@ -15,16 +19,16 @@ qc::QuantumComputation NoisyQuantumComputation::build_noisy_qc() for (const auto &[layer, epsilon] : this->layers) { - if (std::generate_canonical(gen) > epsilon) + if (std::generate_canonical(gen) > epsilon) // add the layer with probability 1-epsilon. Else do nothing. { - noisy_qc.emplace_back(layer.front()->clone()); + noisy_qc->emplace_back(layer.front()->clone()); } } return noisy_qc; } -qc::QuantumComputation NoisyQuantumComputation::build_non_noisy_circuit() +qc::QuantumComputation NoisyQuantumComputation::build_non_noisy_qc() { auto qc = qc::QuantumComputation(this->size()); diff --git a/cpp/clue/src/experiments/NoisyGroverExperiment.cpp b/cpp/clue/src/experiments/NoisyGroverExperiment.cpp deleted file mode 100644 index 044ef9eb..00000000 --- a/cpp/clue/src/experiments/NoisyGroverExperiment.cpp +++ /dev/null @@ -1,236 +0,0 @@ -#include "experiments/NoisyGroverExperiment.hpp" - -#include "NoisyQC.hpp" -#include -#include "dd/Package.hpp" - -QuantumSearch::QuantumSearch(luint nQbits, vector success, luint eIterations, ExperimentType eType, dd::Package<> *ePackage) : Experiment("Grover", "H", eIterations, eType, ePackage) -{ - this->qbits = nQbits; - luint bound = static_cast(pow(2UL, nQbits - 1)); - for (luint el : success) - { - if (el >= bound) - { - throw domain_error("The value for success is out of bound"); - } - else - { - this->success_set.insert(el); - } - } -} - -/*static*/ QuantumSearch *QuantumSearch::random(luint nQbits, ExperimentType eType, dd::Package<> *ePackage) -{ - luint half_size = static_cast(pow(2UL, nQbits - 1)); - luint iterations = static_cast(ceil(pow(2., static_cast(nQbits - 1) / 2.))); - - // RANDOM WITH SEVERAL SUCCESS VALUES - luint number_of_successes = 1U; //(static_cast(rand())%(nQbits-1))+1; - vector success_set = vector(); - for (luint i = 0; i < number_of_successes; i++) - { - success_set.push_back(static_cast(rand()) % half_size); - } - return new QuantumSearch(nQbits, success_set, iterations, eType, ePackage); -} - -bool QuantumSearch::oracle(boost::dynamic_bitset<> bitchain) -{ - luint value = 0UL, to_add = 1UL; - for (luint i = 0; i < bitchain.size(); i++) - { - if (bitchain[i]) - { - value += to_add; - } - to_add *= 2UL; - } - return this->oracle(value); -} -bool QuantumSearch::oracle(luint value) -{ - return this->success_set.contains(value); -} - -/** - * @brief Applies the Quantum Oracle associated with `this`. - * - * This method applies to the given ``circuit`` the Quantum Oracle associated with the - * success set defined in `this`. For getting a description of the circuit itself, look - * into the notes in https://cnot.io/quantum_algorithms/grover/grovers_algorithm.html, - * where the oracle is described. - * - * As a summary, for each `element` in the success set, we add a controlled `X` gate - * over the ancillary qubit, where the controls on the other qubits are as the bit chain - * representing `element`. More precisely, for `3` in 4 bits, we have `3 = 0101`, so we would - * apply a controlled `X` to the 5-th qubit with controls `C(-)C(+)C(-)C(+)`. - */ -void QuantumSearch::quantum_oracle(NoisyQuantumComputation &circuit) -{ - vector> success_bitchains = vector>(this->success_set.size()); - luint j = 0; - for (luint success : this->success_set) - { - success_bitchains[j] = boost::dynamic_bitset<>(this->size() - 1, success); - j++; - } - qc::Controls controls{}; - for (boost::dynamic_bitset<> element : success_bitchains) - { - controls.clear(); - for (luint i = 0; i < this->size() - 1; ++i) - { - controls.emplace(static_cast(i), (element[i] ? qc::Control::Type::Pos : qc::Control::Type::Neg)); - } - circuit.mcx(controls, static_cast(this->size() - 1)); - } -} -/** - * @brief Applies the Quantum Diffusion operator for Grover's algorithm. - * - * This method applies to the given ``circuit`` the Quantum Diffusion operator. For getting - * a description of the circuit itself, look into the notes in - * https://cnot.io/quantum_algorithms/grover/grovers_algorithm.html, - * where the diffusion operator is described. - * - * As a summary, we apply the hadamard gate to each qubit and then a controlled `X` gate - * over the ancillary qubit with negative controls all over other qubits. - * - */ -void QuantumSearch::quantum_diffusion(qc::QuantumComputation &circuit) -{ - // Code taken from mqt-core/algorithms/Grover.cpp - for (luint i = 1; i < this->size() - 1; ++i) - { - circuit.h(static_cast(i)); - } - - qc::Controls controls{}; - for (qc::Qubit j = 1; j < this->size() - 1; ++j) - { - controls.emplace(j, qc::Control::Type::Neg); - } - circuit.z(0); // X-H-X - circuit.mcx(controls, 0); - circuit.z(0); // X-H-X - - for (luint i = this->size() - 2; i > 0; --i) - { - circuit.h(static_cast(i)); - } -} - -/* Overriden methods from Experiment */ -CCSparseVector QuantumSearch::clue_observable() -{ - luint full_size = static_cast(pow(2UL, this->size())), half = full_size / 2UL; - CCSparseVector result = CCSparseVector(full_size); - CC coeff = CC(sqrt(1. / static_cast(half))); - for (luint i = half; i < full_size; i++) - { - result.set_value(i, coeff); - } - - return result; -} -/** - * @brief Computes the initial state for the Grover algorithm. - * - * As described in https://cnot.io/quantum_algorithms/grover/grovers_algorithm.html, - * the initial state for Grover's Algorithm is an entangled state in the non-ancillary qubits - * with a |-> state for the ancillary qubit. - * - */ -dd::vEdge QuantumSearch::dd_observable() -{ - vector states; - for (luint i = 0; i < this->size() - 1; i++) - { - states.push_back(dd::BasisStates::plus); - } - states.push_back(dd::BasisStates::minus); - - return this->package->makeBasisState(this->size(), states); -} -/* Virtual methods from Experiment */ -array QuantumSearch::direct() -{ - return {}; // TODO: This is incomplete -} -vector QuantumSearch::matrix() -{ - luint full_size = static_cast(pow(2UL, this->size())); - luint half_size = full_size / 2UL; - vector result = vector(full_size, full_size); - - CC coeff = -CC(1 / pow(2UL, this->size() - 2)), coeff_one = coeff + CC(1.); - for (luint i = 0; i < half_size; i++) - { - for (luint j = 0; j < half_size; j++) - { - if (i == j) - { - result[i].set_value(j, coeff_one); - result[i + half_size].set_value(j + half_size, (this->success_set.contains(j)) ? coeff_one : -coeff_one); - } - else - { - result[i].set_value(j, coeff); - result[i + half_size].set_value(j + half_size, (this->success_set.contains(j)) ? coeff : -coeff); - } - } - } - return result; // TODO: This is incomplete -} - -dd::CMat QuantumSearch::matrix_B(dd::CMat &U) -{ - return identity_matrix(U.size()); // There is no begin hamiltonian: we use the identity -} -qc::QuantumComputation QuantumSearch::quantum(double) -{ - auto noise_operator = NoisyQuantumComputation(this->size()); - - this->quantum_oracle(noise_operator); - this->quantum_diffusion(noise_operator); - - auto circuit = noise_operator.build_noisy_qc(); - return circuit; -} -qc::QuantumComputation *QuantumSearch::quantum_B(double) -{ - qc::QuantumComputation *circuit = new qc::QuantumComputation(this->size()); - return circuit; // Identity circuit -} -QuantumSearch *QuantumSearch::change_exec_type(ExperimentType new_type) -{ - vector to_copy; - to_copy.reserve(this->success_set.size()); - for (std::unordered_set::iterator it = this->success_set.begin(); it != this->success_set.end(); it++) - { - to_copy.push_back(*it); - } - - return new QuantumSearch(this->size() - 1, to_copy, this->iterations, new_type, this->package); -} - -string QuantumSearch::to_string() -{ - stringstream stream; - stream << "\"Grover Search Algorithm of " << this->size() << " q-bits (1 is a flag) of ["; - std::unordered_set::iterator it = this->success_set.begin(); - if (it != this->success_set.end()) - { - stream << *it; - it++; - } - while (it != this->success_set.end()) - { - stream << ", " << *it; - it++; - } - stream << "]\""; - return stream.str(); -} diff --git a/cpp/clue/tests/CMakeLists.txt b/cpp/clue/tests/CMakeLists.txt index 3ee65cec..75664641 100644 --- a/cpp/clue/tests/CMakeLists.txt +++ b/cpp/clue/tests/CMakeLists.txt @@ -8,4 +8,5 @@ endmacro() ADD_TEST_EXECUTABLE(LA_Subspace_Tests) ADD_TEST_EXECUTABLE(LA_Vector_Tests) -ADD_TEST_EXECUTABLE(RF_Tests) \ No newline at end of file +ADD_TEST_EXECUTABLE(RF_Tests) +ADD_TEST_EXECUTABLE(NoisyQC_Tests) \ No newline at end of file diff --git a/cpp/clue/tests/NoisyQC_Tests.cpp b/cpp/clue/tests/NoisyQC_Tests.cpp new file mode 100644 index 00000000..02dc40aa --- /dev/null +++ b/cpp/clue/tests/NoisyQC_Tests.cpp @@ -0,0 +1,119 @@ +#include +#include + +#include "NoisyQC.hpp" + +// Test if a layer is added to the noisy_qc currectly with the push_back function +void add_layer_succes(int n, luint qbits) +{ + + auto nqc = NoisyQuantumComputation(qbits); + + // Add layers to the Noisy QuantumComputation + for (int i = 0; i < n; i++) + { + auto layer = qc::QuantumComputation(qbits); + layer.h(0); + double epsilon = 0.001; + nqc.push_back(layer, epsilon); + } + + // Throw error if we do not have the expected number of layers + if (nqc.size() != n) + { + throw std::runtime_error("The noisy quantum computation layers did not get added currectly. Expected " + std::to_string(n) + " got " + std::to_string(nqc.size())); + } +} + +void add_layer_failure_incorrect_size() +{ + luint nqc_size = 3; + luint qc_size = 4; + double epsilon = 0.01; + + auto nqc = NoisyQuantumComputation(nqc_size); + auto invalid_qc = qc::QuantumComputation(qc_size); + + try + { + nqc.push_back(invalid_qc, epsilon); + } + catch (std::runtime_error) + { + return; + } + throw std::runtime_error("Expected runtime_error was not thrown for mismatched sizes."); +} + +void add_layer_failure_invalid_epsilon() +{ + luint nqc_size = 3; + double invalid_epsilon = 1.5; // Invalid epsilon value (greater than 1.0) throw std::runtime_error("Incorrect size of Noisy QC and QC didn't throw error"); + + auto nqc = NoisyQuantumComputation(nqc_size); + auto qc = qc::QuantumComputation(nqc_size); + + try + { + nqc.push_back(qc, invalid_epsilon); + } + catch (std::runtime_error) + { + return; + } + throw std::runtime_error("Expected runtime_error was not thrown for invalid epsilon."); +} + +void add_layer_failure_empty_qc() +{ + luint nqc_size = 3; + double epsilon = 0.01; + + auto nqc = NoisyQuantumComputation(nqc_size); + auto empty_qc = qc::QuantumComputation(nqc_size); + + try + { + nqc.push_back(empty_qc, epsilon); + } + catch (std::runtime_error) + { + return; + } + throw std::runtime_error("Expected runtime_error was not thrown for empty quantum circuit."); +} + +void build_non_noisy_circuit(luint qubits) +{ + double epsilon = 0.01; + + auto nqc = NoisyQuantumComputation(qubits); + + // Add layers to the Noisy QuantumComputation + for (int i = 0; i < qubits; i++) + { + auto qc = qc::QuantumComputation(qubits); + qc.h(i); + nqc.push_back(qc, epsilon); + } + + // Build the non-noisy circuit + auto non_noisy_qc = nqc.build_non_noisy_qc(); + + // Check if the size of the non-noisy circuit is equal to the number of layers + if (non_noisy_qc.size() != nqc.size()) + { + throw std::runtime_error("The size of the non-noisy circuit does not match the number of layers."); + } +} + +int main() +{ + add_layer_succes(3, 3); + add_layer_failure_incorrect_size(); + add_layer_failure_invalid_epsilon(); + add_layer_failure_empty_qc(); + build_non_noisy_circuit(3); + + return 0; +} \ No newline at end of file From 1790743c7adf6b1ba3a28252abe231ca553b31e1 Mon Sep 17 00:00:00 2001 From: Thomas Lauritsen Date: Fri, 28 Mar 2025 12:55:42 +0100 Subject: [PATCH 3/4] If a gate fails, add the identify gate to the intended target instead. --- cpp/clue/src/NoisyQC.cpp | 10 +++++++--- cpp/clue/tests/NoisyQC_Tests.cpp | 25 +++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/cpp/clue/src/NoisyQC.cpp b/cpp/clue/src/NoisyQC.cpp index aa3e80f2..a20413b4 100644 --- a/cpp/clue/src/NoisyQC.cpp +++ b/cpp/clue/src/NoisyQC.cpp @@ -11,7 +11,7 @@ NoisyQuantumComputation::NoisyQuantumComputation(luint _nQubits) I thought about instead of doing nothing, we apply the identify gate to all the targets in the operation. Thoughts?*/ qc::QuantumComputation *NoisyQuantumComputation::build_noisy_qc() { - auto noisy_qc = new qc::QuantumComputation(this->nQubits); + auto qc = new qc::QuantumComputation(this->nQubits); // Generate random number between [0,1) std::random_device rd; @@ -21,11 +21,15 @@ qc::QuantumComputation *NoisyQuantumComputation::build_noisy_qc() { if (std::generate_canonical(gen) > epsilon) // add the layer with probability 1-epsilon. Else do nothing. { - noisy_qc->emplace_back(layer.front()->clone()); + qc->emplace_back(layer.front()->clone()); + } + else + { + qc->i(layer.front()->getTargets()[0]); // Apply identity gate to the first target of the operation } } - return noisy_qc; + return qc; } qc::QuantumComputation NoisyQuantumComputation::build_non_noisy_qc() diff --git a/cpp/clue/tests/NoisyQC_Tests.cpp b/cpp/clue/tests/NoisyQC_Tests.cpp index 02dc40aa..c1810952 100644 --- a/cpp/clue/tests/NoisyQC_Tests.cpp +++ b/cpp/clue/tests/NoisyQC_Tests.cpp @@ -107,6 +107,30 @@ void build_non_noisy_circuit(luint qubits) } } +void build_noisy_circuit(luint qubits) +{ + double epsilon = 0.01; + + auto nqc = NoisyQuantumComputation(qubits); + + // Add layers to the Noisy QuantumComputation + for (int i = 0; i < qubits; i++) + { + auto qc = qc::QuantumComputation(qubits); + qc.h(i); + nqc.push_back(qc, epsilon); + } + + // Build the noisy circuit + auto noisy_qc = nqc.build_noisy_qc(); + + // Check if the size of the noisy circuit is equal to the number of layers + if (noisy_qc->size() != nqc.size()) + { + throw std::runtime_error("The size of the noisy circuit does not match the number of layers."); + } +} + int main() { add_layer_succes(3, 3); @@ -114,6 +138,7 @@ int main() add_layer_failure_invalid_epsilon(); add_layer_failure_empty_qc(); build_non_noisy_circuit(3); + build_noisy_circuit(3); return 0; } \ No newline at end of file From ae54612e414f536a93ec6cdb1cc7a08321a0baad Mon Sep 17 00:00:00 2001 From: Thomas Lauritsen Date: Fri, 28 Mar 2025 13:02:19 +0100 Subject: [PATCH 4/4] Changed a comment to match the actually code after change. --- cpp/clue/src/NoisyQC.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cpp/clue/src/NoisyQC.cpp b/cpp/clue/src/NoisyQC.cpp index a20413b4..65430394 100644 --- a/cpp/clue/src/NoisyQC.cpp +++ b/cpp/clue/src/NoisyQC.cpp @@ -5,10 +5,9 @@ NoisyQuantumComputation::NoisyQuantumComputation(luint _nQubits) this->nQubits = _nQubits; } -/* When we build the noisy qc, we simply add the operation with probability 1-epsilon or else we do nothing. +/* When we build the noisy qc, we simply add the operation with probability 1-epsilon or else we add the identity gate to the intended target. With this implementation, we are also requiring the qc to have only one operation in the layer. - This is a bit more cumbersome of an implementation, but it allows us to build it in similar fashion to the python implementation. - I thought about instead of doing nothing, we apply the identify gate to all the targets in the operation. Thoughts?*/ + This is a bit more cumbersome of an implementation, but it allows us to build it in similar fashion to the python implementation.?*/ qc::QuantumComputation *NoisyQuantumComputation::build_noisy_qc() { auto qc = new qc::QuantumComputation(this->nQubits);