Generate QUBO which can be input to dwave-neal simulated annealing solver and reconstruct the solution of the original problem.
Project description
quboassist
This is a package to generate QUBO which can be input to dwave-neal simulated annealing solver and reconstruct the solution of the original problem from the output.
What problems is it applicable to?
This package converts the problem below into QUBO form which can be input directly to dwave-neal package:
$$ {\rm minimize}\ \ \ f(x) =x^t Ax\ $$
$$ s.t. \ \ \ \forall i, a_i \leq x_i\leq b_i,x_i\in\mathbb Z $$
$$ \forall j, \sum_ kc_{jk} x_k \geq d_j\ \ (c_{jk}, d_j \in \mathbb Z) $$
where $A$ is a symmetric real matrix. I.e. problems where the objective function is quadratic, all variables are bounded and integer, all constraints are linear and their all coefficients are integer.
How to use
First, import the all classes of quboassist.
from quboassist import *
import neal
There are three classes: Variable
, Formula
, Problem
. Using Variable
class, we can define variables.
x = [Variable("x{}".format(i), 2, 5) for i in range(10)]
The fist component of input is the name of the variable. The second is the minimum value and the last is the maximum value, so this variable "x" takes $2,3,4,5$. If you want to change the passible values of the variable, you can do the following.
x[4].change_max(4)
x[5].change_min(-2)
Formula
is a class whose instance is generated automatically when variables are operated. For example,
f = - x[0]**2 - 3 * x[1]
g = x[0] > x[1]
then f
, g
are instances of Formula
. Finally, we can define a problem using Problem
.
P = Problem()
P.add_objective(f)
P.add_constraint(10, g)
where the first input of add_constraint
method is the weight of the constraint. Finally, we can get QUBO by compile
method.
P.compile()
sampler = neal.SimulatedAnnealingSampler()
result = sampler.sample_qubo(P.qubo).first.sample
solution = P.solution(result)
print(solution)
The solution is almost always below .
({'x0': 5, 'x1': 4}, [True])
The second component means whether each solution satisfies each constraint conditions. In the above case, because $5 > 4$, the return is true. Note that heuristic algorithms do not necessarily return an exact solution, so we always need to pay attention to the second component.
In general, increasing the weight $w_i$ tends to make it easier to satisfy the condition, but the objective function becomes relatively smaller. Therefore we propose to use a library called optuna to tune these hyperparameters $w_i$.
A sample code is showed below.
import neal
import quboassist
import optuna
import numpy as np
from copy import copy
x = [quboassist.Variable("x{}".format(i), 0, 3) for i in range(10)]
best_solution = []
best_val = np.inf
def objective(trial):
P = quboassist.Problem()
f = - x[0]**2 - x[1]
P.add_objective(f)
w = [trial.suggest_float("w{}".format(i), 0, 5) for i in range(2)]
g = x[0] > x[1]
P.add_constraint(w[0], g)
h = x[0] + x[1] == x[2]
P.add_constraint(w[1], h)
P.compile()
sampler = neal.SimulatedAnnealingSampler()
result = sampler.sample_qubo(P.qubo).first
solution = P.solution(result.sample)
obj = w[0] + w[1] + 10 * sum(np.logical_not(solution[1]))
val = result.energy
# Note that result.energy and the value of objective function may differ by a constant which appears when expanding the product of variables!
global best_solution, best_val
if val < best_val:
best_val = val
best_solution = copy(solution)
return obj
study = optuna.create_study(direction="minimize")
study.optimize(objective, n_trials=15)
print(best_solution)
What kind of technique is used?
The core is as below. We leave it to the reader to consider how to use this to represent any bounded integer variable as a linear combination of a constant and a binary variable, and to convert an inequality into an equality.
Algorithm
Input: $n$
Output: $A(n)$
Define $A_0(n) \leftarrow 2^{\lfloor \log_2 (n + 1) \rfloor - 1}, n_1 \leftarrow n - A_0(n), k \leftarrow 0$
while $n_{k+1} \neq 0$ do
$A_{k + 1}(n) \leftarrow 2^{\lfloor \log_2 (n_{k + 1} + 1) \rfloor - 1}$
$k \leftarrow k + 1$
$n_{k + 1}\leftarrow n_k - A_k(n)$
end while
Lemma
For all $n \in \mathbb N$, the sequence $A(n)$ is finite and the length is at most
$$ 2\lfloor\log_2(n +1)\rfloor. $$
Moreover, a function $f: [0,1]^k \rightarrow \mathbb N$ defined as:
$$ f(x_1,x_2,...,x_k):= \sum_{i =1}^k A_i(n) x_i $$
takes the all numbers $0,...,n$ and no other values.
Proof.
Since $A_0(n)$ is the only power of two which satisfies $4A_0(n)> n+1 \geq 2 A_0(n)$,
$$ n - A_0(n) \geq A_0(n)-1 $$
i.e.
$$ A_1(n)=2^{\lfloor\log_2(n_1 +1)\rfloor-1}\geq \frac{1}{2}A_0(n) $$
holds if $n_1 \neq 0$. Therefore if $A_0(n) \geq 2$, then $A_1(n) = A_0(n)$ or $A_1(n) =\frac{1}{2} A_0(n)$. Moreover, in the case that the first two numbers of $A(n)$ is same,
$$ n_2=n-2A_1(n)<2A_1(n)-1 $$
i.e.
$$ A_2(n)=2^{\lfloor\log_2(n_2 +1)\rfloor-1}<A_1(n). $$
Hence the same number appears at most two times in $A(n)$ and the exponent $\lfloor\log_2(n_k +1)\rfloor-1$ is monotonically non-increasing, thus the length of $A(n)$ is at most
$$ 2\lfloor\log_2(n +1)\rfloor, $$
moreover by the same reason, we also conclude the sequence $A(n)$ includes all powers of two less than or equal to
$$ 2^{\lfloor\log_2(n +1)\rfloor-1}(= A_0(n)). $$
Therefore, numbers $0,...,2A_0(n)-1$ can be expressed as
$$ \sum_{i=1}^kA_i(n)x_i $$
and numbers $n-2A_0(n),...,n$ can be expressed as
$$ n-\sum_{i=1}^kA_i(n)y_i=\sum_{i=1}^kA_i(n)(1-y_i). $$
Since
$$ n-2A_0(n)< 2A_0(n) -1, $$
finally all numbers $0,...,n$ can be expressed as
$$ \sum_{i=1}^kA_i(n)x_i. $$
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Hashes for quboassist-0.0.1-py3-none-any.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 9c9b77e1eb6b1d29b2ab741c0cbf4b6f70871e0c7df8f6a81baf62b410ea9dad |
|
MD5 | db07a40ac26cfc8d22c7334a5cf1bcd2 |
|
BLAKE2b-256 | b6beafa8937f3670a81552fd754234e8039bdc412934ea5e4a03a662fc9fe865 |