Loading README.md +1 −1 Original line number Diff line number Diff line Loading @@ -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 Loading python/examples/openfermion_integration.py 0 → 100644 +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 python/py-qcor.cpp +144 −0 Original line number Diff line number Diff line Loading @@ -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 { Loading Loading @@ -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) { Loading Loading @@ -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, Loading @@ -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", Loading Loading @@ -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: { Loading python/tests/test_openfermion_integration.py 0 → 100644 +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 python/tests/test_qcor_spec_api.py +44 −2 Original line number Diff line number Diff line Loading @@ -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 Loading
README.md +1 −1 Original line number Diff line number Diff line Loading @@ -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 Loading
python/examples/openfermion_integration.py 0 → 100644 +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
python/py-qcor.cpp +144 −0 Original line number Diff line number Diff line Loading @@ -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 { Loading Loading @@ -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) { Loading Loading @@ -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, Loading @@ -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", Loading Loading @@ -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: { Loading
python/tests/test_openfermion_integration.py 0 → 100644 +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
python/tests/test_qcor_spec_api.py +44 −2 Original line number Diff line number Diff line Loading @@ -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