Unverified Commit 77efa7d7 authored by Mccaskey, Alex's avatar Mccaskey, Alex Committed by GitHub
Browse files

Merge pull request #188 from tnguyen-ornl/tnguyen/qaoa

QAOA Algo. Plugin
parents 40915483 e6e6b8f7
Pipeline #101917 failed with stage
in 8 minutes and 45 seconds
......@@ -943,6 +943,125 @@ or in Python
F = qpt.calculate('fidelity', buffer, {'chi-theoretical-real':[0., 0., 0., 0., 0., 1., 0., 1., 0., 0., 0., 0., 0., 1., 0., 1.]})
print('\nFidelity: ', F)
QAOA
++++
The QAOA Algorithm requires the following input information:
+------------------------+-----------------------------------------------------------------+--------------------------------------+
| Algorithm Parameter | Parameter Description | type |
+========================+=================================================================+======================================+
| observable | The hermitian operator represents the cost Hamiltonian. | std::shared_ptr<Observable> |
+------------------------+-----------------------------------------------------------------+--------------------------------------+
| optimizer | The classical optimizer to use | std::shared_ptr<Optimizer> |
+------------------------+-----------------------------------------------------------------+--------------------------------------+
| accelerator | The Accelerator backend to target | std::shared_ptr<Accelerator> |
+------------------------+-----------------------------------------------------------------+--------------------------------------+
| steps | The number of timesteps. Corresponds to 'p' in the literature. | int |
| | This is optional, default = 1 if not provided. | |
+------------------------+-----------------------------------------------------------------+--------------------------------------+
This Algorithm will add ``opt-val`` (``double``) and ``opt-params`` (``std::vector<double>``) to the provided ``AcceleratorBuffer``.
The results of the algorithm are therefore retrieved via these keys (see snippet below). Note you can
control the initial QAOA parameters with the ``Optimizer`` ``initial-parameters`` key (by default all zeros).
.. code:: cpp
#include "xacc.hpp"
#include "xacc_observable.hpp"
#include "xacc_service.hpp"
#include <random>
// Use XACC built-in QAOA to solve a QUBO problem
// QUBO function:
// y = -5x1 - 3x2 - 8x3 - 6x4 + 4x1x2 + 8x1x3 + 2x2x3 + 10x3x4
int main(int argc, char **argv) {
xacc::Initialize(argc, argv);
// Use the Qpp simulator as the accelerator
auto acc = xacc::getAccelerator("qpp");
auto buffer = xacc::qalloc(4);
// The corresponding QUBO Hamiltonian is:
auto observable = xacc::quantum::getObservable(
"pauli",
std::string("-5.0 - 0.5 Z0 - 1.0 Z2 + 0.5 Z3 + 1.0 Z0 Z1 + 2.0 Z0 Z2 + 0.5 Z1 Z2 + 2.5 Z2 Z3"));
const int nbSteps = 12;
const int nbParams = nbSteps*11;
std::vector<double> initialParams;
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_real_distribution<> dis(-2.0, 2.0);
// Init random parameters
for (int i = 0; i < nbParams; ++i)
{
initialParams.emplace_back(dis(gen));
}
auto optimizer = xacc::getOptimizer("nlopt",
xacc::HeterogeneousMap {
std::make_pair("initial-parameters", initialParams),
std::make_pair("nlopt-maxeval", nbParams*100) });
auto qaoa = xacc::getService<xacc::Algorithm>("QAOA");
const bool initOk = qaoa->initialize({
std::make_pair("accelerator", acc),
std::make_pair("optimizer", optimizer),
std::make_pair("observable", observable),
// number of time steps (p) param
std::make_pair("steps", nbSteps)
});
qaoa->execute(buffer);
std::cout << "Min QUBO: " << (*buffer)["opt-val"].as<double>() << "\n";
}
In Python:
.. code:: python
import xacc,sys, numpy as np
# Get access to the desired QPU and
# allocate some qubits to run on
qpu = xacc.getAccelerator('qpp')
# Construct the Hamiltonian as an XACC PauliOperator
# This Hamiltonian corresponds to the QUBO problem:
# y = -5x_1 -3x_2 -8x_3 -6x_4 + 4x_1x_2 + 8x_1x_3 + 2x_2x_3 + 10x_3x_4
ham = xacc.getObservable('pauli', '-5.0 - 0.5 Z0 - 1.0 Z2 + 0.5 Z3 + 1.0 Z0 Z1 + 2.0 Z0 Z2 + 0.5 Z1 Z2 + 2.5 Z2 Z3')
# We need 4 qubits
buffer = xacc.qalloc(4)
# There are 7 gamma terms (non-identity) in the cost Hamiltonian
# and 4 beta terms for mixer Hamiltonian
nbParamsPerStep = 7 + 4
# The number of steps (often referred to as 'p' parameter):
# alternating layers of mixer and cost Hamiltonian exponential.
nbSteps = 4
# Total number of params
nbTotalParams = nbParamsPerStep * nbSteps
# Init params randomly:
initParams = np.random.rand(nbTotalParams)
# The optimizer: nlopt
opt = xacc.getOptimizer('nlopt', { 'initial-parameters': initParams })
# Create the QAOA algorithm
qaoa = xacc.getAlgorithm('QAOA', {
'accelerator': qpu,
'observable': ham,
'optimizer': opt,
'steps': nbSteps
})
result = qaoa.execute(buffer)
print('Min QUBO value = ', buffer.getInformation('opt-val'))
Accelerator Decorators
----------------------
......
import xacc,sys, numpy as np
# Get access to the desired QPU and
# allocate some qubits to run on
qpu = xacc.getAccelerator('qpp')
# Construct the Hamiltonian as an XACC PauliOperator
# This Hamiltonian corresponds to the QUBO problem:
# y = -5x_1 -3x_2 -8x_3 -6x_4 + 4x_1x_2 + 8x_1x_3 + 2x_2x_3 + 10x_3x_4
ham = xacc.getObservable('pauli', '-5.0 - 0.5 Z0 - 1.0 Z2 + 0.5 Z3 + 1.0 Z0 Z1 + 2.0 Z0 Z2 + 0.5 Z1 Z2 + 2.5 Z2 Z3')
# We need 4 qubits
buffer = xacc.qalloc(4)
# There are 7 gamma terms (non-identity) in the cost Hamiltonian
# and 4 beta terms for mixer Hamiltonian
nbParamsPerStep = 7 + 4
# The number of steps (often referred to as 'p' parameter):
# alternating layers of mixer and cost Hamiltonian exponential.
nbSteps = 4
# Total number of params
nbTotalParams = nbParamsPerStep * nbSteps
# Init params randomly:
initParams = np.random.rand(nbTotalParams)
# The optimizer: nlopt
opt = xacc.getOptimizer('nlopt', { 'initial-parameters': initParams })
# Create the QAOA algorithm
qaoa = xacc.getAlgorithm('QAOA', {
'accelerator': qpu,
'observable': ham,
'optimizer': opt,
'steps': nbSteps
})
result = qaoa.execute(buffer)
# Expected result: ~ -11
# Ref: https://docs.entropicalabs.io/qaoa/notebooks/6_solvingqubowithqaoa
print('Min QUBO value = ', buffer.getInformation('opt-val'))
......@@ -32,3 +32,6 @@ target_link_libraries(rotoselect PRIVATE xacc xacc-quantum-gate)
add_executable(nah_ucc3 nah_ucc3_psi4.cpp)
target_link_libraries(nah_ucc3 PRIVATE xacc xacc-quantum-gate)
add_executable(qaoa_qubo solve_qubo_with_qaoa.cpp)
target_link_libraries(qaoa_qubo PRIVATE xacc xacc-quantum-gate)
/*******************************************************************************
* Copyright (c) 2020 UT-Battelle, LLC.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Eclipse Distribution License v1.0 which accompanies this
* distribution. The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html and the Eclipse Distribution
*License is available at https://eclipse.org/org/documents/edl-v10.php
*
* Contributors:
* Thien Nguyen - initial API and implementation
*******************************************************************************/
#include "xacc.hpp"
#include "xacc_observable.hpp"
#include "xacc_service.hpp"
#include <random>
// Use XACC built-in QAOA to solve a QUBO problem
// QUBO function:
// y = -5x1 - 3x2 - 8x3 - 6x4 + 4x1x2 + 8x1x3 + 2x2x3 + 10x3x4
// Adapted from https://docs.entropicalabs.io/qaoa/notebooks/6_solvingqubowithqaoa
int main(int argc, char **argv) {
xacc::Initialize(argc, argv);
// Use the Qpp simulator as the accelerator
auto acc = xacc::getAccelerator("qpp");
auto buffer = xacc::qalloc(4);
// The corresponding QUBO Hamiltonian is:
auto observable = xacc::quantum::getObservable(
"pauli",
std::string("-5.0 - 0.5 Z0 - 1.0 Z2 + 0.5 Z3 + 1.0 Z0 Z1 + 2.0 Z0 Z2 + 0.5 Z1 Z2 + 2.5 Z2 Z3"));
const int nbSteps = 12;
const int nbParams = nbSteps*11;
std::vector<double> initialParams;
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_real_distribution<> dis(-2.0, 2.0);
// Init random parameters
for (int i = 0; i < nbParams; ++i)
{
initialParams.emplace_back(dis(gen));
}
auto optimizer = xacc::getOptimizer("nlopt",
xacc::HeterogeneousMap {
std::make_pair("initial-parameters", initialParams),
// Scale the number of iters by the number of params
// to guarantee convergence.
std::make_pair("nlopt-maxeval", nbParams*100) });
auto qaoa = xacc::getService<xacc::Algorithm>("QAOA");
const bool initOk = qaoa->initialize({
std::make_pair("accelerator", acc),
std::make_pair("optimizer", optimizer),
std::make_pair("observable", observable),
// number of time steps (p) param
std::make_pair("steps", nbSteps)
});
qaoa->execute(buffer);
// Expected -11 (correct answer is 1001)
// y(1001) = -5x1 - 3x2 - 8x3 - 6x4 + 4x1x2 + 8x1x3 + 2x2x3 + 10x3x4
// = -11
std::cout << "Min QUBO: " << (*buffer)["opt-val"].as<double>() << "\n";
}
\ No newline at end of file
......@@ -15,6 +15,7 @@ add_subdirectory(rdm)
add_subdirectory(ml)
add_subdirectory(rotoselect)
add_subdirectory(qpt)
add_subdirectory(qaoa)
file(GLOB PYDECORATORS ${CMAKE_CURRENT_SOURCE_DIR}/vqe/python/*.py
${CMAKE_CURRENT_SOURCE_DIR}/ml/ddcl/python/*.py)
......
# *******************************************************************************
# Copyright (c) 2019 UT-Battelle, LLC.
# All rights reserved. This program and the accompanying materials
# are made available under the terms of the Eclipse Public License v1.0
# and Eclipse Distribution License v.10 which accompany this distribution.
# The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
# and the Eclipse Distribution License is available at
# https://eclipse.org/org/documents/edl-v10.php
#
# Contributors:
# Thien Nguyen - initial API and implementation
# *******************************************************************************/
set(LIBRARY_NAME xacc-algorithm-qaoa)
file(GLOB SRC *.cpp)
usfunctiongetresourcesource(TARGET ${LIBRARY_NAME} OUT SRC)
usfunctiongeneratebundleinit(TARGET ${LIBRARY_NAME} OUT SRC)
add_library(${LIBRARY_NAME} SHARED ${SRC})
target_include_directories(
${LIBRARY_NAME}
PUBLIC .)
target_link_libraries(${LIBRARY_NAME} PUBLIC xacc CppMicroServices xacc-quantum-gate)
set(_bundle_name xacc_algorithm_qaoa)
set_target_properties(${LIBRARY_NAME}
PROPERTIES COMPILE_DEFINITIONS
US_BUNDLE_NAME=${_bundle_name}
US_BUNDLE_NAME
${_bundle_name})
usfunctionembedresources(TARGET
${LIBRARY_NAME}
WORKING_DIRECTORY
${CMAKE_CURRENT_SOURCE_DIR}
FILES
manifest.json)
if(APPLE)
set_target_properties(${LIBRARY_NAME}
PROPERTIES INSTALL_RPATH "@loader_path/../lib")
set_target_properties(${LIBRARY_NAME}
PROPERTIES LINK_FLAGS "-undefined dynamic_lookup")
else()
set_target_properties(${LIBRARY_NAME}
PROPERTIES INSTALL_RPATH "$ORIGIN/../lib")
set_target_properties(${LIBRARY_NAME} PROPERTIES LINK_FLAGS "-shared")
endif()
if(XACC_BUILD_TESTS)
add_subdirectory(tests)
endif()
install(TARGETS ${LIBRARY_NAME} DESTINATION ${CMAKE_INSTALL_PREFIX}/plugins)
/*******************************************************************************
* Copyright (c) 2019 UT-Battelle, LLC.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Eclipse Distribution License v1.0 which accompanies this
* distribution. The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html and the Eclipse Distribution
*License is available at https://eclipse.org/org/documents/edl-v10.php
*
* Contributors:
* Thien Nguyen - initial API and implementation
*******************************************************************************/
#include "qaoa.hpp"
#include "cppmicroservices/BundleActivator.h"
#include "cppmicroservices/BundleContext.h"
#include "cppmicroservices/ServiceProperties.h"
#include <memory>
#include <set>
using namespace cppmicroservices;
namespace {
/**
*/
class US_ABI_LOCAL QAOAActivator : public BundleActivator {
public:
QAOAActivator() {}
/**
*/
void Start(BundleContext context) {
auto c = std::make_shared<xacc::algorithm::QAOA>();
context.RegisterService<xacc::Algorithm>(c);
}
/**
*/
void Stop(BundleContext /*context*/) {}
};
} // namespace
CPPMICROSERVICES_EXPORT_BUNDLE_ACTIVATOR(QAOAActivator)
{
"bundle.symbolic_name" : "xacc_algorithm_qaoa",
"bundle.activator" : true,
"bundle.name" : "XACC QAOA Algorithm",
"bundle.description" : ""
}
/*******************************************************************************
* Copyright (c) 2019 UT-Battelle, LLC.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Eclipse Distribution License v1.0 which accompanies this
* distribution. The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html and the Eclipse Distribution
*License is available at https://eclipse.org/org/documents/edl-v10.php
*
* Contributors:
* Thien Nguyen - initial API and implementation
*******************************************************************************/
#include "qaoa.hpp"
#include "xacc.hpp"
#include "xacc_service.hpp"
#include "xacc_observable.hpp"
#include "Circuit.hpp"
#include <cassert>
#include <iomanip>
namespace xacc {
namespace algorithm {
bool QAOA::initialize(const HeterogeneousMap& parameters)
{
bool initializeOk = true;
// Hyper-parameters for QAOA:
// (1) Accelerator (QPU)
if (!parameters.pointerLikeExists<Accelerator>("accelerator"))
{
std::cout << "'accelerator' is required.\n";
// We check all required params; hence don't early return on failure.
initializeOk = false;
}
// (2) Classical optimizer
if (!parameters.pointerLikeExists<Optimizer>("optimizer"))
{
std::cout << "'optimizer' is required.\n";
initializeOk = false;
}
// (3) Number of mixing and cost function steps to use (default = 1)
m_nbSteps = 1;
if (parameters.keyExists<int>("steps"))
{
m_nbSteps = parameters.get<int>("steps");
}
// (4) Cost Hamiltonian
if (!parameters.pointerLikeExists<Observable>("observable"))
{
std::cout << "'observable' is required.\n";
initializeOk = false;
}
// (5) Reference Hamiltonian: optional.
// Default is X0 + X1 + ...
// i.e. the X-basis where |+>|+>... is the ground state.
m_refHam.clear();
if (parameters.keyExists<std::vector<std::string>>("ref-ham"))
{
m_refHam = parameters.get<std::vector<std::string>>("ref-ham");
}
if (initializeOk)
{
m_costHamObs = parameters.getPointerLike<Observable>("observable");
// Add cost Hamiltonian terms to the list of terms for gamma exp
m_costHam.clear();
for (const auto& term : m_costHamObs->getNonIdentitySubTerms())
{
std::string pauliTermStr = term->toString();
// HACK: the Pauli parser doesn't like '-0', i.e. extra minus sign on integer (no decimal point)
// hence, just reformat it.
std::stringstream s;
s.precision(12);
s << std::fixed << term->coefficient();
// Find the parenthesis
const auto startPosition = pauliTermStr.find("(");
const auto endPosition = pauliTermStr.find(")");
if (startPosition != std::string::npos && endPosition != std::string::npos)
{
const auto length = endPosition - startPosition + 1;
pauliTermStr.replace(startPosition, length, s.str());
}
m_costHam.emplace_back(pauliTermStr);
}
m_qpu = parameters.getPointerLike<Accelerator>("accelerator");
m_optimizer = parameters.getPointerLike<Optimizer>("optimizer");
}
return initializeOk;
}
const std::vector<std::string> QAOA::requiredParameters() const
{
return { "accelerator", "optimizer", "observable" };
}
std::shared_ptr<CompositeInstruction> QAOA::constructParameterizedKernel(const std::shared_ptr<AcceleratorBuffer>& in_buffer) const
{
auto gateRegistry = xacc::getService<xacc::IRProvider>("quantum");
const auto nbQubits = in_buffer->size();
auto qaoaKernel = gateRegistry->createComposite("qaoaKernel");
// Hadamard layer
for (size_t i = 0; i < nbQubits; ++i)
{
qaoaKernel->addInstruction(gateRegistry->createInstruction("H", { i }));
}
// Trotter layers (parameterized): mixing b/w cost and drive (reference) Hamiltonian
int betaParamCounter = 0;
int gammaParamCounter = 0;
for (size_t i = 0; i < m_nbSteps; ++i)
{
for (const auto& term : m_costHam)
{
auto expCirc = std::dynamic_pointer_cast<xacc::quantum::Circuit>(xacc::getService<Instruction>("exp_i_theta"));
const std::string paramName = "gamma" + std::to_string(gammaParamCounter++);
expCirc->addVariable(paramName);
expCirc->expand({ std::make_pair("pauli", term) });
qaoaKernel->addVariable(paramName);
qaoaKernel->addInstructions(expCirc->getInstructions());
}
// Beta params:
// If no drive/reference Hamiltonian is given,
// then assume the default X0 + X1 + ...
std::vector<std::string> refHamTerms(m_refHam);
if (refHamTerms.empty())
{
for (size_t qId = 0; qId < nbQubits; ++qId)
{
refHamTerms.emplace_back("X" + std::to_string(qId));
}
}
for (const auto& term : refHamTerms)
{
auto expCirc = std::dynamic_pointer_cast<xacc::quantum::Circuit>(xacc::getService<Instruction>("exp_i_theta"));
const std::string paramName = "beta" + std::to_string(betaParamCounter++);
expCirc->addVariable(paramName);
expCirc->expand({ std::make_pair("pauli", term) });
qaoaKernel->addVariable(paramName);
qaoaKernel->addInstructions(expCirc->getInstructions());
}
}
return qaoaKernel;
}
void QAOA::execute(const std::shared_ptr<AcceleratorBuffer> buffer) const
{
const int nbQubits = buffer->size();
auto kernel = constructParameterizedKernel(buffer);
// Observe the cost Hamiltonian:
auto kernels = m_costHamObs->observe(kernel);
int iterCount = 0;
// Construct the optimizer/minimizer:
OptFunction f(
[&, this](const std::vector<double>& x, std::vector<double>& dx) {
std::vector<double> coefficients;
std::vector<std::string> kernelNames;
std::vector<std::shared_ptr<CompositeInstruction>> fsToExec;
double identityCoeff = 0.0;
for (auto& f : kernels)
{
kernelNames.push_back(f->name());
std::complex<double> coeff = f->getCoefficient();
int nFunctionInstructions = 0;
if (f->getInstruction(0)->isComposite())
{
nFunctionInstructions = kernel->nInstructions() + f->nInstructions() - 1;
}
else
{
nFunctionInstructions = f->nInstructions();
}
if (nFunctionInstructions > kernel->nInstructions())
{
auto evaled = f->operator()(x);
fsToExec.push_back(evaled);
coefficients.push_back(std::real(coeff));
}
else
{
identityCoeff += std::real(coeff);
}
}
auto tmpBuffer = xacc::qalloc(buffer->size());
m_qpu->execute(tmpBuffer, fsToExec);
auto buffers = tmpBuffer->getChildren();
double energy = identityCoeff;
auto idBuffer = xacc::qalloc(buffer->size());
idBuffer->addExtraInfo("coefficient", identityCoeff);
idBuffer->setName("I");
idBuffer->addExtraInfo("kernel", "I");
idBuffer->addExtraInfo("parameters", x);
idBuffer->addExtraInfo("exp-val-z", 1.0);
buffer->appendChild("I", idBuffer);
for (int i = 0; i < buffers.size(); i++)
{
auto expval = buffers[i]->getExpectationValueZ();
energy += expval * coefficients[i];
buffers[i]->addExtraInfo("coefficient", coefficients[i]);
buffers[i]->addExtraInfo("kernel", fsToExec[i]->name());
buffers[i]->addExtraInfo("exp-val-z", expval);
buffers[i]->addExtraInfo("parameters", x);
buffer->appendChild(fsToExec[i]->name(), buffers[i]);
}
std::stringstream ss;
iterCount++;
ss << "Iter " << iterCount << ": E(" << ( !x.empty() ? std::to_string(x[0]) : "");
for (int i = 1; i < x.size(); i++)
{