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

Clean-up the VQE example



- Move helper code to separate files.

- Makde the code generic w.r.t. Callable signature.

Signed-off-by: default avatarThien Nguyen <nguyentm@ornl.gov>
parent 5ce5507e
Loading
Loading
Loading
Loading
+80 −0
Original line number Diff line number Diff line
#pragma once
#include "qir-types.hpp"

namespace qcor {
namespace qsharp {

// Helper to marshal (pack and unpack) C++ data to Q# QIR data
// e.g. vector is converted to Array type.
template <typename T>
TuplePtr pack(TuplePtr io_tuple, const std::vector<T> &in_vec) {
  static_assert(std::is_same<T, double>::value ||
                    std::is_same<T, int64_t>::value,
                "Only support vector of these types now.");
  ::Array *qirArray = new ::Array(in_vec.size(), sizeof(T));
  for (size_t i = 0; i < in_vec.size(); ++i) {
    auto dest = qirArray->getItemPointer(i);
    auto src = &in_vec[i];
    memcpy(dest, src, sizeof(T));
  }

  TupleHeader *th = ::TupleHeader::getHeader(io_tuple);
  memcpy(io_tuple, &qirArray, sizeof(::Array *));
  return io_tuple + sizeof(::Array *);
}

// Unpack a value from the tuple and move the pointer to the next element.
template <typename T> TuplePtr unpack(TuplePtr in_tuple, T &out_val) {
  static_assert(std::is_same<T, double>::value ||
                    std::is_same<T, int64_t>::value,
                "Only support these types now.");
  out_val = *(reinterpret_cast<T *>(in_tuple));
  return in_tuple + sizeof(T);
}

template <typename T>
TuplePtr unpack(TuplePtr in_tuple, std::vector<T> &out_val) {
  static_assert(std::is_same<T, double>::value ||
                    std::is_same<T, int64_t>::value,
                "Only support vector of these types now.");
  out_val.clear();
  ::Array *arrayPtr = *(reinterpret_cast<::Array **>(in_tuple));
  for (size_t i = 0; i < arrayPtr->size(); ++i) {
    const double el = *(reinterpret_cast<T *>(arrayPtr->getItemPointer(i)));
    out_val.emplace_back(el);
  }
  return in_tuple + sizeof(::Array *);
}

template <typename ReturnType, typename... Args>
class qs_callback : public qsharp::IFunctor {
public:
  virtual void execute(TuplePtr args, TuplePtr result) override {
    auto next = qsharp::unpack(args, m_costVal);
    next = qsharp::unpack(next, m_previousParams);
    auto _result = internal_execute();
    auto test = qsharp::pack(result, _result);
  }
  qs_callback(std::function<ReturnType(Args...)> &functor)
      : m_functor(functor) {}

private:
  std::vector<double> internal_execute() {
    std::vector<double> result = m_functor(m_costVal, m_previousParams);
    return result;
  }

private:
  double m_costVal = 0.0;
  std::vector<double> m_previousParams;
  std::function<ReturnType(Args...)> m_functor;
};

template <typename ReturnType, typename... Args>
Callable *createCallable(std::function<ReturnType(Args...)> &in_func) {
  auto functor = new qs_callback<ReturnType, Args...>(in_func);
  // Create a QIR callable
  return new Callable(functor);
}
} // namespace qsharp
} // namespace qcor
 No newline at end of file
+92 −0
Original line number Diff line number Diff line
#pragma once
#include <thread>             
#include <mutex>              
#include <condition_variable> 

namespace qcor {
// Experimental optimization stepper to guide the Q# VQE loop.
class OptStepper {
public:
  OptStepper(const std::string &in_optName,
             xacc::HeterogeneousMap in_config = {}) {
    m_optimizer = qcor::createOptimizer(in_optName, std::move(in_config));
  }
  // Call by the main thread to update the  params and cost val...
  void update(const std::vector<double> &in_params, double in_costVal) {
    m_nextParamsAvail = false;
    if (m_dim == 0) {
      // First update:
      // Assuming the initial params are set by the caller,
      // i.e. this stepper doesn't control initial values.
      m_dim = in_params.size();
      m_optimizer->appendOption("initial-parameters", in_params);
      m_optFn = xacc::OptFunction(
          [&](const std::vector<double> &x, std::vector<double> &g) {
            return this->eval(x);
          },
          m_dim);
      m_currentCostVal = in_costVal;
      m_optParams = in_params;
      m_resultAvail = true;
      m_optThread = std::thread([&]() {
        auto result = m_optimizer->optimize(m_optFn);
        m_optDone = true;
      });
      return;
    }

    // A new iteration...
    m_optParams = in_params;
    m_currentCostVal = in_costVal;
    m_resultAvail = true;
    // Notify that the new result is avail...
    m_cv.notify_all();
  }

  // Fake evaluator function for the optimizer:
  // Run on the optimizer thread...
  double eval(const std::vector<double> &x) {
    if (!m_resultAvail) {
      m_optParams = x;
    }

    // Wait for the result to be available,
    // update() was called by the main thread.
    std::unique_lock<std::mutex> lck(m_mtx);
    m_cv.wait(lck, [this] { return m_resultAvail; });
    // This result has been processed...
    m_resultAvail = false;
    m_nextParamsAvail = true;
    return m_currentCostVal;
  }

  // Call by main thread...
  std::vector<double> getNextParams() {
    if (m_optDone) {
      // Return empty to signal that the optimizer has done.
      m_optThread.join();
      return {};
    }
    // Wait for the optimizer to give us a new set of parameters.
    // This should be fast, hence just poll.
    while (!m_nextParamsAvail) {
      std::this_thread::sleep_for(std::chrono::milliseconds(1));
    }
    return m_optParams;
  }

private:
  int m_dim = 0;
  std::shared_ptr<xacc::Optimizer> m_optimizer;
  std::vector<double> m_optParams;
  double m_currentCostVal;
  bool m_resultAvail = false;
  bool m_nextParamsAvail = false;
  bool m_optDone = false;
  // Optimizer thread
  std::mutex m_mtx;
  std::condition_variable m_cv;
  std::thread m_optThread;
  xacc::OptFunction m_optFn;
};
} // namespace qcor
+3 −4
Original line number Diff line number Diff line
@@ -6,11 +6,8 @@ open QCOR.Intrinsic;
// Note: parameter initialization is done here.
// Returns the final energy.
operation DeuteronVqe(shots: Int, stepper : ((Double, Double[]) => Double[])) : Double {
    // Deuteron Hamiltonian:
    // H = "5.907 - 2.1433 X0X1 - 2.1433 Y0Y1 + .21829 Z0 - 6.125 Z1");
    // Initial parameters
    let initial_params = [1.23];
        
    let initial_params = [1.234];   
    mutable opt_params = initial_params;
    mutable energy_val = 0.0;
    use qubits = Qubit[2]
@@ -24,6 +21,8 @@ operation DeuteronVqe(shots: Int, stepper : ((Double, Double[]) => Double[])) :
            let z0_z1_exps = DeuteronZ0_Z1(qubits, shots, opt_params[0]);
            let z0Exp = z0_z1_exps[0];
            let z1Exp = z0_z1_exps[1];
            // Deuteron energy:
            // H = 5.907 - 2.1433 X0X1 - 2.1433 Y0Y1 + .21829 Z0 - 6.125 Z1
            set energy_val = 5.907 - 2.1433 * xxExp - 2.1433 * yyExp + 0.21829 * z0Exp - 6.125 * z1Exp;
            // Stepping...
            set opt_params = stepper(energy_val, opt_params);
+24 −68
Original line number Diff line number Diff line
#include <iostream> 
#include <vector>
#include "qir-types.hpp"
#include "opt_stepper.hpp"
#include "callable_util.hpp"

// Include the external QSharp function.
qcor_include_qsharp(QCOR__DeuteronVqe__body, double, int64_t shots, Callable* opt_stepper);

// Implement of a callback for Q# via IFunctor interface.
// TODO: this is a rigid impl. for protityping,
// we will handle generic callback signature transformation.
class vqe_callback : public qsharp::IFunctor {
public:
  virtual void execute(TuplePtr args, TuplePtr result) override {
    auto next = unpack(args, m_costVal);
    next = unpack(next, m_previousParams);
    auto _result = internal_execute();
    auto test = pack(result, _result);
  }
  vqe_callback(
      std::function<std::vector<double>(double, std::vector<double>)> functor)
      : m_functor(functor) {}

private:
  std::vector<double> internal_execute() {
    std::vector<double> result = m_functor(m_costVal, m_previousParams);
    return result;
  }

  TuplePtr pack(TuplePtr io_tuple, const std::vector<double> &in_vec) {
    ::Array *qirArray = new ::Array(in_vec.size(), sizeof(double));
    for (size_t i = 0; i < in_vec.size(); ++i) {
      auto dest = qirArray->getItemPointer(i);
      auto src = &in_vec[i];
      memcpy(dest, src, sizeof(double));
    }

    TupleHeader *th = ::TupleHeader::getHeader(io_tuple);
    memcpy(io_tuple, &qirArray, sizeof(::Array *));
    return io_tuple + sizeof(::Array *);
  }

  TuplePtr unpack(TuplePtr in_tuple, double &out_val) {
    out_val = *(reinterpret_cast<double *>(in_tuple));
    return in_tuple + sizeof(double);
  }

  TuplePtr unpack(TuplePtr in_tuple, std::vector<double> &out_val) {
    out_val.clear();
    ::Array *arrayPtr = *(reinterpret_cast<::Array **>(in_tuple));
    // std::cout << "Array of size " << arrayPtr->size()
    //           << "; element size = " << arrayPtr->element_size() << "\n";
    for (size_t i = 0; i < arrayPtr->size(); ++i) {
      const double el =
          *(reinterpret_cast<double *>(arrayPtr->getItemPointer(i)));
      out_val.emplace_back(el);
    }
    return in_tuple + sizeof(::Array *);
  }

private:
  double m_costVal = 0.0;
  std::vector<double> m_previousParams;
  std::function<std::vector<double>(double, std::vector<double>)> m_functor;
};

// Compile with:
// Include both the qsharp source and this driver file
@@ -70,18 +15,29 @@ private:
// Run with:
// $ ./a.out
int main() {
  std::function<std::vector<double>(double, std::vector<double>)> stepper =
      [&](double in_costVal,
          std::vector<double> previous_params) -> std::vector<double> {
    std::cout << "HELLO CALLBACK!\n";
    std::cout << "Cost value = " << in_costVal << "\n";
    return {previous_params[0] + 0.5};
  // Create an optimizer:
  qcor::OptStepper qcorOptimizer("nlopt", {{"maxeval", 20}});
  using StepperFuncType =
      std::function<std::vector<double>(double, std::vector<double>)>;

  // Create an optimizer stepper as a lambda function to provide to Q#.
  StepperFuncType stepper_callable = [&](double in_costVal,
                                         std::vector<double> previous_params) {
    static size_t iterCount = 0;
    // Update the stepper with new data
    qcorOptimizer.update(previous_params, in_costVal);
    std::cout << "Iter " << iterCount++ << ": Energy(" << previous_params[0]
              << ") = " << in_costVal << "\n";
    // Returns a new set of params for Q# to try.
    return qcorOptimizer.getNextParams();
  };

  vqe_callback test(stepper);
  // Create a QIR callable
  Callable cb(&test);

  const double exp_val_xx = QCOR__DeuteronVqe__body(1024, &cb);
  // Run the Q# Deuteron with the *nlopt* stepper provided as a callable.
  // Note: qsharp::createCallable will marshal the C++ function (lambda) to the
  // Q# Callable type.
  const int64_t nb_shots = 2048;
  const double final_energy = QCOR__DeuteronVqe__body(
      nb_shots, qsharp::createCallable(stepper_callable));
  std::cout << "Final energy = " << final_energy << "\n";
  return 0;
}
 No newline at end of file
+0 −1
Original line number Diff line number Diff line
@@ -28,7 +28,6 @@ TuplePtr __quantum__rt__tuple_copy(int8_t *tuple, bool forceNewInstance) {
}

void Callable::invoke(TuplePtr args, TuplePtr result) {
  std::cout << "CALL: " << __PRETTY_FUNCTION__ << "\n";
  if (m_functor) {
    m_functor->execute(args, result);
  }