Commit 75d6da73 authored by Mccaskey, Alex's avatar Mccaskey, Alex
Browse files

added observe, operator(parent, args...) to qpu_lambda. added constructor to...


added observe, operator(parent, args...) to qpu_lambda. added constructor to KernelSignature to take qpu_lambda. added 2 examples demonstrating functionality

Signed-off-by: Mccaskey, Alex's avatarAlex McCaskey <mccaskeyaj@ornl.gov>
parent f7ede468
Loading
Loading
Loading
Loading
Loading
+21 −0
Original line number Diff line number Diff line
#include "qcor.hpp"

int main() {
  // 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) {
    X(q[0]);
    Ry(q[1], x);
    CX(q[1], q[0]);
  });

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

  auto optimizer = createOptimizer("nlopt");
  auto [ground_energy, opt_params] = optimizer->optimize(opt_function);
  print("Energy: ", ground_energy);
}
 No newline at end of file
+57 −0
Original line number Diff line number Diff line
#include "qcor.hpp"
// Create a general grover search algorithm.
// Let's create that marks 2 states
// Show figures Init - [Oracle - Amplification for i in iters] - Measure
// https://www.nature.com/articles/s41467-017-01904-7

// Show off kernel composition, common patterns, 
// functional programming (kernels taking other kernels)

using GroverPhaseOracle = KernelSignature<qreg>;

__qpu__ void amplification(qreg q) {
  // H q X q ctrl-ctrl-...-ctrl-Z H q Xq
  // compute - action - uncompute

  compute {
    H(q);
    X(q);
  }
  action {
    auto ctrl_bits = q.head(q.size() - 1);
    auto last_qubit = q.tail();
    Z::ctrl(ctrl_bits, last_qubit);
  }
}

__qpu__ void run_grover(qreg q, GroverPhaseOracle oracle,
                        const int iterations) {
  H(q);

  for (int i = 0; i < iterations; i++) {
    oracle(q);
    amplification(q);
  }

  Measure(q);
}

int main() {
  const int N = 3;

  // Write the oracle as a quantum lambda function
  auto oracle = qpu_lambda([](qreg q) {
      print("hey from oracle: ", N);
      CZ(q[0], q[2]);
      CZ(q[1], q[2]);
  }, N);

  // Allocate some qubits
  auto q = qalloc(N);

  // Call grover given the oracle and n iterations
  run_grover(q, oracle, 1);

  // print the histogram
  q.print();
}
 No newline at end of file
+16 −4
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@ class QJIT {

 protected:
  std::map<std::string, std::uint64_t> kernel_name_to_f_ptr;
  std::map<std::string, std::uint64_t> kernel_name_to_f_ptr_with_parent;
  std::map<std::string, std::uint64_t> kernel_name_to_f_ptr_hetmap;
  std::map<std::string, std::uint64_t> kernel_name_to_f_ptr_parent_hetmap;

@@ -63,6 +64,17 @@ class QJIT {
    kernel_functor(args...);
  }

  template <typename... Args>
  void invoke_with_parent(const std::string &kernel_name,
                          std::shared_ptr<xacc::CompositeInstruction> parent,
                          Args... args) {
    auto f_ptr = kernel_name_to_f_ptr_with_parent[kernel_name];
    void (*kernel_functor)(std::shared_ptr<xacc::CompositeInstruction>,
                           Args...) =
        (void (*)(std::shared_ptr<xacc::CompositeInstruction>, Args...))f_ptr;
    kernel_functor(parent, args...);
  }

  int invoke_main(int argc, char **argv) {
    auto f_ptr = kernel_name_to_f_ptr["main"];
    int (*kernel_functor)(int, char **) = (int (*)(int, char **))f_ptr;
@@ -85,8 +97,8 @@ class QJIT {
  enum class KernelType { Regular, HetMapArg, HetMapArgWithParent };
  // Return kernel function pointer (as an integer)
  // Returns 0 if the kernel doesn't exist.
  std::uint64_t
  get_kernel_function_ptr(const std::string &kernelName,
  std::uint64_t get_kernel_function_ptr(
      const std::string &kernelName,
      KernelType subType = KernelType::Regular) const;
};

+40 −20
Original line number Diff line number Diff line
@@ -143,8 +143,10 @@ inline std::vector<std::string> split(const std::string &s, char delim) {
// Handle templated types as well,
// e.g. "qreg q, KernelSignature<qreg,int,double> call_var"
// KernelSignature<qreg,int,double> is considered as one term (arg_type)
// Strategy: using balancing rule to match the '<' and '>' and skipping ',' delimiter.
inline std::vector<std::string> split_args_signature(const std::string &source) {
// Strategy: using balancing rule to match the '<' and '>' and skipping ','
// delimiter.
inline std::vector<std::string> split_args_signature(
    const std::string &source) {
  std::vector<std::string> elems;
  std::string token;
  const int N = source.length();
@@ -238,7 +240,8 @@ const std::pair<std::string, std::string> QJIT::run_syntax_handler(
    auto arg_var = split(arg, ' ');
    if (arg_var[0] == "qreg" || arg_var[0] == "xacc::internal_compiler::qreg") {
      bufferNames.push_back(arg_var[1]);
    } else if (arg_var[0] == "qubit" || arg_var[0] == "xacc::internal_compiler::qubit") {
    } else if (arg_var[0] == "qubit" ||
               arg_var[0] == "xacc::internal_compiler::qubit") {
      bufferNames.push_back(arg_var[1]);
    }
    arg_types.push_back(arg_var[0]);
@@ -533,8 +536,8 @@ void QJIT::jit_compile(const std::string &code,
  // and get the first one that has the kernel name
  // in it as a substring. This is the corrent Function and
  // now we have it as a mangled name
  std::string mangled_name = "", hetmap_mangled_name = "",
              parent_hetmap_mangled_name = "";
  std::string mangled_name = "", parent_mangled_name = "",
              hetmap_mangled_name = "", parent_hetmap_mangled_name = "";
  for (Function &f : *module) {
    auto name = f.getName().str();
    if (demangle(name.c_str()).find(kernel_name) != std::string::npos) {
@@ -544,6 +547,18 @@ void QJIT::jit_compile(const std::string &code,
    }
  }

  // Find the kernel_name(CompositeInstruction parent, Args...) function
  for (Function &f : *module) {
    auto name = f.getName().str();
    auto demangled = demangle(name.c_str());
    if (demangled.find(kernel_name) != std::string::npos && 
        demangled.find(kernel_name+"::"+kernel_name) == std::string::npos && // don't pick the class constructor
        demangled.find("qcor::QuantumKernel<"+kernel_name) == std::string::npos && // don't pick the QuantumKernel 
        demangled.find("std::shared_ptr<xacc::CompositeInstruction>") != std::string::npos) {
      parent_mangled_name = name;
    }
  }

  // Insert dependency kernels as well:
  std::unordered_map<std::string, std::string> mangled_kernel_dep_map;
  for (const auto &dep : kernel_dependency) {
@@ -625,6 +640,11 @@ void QJIT::jit_compile(const std::string &code,
  auto rawFPtr = symbol.getAddress();
  kernel_name_to_f_ptr.insert({kernel_name, rawFPtr});

  // Get and store the kernel_name(CompositeInstruction parent, Args...) function
  auto parent_symbol = cantFail(jit->lookup(parent_mangled_name));
  auto parent_rawFPtr = parent_symbol.getAddress();
  kernel_name_to_f_ptr_with_parent.insert({kernel_name, parent_rawFPtr});

  for (const auto &[orig_name, mangled_name] : mangled_kernel_dep_map) {
    auto symbol = cantFail(jit->lookup(mangled_name));
    auto rawFPtr = symbol.getAddress();
+67 −3
Original line number Diff line number Diff line
@@ -497,6 +497,27 @@ class _qpu_lambda {
    qjit.jit_compile(jit_src);
  }

  template <typename... FunctionArgs>
  void eval_with_parent(std::shared_ptr<CompositeInstruction> parent,
                        FunctionArgs... args) {
    this->operator()(parent, args...);
  }

  template <typename... FunctionArgs>
  void operator()(std::shared_ptr<CompositeInstruction> parent,
                  FunctionArgs... args) {
    // Map the function args to a tuple
    auto kernel_args_tuple = std::make_tuple(args...);

    // Merge the function args and the capture vars and execute
    auto final_args_tuple = std::tuple_cat(kernel_args_tuple, capture_vars);
    std::apply(
        [&](auto &&...args) {
          qjit.invoke_with_parent("foo", parent, args...);
        },
        final_args_tuple);
  }

  template <typename... FunctionArgs>
  void operator()(FunctionArgs... args) {
    // Map the function args to a tuple
@@ -507,6 +528,28 @@ class _qpu_lambda {
    std::apply([&](auto &&...args) { qjit.invoke("foo", args...); },
               final_args_tuple);
  }

  template <typename... FunctionArgs>
  double observe(Observable &obs, FunctionArgs... args) {
    auto tempKernel =
        qcor::__internal__::create_composite("temp_lambda_observe");
    this->operator()(tempKernel, args...);

    auto instructions = tempKernel->getInstructions();
    // Assert that we don't have measurement
    if (!std::all_of(
            instructions.cbegin(), instructions.cend(),
            [](const auto &inst) { return inst->name() != "Measure"; })) {
      error("Unable to observe kernels that already have Measure operations.");
    }

    xacc::internal_compiler::execute_pass_manager();

    // Will fail to compile if more than one qreg is passed.
    std::tuple<FunctionArgs...> tmp(std::forward_as_tuple(args...));
    auto q = std::get<qreg>(tmp);
    return qcor::observe(tempKernel, obs, q);
  }
};

#define qpu_lambda(EXPR, ...) _qpu_lambda(#EXPR, #__VA_ARGS__, ##__VA_ARGS__)
@@ -517,11 +560,26 @@ using callable_function_ptr =

template <typename... Args>
class KernelSignature {
 protected:
 private:
  callable_function_ptr<Args...> * readOnly = 0; 
  callable_function_ptr<Args...> &function_pointer;
  std::function<void(std::shared_ptr<xacc::CompositeInstruction>, Args...)>
      lambda_func;

 public:

  // Here we set function_pointer to null and instead 
  // only use lambda_func. If we set lambda_func, function_pointer 
  // will never be used, so we should be good.
  template <typename... CaptureArgs>
  KernelSignature(
      _qpu_lambda<CaptureArgs...> &lambda)
      : function_pointer(*readOnly),
        lambda_func([&](std::shared_ptr<xacc::CompositeInstruction> pp,
                        Args... a) { lambda(pp, a...); }) {}

  KernelSignature(callable_function_ptr<Args...> &&f) : function_pointer(f) {}

  // Ctor from raw void* funtion pointer.
  // IMPORTANT: since function_pointer is kept as a *reference*,
  // we must keep a reference to the original f_ptr void* as well.
@@ -530,12 +588,18 @@ class KernelSignature {

  void operator()(std::shared_ptr<xacc::CompositeInstruction> ir,
                  Args... args) {
    if (lambda_func) {
      lambda_func(ir, args...);
      return;
    }

    function_pointer(ir, args...);
  }

  void ctrl(std::shared_ptr<xacc::CompositeInstruction> ir, int ctrl_qbit,
            Args... args) {
    auto tempKernel = qcor::__internal__::create_composite("temp_control");
    function_pointer(tempKernel, args...);
    operator()(tempKernel, args...);

    auto ctrlKernel = qcor::__internal__::create_ctrl_u();
    ctrlKernel->expand({
@@ -558,7 +622,7 @@ class KernelSignature {

  void adjoint(std::shared_ptr<CompositeInstruction> ir, Args... args) {
    auto tempKernel = qcor::__internal__::create_composite("temp_adjoint");
    function_pointer(tempKernel, args...);
    operator()(tempKernel, args...);

    // get the instructions
    auto instructions = tempKernel->getInstructions();