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

Merge branch 'master' into tnguyen/pyxasm-ftqc

parents 717dc509 30c988da
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -13,7 +13,7 @@ for hybrid quantum-classical programming.
Documentation
-------------

* [Website and Documentation](https://qcor.readthedocs.io)
* [Website and Documentation](https://aide-qc.github.io/deploy)
* [API Documentation](https://ornl-qci.github.io/qcor-api-docs/)

Quick Start
+27 −0
Original line number Diff line number Diff line
from qcor import *
from openfermion.ops import FermionOperator as FOp

# Define the quantum kernel by providing a 
# python function that is annotated with qjit for 
# quantum just in time compilation
@qjit
def ansatz(q: qreg, theta: List[float]):
    X(q[0])
    Ry(q[1], theta[0])
    CX(q[1], q[0])

# Define the hamiltonian
H = FOp('', 0.0002899) + FOp('0^ 0', -.43658) + \
    FOp('1 0^', 4.2866) + FOp('1^ 0', -4.2866) + FOp('1^ 1', 12.25) 

# Create the ObjectiveFunction, default is VQE
n_params = 1
obj = createObjectiveFunction(ansatz, H, n_params)

# evaluate at a concrete set of params
vqe_energy = obj([.59])

# Run full optimization
optimizer = createOptimizer('nlopt')
results = optimizer.optimize(obj)
print(results)
 No newline at end of file
+144 −0
Original line number Diff line number Diff line
@@ -93,6 +93,93 @@ xacc::HeterogeneousMap heterogeneousMapConvert(

  return result;
}

std::shared_ptr<qcor::Observable> convertToPauliOperator(py::object op) {
  if (py::hasattr(op, "terms")) {
    // this is from openfermion
    if (py::hasattr(op, "is_two_body_number_conserving")) {
      // This is a fermion Operator
      auto terms = op.attr("terms");
      // terms is a list of tuples
      std::stringstream ss;
      int i = 0;
      for (auto term : terms) {
        auto term_tuple = term.cast<py::tuple>();
        if (!term_tuple.empty()) {
          ss << terms[term].cast<std::complex<double>>() << " ";
          for (auto element : term_tuple) {
            auto element_pair = element.cast<std::pair<int, int>>();
            ss << element_pair.first << (element_pair.second ? "^" : "") << " ";
          }
        } else {
          // this was identity
          try {
            auto coeff = terms[term].cast<double>();
            ss << coeff;
          } catch (std::exception &e) {
            try {
              auto coeff = terms[term].cast<std::complex<double>>();
              ss << coeff;
            } catch (std::exception &e) {
              qcor::error(
                  "Could not cast identity coefficient to double or complex.");
            }
          }
        }
        i++;
        if (i != py::len(terms)) {
          ss << " + ";
        }
      }
      auto obs_tmp = qcor::createOperator("fermion", ss.str());
      return qcor::operatorTransform("jw", obs_tmp);

    } else {
      // this is a qubit  operator
      auto terms = op.attr("terms");
      // terms is a list of tuples
      std::stringstream ss;
      int i = 0;
      for (auto term : terms) {
        auto term_tuple = term.cast<py::tuple>();
        if (!term_tuple.empty()) {
          ss << terms[term].cast<std::complex<double>>() << " ";
          for (auto element : term_tuple) {
            auto element_pair = element.cast<std::pair<int, std::string>>();
            ss << element_pair.second << element_pair.first << " ";
          }
        } else {
          // this was identity

          try {
            auto coeff = terms[term].cast<double>();
            ss << coeff;
          } catch (std::exception &e) {
            try {
              auto coeff = terms[term].cast<std::complex<double>>();
              ss << coeff;
            } catch (std::exception &e) {
              qcor::error(
                  "Could not cast identity coefficient to double or complex.");
            }
          }
        }
        i++;
        if (i != py::len(terms)) {
          ss << " + ";
        }
      }
      return qcor::createOperator(ss.str());
    }
  } else {
    // throw an error
    std::cout << "THrowing an error\n";
    qcor::error(
        "Invalid python object passed as a QCOR Operator/Observable. "
        "Currently, we only accept OpenFermion datastructures.");
  }
}

}  // namespace

namespace qcor {
@@ -143,6 +230,31 @@ class PyObjectiveFunction : public qcor::ObjectiveFunction {
    qjit.write_cache();
  }

  PyObjectiveFunction(py::object q, std::shared_ptr<qcor::Observable> &qq,
                      const int n_dim, const std::string &helper_name,
                      xacc::HeterogeneousMap opts = {})
      : py_kernel(q) {
    // Set the OptFunction dimensions
    _dim = n_dim;

    // Set the helper objective
    helper = xacc::getService<qcor::ObjectiveFunction>(helper_name);

    // Store the observable pointer and give it to the helper
    observable = qq;
    options = opts;
    options.insert("observable", observable);
    helper->set_options(options);
    helper->update_observable(observable);

    // Extract the QJIT source code
    auto src = py_kernel.attr("get_internal_src")().cast<std::string>();

    // QJIT compile
    // this will be fast if already done, and we just do it once
    qjit.jit_compile(src, true);
    qjit.write_cache();
  }
  // Evaluate this ObjectiveFunction at the dictionary of kernel args,
  // return the scalar value
  double operator()(const KernelArgDict args, std::vector<double> &dx) {
@@ -368,6 +480,17 @@ PYBIND11_MODULE(_pyqcor, m) {
        return obj;
      },
      "");
  m.def(
      "createObjectiveFunction",
      [](py::object kernel, py::object &py_obs, const int n_params) {
        auto obs = convertToPauliOperator(py_obs);
        auto q = ::qalloc(obs->nBits());
        std::shared_ptr<qcor::ObjectiveFunction> obj =
            std::make_shared<qcor::PyObjectiveFunction>(kernel, obs, n_params,
                                                        "vqe");
        return obj;
      },
      "");
  m.def(
      "createObjectiveFunction",
      [](py::object kernel, qcor::PauliOperator &obs, const int n_params,
@@ -380,6 +503,19 @@ PYBIND11_MODULE(_pyqcor, m) {
        return obj;
      },
      "");
  m.def(
      "createObjectiveFunction",
      [](py::object kernel, py::object &py_obs, const int n_params,
         PyHeterogeneousMap &options) {
        auto nativeHetMap = heterogeneousMapConvert(options);
        auto obs = convertToPauliOperator(py_obs);
        auto q = ::qalloc(obs->nBits());
        std::shared_ptr<qcor::ObjectiveFunction> obj =
            std::make_shared<qcor::PyObjectiveFunction>(kernel, obs, n_params,
                                                        "vqe", nativeHetMap);
        return obj;
      },
      "");

  m.def(
      "createOperator",
@@ -422,6 +558,14 @@ PYBIND11_MODULE(_pyqcor, m) {
        return qcor::observe(kernel, obs, q);
      },
      "");
  m.def(
      "internal_observe",
      [](std::shared_ptr<CompositeInstruction> kernel, py::object obs) {
        auto observable = convertToPauliOperator(obs);
        auto q = ::qalloc(observable->nBits());
        return qcor::observe(kernel, observable, q);
      },
      "");

  // qsim sub-module bindings:
  {
+46 −0
Original line number Diff line number Diff line
import unittest
from qcor import *
try:
    from openfermion.ops import FermionOperator as FOp
    from openfermion.ops import QubitOperator as QOp
    from openfermion.transforms import reverse_jordan_wigner, jordan_wigner
    
    class TestOpenFermion(unittest.TestCase):
        def test_simple_fermion(self):
            # Create Operator as OpenFermion FermionOperator
            H = FOp('', 0.0002899) + FOp('0^ 0', -.43658) + \
                FOp('1 0^', 4.2866) + FOp('1^ 0', -4.2866) + FOp('1^ 1', 12.25) 
              
            @qjit
            def ansatz(q: qreg, theta: float):
                X(q[0])
                Ry(q[1], theta)
                CX(q[1], q[0])
        
            n_params = 1
            obj = createObjectiveFunction(ansatz, H, n_params, {'gradient-strategy':'parameter-shift'})
            optimizer = createOptimizer('nlopt', {'nlopt-optimizer':'l-bfgs'})
            results = optimizer.optimize(obj)
            self.assertAlmostEqual(results[0], -1.74, places=1) 

        def test_simple_qubit(self):
            # Create Operator as OpenFermion FermionOperator
            H = QOp('', 5.907) + QOp('Y0 Y1', -2.1433) + \
                QOp('X0 X1', -2.1433) + QOp('Z0', .21829) + QOp('Z1', -6.125) 
              
            @qjit
            def ansatz(q: qreg, theta: float):
                X(q[0])
                Ry(q[1], theta)
                CX(q[1], q[0])
        
            n_params = 1
            obj = createObjectiveFunction(ansatz, H, n_params, {'gradient-strategy':'parameter-shift'})
            optimizer = createOptimizer('nlopt', {'nlopt-optimizer':'l-bfgs'})
            results = optimizer.optimize(obj)
            self.assertAlmostEqual(results[0], -1.74, places=1) 
except:
    pass

if __name__ == '__main__':
  unittest.main()
 No newline at end of file
+44 −2
Original line number Diff line number Diff line
@@ -70,7 +70,49 @@ class TestQCORSpecAPI(unittest.TestCase):
        optimizer = createOptimizer('nlopt', {'nlopt-maxeval':20})
        opt_val, opt_params = optimizer.optimize(objective_function, 1)   
        self.assertAlmostEqual(opt_val, 0.0, places=1)
        self.assertAlmostEqual(opt_params[0], .5, places=1)

    def test_observe_openfermion(self):
        try:
            from openfermion.ops import FermionOperator as FOp
            from openfermion.ops import QubitOperator as QOp
            from openfermion.transforms import jordan_wigner

            H = FOp('', 0.0002899) + FOp('0^ 0', -.43658) + \
                FOp('1 0^', 4.2866) + FOp('1^ 0', -4.2866) + FOp('1^ 1', 12.25) 
            
            @qjit
            def ansatz(q : qreg, theta : float):
                X(q[0])
                Ry(q[1], theta)
                CX(q[1], q[0])
            
            target_energy = -1.74

            def objective_function(x):
                q = qalloc(2)
                energy = ansatz.observe(H, q, x[0])
                print(energy)
                return abs(target_energy - energy)

            optimizer = createOptimizer('nlopt', {'nlopt-maxeval':20})
            opt_val, opt_params = optimizer.optimize(objective_function, 1)   
            print(opt_val, opt_params)
            self.assertAlmostEqual(opt_val, 0.0, places=1)
 
            Hq = jordan_wigner(H)
            def objective_function2(x):
                q = qalloc(2)
                energy = ansatz.observe(Hq, q, x[0])
                print(energy)
                return abs(target_energy - energy)

            objective_function2([2.2])
            optimizer = createOptimizer('nlopt', {'nlopt-maxeval':20})
            opt_val, opt_params = optimizer.optimize(objective_function2, 1)   
            self.assertAlmostEqual(opt_val, 0.0, places=1)

        except:
            pass

    def test_operator(self):
        H = createOperator('-2.1433 X0X1 - 2.1433 Y0Y1 + .21829 Z0 - 6.125 Z1 + 5.907')
Loading