Commit 3b5eb201 authored by Mccaskey, Alex's avatar Mccaskey, Alex
Browse files

updating vqe hybrid class to support custom translation functors, added qaoa vqe example

parent 54526400
Loading
Loading
Loading
Loading
Loading
+0 −1
Original line number Diff line number Diff line
@@ -10,7 +10,6 @@

// start off, include the hybrid header
// this gives us the VQE class
#include "qcor.hpp"
#include "qcor_hybrid.hpp"

// Define one quantum kernel that takes
+94 −0
Original line number Diff line number Diff line
#include "qcor_hybrid.hpp"

__qpu__ void qaoa_ansatz(qreg q, int n_steps, std::vector<double> gamma,
                         std::vector<double> beta,
                         qcor::PauliOperator &cost_ham) {

  // Local Declarations
  auto nQubits = q.size();
  int gamma_counter = 0;
  int beta_counter = 0;

  // Start of in the uniform superposition
  for (int i = 0; i < nQubits; i++) {
    H(q[0]);
  }

  // Get all non-identity hamiltonian terms
  // for the following exp(H_i) trotterization
  auto cost_terms = cost_ham.getNonIdentitySubTerms();

  // Loop over qaoa steps
  for (int step = 0; step < n_steps; step++) {

    // Loop over cost hamiltonian terms
    for (int i = 0; i < cost_terms.size(); i++) {

      // for xasm we have to allocate the variables
      // cant just run exp_i_theta(q, gamma[gamma_counter], cost_terms[i]) yet
      // :(
      auto cost_term = cost_terms[i];
      auto m_gamma = gamma[gamma_counter];

      // trotterize
      exp_i_theta(q, m_gamma, cost_term);

      gamma_counter++;
    }

    // Add the reference hamiltonian term
    for (int i = 0; i < nQubits; i++) {
      auto ref_ham_term = qcor::X(i);
      auto m_beta = beta[beta_counter];
      exp_i_theta(q, m_beta, ref_ham_term);
      beta_counter++;
    }
  }
}

int main(int argc, char **argv) {

  // Specify the size of the problem
  int nGamma = 7;
  int nBeta = 4;
  int nParamsPerStep = nGamma + nBeta;
  int nSteps = 3;
  int total_params = nSteps * nParamsPerStep;

  // Generate a random initial parameter set
  auto initial_gamma = qcor::random_vector(0., 1., nSteps * nGamma);
  auto initial_beta = qcor::random_vector(0., 1., nSteps * nBeta);

  // Construct the cost hamiltonian
  auto cost_ham =
      -5.0 - 0.5 * (qcor::Z(0) - qcor::Z(3) - qcor::Z(1) * qcor::Z(2)) -
      qcor::Z(2) + 2 * qcor::Z(0) * qcor::Z(2) + 2.5 * qcor::Z(2) * qcor::Z(3);

  // VQE needs a way to translate std::vector<double> parameters
  // coming from the OptFunction being optimized into arguments
  // for the quantum kernel. We call this a TranslationFunctor.
  // Provide one here that maps vector<double> x to gamma and beta
  // on the quantum kernel, with captured variables for the other args.
  auto args_translation =
      qcor::TranslationFunctor<qreg, int, std::vector<double>,
                               std::vector<double>, qcor::PauliOperator>(
          [&](const std::vector<double> x) {
            // split x into gamma and beta sets
            std::vector<double> gamma(x.begin(), x.begin() + nSteps * nGamma),
                beta(x.begin() + nSteps * nGamma,
                     x.begin() + nSteps * nGamma + nSteps * nBeta);
            return std::make_tuple(qalloc(4), nSteps, gamma, beta, cost_ham);
          });

  qcor::set_verbose(true);

  auto optimizer = qcor::createOptimizer(
      "nlopt", {std::make_pair("nlopt-optimizer", "l-bfgs"),
                std::make_pair("nlopt-maxeval", 100)});
  qcor::VQE vqe(qaoa_ansatz, cost_ham, args_translation);
  auto [energy, params] =
      vqe.execute(optimizer, nSteps, initial_gamma, initial_beta, cost_ham);

  // Print the optimal value.
  printf("Min QUBO value = %f\n", energy);
}
+2 −2
Original line number Diff line number Diff line
@@ -4,12 +4,12 @@ namespace qcor {
namespace __internal__ {

qcor::TranslationFunctor<qreg, double>
TranslationFunctorGenerator::operator()(qreg &q, std::tuple<double> &&t) {
TranslationFunctorAutoGenerator::operator()(qreg &q, std::tuple<double> &&t) {
  return qcor::TranslationFunctor<qreg, double>(
      [&](const std::vector<double> x) { return std::make_tuple(q, x[0]); });
}
qcor::TranslationFunctor<qreg, std::vector<double>>
TranslationFunctorGenerator::operator()(qreg &q,
TranslationFunctorAutoGenerator::operator()(qreg &q,
                                        std::tuple<std::vector<double>> &&) {
  return qcor::TranslationFunctor<qreg, std::vector<double>>(
      [&](const std::vector<double> x) { return std::make_tuple(q, x); });
+54 −8
Original line number Diff line number Diff line
@@ -10,12 +10,19 @@ namespace __internal__ {
// enumerate commonly seen TranslationFunctors, a utility
// that maps quantum kernel argument structure to the
// qcor Optimizer / OptFunction std::vector<double> x parameters.
struct TranslationFunctorGenerator {
struct TranslationFunctorAutoGenerator {

  qcor::TranslationFunctor<qreg, double> operator()(qreg &q,
                                                    std::tuple<double> &&);
  qcor::TranslationFunctor<qreg, std::vector<double>>
  operator()(qreg &q, std::tuple<std::vector<double>> &&);

  template <typename... Args>
  qcor::TranslationFunctor<qreg, Args...> operator()(qreg &q,
                                                     std::tuple<Args...> &&) {
    return
        [](const std::vector<double>) { return std::tuple<qreg, Args...>(); };
  }
};

class CountRotationAngles {
@@ -26,6 +33,7 @@ public:
    count += tuple_element_vec.size();
  }
  void operator()(double tuple_element_double) { count++; }
  template <typename T> void operator()(T &tuple_element) {}
};

} // namespace __internal__
@@ -36,6 +44,10 @@ public:
// interest
template <typename QuantumKernel> class VQE {
protected:
  inline static const std::string OBJECTIVE_NAME = "vqe";
  inline static const std::string OPTIMIZER_INIT_PARAMS = "initial-parameters";
  inline static const std::string DEFAULT_OPTIMIZER = "nlopt";

  // Reference to the paramerized
  // quantum kernel functor
  QuantumKernel &ansatz;
@@ -51,8 +63,15 @@ protected:
  // Register of qubits to operate on
  qreg q;

  // The GradientEvaluator to use if
  // we are given an Optimizer that is gradient-based
  GradientEvaluator grad_eval;

  // Any holding user-specified qcor::TranslationFunctor<qreg, Args...>
  // Using any here to keep us from having to
  // template VQE class any further.
  std::any translation_functor;

public:
  // Typedef for describing the energy / params return type
  using VQEResultType = std::pair<double, std::vector<double>>;
@@ -73,16 +92,36 @@ public:
    q = qalloc(obs.nBits());
  }

  // Constructor, takes a TranslationFunctor as a general
  // template type that we store to the protected any member
  // and cast later
  template <typename TranslationFunctorT>
  VQE(QuantumKernel &kernel, Observable &obs, TranslationFunctorT &&tfunc)
      : ansatz(kernel), observable(obs), translation_functor(tfunc) {
    q = qalloc(obs.nBits());
  }

  // Constructor, takes a TranslationFunctor as a general
  // template type that we store to the protected any member
  // and cast later. Also takes gradient evaluator
  template <typename TranslationFunctorT>
  VQE(QuantumKernel &kernel, Observable &obs, GradientEvaluator &geval,
      TranslationFunctorT &&tfunc)
      : ansatz(kernel), observable(obs), translation_functor(tfunc),
        grad_eval(geval) {
    q = qalloc(obs.nBits());
  }

  // Execute the VQE task synchronously, assumes default optimizer
  template <typename... Args> VQEResultType execute(Args... initial_params) {
    auto optimizer = qcor::createOptimizer("nlopt");
    auto optimizer = qcor::createOptimizer(DEFAULT_OPTIMIZER);
    auto handle = execute_async<Args...>(optimizer, initial_params...);
    return this->sync(handle);
  }

  // Execute the VQE task asynchronously, default optimizer
  template <typename... Args> Handle execute_async(Args... initial_params) {
    auto optimizer = qcor::createOptimizer("nlopt");
    auto optimizer = qcor::createOptimizer(DEFAULT_OPTIMIZER);
    return execute_async<Args...>(optimizer, initial_params...);
  }

@@ -99,7 +138,8 @@ public:
  Handle execute_async(std::shared_ptr<Optimizer> optimizer,
                       Args... initial_params) {
    // Get the VQE ObjectiveFunction and set the qreg
    auto objective = qcor::createObjectiveFunction("vqe", ansatz, observable);
    auto objective =
        qcor::createObjectiveFunction(OBJECTIVE_NAME, ansatz, observable);
    objective->set_qreg(q);

    // Convert input args to a tuple
@@ -110,11 +150,17 @@ public:
    __internal__::ConvertDoubleLikeToVectorDouble build_up_init_params(
        init_params);
    __internal__::tuple_for_each(init_args_tuple, build_up_init_params);
    optimizer->appendOption("initial-parameters", init_params);
    optimizer->appendOption(OPTIMIZER_INIT_PARAMS, init_params);

    // Create the Arg Translator
    __internal__::TranslationFunctorGenerator gen;
    auto arg_translator = gen(q, std::tuple<Args...>());
    TranslationFunctor<qreg, Args...> arg_translator;
    if (translation_functor.has_value()) {
      arg_translator =
          std::any_cast<TranslationFunctor<qreg, Args...>>(translation_functor);
    } else {
      __internal__::TranslationFunctorAutoGenerator auto_gen;
      arg_translator = auto_gen(q, std::tuple<Args...>());
    }

    // Count all rotation angles (n parameters)
    __internal__::CountRotationAngles count_params(n_params);
+18 −0
Original line number Diff line number Diff line
@@ -7,6 +7,7 @@
#include <memory>
#include <tuple>
#include <vector>
#include <random>

#include "CompositeInstruction.hpp"
#include "Observable.hpp"
@@ -207,6 +208,23 @@ constexpr auto enumerate(T &&iterable) {
  return iterable_wrapper{std::forward<T>(iterable)};
}

template<typename ScalarType>
auto random_vector(const ScalarType l_range, const ScalarType r_range,
                   const std::size_t size) {
  // Generate a random initial parameter set
  std::random_device rnd_device;
  std::mt19937 mersenne_engine{rnd_device()}; // Generates random integers
  std::uniform_real_distribution<ScalarType> dist{l_range, r_range};
  auto gen = [&dist, &mersenne_engine]() { return dist(mersenne_engine); };
  std::vector<ScalarType> vec(size);
  std::generate(vec.begin(), vec.end(), gen);
  return vec;
}

template<typename... Args>
auto args_translator(std::function<std::tuple<Args...>(const std::vector<double>)>&& functor_lambda) {
   return TranslationFunctor<Args...>(functor_lambda);
}
// This function allows programmers to get a QASM like string view
// of the quantum kernel persisted to teh provided ostream
template <typename QuantumKernel, typename... Args>