Commit 00ca132b authored by Mccaskey, Alex's avatar Mccaskey, Alex
Browse files

update syntax handler to assume can write past r_brace (using...


update syntax handler to assume can write past r_brace (using ornl-qci/llvm-project-csp fork). added more documentation throughout

Signed-off-by: Mccaskey, Alex's avatarAlex McCaskey <mccaskeyaj@ornl.gov>
parent bf44e526
Loading
Loading
Loading
Loading
+151 −67
Original line number Diff line number Diff line
@@ -15,7 +15,7 @@ namespace {

bool qrt = false;
std::string qpu_name = "qpp";
int shots = 0;
int shots = 1024;

class QCORSyntaxHandler : public SyntaxHandler {
public:
@@ -24,8 +24,6 @@ public:
  void GetReplacement(Preprocessor &PP, Declarator &D, CachedTokens &Toks,
                      llvm::raw_string_ostream &OS) override {

    // FIXME need way to get backend name from user/command line

    // Get the Diagnostics engine and create a few custom
    // error messgaes
    auto &diagnostics = PP.getDiagnostics();
@@ -89,70 +87,121 @@ public:

    auto new_src = qcor::run_token_collector(PP, Toks, bufferNames);

    OS << function_prototype << "{\n";

    OS << "quantum::initialize(\"" << qpu_name << "\", \"" << kernel_name
       << "\");\n";
    for (auto &buf : bufferNames) {
      OS << buf << ".setNameAndStore(\"" + buf + "\");\n";
    // First re-write, forward declare a function
    // we will implement further down
    OS << "void __internal_call_function_" << kernel_name << "("
       << program_arg_types[0];
    for (int i = 1; i < program_arg_types.size(); i++) {
      OS << ", " << program_arg_types[i];
    }
    OS << ");\n";

    if (shots > 0) {
      OS << "quantum::set_shots(" << shots << ");\n";
    // Call that forward declared function with the function args
    OS << "__internal_call_function_" << kernel_name << "("
       << program_parameters[0];
    for (int i = 1; i < program_parameters.size(); i++) {
      OS << ", " << program_parameters[i];
    }
    OS << new_src;
    OS << "if (__execute) {\n";
    OS << ");\n";

    if (bufferNames.size() > 1) {
      OS << "xacc::AcceleratorBuffer * buffers[" << bufferNames.size()
         << "] = {";
      OS << bufferNames[0] << ".results()";
      for (unsigned int k = 1; k < bufferNames.size(); k++) {
        OS << ", " << bufferNames[k] << ".results()";
      }
      OS << "};\n";
      OS << "std::cout << \"execing: \" << quantum::getProgram()->toString() "
            "<< \"\\n\";\n";
    // Close the transformed function
    OS << "}\n";

      OS << "quantum::submit(buffers," << bufferNames.size();
    } else {
      OS << "quantum::submit(" << bufferNames[0] << ".results()";
    // Declare the QuantumKernel subclass
    OS << "class " << kernel_name << " : public qcor::QuantumKernel<class "
       << kernel_name << ", " << program_arg_types[0];
    for (int i = 1; i < program_arg_types.size(); i++) {
      OS << ", " << program_arg_types[i];
    }
    OS << "> {\n";

    OS << ");\n";
    OS << "}";
    OS << "\n}\n";

    OS << "class " << kernel_name << "{\n";
    OS << "public:\n";
    OS << "static void adjoint(";
    for (int i = 0; i < program_arg_types.size(); i++) {
      if (i > 0) {
        OS << ",";
    // declare the super-type as a friend
    OS << "friend class qcor::QuantumKernel<class " << kernel_name << ", "
       << program_arg_types[0];
    for (int i = 1; i < program_arg_types.size(); i++) {
      OS << ", " << program_arg_types[i];
    }
      auto arg_type = program_arg_types[i];
      auto arg_var = program_parameters[i];

      OS << arg_type << " " << arg_var;
    OS << ">;\n";

    // declare protected operator()() method
    OS << "protected:\n";
    OS << "void operator()(" << program_arg_types[0] << " "
       << program_parameters[0];
    for (int i = 1; i < program_arg_types.size(); i++) {
      OS << ", " << program_arg_types[i] << " " << program_parameters[i];
    }
    OS << ") {\n";
    OS << "if (!parent_kernel) {\n";
    OS << "parent_kernel = "
          "qcor::__internal__::create_composite(kernel_name);\n";
    OS << "// q.setNameAndStore();\n";
    OS << "}\n";
    OS << "quantum::set_current_program(parent_kernel);\n";
    OS << new_src << "\n";
    OS << "}\n";

    OS << "quantum::initialize(\"" << qpu_name << "\", \"" << kernel_name
       << "\");\n";
    for (auto &buf : bufferNames) {
      OS << buf << ".setNameAndStore(\"" + buf + "\");\n";
    // declare public members, methods, constructors
    OS << "public:\n";
    OS << "inline static const std::string kernel_name = \"" << kernel_name
       << "\";\n";

    // First constructor, default one KERNEL_NAME(Args...);
    OS << kernel_name << "(" << program_arg_types[0] << " "
       << program_parameters[0];
    for (int i = 1; i < program_arg_types.size(); i++) {
      OS << ", " << program_arg_types[i] << " " << program_parameters[i];
    }

    if (shots > 0) {
      OS << "quantum::set_shots(" << shots << ");\n";
    OS << "): QuantumKernel<" << kernel_name << ", " << program_arg_types[0];
    for (int i = 1; i < program_arg_types.size(); i++) {
      OS << ", " << program_arg_types[i];
    }
    OS << "> (" << program_parameters[0];
    for (int i = 1; i < program_parameters.size(); i++) {
      OS << ", " << program_parameters[i];
    }
    OS << ") {}\n";

    // Second constructor, takes parent CompositeInstruction
    // KERNEL_NAME(CompositeInstruction, Args...)
    OS << kernel_name
       << "(std::shared_ptr<qcor::CompositeInstruction> _parent, "
       << program_arg_types[0] << " " << program_parameters[0];
    for (int i = 1; i < program_arg_types.size(); i++) {
      OS << ", " << program_arg_types[i] << " " << program_parameters[i];
    }
    OS << "): QuantumKernel<" << kernel_name << ", " << program_arg_types[0];
    for (int i = 1; i < program_arg_types.size(); i++) {
      OS << ", " << program_arg_types[i];
    }
    OS << "> (_parent, " << program_parameters[0];
    for (int i = 1; i < program_parameters.size(); i++) {
      OS << ", " << program_parameters[i];
    }
    OS << ") {}\n";

    OS << new_src;

    OS << "quantum::adjoint();\n";

    OS << "if (__execute) {\n";

    // Third constructor, nullary constructor
    OS << kernel_name << "() : QuantumKernel<" << kernel_name << ", "
       << program_arg_types[0];
    for (int i = 1; i < program_arg_types.size(); i++) {
      OS << ", " << program_arg_types[i];
    }
    OS << ">() {}\n";

    // Destructor definition
    OS << "virtual ~" << kernel_name << "() {\n";
    OS << "if (disable_destructor) {return;}\n";
    OS << "quantum::set_backend(\"" << qpu_name << "\", " << shots << ");\n";
    OS << "auto [" << program_parameters[0];
    for (int i = 1; i < program_parameters.size(); i++) {
      OS << ", " << program_parameters[i];
    }
    OS << "] = args_tuple;\n";
    OS << "operator()(" << program_parameters[0];
    for (int i = 1; i < program_parameters.size(); i++) {
      OS << ", " << program_parameters[i];
    }
    OS << ");\n";
    OS << "if (is_callable) {\n";
    if (bufferNames.size() > 1) {
      OS << "xacc::AcceleratorBuffer * buffers[" << bufferNames.size()
         << "] = {";
@@ -166,13 +215,42 @@ public:
      OS << "quantum::submit(" << bufferNames[0] << ".results()";
    }

    OS << ");\n";
    OS << "}\n";
    OS << "}\n";

    // close the quantum kernel subclass
    OS << "};\n";

    // Add a function with the kernel_name that takes
    // a parent CompositeInstruction as its first arg
    OS << "void " << kernel_name
       << "(std::shared_ptr<qcor::CompositeInstruction> parent, "
       << program_arg_types[0] << " " << program_parameters[0];
    for (int i = 1; i < program_arg_types.size(); i++) {
      OS << ", " << program_arg_types[i] << " " << program_parameters[i];
    }
    OS << ") {\n";
    OS << "class " << kernel_name << " k(parent, " << program_parameters[0];
    for (int i = 1; i < program_parameters.size(); i++) {
      OS << ", " << program_parameters[i];
    }
    OS << ");\n";
    OS << "}\n";

    // close adjoint()
    // Declare the previous forward declaration
    OS << "void __internal_call_function_" << kernel_name << "("
       << program_arg_types[0] << " " << program_parameters[0];
    for (int i = 1; i < program_arg_types.size(); i++) {
      OS << ", " << program_arg_types[i] << " " << program_parameters[i];
    }
    OS << ") {\n";
    OS << "class " << kernel_name << " k(" << program_parameters[0];
    for (int i = 1; i < program_parameters.size(); i++) {
      OS << ", " << program_parameters[i];
    }
    OS << ");\n";
    OS << "}\n";
    // close class
    OS << "};";

    auto s = OS.str();
    qcor::info("[qcor syntax-handler] Rewriting " + kernel_name + " to\n\n" +
@@ -180,10 +258,9 @@ public:
  }

  void AddToPredefines(llvm::raw_string_ostream &OS) override {
    OS << "#include \"qrt.hpp\"\n";
    OS << "#include \"qcor.hpp\"\n";

    OS << "#include \"xacc_internal_compiler.hpp\"\nusing namespace "
          "xacc::internal_compiler;\n";
    OS << "using namespace xacc::internal_compiler;\n";
  }
};

@@ -259,7 +336,8 @@ public:
//             instructions.cbegin(), instructions.cend(),
//             [](const auto &inst) { return inst->name() != "Measure"; })) {
//       xacc::error(
//           "Unable to create Adjoint for kernels that have Measure operations.");
//           "Unable to create Adjoint for kernels that have Measure
//           operations.");
//     }
//     std::reverse(instructions.begin(), instructions.end());
//     for (const auto &inst : instructions) {
@@ -297,7 +375,8 @@ public:
//   // kernelFuncClass(abc).adjoint();
//   // -> DTor of the Adjoint instance called here (m_usedAsCallable = true)
//   // hence adding the adjoint body to the global composite.
//   // -> DTor of the kernelFuncClass(abc) instance called here (m_usedAsCallable
//   // -> DTor of the kernelFuncClass(abc) instance called here
//   (m_usedAsCallable
//   // = false) hence having no effect.
//   // ... code ...
//   virtual ~KernelBase() {
@@ -313,9 +392,12 @@ public:
// protected:
//   // Copy ctor:
//   // Deep copy of the CompositeInstruction to prevent dangling references.
//   KernelBase(KernelBase *other, const std::string &in_optional_newName = "") {
//     const auto kernelName = in_optional_newName.empty() ? other->m_body->name()
//                                                         : in_optional_newName;
//   KernelBase(KernelBase *other, const std::string &in_optional_newName = "")
//   {
//     const auto kernelName = in_optional_newName.empty() ?
//     other->m_body->name()
//                                                         :
//                                                         in_optional_newName;
//     auto provider = xacc::getIRProvider("quantum");
//     m_body = provider->createComposite(kernelName);
//     for (const auto &inst : other->m_body->getInstructions()) {
@@ -332,7 +414,8 @@ public:
//   // kernelFuncClass(qubitReg).adjoint(); => FALSE (on the original
//   // kernelFuncClass instance) but TRUE for the one returned by the adjoint()
//   // member function. This will allow arbitrary chaining: e.g.
//   // kernelFuncClass(qubitReg).adjoint().ctrl(k); only the last kernel returned
//   // kernelFuncClass(qubitReg).adjoint().ctrl(k); only the last kernel
//   returned
//   // by ctrl() will be the *Callable*;
//   bool m_usedAsCallable;
//   // The XACC composite instruction described by this kernel body:
@@ -382,7 +465,8 @@ public:
// // returning *void* to returing *KernelBase*,
// // i.e. we need to be able to rewrite:
// // "__qpu__ void" ==> "__qpu__ KernelBase" (__qpu__ is handled by the
// // pre-processor) Possibility: the *qcor* script to do that before calling clang
// // pre-processor) Possibility: the *qcor* script to do that before calling
// clang
// // ??
// //////////////////////////////////////////////////
// // TEST kernel-in-kernel
+2 −0
Original line number Diff line number Diff line
@@ -27,6 +27,8 @@ struct TranslationFunctorAutoGenerator {
  }
};

// Utility class to count the number of rotation 
// angles in a parameterized circuit evaluation
class CountRotationAngles {
public:
  std::size_t &count;
+17 −0
Original line number Diff line number Diff line
@@ -7,20 +7,33 @@

namespace qcor {

// Execute asynchronous task - find optimal value of the given objective function
// using the provided optimizer, and custom std::function OptFunction delegating 
// to the ObjectiveFunction. Provide the number of variational parameters
Handle taskInitiate(std::shared_ptr<ObjectiveFunction> objective,
                    std::shared_ptr<Optimizer> optimizer,
                    std::function<double(const std::vector<double>,
                                         std::vector<double> &)> &&opt_function,
                    const int nParameters);

// Execute asynchronous task - find optimal value of the given objective function
// using the provided optimizer OptFunction delegating 
// to the ObjectiveFunction.
Handle taskInitiate(std::shared_ptr<ObjectiveFunction> objective,
                    std::shared_ptr<Optimizer> optimizer,
                    qcor::OptFunction &&opt_function);

// Execute asynchronous task - find optimal value of the given objective function
// using the provided optimizer OptFunction delegating 
// to the ObjectiveFunction.
Handle taskInitiate(std::shared_ptr<ObjectiveFunction> objective,
                    std::shared_ptr<Optimizer> optimizer,
                    qcor::OptFunction &opt_function);

// Execute asynchronous task - find optimal value of the given objective function
// using the provided optimizer, and custom TranslationFunctor, which will map 
// vector<double> to the required ObjectiveFunction arguments. Provide the number
// variational parameters
template <typename... Args>
Handle taskInitiate(std::shared_ptr<ObjectiveFunction> objective,
                    std::shared_ptr<Optimizer> optimizer,
@@ -35,6 +48,10 @@ Handle taskInitiate(std::shared_ptr<ObjectiveFunction> objective,
      nParameters);
}

// Execute asynchronous task - find optimal value of the given objective function
// using the provided optimizer, and custom TranslationFunctor, which will map 
// vector<double> to the required ObjectiveFunction arguments. Provide the number
// variational parameters and GradientEvaluator
template <typename... Args>
Handle taskInitiate(std::shared_ptr<ObjectiveFunction> objective,
                    std::shared_ptr<Optimizer> optimizer,
+46 −0
Original line number Diff line number Diff line
@@ -4,6 +4,30 @@

namespace qcor {

// The QuantumKernel represents the super-class of all qcor
// quantum kernel functors. Subclasses of this are auto-generated
// via the Clang Syntax Handler capability. Derived classes
// provide a destructor implementation that builds up and
// submits quantum instructions to the specified backend. This enables
// functor-like capability whereby programmers instantiate temporary
// instances of this via the constructor call, and the destructor is
// immediately called. More advanced usage is of course possible for
// qcor developers.
//
// This class works by taking the Derived type (CRTP) and the kernel function
// arguments as template parameters. The Derived type is therefore available for
// instantiation within provided static methods on QuantumKernel. The Args...
// are stored in a member tuple, and are available for use when evaluating the
// kernel. Importantly, QuantumKernel provides static adjoint and ctrl methods
// for auto-generating those circuits.
//
// The Syntax Handler will take kernels like this
// __qpu__ void foo(qreg q) { H(q[0]); }
// and create a derived type of QuantumKernel like this
// class foo : public qcor::QuantumKernel<class foo, qreg> {...};
// with an appropriate implementation of constructors and destructors.
// Users can then call for adjoint/ctrl methods like this
// foo::adjoint(q); foo::ctrl(1, q);
template <typename Derived, typename... Args> class QuantumKernel {
protected:
  // Tuple holder for variadic kernel arguments
@@ -17,6 +41,8 @@ protected:
  // turn this to false
  bool is_callable = true;

  // Turn off destructor execution, useful for 
  // qcor developers, not to be used by clients / programmers
  bool disable_destructor = false;

public:
@@ -31,8 +57,10 @@ public:
      : args_tuple(std::make_tuple(args...)), parent_kernel(_parent_kernel),
        is_callable(false) {}

  // Nullary constructor, should not be callable
  QuantumKernel() : is_callable(false) {}

  // Create the Adjoint of this quantum kernel
  static void adjoint(std::shared_ptr<CompositeInstruction> parent_kernel,
                      Args... args) {

@@ -70,6 +98,24 @@ public:
    // no measures, so no execute
  }

  // Create the controlled version of this quantum kernel
  static void ctrl(std::shared_ptr<CompositeInstruction> parent_kernel,
                   size_t ctrlIdx, Args... args) {
    // instantiate and don't let it call the destructor
    Derived derived;
    derived.disable_destructor = true;

    // run the operator()(args...) call to get the parent_kernel
    derived(args...);

    // get the instructions
    auto instructions = derived.parent_kernel->getInstructions();

    // Use the controlled gate module of XACC to transform
    // controlledKernel.m_body
    // auto ctrlKernel = quantum::controlledKernel(derived.parent_kernel,
    // ctrlIdx);
  }
  virtual ~QuantumKernel() {}
};
} // namespace qcor
 No newline at end of file
+27 −12
Original line number Diff line number Diff line
@@ -17,13 +17,13 @@ std::shared_ptr<ObjectiveFunction> get_objective(const std::string &type);
// The ObjectiveFunction represents a functor-like data structure that
// models a general parameterized scalar function. It is initialized with a
// problem-specific Observable and Quantum Kernel, and exposes a method for
// evaluation, given a list or array of scalar parameters.
// evaluation, given a sequence of general function arguments.
// Implementations of this concept are problem-specific, and leverage the
// observe() functionality of the provided Observable to produce one or many
// measured Kernels that are then queued for execution on the available quantum
// co-processor, given the current value of the input parameters. The results of
// co-processor, given the current value of the input arguments. The results of
// these quantum executions are to be used by the ObjectiveFunction to return a
// list of scalar values, representing the evaluation of the ObjectiveFunction
// scalar value (double), representing the evaluation of the ObjectiveFunction
// at the given set of input parameters. Furthermore, the ObjectiveFunction has
// access to a global ResultBuffer that it uses to publish execution results at
// the current input parameters
@@ -43,9 +43,17 @@ protected:

  // The buffer containing all execution results
  xacc::internal_compiler::qreg qreg;

  // Bool to indicate if this ObjectiveFunction 
  // was initialized with a CompositeInstruction
  // and not a functor
  bool kernel_is_xacc_composite = false;

  // Reference to any extra options for objective function 
  // execution
  HeterogeneousMap options;

  // Reference to the current iteration's parameters
  std::vector<double> current_iterate_parameters;

  // To be implemented by subclasses. Subclasses
@@ -79,6 +87,7 @@ public:
    pointer_to_functor = qk;
  }

  // Set any extra options needed for the objective function
  void set_options(HeterogeneousMap &opts) { options = opts; }

  // Set the results buffer
@@ -105,33 +114,41 @@ public:
    if (kernel_is_xacc_composite) {
      kernel->updateRuntimeArguments(args...);
    } else {
      // create a temporary
      // create a temporary with name given by mem_location_qkernel
      std::stringstream name_ss;
      name_ss << this << "_qkernel";
      kernel = qcor::__internal__::create_composite(name_ss.str());

      // Run the functor, this will add 
      // all quantum instructions to the parent kernel
      functor(kernel, args...);

      // Set the current iteration parameters
      current_iterate_parameters.clear();
      __internal__::ConvertDoubleLikeToVectorDouble convert(
          current_iterate_parameters);
      __internal__::tuple_for_each(std::make_tuple(args...), convert);
    }

    // Run the subclass operator()() method
    return operator()();
  }
};

// Create an ObjectiveFunction of given name, with a CompositeInstruction 
// and a shared_ptr to an Observable. 
std::shared_ptr<ObjectiveFunction> createObjectiveFunction(
    const std::string &obj_name, std::shared_ptr<CompositeInstruction> kernel,
    std::shared_ptr<Observable> observable, HeterogeneousMap &&options = {});

// Create an ObjectiveFunction of given name, with a CompositeInstruction 
// and a reference to an Observable. 
std::shared_ptr<ObjectiveFunction> createObjectiveFunction(
    const std::string &obj_name, std::shared_ptr<CompositeInstruction> kernel,
    Observable &observable, HeterogeneousMap &&options = {});

// Create an Objective Function that makes calls to the
// provided Quantum Kernel, with measurements dictated by
// the provided Observable. Optionally can provide problem-specific
// options map.
// Create an ObjectiveFunction of given name, with a quantum kernel functor 
// and a shared_ptr to an Observable. 
template <typename... Args>
std::shared_ptr<ObjectiveFunction> createObjectiveFunction(
    const std::string &obj_name,
@@ -147,10 +164,8 @@ std::shared_ptr<ObjectiveFunction> createObjectiveFunction(
  return obj_func;
}

// This method takes as input a functor with a signature
// that takes a CompositeInstruction as the first input argument,
// followed by other arguments that feed into the creation of the
// quantum kernel.
// Create an ObjectiveFunction of given name, with a quantum kernel functor 
// and a reference to an Observable. 
template <typename... Args>
std::shared_ptr<ObjectiveFunction> createObjectiveFunction(
    const std::string &obj_name,
Loading