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

finished first pass of qaoa high-level hybrid lib class

parent 988c6925
Loading
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -99,7 +99,7 @@ int main(int argc, char **argv) {
  auto q = qalloc(2);

  // Create the Deuteron Hamiltonian (Observable)
  auto H = qcor::getObservable(
  auto H = qcor::createObservable(
      "5.907 - 2.1433 X0X1 - 2.1433 Y0Y1 + .21829 Z0 - 6.125 Z1");

  // Create the ObjectiveFunction, here we want to run VQE
+51 −0
Original line number Diff line number Diff line
#include "qcor_hybrid.hpp"

using namespace qcor;

int main() {

  // Define the Hamiltonian using the QCOR API
  auto H = 5.907 - 2.1433 * X(0) * X(1) - 2.1433 * Y(0) * Y(1) + .21829 * Z(0) -
           6.125 * Z(1);

  // Define the reference hamiltonian
  auto ref_ham = X(0) + X(1);

  // Create a gradient based optimizer
  // If we don't provide this, QAOA will
  // use NLOpt QAOA. 
  auto lbfgs = qcor::createOptimizer(
      "nlopt", {std::make_pair("nlopt-optimizer", "l-bfgs")});

  // Turn on verbose output to see 
  // the iterations progress
  qcor::set_verbose(true);

  // we want 2 qaoa steps
  auto steps = 2;

  // Create the QAOA instance
  QAOA qaoa(H, ref_ham, steps);

  // we're using a gradient-based optimizer, 
  // by default QAOA will use parameter-shift-rule, 
  // here we set the scale factor. 
  qaoa.set_parameter_shift_scale_factor(.1);

  // Execute synchronously and display
  const auto [energy, params] = qaoa.execute(lbfgs);

  std::cout << "<H> = " << energy << "\n";

  // note you could also call execute_async() -> Handle and
  // manually qcor::sync(handle)

  // or you could call execute(initial_params:vector<double>)
  // or you could call execute_async(initial_params:vector<double>)
  // or you could call execute(optimizer, initial_params:vector<double>)
  // or you could call execute(optimizer)

  // If you wanted to provide initial-parameters, you can
  // query the size of vector you need with
  // qaoa.n_parameters() (also have n_gamma(), n_beta(), and n_steps())
}
 No newline at end of file
+10 −4
Original line number Diff line number Diff line
@@ -2,10 +2,13 @@
// with QCOR's VQE Objective Function.
// Note: we can also explicitly construct this ansatz circuit in QCOR.
// e.g., see qaoa_example.cpp
#include <qalloc>
#include "qcor.hpp"
#include <qalloc>

__qpu__ void qaoa_ansatz(qreg q, int n, std::vector<double> betas, std::vector<double> gammas, qcor::PauliOperator& costHamiltonian, qcor::PauliOperator& refHamiltonian) {
__qpu__ void qaoa_ansatz(qreg q, int n, std::vector<double> betas,
                         std::vector<double> gammas,
                         qcor::PauliOperator &costHamiltonian,
                         qcor::PauliOperator &refHamiltonian) {
  // Just use the built-in qaoa circuit
  qaoa(q, n, betas, gammas, costHamiltonian, refHamiltonian);
}
@@ -16,7 +19,9 @@ int main(int argc, char **argv) {
  auto buffer = qalloc(2);
  auto optimizer = qcor::createOptimizer("nlopt");
  // Cost Hamiltonian
  auto observable = 5.907-2.1433*qcor::X(0)*qcor::X(1)-2.1433*qcor::Y(0)*qcor::Y(1)+0.21829*qcor::Z(0)-6.125*qcor::Z(1);
  auto observable = 5.907 - 2.1433 * qcor::X(0) * qcor::X(1) -
                    2.1433 * qcor::Y(0) * qcor::Y(1) + 0.21829 * qcor::Z(0) -
                    6.125 * qcor::Z(1);
  // Mixer Hamiltonian
  auto refHamiltonian = qcor::X(0) + qcor::X(1);

@@ -44,7 +49,8 @@ int main(int argc, char **argv) {
          gammas.emplace_back(x[i]);
        }
        // Evaluate the objective function
        const double costVal = (*vqe)(buffer, buffer.size(), betas, gammas, observable, refHamiltonian);
        const double costVal = (*vqe)(buffer, buffer.size(), betas, gammas,
                                      observable, refHamiltonian);
        std::cout << "Iter " << iterCount << ": Cost = " << costVal << "\n";
        iterCount++;
        return costVal;
+14 −2
Original line number Diff line number Diff line
#include "qcor_hybrid.hpp"
#include "Compiler.hpp"
#include "xacc.hpp"
#include "xacc_service.hpp"

namespace qcor {
namespace __internal__ {
@@ -9,11 +12,20 @@ TranslationFunctorAutoGenerator::operator()(qreg &q, std::tuple<double> &&t) {
      [&](const std::vector<double> x) { return std::make_tuple(q, x[0]); });
}
qcor::TranslationFunctor<qreg, std::vector<double>>
TranslationFunctorAutoGenerator::operator()(qreg &q,
                                        std::tuple<std::vector<double>> &&) {
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); });
}
} // namespace __internal__

void QAOA::initial_compile_qaoa_code() {
  if (!xacc::isInitialized()) {
    xacc::Initialize();
  }
  auto xasm = xacc::getService<xacc::Compiler>("xasm");
  qaoa_circuit = xasm->compile(qaoa_xasm_code)->getComposites()[0];
}
void QAOA::error(const std::string &message) { xacc::error(message); }

} // namespace qcor
 No newline at end of file
+218 −1
Original line number Diff line number Diff line
@@ -2,6 +2,9 @@

#include "AcceleratorBuffer.hpp"
#include "qcor.hpp"
#include "qrt.hpp"
#include <memory>
// #include <xacc_internal_compiler.hpp>

namespace qcor {
static constexpr double pi = 3.141592653589793238;
@@ -230,6 +233,220 @@ public:
  }
};

// Next, add QAOA...
// QAOA provides a high-level data structure
// for driving the qcor API in a way that affects the
// typical QAOA algorithm. Users instantiate this class
// by providing the cost hamiltonian and number of qaoa steps,
// and optionally the reference hamiltonian. Running the
// algorithm is then just simply invoking the execute() method
class QAOA {
protected:
  // The number of qaoa steps
  std::size_t nSteps = 1;

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

  // The cost hamiltonian
  PauliOperator &cost;

  // The reference hamiltonian
  PauliOperator ref;

  // The qubit register to run on
  qreg q;

  // Scale factor for parameter shift gradient rule
  double ps_scale_factor = 1.0;

  // Internal helper function for creating
  // the quantum kernel from an xasm string
  void initial_compile_qaoa_code();

  // Reference to the qaoa parameterized circuit
  std::shared_ptr<CompositeInstruction> qaoa_circuit;

  // XASM QAOA code for creating the qaoa circuit
  inline static const std::string qaoa_xasm_code =
      R"#(__qpu__ void qaoa_ansatz(qreg q, int n, std::vector<double> betas, std::vector<double> gammas, qcor::PauliOperator& costHamiltonian, qcor::PauliOperator& refHamiltonian) {
  qaoa(q, n, betas, gammas, costHamiltonian, refHamiltonian);
})#";

  // utility for delegating to xacc error call
  // implemented in qcor_hybrid.cpp to avoid
  // xacc.hpp include here
  void error(const std::string &message);

public:
  // Helper qaoa algorithm result type
  using QAOAResultType = std::pair<double, std::vector<double>>;

  // The Constructionr, takes the cost hamiltonian and the number of steps.
  // will assume reference hamiltonian of an X pauli on all qubits
  QAOA(PauliOperator &obs, const std::size_t _n_steps)
      : cost(obs), nSteps(_n_steps), ref(PauliOperator()) {
    for (int i = 0; i < cost.nBits(); i++) {
      ref += qcor::X(i);
    }
    initial_compile_qaoa_code();
  }

  // The constructor, takes cost and reference hamiltonian and number of steps
  QAOA(PauliOperator &obs, PauliOperator &ref_ham, const std::size_t _n_steps)
      : cost(obs), nSteps(_n_steps), ref(ref_ham) {
    initial_compile_qaoa_code();
  }

  // The Constructionr, takes the cost hamiltonian and the number of steps.
  // will assume reference hamiltonian of an X pauli on all qubits
  // also provide gradient evaluator
  QAOA(PauliOperator &obs, GradientEvaluator &geval, const std::size_t _n_steps)
      : cost(obs), nSteps(_n_steps), ref(PauliOperator()), grad_eval(geval) {
    for (int i = 0; i < cost.nBits(); i++) {
      ref += qcor::X(i);
    }
    initial_compile_qaoa_code();
  }

  // The constructor, takes cost and reference hamiltonian and number of steps
  // Also provide gradient evaluator
  QAOA(PauliOperator &obs, PauliOperator &ref_ham, GradientEvaluator &geval,
       const std::size_t _n_steps)
      : cost(obs), nSteps(_n_steps), ref(ref_ham), grad_eval(geval) {
    initial_compile_qaoa_code();
  }

  // Return the number of gamma parameters
  auto n_gamma() { return cost.getNonIdentitySubTerms().size(); }
  // Return the number of beta parameters
  auto n_beta() { return ref.getNonIdentitySubTerms().size(); }
  // Return the number of qaoa steps
  auto n_steps() { return nSteps; }
  // Return the total number of parameters
  auto n_parameters() { return n_steps() * (n_gamma() + n_beta()); }

  // Provide the scale factor for the parameter shift rule
  void set_parameter_shift_scale_factor(const double scale) {
    if (scale < 0.0) {
      error("invalid parameter shift scale factor, must be greater than 0.0");
    }
    ps_scale_factor = scale;
  }
  // Execute the algorithm synchronously, optionally provide the initial
  // parameters. Will fail if initial_parameters.size() != n_parameters()
  QAOAResultType execute(const std::vector<double> initial_parameters = {}) {
    auto optimizer = qcor::createOptimizer("nlopt");
    return execute(optimizer, initial_parameters);
  }

  // Execute the algorithm synchronously, provding an Optimizer and optionally
  // initial parameters Will fail if initial_parameters.size() != n_parameters()
  QAOAResultType execute(std::shared_ptr<Optimizer> optimizer,
                         const std::vector<double> initial_parameters = {}) {
    auto handle = execute_async(optimizer, initial_parameters);
    auto results = this->sync(handle);
    return std::make_pair(results.first, results.second);
  }

  // Execute the algorithm asynchronously, provding an Optimizer and optionally
  // initial parameters Will fail if initial_parameters.size() != n_parameters()
  Handle execute_async(std::shared_ptr<Optimizer> optimizer,
                       const std::vector<double> initial_parameters = {}) {

    q = qalloc(cost.nBits());
    auto n_params = nSteps * (n_gamma() + n_beta());

    std::vector<double> init_params;
    if (initial_parameters.empty()) {
      init_params = qcor::random_vector(0.0, 1.0, n_params);
    } else {
      if (initial_parameters.size() != n_parameters()) {
        error("invalid initial_parameters provided, " +
              std::to_string(n_parameters()) +
              " != " + std::to_string(initial_parameters.size()));
      }
      init_params = initial_parameters;
    }

    auto objective = qcor::createObjectiveFunction("vqe", qaoa_circuit, cost);
    objective->set_qreg(q);
    optimizer->appendOption("initial-parameters", init_params);

    // See if we are using a gradient-based optimizer
    const auto use_gradients = optimizer->isGradientBased();

    return qcor::taskInitiate(
        objective, optimizer,
        [objective, use_gradients, this](const std::vector<double> x,
                                         std::vector<double> &dx) {
          // Set the size of the beta vector
          const auto n_betas = nSteps * q.size();

          // Utility to split x into beta and gamma vecs
          auto create_betas_gammas = [n_betas](const std::vector<double> x) {
            std::vector<double> betas;
            std::vector<double> gammas;
            for (int i = 0; i < n_betas; ++i) {
              betas.emplace_back(x[i]);
            }
            for (int i = betas.size(); i < x.size(); ++i) {
              gammas.emplace_back(x[i]);
            }

            return std::make_pair(betas, gammas);
          };

          // If we need gradients, evaluate them
          // with our default parameter shift or the
          // provide GradientEvaluator
          if (use_gradients) {
            if (!grad_eval) {
              // Parameter shift rule...
              for (int i = 0; i < dx.size(); i++) {
                auto xplus = x[i] + ps_scale_factor * pi / 2.;
                auto xminus = x[i] - ps_scale_factor * pi / 2.;
                std::vector<double> tmpx_plus = x, tmpx_minus = x;
                tmpx_plus[i] = xplus;
                tmpx_minus[i] = xminus;

                // Don't print the verbose output for gradients...
                const auto cached_verbose = qcor::get_verbose();
                qcor::set_verbose(false);

                // evaluate at x[i] + scale * pi / 2
                auto [betas_p, gammas_p] = create_betas_gammas(tmpx_plus);
                auto results1 =
                    (*objective)(q, q.size(), betas_p, gammas_p, cost, ref);

                // evaluate at x[i] - scale * pi / 2
                auto [betas_m, gammas_m] = create_betas_gammas(tmpx_minus);
                auto results2 =
                    (*objective)(q, q.size(), betas_m, gammas_m, cost, ref);
                qcor::set_verbose(cached_verbose);

                // set the gradient element
                dx[i] = 0.5 * (results1 - results2);
              }
            } else {
              // evaluate with the custom evaluator
              grad_eval(x, dx);
            }
          }

          // Evaluate at x and return
          auto [betas, gammas] = create_betas_gammas(x);
          return (*objective)(q, q.size(), betas, gammas, cost, ref);
        },
        n_params);
  }

  // Sync up the results with the host thread
  QAOAResultType sync(Handle &h) {
    auto results = qcor::sync(h);
    return std::make_pair(results.opt_val, results.opt_params);
  }
}; // namespace qcor
// Next, add Adapt

} // namespace qcor
Loading