Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cpp/clue/apps/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
42 changes: 42 additions & 0 deletions cpp/clue/include/NoisyQC.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#pragma once

#include "QuantumComputation.hpp"

typedef long unsigned int luint;
/*************************************************************************/

/* Class for noisy quantum circuits
*/
class NoisyQuantumComputation
{
protected:
luint nQubits;
std::vector<std::tuple<qc::QuantumComputation, double>> layers;

public:
/* Constructor*/
NoisyQuantumComputation(luint);

/* Helper functions*/
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() == 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});
}
};
3 changes: 2 additions & 1 deletion cpp/clue/src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
44 changes: 44 additions & 0 deletions cpp/clue/src/NoisyQC.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#include "NoisyQC.hpp"

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 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.?*/
qc::QuantumComputation *NoisyQuantumComputation::build_noisy_qc()
{
auto qc = new 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<double, 10>(gen) > epsilon) // add the layer with probability 1-epsilon. Else do nothing.
{
qc->emplace_back(layer.front()->clone());
}
else
{
qc->i(layer.front()->getTargets()[0]); // Apply identity gate to the first target of the operation
}
}

return qc;
}

qc::QuantumComputation NoisyQuantumComputation::build_non_noisy_qc()
{
auto qc = qc::QuantumComputation(this->size());

for (const auto &[layer, _] : this->layers)
{
qc.emplace_back(layer.front()->clone());
}

return qc;
}
3 changes: 2 additions & 1 deletion cpp/clue/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ endmacro()

ADD_TEST_EXECUTABLE(LA_Subspace_Tests)
ADD_TEST_EXECUTABLE(LA_Vector_Tests)
ADD_TEST_EXECUTABLE(RF_Tests)
ADD_TEST_EXECUTABLE(RF_Tests)
ADD_TEST_EXECUTABLE(NoisyQC_Tests)
144 changes: 144 additions & 0 deletions cpp/clue/tests/NoisyQC_Tests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
#include <iostream>
#include <string>

#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.");
}
}

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);
add_layer_failure_incorrect_size();
add_layer_failure_invalid_epsilon();
add_layer_failure_empty_qc();
build_non_noisy_circuit(3);
build_noisy_circuit(3);

return 0;
}