Commit a3390ac6 authored by Mccaskey, Alex's avatar Mccaskey, Alex
Browse files

add pre-processing step to incoming observable for kernel::observe(), fix #122

parent 7a961155
Loading
Loading
Loading
Loading
Loading
+29 −0
Original line number Diff line number Diff line
__qpu__ void ansatz(qreg q, double theta) {
  X(q[0]);
  X(q[2]);
  compute {
    Rx(q[0], constants::pi / 2);
    for (auto i : range(3)) H(q[i + 1]);
    for (auto i : range(3)) {
      CX(q[i], q[i + 1]);
    }
  }
  action { Rz(q[3], theta); }
}

int main() {

  std::string h2_geom = R"#(H  0.000000   0.0      0.0
H   0.0        0.0  .7474)#";
  auto H =
      createOperator("pyscf", {{"basis", "sto-3g"}, {"geometry", h2_geom}});

  OptFunction opt_function(
      [&](std::vector<double> x) {
        return ansatz::observe(H, qalloc(4), x[0]);
      },
      1);
  
   auto [energy, opt_params] = createOptimizer("nlopt")->optimize(opt_function);
   print(energy);
}
 No newline at end of file
+104 −65
Original line number Diff line number Diff line
#pragma once
#include <optional>

#include "qcor_jit.hpp"
#include "qcor_observable.hpp"
#include "qcor_utils.hpp"
#include "qrt.hpp"
#include <optional>

namespace qcor {
enum class QrtType { NISQ, FTQC };

// Forward declare
template <typename... Args> class KernelSignature;
template <typename... Args>
class KernelSignature;

namespace internal {
// KernelSignature is the base of all kernel-like objects
@@ -67,7 +69,8 @@ std::size_t n_instructions(KernelSignature<Args...> &kernelCallable,
// 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 {
template <typename Derived, typename... Args>
class QuantumKernel {
 protected:
  // Tuple holder for variadic kernel arguments
  std::tuple<Args...> args_tuple;
@@ -100,7 +103,8 @@ public:
  QuantumKernel(std::shared_ptr<qcor::CompositeInstruction> _parent_kernel,
                Args... args)
      : args_tuple(std::forward_as_tuple(args...)),
        parent_kernel(_parent_kernel), is_callable(false) {
        parent_kernel(_parent_kernel),
        is_callable(false) {
    runtime_env = (__qrt_env == "ftqc") ? QrtType::FTQC : QrtType::NISQ;
  }

@@ -191,7 +195,8 @@ public:

  virtual ~QuantumKernel() {}

  template <typename... ArgTypes> friend class KernelSignature;
  template <typename... ArgTypes>
  friend class KernelSignature;
};

// We use the following to enable ctrl operations on our single
@@ -243,7 +248,8 @@ ONE_QUBIT_KERNEL_CTRL_ENABLER(Sdg, sdg)
// trailing variadic argument for the lambda class constructor. Once
// instantiated lambda invocation looks just like kernel invocation.

template <typename... CaptureArgs> class _qpu_lambda {
template <typename... CaptureArgs>
class _qpu_lambda {
 private:
  // Private inner class for getting the type
  // of a capture variable as a string at runtime
@@ -253,7 +259,8 @@ private:
    std::vector<std::string> var_names;
    int counter = 0;

    template <class T> std::string type_name() {
    template <class T>
    std::string type_name() {
      typedef typename std::remove_reference<T>::type TR;
      std::unique_ptr<char, void (*)(void *)> own(
          abi::__cxa_demangle(typeid(TR).name(), nullptr, nullptr, nullptr),
@@ -266,7 +273,8 @@ private:
    TupleToTypeArgString(std::string &t) : tmp(t) {}
    TupleToTypeArgString(std::string &t, std::vector<std::string> &_var_names)
        : tmp(t), var_names(_var_names) {}
    template <typename T> void operator()(T &t) {
    template <typename T>
    void operator()(T &t) {
      tmp += type_name<decltype(t)>() + "& " +
             (var_names.empty() ? "arg_" + std::to_string(counter)
                                : var_names[counter]) +
@@ -309,7 +317,8 @@ public:
  // specifying them since we're using C++17
  _qpu_lambda(std::string &&ff, std::string &&_capture_var_names,
              CaptureArgs &..._capture_vars)
      : src_str(ff), capture_var_names(_capture_var_names),
      : src_str(ff),
        capture_var_names(_capture_var_names),
        capture_vars(std::forward_as_tuple(_capture_vars...)) {
    // Get the original args list
    auto first = src_str.find_first_of("(");
@@ -364,7 +373,6 @@ public:
      return result;
    }(tt);


    // Determine if this lambda has a VQE-compatible type:
    // QReg then variational params.
    if (arg_type_and_names.size() == 2) {
@@ -397,8 +405,7 @@ public:
    // i.e. by-value arguments of these types are incompatible with a by-ref
    // casted function.
    static const std::unordered_map<std::string, std::string>
        FORWARD_TYPE_CONVERSION_MAP{{"int", "int&"},
                                    {"double", "double&"}};
        FORWARD_TYPE_CONVERSION_MAP{{"int", "int&"}, {"double", "double&"}};
    std::vector<std::pair<std::string, std::string>> forward_types;
    // Replicate by-value by create copies and restore the variables.
    std::vector<std::string> byval_casted_arg_names;
@@ -446,7 +453,8 @@ public:
        // Store capture vars (by-value)
        optional_copy_capture_vars = std::forward_as_tuple(_capture_vars...);
      } else {
        error("Capture variable type is non-copyable. Cannot use capture by "
        error(
            "Capture variable type is non-copyable. Cannot use capture by "
            "value.");
      }
    }
@@ -496,8 +504,7 @@ public:
    // preamble if necessary
    auto jit_src = ss.str();
    first = jit_src.find_first_of("{");
    if (!capture_var_names.empty())
      jit_src.insert(first + 1, capture_preamble);
    if (!capture_var_names.empty()) jit_src.insert(first + 1, capture_preamble);

    if (!byval_casted_arg_names.empty()) {
      std::stringstream cache_string, restore_string;
@@ -552,7 +559,8 @@ public:
    }
  }

  template <typename... FunctionArgs> void operator()(FunctionArgs &&... args) {
  template <typename... FunctionArgs>
  void operator()(FunctionArgs &&...args) {
    // Map the function args to a tuple
    auto kernel_args_tuple = std::forward_as_tuple(args...);
    if (!optional_copy_capture_vars.has_value()) {
@@ -637,7 +645,8 @@ public:
    return internal::print_kernel(callable, os, args...);
  }

  template <typename... FunctionArgs> void print_kernel(FunctionArgs... args) {
  template <typename... FunctionArgs>
  void print_kernel(FunctionArgs... args) {
    print_kernel(std::cout, args...);
  }

@@ -666,7 +675,8 @@ template <typename... Args>
using callable_function_ptr =
    void (*)(std::shared_ptr<xacc::CompositeInstruction>, Args...);

template <typename... Args> class KernelSignature {
template <typename... Args>
class KernelSignature {
 private:
  callable_function_ptr<Args...> *readOnly = 0;
  callable_function_ptr<Args...> &function_pointer;
@@ -918,10 +928,38 @@ double observe(Observable &obs, KernelSignature<Args...> &kernelCallable,

  xacc::internal_compiler::execute_pass_manager();

  // Operator pre-processing, map down to Pauli and cache
  std::shared_ptr<Observable> observable;
  auto obs_str = obs.toString();
  std::hash<std::string> hasher;
  auto operator_hash = hasher(obs_str);
  if (__internal__::cached_observables.count(operator_hash)) {
    observable = __internal__::cached_observables[operator_hash];
  } else {
    if (obs_str.find("^") != std::string::npos) {
      try {
        observable =
            operatorTransform("jw", dynamic_cast<FermionOperator &>(obs));
      } catch (std::exception &ex) {
        auto fermionObservable = createOperator("fermion", obs_str);
        observable = operatorTransform("jw", fermionObservable);
      }

      // observable is PauliOperator, but does not cast down to it
      // Not sure about the likelihood of this happening, but want to cover all
      // bases
    } else if (obs_str.find("X") != std::string::npos ||
               obs_str.find("Y") != std::string::npos ||
               obs_str.find("Z") != std::string::npos) {
      observable = createOperator("pauli", obs_str);
    }
    __internal__::cached_observables.insert({operator_hash, observable});
  }

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

template <typename... Args>
@@ -934,7 +972,8 @@ Eigen::MatrixXcd as_unitary_matrix(KernelSignature<Args...> &kernelCallable,
  if (!std::all_of(
          instructions.cbegin(), instructions.cend(),
          [](const auto &inst) { return inst->name() != "Measure"; })) {
    error("Unable to compute unitary matrix for kernels that already have "
    error(
        "Unable to compute unitary matrix for kernels that already have "
        "Measure operations.");
  }
  qcor::KernelToUnitaryVisitor visitor(tempKernel->nLogicalBits());
+1 −0
Original line number Diff line number Diff line
@@ -130,6 +130,7 @@ std::shared_ptr<Observable> _internal_python_createObservable(
}

namespace __internal__ {
std::map<std::size_t, std::shared_ptr<Observable>> cached_observables = {};

std::vector<std::shared_ptr<xacc::CompositeInstruction>> observe(
    std::shared_ptr<xacc::Observable> obs,
+2 −0
Original line number Diff line number Diff line
@@ -104,6 +104,8 @@ std::vector<std::shared_ptr<CompositeInstruction>> observe(
    std::shared_ptr<Observable> obs,
    std::shared_ptr<CompositeInstruction> program);

extern std::map<std::size_t, std::shared_ptr<Observable>> cached_observables;

}  // namespace __internal__

std::shared_ptr<Observable> _internal_python_createObservable(