Skip to content

Commit

Permalink
Initial Thread-Safe Implementation
Browse files Browse the repository at this point in the history
Add thread-safety to XACC/QCOR (Experimental). We introduce `_XACC_MUTEX` macro in `xacc.hpp` to turn on/off the feature (default is OFF). When the macro is ON, only a single thread can execute the following routines by using either `std::mutex` or `std::recursive_mutex`:

- User-facing API routines
   - `createObjectiveFunction()` (`qcor::__internal__::get_objective()`)
   - `createOptimizer()`

- Compiler-related modules
   - Syntax Handler
       - `QuantumKernel::operator()`
       - `QuantumKernel::~QuantumKernel()`
   - QJIT
      -  `QJIT::QJIT()`
      -  `QJIT::jit_compile()`
      -  `QJIT::write_cache()`

Also, regardless of the macro, the `VQEObjective` class is now `xacc::Cloneable`.

Add the following test files to test multi-thread execution

- `examples/simple/bell_threaded.cpp`
- `examples/simple/simple-objective-function-async.cpp`
- `examples/simple/simple-objective-function-threaded.cpp`
- `examples/qpu_lambda/deuteron_threaded.cpp`
- `examples/qpu_lambda/deuteron_vqe_threaded.cpp`
- `examples/qpu_lambda/deuteron_vqe_obj_func_threaded.cpp`
- `examples/qpu_lambda/lambda_test_bell_threaded.cpp`

Signed-off-by: Akihiro Hayashi <ahayashi@gatech.edu>
  • Loading branch information
ahayashi committed Jun 24, 2021
1 parent e401f95 commit 9eafcb7
Show file tree
Hide file tree
Showing 14 changed files with 456 additions and 4 deletions.
10 changes: 9 additions & 1 deletion examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,15 @@ add_qcor_compile_and_exe_test(qrt_qpu_lambda_compute_action qpu_lambda/lambda_wi
add_qcor_compile_and_exe_test(qrt_qpu_arith_adder arithmetic/simple.cpp)
add_qcor_compile_and_exe_test(qrt_qpu_arith_integer_add arithmetic/integer_add.cpp)

# Multi-threading tests
add_test(NAME qrt_simple-objective-function-async COMMAND ${CMAKE_BINARY_DIR}/qcor -c ${CMAKE_CURRENT_SOURCE_DIR}/simple/simple-objective-function-async.cpp)
add_test(NAME qrt_simple-objective-function-threaded COMMAND ${CMAKE_BINARY_DIR}/qcor -c ${CMAKE_CURRENT_SOURCE_DIR}/simple/simple-objective-function-threaded.cpp)
add_test(NAME qrt_bell-threaded COMMAND ${CMAKE_BINARY_DIR}/qcor -c ${CMAKE_CURRENT_SOURCE_DIR}/simple/bell_threaded.cpp)
add_qcor_compile_and_exe_test(qrt_qpu_lambda_bell_threaded qpu_lambda/lambda_test_bell_threaded.cpp)
add_qcor_compile_and_exe_test(qrt_qpu_lambda_deuteron_threaded qpu_lambda/deuteron_threaded.cpp)
add_qcor_compile_and_exe_test(qrt_qpu_lambda_deuteron_vqe_threaded qpu_lambda/deuteron_vqe_threaded.cpp)
add_qcor_compile_and_exe_test(qrt_qpu_lambda_deuteron_vqe_objfunc_threaded qpu_lambda/deuteron_vqe_obj_func_threaded.cpp)

if (QCOR_BUILD_QSHARP_TESTS)
add_subdirectory(qsharp)
endif()
endif()
44 changes: 44 additions & 0 deletions examples/qpu_lambda/deuteron_threaded.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#include "qcor.hpp"
// for _XACC_MUTEX
#include "xacc.hpp"

void foo() {

auto ansatz_X0X1 = qpu_lambda([](qreg q, double x) {
X(q[0]);
Ry(q[1], x);
CX(q[1], q[0]);
H(q);
Measure(q);
});

OptFunction obj(
[&](const std::vector<double> &x, std::vector<double> &) {
auto q = qalloc(2);
ansatz_X0X1(q, x[0]);
auto exp = q.exp_val_z();
print("<X0X1(",x[0],") = ", exp);
return exp;
},
1);

auto optimizer = createOptimizer(
"nlopt",
{{"initial-parameters", std::vector<double>{1.2}}, {"maxeval", 10}});
auto [opt_val, opt_params] = optimizer->optimize(obj);
print("opt_val = ", opt_val);
}

int main(int argc, char **argv) {
#ifdef _XACC_MUTEX
std::cout << "_XACC_MUTEX is defined: multi-threding execution" << std::endl;
std::thread t0(foo);
std::thread t1(foo);
t0.join();
t1.join();
#else
std::cout << "_XACC_MUTEX is NOT defined: sequential execution" << std::endl;
foo();
foo();
#endif
}
57 changes: 57 additions & 0 deletions examples/qpu_lambda/deuteron_vqe_obj_func_threaded.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#include "qcor.hpp"
// for _XACC_MUTEX
#include "xacc.hpp"

void foo() {
// Create the Hamiltonian
auto H = -2.1433 * X(0) * X(1) - 2.1433 * Y(0) * Y(1) + .21829 * Z(0) -
6.125 * Z(1) + 5.907;
int iter_count = 0;
auto ansatz = qpu_lambda(
[](qreg q, double x) {
X(q[0]);
Ry(q[1], x);
CX(q[1], q[0]);
print("Iter", iter_count, "; angle = ", x);
iter_count++;
},
iter_count);

auto q = qalloc(2);
auto objective = createObjectiveFunction(ansatz, H, q, 1);
// Create a qcor Optimizer
auto optimizer = createOptimizer("nlopt");

// Optimize the above function
auto [optval, opt_params] = optimizer->optimize(*objective.get());
std::cout << "Energy: " << optval << "\n";
qcor_expect(std::abs(optval + 1.74886) < 0.1);

auto ansatz_vec_param = qpu_lambda([](qreg q, std::vector<double> x) {
X(q[0]);
Ry(q[1], x[0]);
CX(q[1], q[0]);
});

auto q1 = qalloc(2);
auto objective_vec = createObjectiveFunction(ansatz_vec_param, H, q1, 1);

// Optimize the above function
auto [optval_vec, opt_params_vec] = optimizer->optimize(*objective_vec.get());
std::cout << "Energy: " << optval_vec << "\n";
qcor_expect(std::abs(optval_vec + 1.74886) < 0.1);
}

int main(int argc, char **argv) {
#ifdef _XACC_MUTEX
std::cout << "_XACC_MUTEX is defined: multi-threding execution" << std::endl;
std::thread t0(foo);
std::thread t1(foo);
t0.join();
t1.join();
#else
std::cout << "_XACC_MUTEX is NOT defined: sequential execution" << std::endl;
foo();
foo();
#endif
}
57 changes: 57 additions & 0 deletions examples/qpu_lambda/deuteron_vqe_threaded.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#include "qcor.hpp"
// for _XACC_MUTEX
#include "xacc.hpp"

void foo() {
// Create the Hamiltonian
auto H = -2.1433 * X(0) * X(1) - 2.1433 * Y(0) * Y(1) + .21829 * Z(0) -
6.125 * Z(1) + 5.907;

auto ansatz = qpu_lambda([](qreg q, double x) {
print("x = ", x);
X(q[0]);
Ry(q[1], x);
CX(q[1], q[0]);
});

auto ansatz_take_vec = qpu_lambda([](qreg q, std::vector<double> x) {
print("x = ", x[0]);
X(q[0]);
Ry(q[1], x[0]);
CX(q[1], q[0]);
});

OptFunction opt_function(
[&](std::vector<double> x) { return ansatz.observe(H, qalloc(2), x[0]); },
1);

OptFunction opt_function_vec(
[&](std::vector<double> x) {
return ansatz_take_vec.observe(H, qalloc(2), x);
},
1);

auto optimizer = createOptimizer("nlopt");
auto [ground_energy, opt_params] = optimizer->optimize(opt_function);
print("Energy: ", ground_energy);
qcor_expect(std::abs(ground_energy + 1.74886) < 0.1);

auto [ground_energy_vec, opt_params_vec] =
optimizer->optimize(opt_function_vec);
print("Energy: ", ground_energy_vec);
qcor_expect(std::abs(ground_energy_vec + 1.74886) < 0.1);
}

int main(int argc, char **argv) {
#ifdef _XACC_MUTEX
std::cout << "_XACC_MUTEX is defined: multi-threding execution" << std::endl;
std::thread t0(foo);
std::thread t1(foo);
t0.join();
t1.join();
#else
std::cout << "_XACC_MUTEX is NOT defined: sequential execution" << std::endl;
foo();
foo();
#endif
}
38 changes: 38 additions & 0 deletions examples/qpu_lambda/lambda_test_bell_threaded.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#include "qcor.hpp"
// for _XACC_MUTEX
#include "xacc.hpp"
void foo() {
auto x_lambda = qpu_lambda([](qubit q) {
X(q); });

auto bell = qpu_lambda([](qreg q) {
H(q[0]);
// Call the captured lambda
x_lambda.ctrl(q[0], q[1]);
Measure(q);
}, x_lambda);

auto q = qalloc(2);
bell(q);
q.print();
qcor_expect(q.counts().size() == 2);
qcor_expect(q.counts()["00"] > 400);
qcor_expect(q.counts()["11"] > 400);
// Entangled...
qcor_expect(q.counts()["00"] + q.counts()["11"] == 1024);
}

int main(int argc, char **argv) {
set_shots(1024);
#ifdef _XACC_MUTEX
std::cout << "_XACC_MUTEX is defined: multi-threding execution" << std::endl;
std::thread t0(foo);
std::thread t1(foo);
t0.join();
t1.join();
#else
std::cout << "_XACC_MUTEX is NOT defined: sequential execution" << std::endl;
foo();
foo();
#endif
}
38 changes: 38 additions & 0 deletions examples/simple/bell_threaded.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// for _XACC_MUTEX
#include "xacc.hpp"

// Define the bell kernel
__qpu__ void bell(qreg q) {
using qcor::xasm;
H(q[0]);
CX(q[0], q[1]);

for (int i = 0; i < q.size(); i++) {
Measure(q[i]);
}
}

void foo() {
// Create two qubit registers, each size 2
auto q = qalloc(2);

// Run the quantum kernel
bell(q);

// dump the results
q.print();
}

int main(int argc, char **argv) {
#ifdef _XACC_MUTEX
std::cout << "_XACC_MUTEX is defined: multi-threding execution" << std::endl;
std::thread t0(foo);
std::thread t1(foo);
t0.join();
t1.join();
#else
std::cout << "_XACC_MUTEX is NOT defined: sequential execution" << std::endl;
foo();
foo();
#endif
}
82 changes: 82 additions & 0 deletions examples/simple/simple-objective-function-async.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Note no includes here, we are just
// using the language extension
//
// run this with
// qcor -qpu qpp simple-objective-function-async.cpp
// ./a.out

// for _XACC_MUTEX
#include "xacc.hpp"

__qpu__ void ansatz(qreg q, double theta) {
X(q[0]);
Ry(q[1], theta);
CX(q[1], q[0]);
}

int main(int argc, char **argv) {
#ifdef _XACC_MUTEX
std::cout << "_XACC_MUTEX is defined: execute taskInitiate asynchronously" << std::endl;
#else
std::cout << "_XACC_MUTEX is NOT defined: execute taskInitiate sequentially" << std::endl;
#endif
// Allocate 2 qubits
auto q1 = qalloc(2);
auto q2 = qalloc(2);

// Programmer needs to set
// the number of variational params
auto n_variational_params1 = 1;
auto n_variational_params2 = 1;

// Create the Deuteron Hamiltonian
auto H1 = 5.907 - 2.1433 * X(0) * X(1) - 2.1433 * Y(0) * Y(1) + .21829 * Z(0) -
6.125 * Z(1);
auto H2 = 5.907 - 2.1433 * X(0) * X(1) - 2.1433 * Y(0) * Y(1) + .21829 * Z(0) -
6.125 * Z(1);

// Create the ObjectiveFunction, here we want to run VQE
// need to provide ansatz, Operator, and qreg
auto objective1 = createObjectiveFunction(
ansatz, H1, q1, n_variational_params1,
{{"gradient-strategy", "parameter-shift"}});
auto objective2 = createObjectiveFunction(
ansatz, H2, q2, n_variational_params2,
{{"gradient-strategy", "parameter-shift"}});

// Create the Optimizer.
auto optimizer1 = createOptimizer("nlopt", {{"nlopt-optimizer", "l-bfgs"}});
auto optimizer2 = createOptimizer("nlopt", {{"nlopt-optimizer", "l-bfgs"}});

#ifdef _XACC_MUTEX
// Launch the Optimization Task with taskInitiate
auto handle1 = taskInitiate(objective1, optimizer1);
// Go do other work...
auto handle2 = taskInitiate(objective2, optimizer2);

// Query results when ready.
auto results1 = sync(handle1);
auto results2 = sync(handle2);
#else
// Launch the Optimization Task with taskInitiate
auto handle1 = taskInitiate(objective1, optimizer1);
// Query results when ready.
auto results1 = sync(handle1);

// Launch the Optimization Task with taskInitiate
auto handle2 = taskInitiate(objective2, optimizer2);
// Query results when ready.
auto results2 = sync(handle2);
#endif
printf("vqe-energy from taskInitiate1 = %f\n", results1.opt_val);
printf("vqe-energy from taskInitiate2 = %f\n", results2.opt_val);

printf("From objetive1\n");
for (auto &x : linspace(-constants::pi, constants::pi, 20)) {
std::cout << x << ", " << (*objective1)({x}) << "\n";
}
printf("From objetive2\n");
for (auto &x : linspace(-constants::pi, constants::pi, 20)) {
std::cout << x << ", " << (*objective2)({x}) << "\n";
}
}
Loading

0 comments on commit 9eafcb7

Please sign in to comment.