Skip to content

Commit

Permalink
v1.2.2 (#30)
Browse files Browse the repository at this point in the history
* Recreated dev branch and incremented version

* sped up value functions

* updated demo notebook

* added to_enumerated, anneal warning, sum, lam=0

* sum function to docs
  • Loading branch information
jtiosue authored Jul 4, 2020
1 parent beaea39 commit e60181d
Show file tree
Hide file tree
Showing 20 changed files with 957 additions and 180 deletions.
2 changes: 2 additions & 0 deletions docs/utils/methods.rst
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ Approximate Extrema
Useful functions
----------------

.. autofunction:: qubovert.utils.sum

.. autofunction:: qubovert.utils.subgraph

.. autofunction:: qubovert.utils.subvalue
Expand Down
350 changes: 247 additions & 103 deletions notebook_examples/qubovert_tutorial.ipynb

Large diffs are not rendered by default.

12 changes: 12 additions & 0 deletions qubovert/_pcbo.py
Original file line number Diff line number Diff line change
Expand Up @@ -730,6 +730,8 @@ def add_constraint_eq_zero(self,
"""
P = PUBO(P)
self._append_constraint("eq", P)
if not lam:
return self

if _special_constraints_eq_zero(self, P, lam):
return self
Expand Down Expand Up @@ -849,6 +851,8 @@ def add_constraint_ne_zero(self,
"""
P = PUBO(P)
self._append_constraint("ne", P)
if not lam:
return self

min_val, max_val = _get_bounds(P, bounds)

Expand Down Expand Up @@ -984,6 +988,8 @@ def add_constraint_lt_zero(self,
"""
P = PUBO(P)
self._append_constraint("lt", P)
if not lam:
return self

min_val, max_val = _get_bounds(P, bounds)

Expand Down Expand Up @@ -1100,6 +1106,8 @@ def add_constraint_le_zero(self,
"""
P = PUBO(P)
self._append_constraint("le", P)
if not lam:
return self

bounds = min_val, max_val = _get_bounds(P, bounds)
if _special_constraints_le_zero(self, P, lam, log_trick, bounds):
Expand Down Expand Up @@ -1216,6 +1224,8 @@ def add_constraint_gt_zero(self,
"""
P = PUBO(P)
self._append_constraint("gt", P)
if not lam:
return self
min_val, max_val = _get_bounds(P, bounds)
bounds = -max_val, -min_val
self.add_constraint_lt_zero(
Expand Down Expand Up @@ -1320,6 +1330,8 @@ def add_constraint_ge_zero(self,
"""
P = PUBO(P)
self._append_constraint("ge", P)
if not lam:
return self
min_val, max_val = _get_bounds(P, bounds)
bounds = -max_val, -min_val
self.add_constraint_le_zero(
Expand Down
12 changes: 12 additions & 0 deletions qubovert/_pcso.py
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,8 @@ def add_constraint_eq_zero(self,
"""
H = PUSO(H)
self._append_constraint("eq", H)
if not lam:
return self

h = _empty_pcbo(self).add_constraint_eq_zero(
puso_to_pubo(H), lam=lam,
Expand Down Expand Up @@ -463,6 +465,8 @@ def add_constraint_ne_zero(self,
"""
H = PUSO(H)
self._append_constraint("ne", H)
if not lam:
return self
h = _empty_pcbo(self).add_constraint_ne_zero(
puso_to_pubo(H), lam=lam, log_trick=log_trick,
bounds=bounds, suppress_warnings=suppress_warnings
Expand Down Expand Up @@ -540,6 +544,8 @@ def add_constraint_lt_zero(self,
"""
H = PUSO(H)
self._append_constraint("lt", H)
if not lam:
return self
h = _empty_pcbo(self).add_constraint_lt_zero(
puso_to_pubo(H), lam=lam, log_trick=log_trick,
bounds=bounds, suppress_warnings=suppress_warnings
Expand Down Expand Up @@ -620,6 +626,8 @@ def add_constraint_le_zero(self,
"""
H = PUSO(H)
self._append_constraint("le", H)
if not lam:
return self
h = _empty_pcbo(self).add_constraint_le_zero(
puso_to_pubo(H), lam=lam, log_trick=log_trick,
bounds=bounds, suppress_warnings=suppress_warnings
Expand Down Expand Up @@ -696,6 +704,8 @@ def add_constraint_gt_zero(self,
"""
H = PUSO(H)
self._append_constraint("gt", H)
if not lam:
return self
h = _empty_pcbo(self).add_constraint_gt_zero(
puso_to_pubo(H), lam=lam, log_trick=log_trick,
bounds=bounds, suppress_warnings=suppress_warnings
Expand Down Expand Up @@ -775,6 +785,8 @@ def add_constraint_ge_zero(self,
"""
H = PUSO(H)
self._append_constraint("ge", H)
if not lam:
return self
h = _empty_pcbo(self).add_constraint_ge_zero(
puso_to_pubo(H), lam=lam, log_trick=log_trick,
bounds=bounds, suppress_warnings=suppress_warnings
Expand Down
48 changes: 47 additions & 1 deletion qubovert/_puso.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"""

from .utils import BO, PUSOMatrix, puso_to_pubo
from .utils import BO, PUSOMatrix, puso_to_pubo, QUSOMatrix
from . import QUSO


Expand Down Expand Up @@ -351,6 +351,52 @@ def to_qubo(self, lam=None, pairs=None):
"""
return self._create_pubo().to_qubo(lam, pairs)

def to_quso(self, lam=None, pairs=None):
"""to_quso.
Create and return upper triangular QUSO representing the problem.
The labels will be integers from 0 to n-1. We introduce ancilla
variables in order to reduce the degree of the PUSO to a QUSO. The
solution to the PUSO can be read from the solution to the QUSO by
using the ``convert_solution`` method.
Parameters
----------
lam : function (optional, defaults to None).
If ``lam`` is None, the function ``PUBO.default_lam`` will be used.
``lam`` is the penalty factor to introduce in order to enforce the
ancilla constraints. When we reduce the degree of the PUSO to a
QUSO, we add penalties to the QUSO in order to enforce ancilla
variable constraints. These constraints will be multiplied by
``lam(v)``, where ``v`` is the value associated with the term that
it is reducing. For example, a term ``(0, 1, 2): 3`` in the PUSO
may be reduced to a term ``(0, 3): 3`` for the QUSO, and then the
fact that ``3`` should be the product of ``1`` and ``2`` will be
enforced with a penalty weight ``lam(3)``.
pairs : set (optional, defaults to None).
A set of tuples of variable pairs to prioritize pairing together in
to degree reduction. If a pair in ``pairs`` is found together in
the PUSO, it will be chosen as a pair to reduce to a single
ancilla. You should supply this parameter if you have a good idea
of an efficient way to reduce the degree of the PUSO. If ``pairs``
is None, then it will be the empty set ``set()``. In other words,
no variable pairs will be prioritized, and instead variable pairs
will be chosen to reduce to an ancilla bases solely on frequency
of occurrance.
Return
------
L : qubovert.utils.QUSOMatrix object.
The upper triangular QUSO matrix, an QUSOMatrix object.
For most practical purposes, you can use QUSOMatrix in the
same way as an ordinary dictionary. For more information,
see ``help(qubovert.utils.QUSOMatrix)``.
"""
if self.degree <= 2:
return QUSOMatrix(self._to_puso())
return super().to_quso(lam, pairs)

def convert_solution(self, solution, spin=True):
"""convert_solution.
Expand Down
2 changes: 1 addition & 1 deletion qubovert/_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
)


__version__ = "1.2.1"
__version__ = "1.2.2"
__author__ = "Joseph T. Iosue"
__authoremail__ = "[email protected]"
__license__ = "Apache Software License 2.0"
Expand Down
14 changes: 14 additions & 0 deletions qubovert/sim/_anneal.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,9 @@ def anneal_puso(H, num_anneals=1, anneal_duration=1000, initial_state=None,
qubovert.utils.QUBOVertWarning
If both the ``temperature_range`` and explicit ``schedule`` arguments
are provided.
qubovert.utils.QUBOVertWarning
If the degree of the model is 2 or less then a warning is issued that
says you should use the ``anneal_qubo`` or ``anneal_quso`` functions.
Example
-------
Expand Down Expand Up @@ -279,6 +282,14 @@ def anneal_puso(H, num_anneals=1, anneal_duration=1000, initial_state=None,
model = H.to_puso()
reverse_mapping = H.reverse_mapping

if model.degree <= 2:
QUBOVertWarning.warn(
"The input problem has degree <= 2; consider using the "
"``qubovert.sim.anneal_qubo`` or ``qubovert.sim.anneal_quso`` "
"functions, which are significantly faster than this function "
"because they take advantage of the low degree."
)

# solve `model`, convert solutions back to `H`

if not N:
Expand Down Expand Up @@ -561,6 +572,9 @@ def anneal_pubo(P, num_anneals=1, anneal_duration=1000, initial_state=None,
qubovert.utils.QUBOVertWarning
If both the ``temperature_range`` and explicit ``schedule`` arguments
are provided.
qubovert.utils.QUBOVertWarning
If the degree of the model is 2 or less then a warning is issued that
says you should use the ``anneal_qubo`` or ``anneal_quso`` functions.
Example
-------
Expand Down
40 changes: 39 additions & 1 deletion qubovert/utils/_binary_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

from math import ceil

__all__ = 'is_solution_spin', 'num_bits'
__all__ = 'is_solution_spin', 'num_bits', 'sum'


def is_solution_spin(solution, default=False):
Expand Down Expand Up @@ -113,3 +113,41 @@ def num_bits(val, log_trick=True):
raise ValueError("``val`` must be >= 0")
val = int(ceil(val))
return int.bit_length(val) if log_trick else val


def sum(iterable, start=0):
"""sum.
A utility for summing qubovert types. This will perform way faster than
Python's built-in ``sum`` function when you use it with qubovert types.
Parameters
----------
iterable : any iterable.
start : numeric or qubovert type (optional, defaults to 0).
Returns
-------
res : same type as ``sum(iterable, start)`` with Python's builtin ``sum``.
Examples
--------
>>> import time
>>> import qubovert as qv
>>>
>>> xs = [qv.boolean_var(i) for i in range(1000)]
>>> t0 = time.time()
>>> sum(xs)
>>> print(time.time() - t0)
3.345559597015381
>>> t0 = time.time()
>>> qv.utils.sum(xs)
>>> print(time.time() - t0)
0.011152505874633789
"""
for i in iterable:
start += i
return start
34 changes: 34 additions & 0 deletions qubovert/utils/_bo_parentclass.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,3 +223,37 @@ def __setitem__(self, key, value):
self._mapping[i] = self._next_label
self._reverse_mapping[self._next_label] = i
self._next_label += 1

def to_enumerated(self):
"""to_enumerated.
Return the default enumerated Matrix object.
If ``self`` is a QUBO,
``self.to_enumerated()`` is equivalent to ``self.to_qubo()``.
If ``self`` is a QUSO,
``self.to_enumerated()`` is equivalent to ``self.to_quso()``.
If ``self`` is a PUBO or PCBO,
``self.to_enumerated()`` is equivalent to ``self.to_pubo()``.
If ``self`` is a PUSO or PCSO,
``self.to_enumerated()`` is equivalent to ``self.to_puso()``.
Returns
-------
res : QUBOMatrix, QUSOMatrix, PUBOMatrix, or PUSOMatrix object.
If ``self`` is a QUBO type, then this method returns the
corresponding QUBOMatrix type. If ``self`` is a QUSO type,
then this method returns the corresponding QUSOMatrix type.
If ``self`` is a PUBO or PCBO type, then this method returns the
corresponding PUBOMatrix type. If ``self`` is a PUSO or PCSO type,
then this method returns the corresponding PUSOMatrix type.
"""
# we replace c with u so that pcbo and pcso go to pubo and puso.
return getattr(
self,
"to_" + self.__class__.__name__.lower().replace('c', 'u')
)()
26 changes: 18 additions & 8 deletions qubovert/utils/_solve_bruteforce.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,19 @@
"""

import itertools
from . import pubo_value, puso_value
from . import pubo_value, puso_value, qubo_value, quso_value

__all__ = (
'solve_pubo_bruteforce', 'solve_qubo_bruteforce',
'solve_puso_bruteforce', 'solve_quso_bruteforce'
)


def _solve_bruteforce(D, all_solutions, valid, spin):
def _solve_bruteforce(D, all_solutions, valid, spin, value):
"""_solve_bruteforce.
Helper function for solve_pubo_bruteforce and solve_puso_bruteforce.
Helper function for solve_pubo_bruteforce, solve_puso_bruteforce,
solve_qubo_bruteforce, and solve_quso_bruteforce.
Iterate through all the possible solutions to a BO formulated problem
and find the best one (the one that gives the minimum objective value). Do
Expand All @@ -50,6 +51,9 @@ def _solve_bruteforce(D, all_solutions, valid, spin):
indicating whether that bitstring or spinstring is a valid solutions.
spin : bool.
Whether we're bruteforce solving a spin model or boolean model.
value : function.
One of ``qubo_value``, ``quso_value``, ``pubo_value``, or
``puso_value``.
Returns
-------
Expand Down Expand Up @@ -102,7 +106,7 @@ def _solve_bruteforce(D, all_solutions, valid, spin):
x = {mapping[i]: v for i, v in enumerate(test_sol)}
if not valid(x):
continue
v = puso_value(x, D) if spin else pubo_value(x, D)
v = value(x, D)
if all_solutions and (best[0] is None or v <= best[0]):
best = v, x
all_sols.setdefault(v, []).append(x)
Expand Down Expand Up @@ -177,7 +181,7 @@ def solve_pubo_bruteforce(P, all_solutions=False, valid=lambda x: True):
is 0, :math:`x_2` is 1.
"""
return _solve_bruteforce(P, all_solutions, valid, False)
return _solve_bruteforce(P, all_solutions, valid, False, pubo_value)


def solve_qubo_bruteforce(Q, all_solutions=False, valid=lambda x: True):
Expand Down Expand Up @@ -244,7 +248,10 @@ def solve_qubo_bruteforce(Q, all_solutions=False, valid=lambda x: True):
is 0, :math:`x_2` is 1.
"""
return solve_pubo_bruteforce(Q, all_solutions, valid)
# we could just do the same as we did in solve_pubo_bruteforce, but the
# qubo_value function is much faster than the pubo_value function, so this
# will be much faster!
return _solve_bruteforce(Q, all_solutions, valid, False, qubo_value)


def solve_puso_bruteforce(H, all_solutions=False, valid=lambda x: True):
Expand Down Expand Up @@ -309,7 +316,7 @@ def solve_puso_bruteforce(H, all_solutions=False, valid=lambda x: True):
is -1, :math:`z_2` is 1.
"""
return _solve_bruteforce(H, all_solutions, valid, True)
return _solve_bruteforce(H, all_solutions, valid, True, puso_value)


def solve_quso_bruteforce(L, all_solutions=False, valid=lambda x: True):
Expand Down Expand Up @@ -374,4 +381,7 @@ def solve_quso_bruteforce(L, all_solutions=False, valid=lambda x: True):
is -1, :math:`z_2` is 1.
"""
return solve_puso_bruteforce(L, all_solutions, valid)
# we could just do the same as we did in solve_puso_bruteforce, but the
# quso_value function is much faster than the puso_value function, so this
# will be much faster!
return _solve_bruteforce(L, all_solutions, valid, True, quso_value)
Loading

0 comments on commit e60181d

Please sign in to comment.