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

Code refactor



Using workflow->execute(model) interface.

Next: put it into xacc and qcor hierarchy (it looks standalone atm)
Signed-off-by: default avatarThien Nguyen <nguyentm@ornl.gov>
parent 318824ba
Loading
Loading
Loading
Loading
+40 −71
Original line number Diff line number Diff line
#include "qcor_qsim.hpp"
#include "Circuit.hpp"
#include "qsim_impl.hpp"
#include "xacc_service.hpp"

namespace qcor {
@@ -24,12 +23,9 @@ Ansatz TrotterEvolution::create_ansatz(Observable *obs,
  return result;
}

bool CostFunctionEvaluator::initialize(const HeterogeneousMap &params) {
  target_operator = nullptr;
  if (params.pointerLikeExists<Observable>("observable")) {
    target_operator = params.getPointerLike<Observable>("observable");
  }

bool CostFunctionEvaluator::initialize(Observable *observable,
                                       const HeterogeneousMap &params) {
  target_operator = observable;
  // TODO: use qcor data
  quantum_backend = nullptr;
  if (params.pointerLikeExists<Accelerator>("accelerator")) {
@@ -47,8 +43,8 @@ CostFunctionEvaluator *CostFunctionEvaluator::getInstance() {
  return instance;
}

double
CostFunctionEvaluator::evaluate(std::shared_ptr<CompositeInstruction> state_prep) {
double CostFunctionEvaluator::evaluate(
    std::shared_ptr<CompositeInstruction> state_prep) {

  // Measure the observables:
  // TODO: Port the existing VQE impl. as the default.
@@ -64,67 +60,26 @@ QsimWorkflow *TimeDependentWorkflow::getInstance() {
  return instance;
}

bool QsimModel::initialize(WorkFlow workflow_type,
QsimModel ModelBuilder::createModel(Observable *obs, TdObservable td_ham,
                                    const HeterogeneousMap &params) {
  type = workflow_type;
  qsim_workflow = nullptr;
  switch (workflow_type) {
  case (WorkFlow::PE):
    // TODO
    break;
  case (WorkFlow::TD):
    qsim_workflow = TimeDependentWorkflow::getInstance();
    break;
  case (WorkFlow::VQE):
    // TODO:
    break;
  }

  if (qsim_workflow) {
    const bool workflowInit = qsim_workflow->initialize(params);
    if (!workflowInit) {
      return false;
    }

    return true;
  }

  return false;
}

QsimModel ModelBuilder::createModel(const HeterogeneousMap &params) {
  // TODO: we need to formalize the input here, e.g.
  // (1) Problem descriptions
  // (2) Workflow + method
  QsimModel model;

  // ==== TEMP CODE ====
  // We need to support problem decomposition as well.
  // i.e. from high-level problem descriptions.
  const std::string protocol = params.getString("protocol");
  const std::string method = params.getString("method");
  // Only have this currently:
  if (protocol == "time-evolution") 
  {
    if (method == "trotter") {
      model.initialize(WorkFlow::TD, params);
    }
  model.observable = obs;
  model.hamiltonian = td_ham;
  return model;
}
  // ==== TEMP CODE ====

QsimModel ModelBuilder::createModel(Observable *obs,
                                    const HeterogeneousMap &params) {
  QsimModel model;
  model.observable = obs;
  model.hamiltonian = [&](double t) {
    return *(static_cast<PauliOperator *>(obs));
  };
  return model;
}

bool TimeDependentWorkflow::initialize(const HeterogeneousMap &params) {
  if (params.keyExists<TdObservable>("hamiltonian")) {
    ham_func = params.get<TdObservable>("hamiltonian");
  }

  // TODO: support multiple evaluator
  evaluator = CostFunctionEvaluator::getInstance();
  evaluator->initialize(params);

  // Get parameters (specific to TD workflow):
  // Get workflow parameters (specific to TD workflow):
  t_0 = 0.0;
  dt = 0.1;
  if (params.keyExists<double>("dt")) {
@@ -139,8 +94,13 @@ bool TimeDependentWorkflow::initialize(const HeterogeneousMap &params) {
  return true;
}

QsimResult TimeDependentWorkflow::execute() {
QsimResult TimeDependentWorkflow::execute(const QsimModel &model) {
  QsimResult result;

  // TODO: support multiple evaluator
  evaluator = CostFunctionEvaluator::getInstance();
  evaluator->initialize(model.observable);
  auto ham_func = model.hamiltonian;
  // A TD workflow: stepping through Trotter steps,
  // compute expectations at each step.
  double currentTime = t_0;
@@ -149,16 +109,14 @@ QsimResult TimeDependentWorkflow::execute() {
  // Just support Trotter for now
  // TODO: support different methods:
  TrotterEvolution method;
  for (;;)
  {
  for (;;) {
    // Evaluate the time-dependent Hamiltonian:
    auto ham_t = ham_func(currentTime);
    auto stepAnsatz = method.create_ansatz(&ham_t, {{"dt", dt}});
    // First step:
    if (!totalCirc) {
      totalCirc = stepAnsatz.circuit;
    }
    else {
    } else {
      // Append Trotter steps
      totalCirc->addInstructions(stepAnsatz.circuit->getInstructions());
    }
@@ -177,4 +135,15 @@ QsimResult TimeDependentWorkflow::execute() {
  return result;
}

QsimWorkflow *getWorkflow(WorkFlow type, const HeterogeneousMap &init_params) {
  // == TEMP-CODE
  // TODO: set-up service registry for workflow
  auto qsim_workflow = TimeDependentWorkflow::getInstance();
  if (qsim_workflow->initialize(init_params)) {
    return qsim_workflow;
  }

  return nullptr;
}

} // namespace qcor
 No newline at end of file

lib/qsim/qsim_impl.hpp

0 → 100644
+58 −0
Original line number Diff line number Diff line
#pragma once
#include "qsim_interfaces.hpp"

namespace qcor {
// ========== Prototype Impl. ========================
// Example implementation:
class TrotterEvolution : public AnsatzGenerator {
public:
  Ansatz create_ansatz(Observable *obs,
                       const HeterogeneousMap &params) override;
};

// Estimate the cost function based on bitstring distribution,
// e.g. actual quantum hardware.
// Note: we can sub-class CostFunctionEvaluator to add post-processing or
// analysis of the result.
class BitCountExpectationEstimator : public CostFunctionEvaluator {
public:
  // Evaluate the cost
  virtual double
  evaluate(std::shared_ptr<CompositeInstruction> state_prep) override;

private:
  size_t nb_samples;
};

// VQE-type workflow which involves an optimization loop, i.e. an Optimizer.
class VqeWorkflow : public QsimWorkflow {
public:
  virtual bool initialize(const HeterogeneousMap &params) override;
  virtual QsimResult execute(const QsimModel &model) override;

  virtual const std::string name() const override { return "vqe"; }
  virtual const std::string description() const override { return ""; }
private:
  Optimizer *optimizer;
};



// Time-dependent evolution workflow which can handle
// time-dependent Hamiltonian operator.
class TimeDependentWorkflow : public QsimWorkflow {
public:
  virtual bool initialize(const HeterogeneousMap &params) override;
  virtual QsimResult execute(const QsimModel &model) override;

  static QsimWorkflow *getInstance();
  virtual const std::string name() const override { return "td-evolution"; }
  virtual const std::string description() const override { return ""; }
private:
  static inline TimeDependentWorkflow *instance = nullptr;
  double t_0;
  double t_final;
  double dt;
  TdObservable ham_func;
};
} // namespace qcor
 No newline at end of file
+52 −75
Original line number Diff line number Diff line
@@ -40,7 +40,8 @@ class CostFunctionEvaluator {
public:
  // Evaluate the cost
  virtual double evaluate(std::shared_ptr<CompositeInstruction> state_prep);
  virtual bool initialize(const HeterogeneousMap &params);
  virtual bool initialize(Observable *observable,
                          const HeterogeneousMap &params = {});
  static CostFunctionEvaluator *getInstance();

protected:
@@ -52,16 +53,62 @@ private:
  static inline CostFunctionEvaluator *instance = nullptr;
};

// Trotter time-dependent simulation workflow
// Time-dependent Hamiltonian is a function mapping from time t to Observable
// operator.
using TdObservable = std::function<PauliOperator(double)>;

// Capture a quantum chemistry problem.
// TODO: generalize this to capture all potential use cases.
struct QsimModel {
  // Model name.
  std::string name;
  
  // The Observable operator that needs to be measured/minimized. 
  Observable *observable;
  
  // The system Hamiltonian which can be static or dynamic (time-dependent).
  // This can be the same or different from the observable operator.
  TdObservable hamiltonian;
};

// Generic model builder (factory)
// Create a model which capture the problem description.
class ModelBuilder {
public:
  // ======== Direct model builder ==============
  // Strongly-typed parameters/argument.
  // Build a simple Hamiltonian-based model: static Hamiltonian which is also
  // the observable of interest.
  static QsimModel createModel(Observable *obs, const HeterogeneousMap &params = {});
  // Build a time-dependent problem model:
  //  -  obs: observable operator to measure.
  //  -  td_ham: time-dependent Hamiltonian to evolve the system.
  //     e.g. a function to map from time to Hamiltonian operator.
  static QsimModel createModel(Observable* obs, TdObservable td_ham,const HeterogeneousMap &params = {});

  // ========  High-level model builder ==============
  // The idea here is to have contributed modules to translate problem descriptions
  // in various formats, e.g. PyScf, Psi4, broombridge, etc. 
  // into QCOR's Observable type.
  // Inputs:
  //  - format: key to look up module/plugin to digest the input data.
  //  - data: model descriptions in plain-text format, e.g. load from file.
  static QsimModel createModel(const std::string &format,
                               const std::string &data,
                               const HeterogeneousMap &params = {});
};

// Quantum Simulation Workflow (Protocol)
// This can handle both variational workflow (optimization loop)
// as well as simple Trotter evolution workflow.
// Result is stored in a HetMap
using QsimResult = HeterogeneousMap;
// Abstract workflow:
class QsimWorkflow {
class QsimWorkflow : Identifiable {
public:
  virtual QsimResult execute() = 0;
  virtual bool initialize(const HeterogeneousMap &params) = 0;
  virtual QsimResult execute(const QsimModel &model) = 0;

protected:
  CostFunctionEvaluator *evaluator;
@@ -69,78 +116,8 @@ protected:

// Supported Workflow: to be defined and implemented
enum class WorkFlow { VQE, TD, PE };
// Quantum Simulation Model: to capture the problem description and the workflow.
// TODO: support high-level problem descriptions.
class QsimModel {
public:
  bool initialize(WorkFlow workflow_type,
                          const HeterogeneousMap &params);
  QsimWorkflow *get_workflow() { return qsim_workflow; }

private:
  WorkFlow type;
  QsimWorkflow *qsim_workflow;
};

// Generic model builder (factory)
// Create a model which capture the problem statement as well as the simulation
// workflow.
class ModelBuilder {
public:
  static QsimModel createModel(const HeterogeneousMap &params);
};

// TODO: create a service registry for these.
QsimWorkflow* getWorkflow(WorkFlow type, const HeterogeneousMap &init_params); 

// ========== Prototype Impl. ========================
// Example implementation:
class TrotterEvolution : public AnsatzGenerator {
public:
  Ansatz create_ansatz(Observable *obs,
                       const HeterogeneousMap &params) override;
};

// Estimate the cost function based on bitstring distribution,
// e.g. actual quantum hardware.
// Note: we can sub-class CostFunctionEvaluator to add post-processing or
// analysis of the result.
class BitCountExpectationEstimator : public CostFunctionEvaluator {
public:
  // Evaluate the cost
  virtual double
  evaluate(std::shared_ptr<CompositeInstruction> state_prep) override;

private:
  size_t nb_samples;
};

// VQE-type workflow which involves an optimization loop, i.e. an Optimizer.
class VqeWorkflow : public QsimWorkflow {
public:
  virtual QsimResult execute() override;
  virtual bool initialize(const HeterogeneousMap &params) override;

private:
  Optimizer *optimizer;
};

// Trotter time-dependent simulation workflow
// Time-dependent Hamiltonian is a function mapping from time t to Observable
// operator.
using TdObservable = std::function<PauliOperator(double)>;

// Time-dependent evolution workflow which can handle
// time-dependent Hamiltonian operator.
class TimeDependentWorkflow : public QsimWorkflow {
public:
  virtual QsimResult execute() override;
  virtual bool initialize(const HeterogeneousMap &params) override;
  static QsimWorkflow *getInstance();

private:
  static inline TimeDependentWorkflow *instance = nullptr;
  double t_0;
  double t_final;
  double dt;
  TdObservable ham_func;
};
} // namespace qcor
 No newline at end of file
+7 −12
Original line number Diff line number Diff line
#include "qcor_qsim.hpp"

#include "qsim_interfaces.hpp"
// TODO: fix so that this can be build w/ the QCOR compiler.
using namespace qcor;

// High-level usage of Model Builder
@@ -20,19 +20,14 @@ int main(int argc, char **argv) {
  // Observable = average magnetization
  auto observable = (1.0 / 3.0) * (Z(0) + Z(1) + Z(2));

  // Example: build model for Fig. 2 of
  // Example: build model and TD workflow for Fig. 2 of
  // https://journals.aps.org/prb/pdf/10.1103/PhysRevB.101.184305
  auto problemModel = ModelBuilder::createModel({{"protocol", "time-evolution"},
                                                 {"method", "trotter"},
                                                 {"dt", 3.0},
                                                 {"steps", 100},
                                                 {"hamiltonian", H},
                                                 {"observable", observable}});

  auto workflow = problemModel.get_workflow();
  auto problemModel = ModelBuilder::createModel(&observable, H);
  auto workflow = qcor::getWorkflow(
      WorkFlow::TD, {{"method", "trotter"}, {"dt", 3.0}, {"steps", 100}});

  // Result should contain the observable expectation value along Trotter steps.
  auto result = workflow->execute();
  auto result = workflow->execute(problemModel);

  return 0;
}