Commit 83af7f31 authored by Nguyen, Thien Minh's avatar Nguyen, Thien Minh
Browse files

Added ability to call kernels within kernel body



- Handle QRT's initialize and submit (execution) in the context of multiple kernels (but only one entry point)

- Added an example to test the basic set-up.

Signed-off-by: default avatarNguyen, Thien <nguyentm@ornl.gov>
parent 07c2d323
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -10,3 +10,4 @@ add_test(NAME qrt_deuteron_task_initiate COMMAND ${CMAKE_BINARY_DIR}/qcor -qrt -
add_test(NAME qrt_qaoa_example COMMAND ${CMAKE_BINARY_DIR}/qcor -qrt -c ${CMAKE_CURRENT_SOURCE_DIR}/qaoa_example.cpp)
add_test(NAME qrt_simple-objective-function COMMAND ${CMAKE_BINARY_DIR}/qcor -qrt -c ${CMAKE_CURRENT_SOURCE_DIR}/simple-objective-function.cpp)
add_test(NAME qrt_qpe_example COMMAND ${CMAKE_BINARY_DIR}/qcor -qrt -c ${CMAKE_CURRENT_SOURCE_DIR}/qpe_example_qrt.cpp)
add_test(NAME qrt_kernel_include COMMAND ${CMAKE_BINARY_DIR}/qcor -qrt -c ${CMAKE_CURRENT_SOURCE_DIR}/multiple_kernels.cpp)
+30 −0
Original line number Diff line number Diff line
#include "qcor.hpp"
// The header file which contains QFT kernel def
#include "qft.hpp"

// Entry point kernel
__qpu__ void f(qreg q) {
  const auto nQubits = q.size();
  // Add some gates
  for (int qIdx = 0; qIdx < nQubits; ++qIdx) {
    H(q[qIdx]);
  }  

  // Call qft kernel (defined in a separate header file)
  qft(q);  
  
  for (int qIdx = 0; qIdx < nQubits; ++qIdx) {
    Measure(q[qIdx]);
  }  
}

// Compile:
// qcor -o multiple_kernels -qpu qpp -shots 1024 -qrt multiple_kernels.cpp
int main(int argc, char **argv) {
  // Allocate 3 qubits
  auto q = qalloc(3);
  // Call entry-point kernel
  f(q);
  // dump the results
  q.print();
}

examples/qrt/qft.hpp

0 → 100644
+26 −0
Original line number Diff line number Diff line
// Example code structure whereby quantum kernels are defined
// in separate header files which can be included into a cpp file
// which is compiled by QCOR.
#pragma once

#include "qcor.hpp"

// QFT kernel
__qpu__ void qft(qreg q) {
  // Local Declarations
  const auto nQubits = q.size();

  for (int qIdx = 0; qIdx < nQubits; ++qIdx) {
    auto bitIdx = nQubits - qIdx - 1;
    H(q[bitIdx]);
    for (int j = 0; j < bitIdx; ++j) {
      const double theta = M_PI/std::pow(2.0, bitIdx - j);
      CPhase(q[j], q[bitIdx], theta);
    }
  }

  // Swap qubits
  for (int qIdx = 0; qIdx < nQubits / 2; ++qIdx) {
    Swap(q[qIdx], q[nQubits - qIdx - 1]);
  }
}
 No newline at end of file
+37 −6
Original line number Diff line number Diff line
@@ -61,7 +61,9 @@ class qrt_mapper : public AllGateVisitor,
                   public xacc::InstructionVisitor<Circuit> {
protected:
  std::stringstream ss;

  // The kernel name of the CompositeInstruction 
  // that this mapper is visiting.
  std::string kernelName;
  void addOneQubitGate(const std::string name, xacc::Instruction &inst) {
    auto expr = inst.getBitExpression(0);
    ss << "quantum::" + name + "(" << inst.getBufferNames()[0] << "["
@@ -93,6 +95,11 @@ protected:
  }

public:
  // Ctor: cache the kernel name of the CompositeInstruction
  qrt_mapper(const std::string& top_level_kernel_name):
    kernelName(top_level_kernel_name)
  {}

  auto get_new_src() { return ss.str(); }
  // One-qubit gates
  void visit(Hadamard &h) override { addOneQubitGate("h", h); }
@@ -120,11 +127,35 @@ public:
  void visit(Identity &i) override { addOneQubitGate("i", i); }
  void visit(U &u) override { addOneQubitGate("u", u); }
  void visit(Circuit &circ) override {
    if (circ.name() == kernelName) {
      return;
    }
    if (circ.name() == "exp_i_theta") {
      ss << "quantum::exp(" << circ.getBufferNames()[0] << ", "
         << circ.getArguments()[0]->name << ", " << circ.getArguments()[1]->name
         << ");\n";
    }
    else {
      // Call a previously-defined QCOR kernel:
      // In this context, we disable __execute flag around this hence
      // this sub-kernel will not be submitted.
      // i.e. only the outer-most kernel will be submitted.
      // Open a new scope since we use a local var '__cached_execute_flag'
      ss << "{\n";
      // Cache the state of the __execute flag
      ss << "const auto __cached_execute_flag = __execute;\n";
      // Reset the flag:
      ss << "__execute = false;\n";
      // Add the circuit invocation.
      ss << circ.name() << "(" << circ.getBufferNames()[0];
      for (const auto& arg : circ.getArguments()){
        ss << ", " << arg->name;
      }
      ss << ")" << ";\n";
      // Reinstate the __execute flag
      ss << "__execute = __cached_execute_flag;\n";
      ss << "}\n";
    }
  }
  void visit(IfStmt &ifStmt) override {}
};
@@ -266,7 +297,7 @@ void run_token_collector_llvm_rt(clang::Preprocessor &PP,
                      process_inst_stmt(i, compiler, current_token,
                                        terminating_char, extra_preamble);
                  if (comp_inst) {
                    auto visitor = std::make_shared<qrt_mapper>();
                    auto visitor = std::make_shared<qrt_mapper>(comp_inst->name());
                    xacc::InstructionIterator iter(comp_inst);
                    while (iter.hasNext()) {
                      auto next = iter.next();
@@ -301,7 +332,7 @@ void run_token_collector_llvm_rt(clang::Preprocessor &PP,
              auto [comp_inst, src_str] = process_inst_stmt(
                  i, compiler, current_token, terminating_char, extra_preamble);
              if (comp_inst) {
                auto visitor = std::make_shared<qrt_mapper>();
                auto visitor = std::make_shared<qrt_mapper>(comp_inst->name());
                xacc::InstructionIterator iter(comp_inst);
                while (iter.hasNext()) {
                  auto next = iter.next();
@@ -391,7 +422,7 @@ void run_token_collector_llvm_rt(clang::Preprocessor &PP,
      auto [comp_inst, src_str] = process_inst_stmt(
          i, compiler, current_token, terminating_char, extra_preamble);
      if (comp_inst) {
        auto visitor = std::make_shared<qrt_mapper>();
        auto visitor = std::make_shared<qrt_mapper>(comp_inst->name());
        xacc::InstructionIterator iter(comp_inst);
        while (iter.hasNext()) {
          auto next = iter.next();
@@ -426,7 +457,7 @@ void run_token_collector_llvm_rt(clang::Preprocessor &PP,
        auto [comp_inst, src_str] = process_inst_stmt(
            i, compiler, current_token, terminating_char, extra_preamble);
        if (comp_inst) {
          auto visitor = std::make_shared<qrt_mapper>();
          auto visitor = std::make_shared<qrt_mapper>(comp_inst->name());
          xacc::InstructionIterator iter(comp_inst);
          while (iter.hasNext()) {
            auto next = iter.next();
@@ -494,7 +525,7 @@ void run_token_collector_llvm_rt(clang::Preprocessor &PP,
    auto [comp_inst, src_str] = process_inst_stmt(
        i, compiler, current_token, terminating_char, extra_preamble);
    if (comp_inst) {
      auto visitor = std::make_shared<qrt_mapper>();
      auto visitor = std::make_shared<qrt_mapper>(comp_inst->name());
      xacc::InstructionIterator iter(comp_inst);
      while (iter.hasNext()) {
        auto next = iter.next();
+13 −3
Original line number Diff line number Diff line
@@ -9,13 +9,23 @@
namespace quantum {
std::shared_ptr<xacc::CompositeInstruction> program = nullptr;
std::shared_ptr<xacc::IRProvider> provider = nullptr;
// We only allow *single* quantum entry point,
// i.e. a master quantum kernel which is invoked from classical code.
// Multiple kernels can be defined to be used inside the *entry-point* kernel.
// Once the *entry-point* kernel has been invoked, initialize() calls
// by sub-kernels will be ignored. 
bool __entry_point_initialized = false; 

void initialize(const std::string qpu_name, const std::string kernel_name) {
  if (!__entry_point_initialized) {
    xacc::internal_compiler::compiler_InitializeXACC(qpu_name.c_str());
    provider = xacc::getIRProvider("quantum");
    program = provider->createComposite(kernel_name);
  }

  __entry_point_initialized = true;
}

void set_shots(int shots) {
  xacc::internal_compiler::get_qpu()->updateConfiguration(
      {std::make_pair("shots", shots)});