https://github.com/jtiosue/qubovert
The one-stop package for formulating, simulating, and solving problems in boolean and spin form
https://github.com/jtiosue/qubovert
ising optimization quantum-computing qubo
Last synced: 2 months ago
JSON representation
The one-stop package for formulating, simulating, and solving problems in boolean and spin form
- Host: GitHub
- URL: https://github.com/jtiosue/qubovert
- Owner: jtiosue
- License: apache-2.0
- Created: 2019-07-16T00:10:25.000Z (over 6 years ago)
- Default Branch: master
- Last Pushed: 2024-12-21T04:08:40.000Z (over 1 year ago)
- Last Synced: 2025-11-20T22:03:31.939Z (4 months ago)
- Topics: ising, optimization, quantum-computing, qubo
- Language: Python
- Homepage: https://jtiosue.github.io/qubovert
- Size: 1.19 MB
- Stars: 41
- Watchers: 2
- Forks: 8
- Open Issues: 1
-
Metadata Files:
- Readme: README.rst
- License: LICENSE
Awesome Lists containing this project
- awesome-quantum-software - qubovert - Formulating and simulated annealing of Ising, QUBO, and higher order problems with constraints. (Quantum annealing)
README
qubovert
========
The one-stop package for formulating, simulating, and solving problems in boolean and spin form.
*master branch*
.. image:: https://github.com/jtiosue/qubovert/workflows/build/badge.svg?branch=master
:target: https://github.com/jtiosue/qubovert/actions?query=workflow%3Abuild+branch%3Amaster
:alt: GitHub Actions CI
.. image:: https://readthedocs.org/projects/qubovert/badge/?version=latest
:target: https://qubovert.readthedocs.io/en/latest/
:alt: Documentation Status
.. image:: https://codecov.io/gh/jtiosue/qubovert/branch/master/graph/badge.svg
:target: https://codecov.io/gh/jtiosue/qubovert/branch/master
:alt: Code Coverage
.. image:: https://img.shields.io/lgtm/grade/python/g/jtiosue/qubovert.svg?logo=lgtm&logoWidth=18
:target: https://lgtm.com/projects/g/jtiosue/qubovert/context:python
:alt: Code Quality
*dev branch*
.. image:: https://github.com/jtiosue/qubovert/workflows/build/badge.svg?branch=dev
:target: https://github.com/jtiosue/qubovert/actions?query=workflow%3Abuild+branch%3Adev
:alt: GitHub Actions CI
.. image:: https://readthedocs.org/projects/qubovert/badge/?version=dev
:target: https://qubovert.readthedocs.io/en/dev/
:alt: Documentation Status
.. image:: https://codecov.io/gh/jtiosue/qubovert/branch/dev/graph/badge.svg
:target: https://codecov.io/gh/jtiosue/qubovert/branch/dev
:alt: Code Coverage
*pypi distribution*
.. image:: https://badge.fury.io/py/qubovert.svg
:target: https://badge.fury.io/py/qubovert
:alt: pypi dist
.. image:: https://pepy.tech/badge/qubovert
:target: https://pepy.tech/project/qubovert
:alt: pypi dist downloads
Please see the `Repository `_ and `Docs `_. For examples/tutorials, see the `notebooks `_.
.. contents::
:local:
:backlinks: top
Installation
------------
For the stable release (same version as the *master* branch):
.. code:: shell
pip install qubovert
Or to install from source:
.. code:: shell
git clone https://github.com/jtiosue/qubovert.git
cd qubovert
pip install -e .
Then you can use it in Python **versions 3.6 and above** with
.. code:: python
import qubovert as qv
Note that to install from source on Windows you will need `Microsoft Visual C++ Build Tools 14 `_ installed.
Example of the typical workflow
-------------------------------
Here we show an example of formulating a pseudo-boolean objective function. We can also make spin objective functions (Hamiltonians) in a very similar manner. See the `notebooks `_ for examples.
Create the boolean objective function to minimize
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. code:: python
from qubovert import boolean_var
N = 10
# create the variables
x = {i: boolean_var('x(%d)' % i) for i in range(N)}
# minimize \sum_{i=0}^{N-2} (1-2x_{i}) x_{i+1}
model = 0
for i in range(N-1):
model += (1 - 2 * x[i]) * x[i+1]
# subject to the constraint that x_1 equals the XOR of x_3 and x_5
# enforce with a penalty factor of 3
model.add_constraint_eq_XOR(x[1], x[3], x[5], lam=3)
# subject to the constraints that the sum of all variables is less than 4
# enforce with a penalty factor of 5
model.add_constraint_lt_zero(sum(x.values()) - 4, lam=5)
Next we will show multiple ways to solve the model.
Solving the model with bruteforce
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Before using the bruteforce solver, always check that ``model.num_binary_variables`` is relatively small!
.. code:: python
model_solution = model.solve_bruteforce()
print("Variable assignment:", model_solution)
print("Model value:", model.value(model_solution))
print("Constraints satisfied?", model.is_solution_valid(model_solution))
Solving the model with *qubovert*'s simulated annealing
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Please see the definition of PUBO in the next section. We will anneal the PUBO.
.. code:: python
from qubovert.sim import anneal_pubo
res = anneal_pubo(model, num_anneals=10)
model_solution = res.best.state
print("Variable assignment:", model_solution)
print("Model value:", res.best.value)
print("Constraints satisfied?", model.is_solution_valid(model_solution))
Solving the model with D-Wave's simulated annealer
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
`D-Wave's simulated annealer `_ cannot anneal PUBOs as we did above. Instead the model must be reduced to a QUBO. See the next section for definitions of QUBO and PUBO.
.. code:: python
from neal import SimulatedAnnealingSampler
# Get the QUBO form of the model
qubo = model.to_qubo()
# D-Wave accept QUBOs in a different format than qubovert's format
# to get the qubo in this form, use the .Q property
dwave_qubo = qubo.Q
# solve with D-Wave
res = SimulatedAnnealingSampler().sample_qubo(dwave_qubo, num_reads=10)
qubo_solution = res.first.sample
# convert the qubo solution back to the solution to the model
model_solution = model.convert_solution(qubo_solution)
print("Variable assignment:", model_solution)
print("Model value:", model.value(model_solution))
print("Constraints satisfied?", model.is_solution_valid(model_solution))
Managing QUBO, QUSO, PUBO, PUSO, PCBO, and PCSO formulations
------------------------------------------------------------
*qubovert* defines, among many others, the following objects.
- QUBO: Quadratic Unconstrained Boolean Optimization (``qubovert.QUBO``)
- QUSO: Quadratic Unconstrained Spin Optimization (``qubovert.QUSO``)
- PUBO: Polynomial Unconstrained Boolean Optimization (``qubovert.PUBO``)
- PUSO: Polynomial Unconstrained Spin Optimization (``qubovert.PUSO``)
- PCBO: Polynomial Constrained Boolean Optimization (``qubovert.PCBO``)
- PCSO: Polynomial Constrained Spin Optimization (``qubovert.PCSO``)
Each of the objects has many methods and arbitary arithmetic defined; see the docstrings of each object and the `notebooks `_ for more info. A boolean optimization model is one whose variables can be assigned to be either 0 or 1, while a spin optimization model is one whose variables can be assigned to be either 1 or -1. The ``qubovert.boolean_var(name)`` function will create a PCBO representing the boolean variable with name ``name``. Similarly, the ``qubovert.spin_var(name)`` function will create a PCSO representing the spin variable with name ``name``.
There are many utilities in the *utils* library that can be helpful. Some examples of utility functions are listed here.
- ``qubovert.utils.solve_pubo_bruteforce``, solve a PUBO by iterating through all possible solutions.
- ``qubovert.utils.solve_puso_bruteforce``, solve a PUSO by iterating through all possible solutions.
- ``qubovert.utils.pubo_to_puso``, convert a PUBO to a PUSO.
- ``qubovert.utils.puso_to_pubo``, convert a PUSO to a PUBO.
- ``qubovert.utils.pubo_value``, determine the value that a PUBO takes with a particular solution mapping.
- ``qubovert.utils.puso_value``, determine the value that a PUSO takes with a particular solution mapping.
- ``qubovert.utils.approximate_pubo_extrema``, approximate the minimum and maximum values that a PUBO can take; the true extrema will lie within these bounds.
- ``qubovert.utils.approximate_puso_extrema``, approximate the minimum and maximum values that a PUSO can take; the true extrema will lie within these bounds.
- ``qubovert.utils.subgraph``, create the subgraph of a model that only contains certain given variables.
- ``qubovert.utils.subvalue``, create the submodel of a model with certain values of the model replaced with values.
- ``qubovert.utils.normalize``, normalize a model such that its coefficients have a maximum absolute magnitude.
See ``qubovert.utils.__all__`` for more. Please note that all conversions between boolean and spin map {0, 1} to/from {1, -1} in that order! This is the convention that *qubovert* uses everywhere.
The PCBO and PCSO objects have constraint methods; for example, the ``.add_constraint_le_zero`` method will enforce that an expression is less than or equal to zero by adding a penalty to the model whenever it does not. The PCBO object also has constraint methods for satisfiability expressions; for example, the ``.add_constraint_OR`` will enforce that the OR of the given boolean expression evaluates to True by adding a penalty to the model whenever it does not. See the docstrings and `notebooks `_ for more info.
For more utilities on satisfiability expressions, *qubovert* also has a *sat* library; see ``qubovert.sat.__all__``. Consider the following 3-SAT example. We have variables ``x0, x1, x2, x3``, labeled by ``0, 1, 2, 3``. We can create an expression ``C`` that evaluates to 1 whenever the 3-SAT conditions are satisfied.
.. code:: python
from qubovert.sat import AND, NOT, OR
C = AND(OR(0, 1, 2), OR(NOT(0), 2, NOT(3)), OR(NOT(1), NOT(2), 3))
# C = 1 for a satisfying assignment, C = 0 otherwise
# So minimizing -C will solve it.
P = -C
solution = P.solve_bruteforce()
Basic examples of common functionality
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
See the `notebooks `_ for many fully worked out examples. Here we will just show some basic and brief examples.
The basic building block of a binary optimization model is a Python dictionary. The keys of the dictionary are tuples of variable names, and the values are their corresponding coefficients. For example, in the below code block, ``model1``, ``model2``, and ``model3`` are equivalent.
.. code:: python
from qubovert import boolean_var, PUBO
x0, x1, x2 = boolean_var('x0'), boolean_var('x1'), boolean_var('x2')
model1 = -1 + x0 + 2 * x0 * x1 - 3 * x0 * x2 + x0 * x1 * x2
model2 = {(): -1, ('x0',): 1, ('x0', 'x1'): 2, ('x0', 'x2'): -3, ('x0', 'x1', 'x2'): 1}
model3 = PUBO(model2)
Similarly, in the below code block, ``model1``, ``model2``, and ``model3`` are equivalent.
.. code:: python
from qubovert import spin_var, PUSO
z0, z1, z2 = spin_var('z0'), spin_var('z1'), spin_var('z2')
model1 = -1 + z0 + 2 * z0 * z1 - 3 * z0 * z2 + z0 * z1 * z2
model2 = {(): -1, ('z0',): 1, ('z0', 'z1'): 2, ('z0', 'z2'): -3, ('z0', 'z1', 'z2'): 1}
model3 = PUSO(model2)
Let's take the same model from above (ie define :code:`model = model1.copy()`). Suppose we want to find the ground state of the model subject to the constraints that the sum of the variables is negative and that the product of ``z0`` and ``z1`` is 1. We have to enforce these constraints with a penalty called ``lam``. For now, let's set it as a Symbol that we can adjust later.
.. code:: python
from sympy import Symbol
lam = Symbol('lam')
model.add_constraint_lt_zero(z0 + z1 + z2, lam=lam)
model.add_constraint_eq_zero(z0 * z1 - 1, lam=lam)
Note that constraint methods can also be strung together if you want. So we could have written this as
.. code:: python
model.add_constraint_lt_zero(
z0 + z1 + z2, lam=lam
).add_constraint_eq_zero(
z0 * z1 - 1, lam=lam
)
The first thing you notice if you :code:`print(model.variables)` is that there are now new variables in the model called ``'__a0'`` and ``'__a1'``. These are auxillary or *ancilla* variables that are needed to enforce the constraints. The next thing to notice if you :code:`print(model.degree)` is that the model is a polynomial of degree 3. Many solvers (for example D-Wave's solvers) only solve degree 2 models. To get a QUBO or QUSO (which are degree two modes) from ``model``, simply call the ``.to_qubo`` or ``.to_quso`` methods, which will reduce the degree to 2 by introducing more variables.
.. code:: python
qubo = model.to_qubo()
quso = model.to_quso()
Next let's solve the QUBO and/or QUSO formulations. First we have to substitute a value in for our placeholder symbol ``lam`` that is used to enforce the constraints. We'll just use ``lam=3`` for now.
.. code:: python
qubo = qubo.subs({lam: 3})
quso = quso.subs({lam: 3})
Here we will use `D-Wave's simulated annealer `_.
.. code:: python
from neal import SimulatedAnnealingSampler
# D-Wave represents QUBOs a little differently than qubovert does.
# to get D-Wave's form, use the .Q property
dwave_qubo = qubo.Q
# D-Wave represents QUSOs a little differently than qubovert does.
# to get D-Wave's form, use the .h property the linear terms and the
# .J property for the quadratic terms
dwave_linear, dwave_quadratic = quso.h, quso.J
# call dwave
qubo_res = SimulatedAnnealingSampler().sample_qubo(dwave_qubo)
quso_res = SimulatedAnnealingSampler().sample_ising(dwave_linear, dwave_quadratic)
qubo_solution = qubo_res.first.sample
quso_solution = quso_res.first.sample
Now we have to convert the solution in terms of the QUBO/QUSO variables back to a solution in terms of the original variables. We can then check if the proposed solution satisfies all of the constraints!
.. code:: python
converted_qubo_solution = model.convert_solution(qubo_solution)
print(model.is_solution_valid(converted_qubo_solution))
converted_quso_solution = model.convert_solution(quso_solution)
print(model.is_solution_valid(converted_quso_solution))
Convert common problems to quadratic form (the *problems* library)
------------------------------------------------------------------
One of the goals of *qubovert* is to become a large collection of problems mapped to QUBO and QUSO forms in order to aid the recent increase in study of these problems due to quantum optimization algorithms. Use Python's ``help`` function! I have very descriptive doc strings on all the functions and classes. Please see the `notebooks `_ for a few more examples as well.
See the following Set Cover example.
.. code:: python
from qubovert.problems import SetCover
from any_module import qubo_solver
# or you can use my bruteforce solver...
# from qubovert.utils import solve_qubo_bruteforce as qubo_solver
U = {"a", "b", "c", "d"}
V = [{"a", "b"}, {"a", "c"}, {"c", "d"}]
problem = SetCover(U, V)
Q = problem.to_qubo()
obj, sol = qubo_solver(Q)
solution = problem.convert_solution(sol)
print(solution)
# {0, 2}
print(problem.is_solution_valid(solution))
# will print True, since V[0] + V[2] covers all of U
print(obj == len(solution))
# will print True
To use the QUSO formulation instead:
.. code:: python
from qubovert.problems import SetCover
from any_module import quso_solver
# or you can use my bruteforce solver...
# from qubovert.utils import solve_quso_bruteforce as quso_solver
U = {"a", "b", "c", "d"}
V = [{"a", "b"}, {"a", "c"}, {"c", "d"}]
problem = SetCover(U, V)
L = problem.to_quso()
obj, sol = quso_solver(L)
solution = problem.convert_solution(sol)
print(solution)
# {0, 2}
print(problem.is_solution_valid(solution))
# will print True, since V[0] + V[2] covers all of U
print(obj == len(solution))
# will print True
To see problem specifics, run
.. code:: python
help(qubovert.problems.SetCover)
help(qubovert.problems.VertexCover)
# etc
====
.. image:: https://raw.githubusercontent.com/jtiosue/qubovert/master/assets/qvfire.png