PyQUBO¶
PyQUBO allows you to create QUBOs or Ising models from flexible mathematical expressions easily. Some of the features of PyQUBO are
- Python based (C++ backend).
- Fully integrated with Ocean SDK. (details)
- Automatic validation of constraints. (details)
- Placeholder for parameter tuning. (details)
For more details, see PyQUBO Documentation.
Example Usage¶
Creating QUBO¶
This example constructs a simple expression and compile it to model
.
By calling model.to_qubo()
, we get the resulting QUBO.
(This example solves Number Partitioning Problem with a set S = {4, 2, 7, 1})
>>> from pyqubo import Spin
>>> s1, s2, s3, s4 = Spin("s1"), Spin("s2"), Spin("s3"), Spin("s4")
>>> H = (4*s1 + 2*s2 + 7*s3 + s4)**2
>>> model = H.compile()
>>> qubo, offset = model.to_qubo()
>>> pprint(qubo)
{('s1', 's1'): -160.0,
('s1', 's2'): 64.0,
('s2', 's2'): -96.0,
('s3', 's1'): 224.0,
('s3', 's2'): 112.0,
('s3', 's3'): -196.0,
('s4', 's1'): 32.0,
('s4', 's2'): 16.0,
('s4', 's3'): 56.0,
('s4', 's4'): -52.0}
Integration with D-Wave Ocean¶
PyQUBO can output the BinaryQuadraticModel(BQM) which is compatible with Sampler class defined in D-Wave Ocean SDK. In the example below, we solve the problem with SimulatedAnnealingSampler.
>>> import neal
>>> sampler = neal.SimulatedAnnealingSampler()
>>> bqm = model.to_bqm()
>>> sampleset = sampler.sample(bqm, num_reads=10)
>>> decoded_samples = model.decode_sampleset(sampleset)
>>> best_sample = min(decoded_samples, key=lambda x: x.energy)
>>> best_sample.sample # doctest: +SKIP
{'s1': 0, 's2': 0, 's3': 1, 's4': 0}
If you want to solve the problem by actual D-Wave machines, just replace the sampler by a DWaveCliqueSampler instance, for example.
For more examples, see example notebooks.
Benchmarking¶
Since the core logic of the new PyQUBO (>=1.0.0) is written in C++ and the logic itself is also optimized, the execution time to produce QUBO has become shorter. We benchmarked the execution time to produce QUBOs of TSP with the new PyQUBO (1.0.0) and the previous PyQUBO (0.4.0). The result shows the new PyQUBO runs 1000 times faster as the problem size increases.
Execution time includes building Hamiltonian, compilation, and producing QUBOs. The code to produce the above result is found in here.
Supported Python Versions¶
Python 3.5, 3.6, 3.7 and 3.8 are supported.
Supported Operating Systems¶
- Linux (32/64bit)
- OSX (64bit, >=10.9)
- Win (64bit)
Getting Started¶
Installation¶
If you use pip, just type
pip install pyqubo
You can install from the source code like
git clone https://github.com/recruit-communications/pyqubo.git
cd pyqubo
python setup.py install
QUBO and Ising Model¶
If you want to solve a combinatorial optimization problem by quantum or classical annealing machines, you need to represent your problem in QUBO (Quadratic Unconstrained Binary Optimization) or Ising model. PyQUBO converts your problem into QUBO or Ising model format.
The objective function of QUBO is defined as:
where \(x_{i}\) represents a binary variable which takes 0 or 1, and \(q_{ij}\) represents a quadratic coefficient. Note that \(q_{ii}x_{i}x_{i}=q_{ii}x_{i}\), since \(x_{i}^2=x_{i}\). Thus, the above expression includes linear terms of \(x_{i}\).
The objective function of Ising model is defined as:
where \(s_{i}\) represents spin variable which takes -1 or 1, \(h_{i}\) represents an external magnetic field and \(J_{ij}\) represents an interaction between spin \(i\) and \(j\).
Basic Usage¶
With PyQUBO, you can construct QUBOs with 3 steps:
- Define the Hamiltonian.
>>> from pyqubo import Spin
>>> s1, s2, s3, s4 = Spin("s1"), Spin("s2"), Spin("s3"), Spin("s4")
>>> H = (4*s1 + 2*s2 + 7*s3 + s4)**2
- Compile the Hamiltonian to get a model.
>>> model = H.compile()
- Call ‘to_qubo()’ to get QUBO coefficients.
>>> qubo, offset = model.to_qubo()
>>> pprint(qubo) # doctest: +SKIP
{('s1', 's1'): -160.0,
('s1', 's2'): 64.0,
('s1', 's3'): 224.0,
('s1', 's4'): 32.0,
('s2', 's2'): -96.0,
('s2', 's3'): 112.0,
('s2', 's4'): 16.0,
('s3', 's3'): -196.0,
('s3', 's4'): 56.0,
('s4', 's4'): -52.0}
>>> print(offset)
196.0
In this example, you want to solve Number Partitioning Problem with a set S = {4, 2, 7, 1}. The hamiltonian \(H\) is represented as
where \(s_{i}\) is a \(i\) th spin variable which indicates a group the \(i\) th number should belong to.
In PyQUBO, spin variables are internally converted to binary variables via the relationship \(x_{i} = (s_{i}+1)/2\). The QUBO coefficents and the offset returned from Model.to_qubo()
represents the following objective function:
- Call ‘to_ising()’ to get Ising coefficients.
If you want to get the coefficient of the Ising model, just call to_ising()
method like below.
>>> linear, quadratic, offset = model.to_ising()
>>> pprint(linear) # doctest: +SKIP
{'s1': 0.0, 's2': 0.0, 's3': 0.0, 's4': 0.0}
>>> pprint(quadratic) # doctest: +SKIP
{('s1', 's2'): 16.0,
('s1', 's3'): 56.0,
('s1', 's4'): 8.0,
('s2', 's3'): 28.0,
('s2', 's4'): 4.0,
('s3', 's4'): 14.0}
>>> print(offset)
70.0
where linear represents external magnetic fields \(h\), quadratic represents interactions \(J\) and offset represents the constant value in the objective function below.
Variable: Binary and Spin¶
When you define a Hamiltonian, you can use Binary
or Spin
class to represent \(\{0,1\}\) or \(\{1,-1\}\) variable.
Example:
If you want to define a Hamiltonian with binary variables \(x \in \{0, 1\}\), use Binary
.
>>> from pyqubo import Binary
>>> x1, x2 = Binary('x1'), Binary('x2')
>>> H = 2*x1*x2 + 3*x1
>>> pprint(H.compile().to_qubo()) # doctest: +SKIP
({('x1', 'x1'): 3.0, ('x1', 'x2'): 2.0, ('x2', 'x2'): 0.0}, 0.0)
Example:
If you want to define a Hamiltonian with spin variables \(s \in \{-1, 1\}\), use Spin
.
>>> from pyqubo import Spin
>>> s1, s2 = Spin('s1'), Spin('s2')
>>> H = 2*s1*s2 + 3*s1
>>> pprint(H.compile().to_qubo()) # doctest: +SKIP
({('s1', 's1'): 2.0, ('s1', 's2'): 8.0, ('s2', 's2'): -4.0}, -1.0)
Solve QUBO by dimod Sampler¶
PyQUBO model can output the BinaryQuadraticModel(BQM).
You can solve BQM by using Sampler
class.
Sampler
is an abstract class defined by dimod package.
Various kinds of sampler class, such as SimulatedAnnealingSampler or DWaveSampler, inherits Sampler class.
First, we craete BQM object using to_bqm()
method.
(If you want to use DWaveSampler which only takes integer-indexed QUBO,
you can simply do like to_bqm(index_label=True)
.)
>>> from pyqubo import Binary
>>> x1, x2 = Binary('x1'), Binary('x2')
>>> H = (x1 + x2 - 1)**2
>>> model = H.compile()
>>> bqm = model.to_bqm()
Next, we create neal.SimulatedAnnealingSampler
and use sample()
method to get the solutions of QUBO as SampleSet.
You can use Model.decode_sampleset()
to interpret the sampleset object, and it returns decoded_samples which is a list of pyqubo.DecodedSample
object.
>>> import neal
>>> sa = neal.SimulatedAnnealingSampler()
>>> sampleset = sa.sample(bqm, num_reads=10)
>>> decoded_samples = model.decode_sampleset(sampleset)
>>> best_sample = min(decoded_samples, key=lambda x: x.energy)
>>> pprint(best_sample.sample)
{'x1': 0, 'x2': 1}
Array of Variables¶
Array
class represents a multi-dimensional array of Binary
or Spin
.
Example: You can access each element of the matrix with an index like:
>>> from pyqubo import Array
>>> x = Array.create('x', shape=(2, 3), vartype='BINARY')
>>> x[0, 1] + x[1, 2]
(Binary(x[0][1])+Binary(x[1][2]))
Example:
You can use Array
to represent multiple spins in the example of partitioning problem above.
>>> from pyqubo import Array
>>> numbers = [4, 2, 7, 1]
>>> s = Array.create('s', shape=4, vartype='SPIN')
>>> H = sum(n * s for s, n in zip(s, numbers))**2
>>> model = H.compile()
>>> qubo, offset = model.to_qubo()
>>> pprint(qubo) # doctest: +SKIP
{('s[0]', 's[0]'): -160.0,
('s[0]', 's[1]'): 64.0,
('s[0]', 's[2]'): 224.0,
('s[0]', 's[3]'): 32.0,
('s[1]', 's[1]'): -96.0,
('s[1]', 's[2]'): 112.0,
('s[1]', 's[3]'): 16.0,
('s[2]', 's[2]'): -196.0,
('s[2]', 's[3]'): 56.0,
('s[3]', 's[3]'): -52.0}
Placeholder¶
If you have a parameter that you will probably update, such as the strength of the constraints in your hamiltonian, using Placeholder
will save your time.
If you define the parameter by Placeholder
, you can specify the value of the parameter after compile.
This means that you don’t have to compile repeatedly for getting QUBOs with various parameter values.
It takes longer time to execute a compile when the problem size is bigger. In that case, you can save your time by using Placeholder
.
Example: If you have an objective function \(2a+b\), and a constraint \(a+b=1\) whose hamiltonian is \((a+b-1)^2\) where \(a,b\) is qbit variable, you need to find the penalty strength \(M\) such that the constraint is satisfied. Thus, you need to create QUBO with different values of \(M\). In this example, we create QUBO with \(M=5.0\) and \(M=6.0\).
In the first code, we don’t use placeholder. In this case, you need to compile the hamiltonian twice to get a QUBO with \(M=5.0\) and \(M=6.0\).
>>> from pyqubo import Binary
>>> a, b = Binary('a'), Binary('b')
>>> M = 5.0
>>> H = 2*a + b + M*(a+b-1)**2
>>> model = H.compile()
>>> qubo, offset = model.to_qubo() # QUBO with M=5.0
>>> M = 6.0
>>> H = 2*a + b + M*(a+b-1)**2
>>> model = H.compile()
>>> qubo, offset = model.to_qubo() # QUBO with M=6.0
If you don’t want to compile twice, define \(M\) by Placeholder
.
>>> from pyqubo import Placeholder
>>> a, b = Binary('a'), Binary('b')
>>> M = Placeholder('M')
>>> H = 2*a + b + M*(a+b-1)**2
>>> model = H.compile()
>>> qubo, offset = model.to_qubo(feed_dict={'M': 5.0})
You get a QUBO with different value of M without compile
>>> qubo, offset = model.to_qubo(feed_dict={'M': 6.0})
The actual value of the placeholder \(M\) is specified in calling Model.to_qubo()
as a value of the feed_dict.
Validation of Constraints¶
When you get a solution from the Sampler, Model.decode_sample()
decodes the sample and returns DecodedSample
object.
Example: You are solving a partitioning problem.
>>> from pyqubo import Binary, Constraint
>>> a, b = Binary('a'), Binary('b')
>>> M = 5.0 # strength of the constraint
>>> H = 2*a + b + M * Constraint((a+b-1)**2, label='a+b=1')
>>> model = H.compile()
Let’s assume that you get a solution {'a': 0, 'b': 1}
from the solver.
>>> raw_solution = {'a': 0, 'b': 1} # solution from the solver
>>> decoded_sample = model.decode_sample(raw_solution, vartype='BINARY')
>>> pprint(decoded_sample.sample)
{'a': 0, 'b': 1}
>>> pprint(decoded_sample.constraints())
{'a+b=1': (True, 0.0)}
>>> pprint(decoded_sample.constraints(only_broken=True))
{}
You can access to the dict of the sample via decoded_sample.sample
.
You can also access to the value of the constraint of the Hamiltonian via decoded_sample.constraints().
If you specify the argument only_broken=True, only broken constraint will be returned.
If the empty dict is returned, it indicates that there is no broken constraint corresponding to the given sample.
Contribution¶
Thank you for contributing to PyQUBO.
Propose a new feature and implement
- If you have a proposal of new features, send a pull request with your idea and we will discuss it.
- Once we agree with the new feature, implement the feature. If you implement a new module on top of PyQUBO, create your module inside the
pyqubo/contrib
directory.
Implement a feature or bug-fix for an existing issue
- See the issue list of github.
- Choose an issue and comment on the task that you will work on.
- Send a pull request.
Implementing unittests for your feature helps a review process.
Installation¶
If you already installed PyQUBO, uninstall it.
pip uninstall pyqubo
Install PyQUBO with development mode
python setup.py develop
Coding Conventions¶
- Follow PEP8.
- Write docstring with Google docstrings convention.
- Write unit tests.
- Write comments when the code is complicated. But the best documentation is clean code with good variable names.
Unit Testing¶
To run unit tests, you have two options. One option is to run with unittest or coverage command. To run all tests with unittest, execute
python -m unittest discover tests
To generate coverage reports, execute
coverage run -m unittest discover
coverage html
You will see html files of the report in htmlcov
directory.
Second option is to run test using docker container with circleci CLI locally. To run test with circleci CLI, execute
circleci build --job $JOBNAME
$JOBNAME needs to be replaced with a job name such as test-3.6, listed in .circleci/config.yml
.
To install circleci CLI, refer to https://circleci.com/docs/2.0/local-cli/.
Documentation¶
Documents are created by sphinx from the docstring in Python code. When you add a new class, please create a new rst file in docs/reference
directory. If the information of the class is not important for library users, create a file under
internal
directory. To build html files of document locally, execute
make clean html
You can see built htmls in docs/_build
directory.
When you write an example code in docstring, you can test the code with doctest. To run doctest, execute
make doctest
Expression¶
-
class
Base
¶ Abstract class of pyqubo expression.
All basic component class such as
Binary
,Spin
orAdd
inheritsBase
.For example, an expression \(2ab+1\) (where \(a, b\) is
Binary
variable) is represented by the binary tree above.Note
This class is an abstract class of all component of expressions.
Example:
-
compile
(strength=5.0)¶ Returns the compiled
Model
.This method reduces the degree of the expression if the degree is higher than 2, and convert it into
Model
which has information about QUBO.Parameters: strength (float) – The strength of the reduction constraint. Insufficient strength can result in the binary quadratic model not having the same minimizations as the polynomial. Returns: The model compiled from the Base
.Return type: Model
Examples:
In this example, there are higher order terms \(abc\) and \(abd\). It is decomposed as [[
a*b
,c
],d
] hierarchically and converted into QUBO. By callingto_qubo()
of themodel
, we get the QUBO.>>> from pyqubo import Binary >>> a, b, c, d = Binary("a"), Binary("b"), Binary("c"), Binary("d") >>> model = (a*b*c + a*b*d).compile() >>> pprint(model.to_qubo()) # doctest: +SKIP ({('a', 'a'): 0.0, ('a', 'a*b'): -10.0, ('a', 'b'): 5.0, ('a*b', 'a*b'): 15.0, ('a*b', 'b'): -10.0, ('a*b', 'c'): 1.0, ('a*b', 'd'): 1.0, ('b', 'b'): 0.0, ('c', 'c'): 0, ('d', 'd'): 0}, 0.0)
Binary¶
-
class
Binary
(label)¶ Binary variable i.e. {0, 1}.
Parameters: label (str) – The label of a variable. A variable is identified by this label. Example:
Example code to create an expression.
>>> from pyqubo import Binary >>> a, b = Binary('a'), Binary('b') >>> exp = 2*a*b + 3*a >>> pprint(exp.compile().to_qubo()) # doctest: +SKIP ({('a', 'a'): 3.0, ('a', 'b'): 2.0, ('b', 'b'): 0}, 0.0)
Spin¶
-
class
Spin
(label)¶ Spin variable i.e. {-1, 1}.
Parameters: label (str) – The label of a variable. A variable is identified by this label. Example:
Example code to create an expression.
>>> from pyqubo import Spin >>> a, b = Spin('a'), Spin('b') >>> exp = 2*a*b + 3*a >>> pprint(exp.compile().to_qubo()) # doctest: +SKIP ({('a', 'a'): 2.0, ('a', 'b'): 8.0, ('b', 'b'): -4.0}, -1.0)
Placeholder¶
-
class
Placeholder
(label)¶ Placeholder expression.
You can specify the value of the
Placeholder
when creating the QUBO. By usingPlaceholder
, you can change the value without compiling again. This is useful when you need to update the strength of constraint gradually.Parameters: label (str) – The label of the placeholder. Example:
The value of the placeholder is specified when you call
to_qubo()
.>>> from pyqubo import Binary, Placeholder >>> x, y, a = Binary('x'), Binary('y'), Placeholder('a') >>> exp = a*x*y + 2.0*x >>> pprint(exp.compile().to_qubo(feed_dict={'a': 3.0})) # doctest: +SKIP ({('x', 'x'): 2.0, ('x', 'y'): 3.0, ('y', 'y'): 0}, 0.0) >>> pprint(exp.compile().to_qubo(feed_dict={'a': 5.0})) # doctest: +SKIP ({('x', 'x'): 2.0, ('x', 'y'): 5.0, ('y', 'y'): 0}, 0.0)
SubH¶
-
class
SubH
(hamiltonian, label, as_constraint=False)¶ SubH expression. The parent class of Constraint. You can specify smaller sub-hamiltonians in your expression.
Parameters: - hamiltonian (Base) – The expression you want to specify as a sub-hamiltonian.
- label (str) – The label of the sub-hamiltonian. Sub-hamiltonians can be identified by their labels.
- as_constraint (boolean) – Whether or not the sub-hamiltonian should also be treated as a constraint. False by default.
Example:
You can call namespaces to identify the labels defined in a model.
>>> from pyqubo import Spin, SubH >>> s1, s2, s3 = Spin('s1'), Spin('s2'), Spin('s3') >>> exp = (SubH(s1 + s2, 'n1'))**2 + (SubH(s1 + s3, 'n2'))**2 >>> model = exp.compile() >>> model.namespaces #doctest: +SKIP ({'n1': {'s1', 's2'}, 'n2': {'s1', 's3'}}, {'s1', 's2', 's3'})
Constraint¶
-
class
Constraint
(hamiltonian, label, condition=lambda x: x==0.0)¶ Constraint expression. You can specify the constraint part in your expression.
Parameters: - child (Express) – The expression you want to specify as a constraint.
- label (str) – The label of the constraint. You can identify constraints by the label.
- (float => boolean) condition (func) – function to indicate whether the constraint is satisfied or not. Default is lambda x: x == 0.0. function takes float value and returns boolean value. You can define the condition where the constraint is satisfied.
Example:
When the Hamiltonian contains
Constraint
, you know whether each constraint is satisfied or not by accessing toDecodedSample
.>>> from pyqubo import Binary, Constraint >>> a, b = Binary('a'), Binary('b') >>> H = Constraint(a+b-2, "const1") + Constraint(a+b-1, "const2") >>> model = H.compile() >>> dec = model.decode_sample({'a': 1, 'b': 0}, vartype='BINARY') >>> pprint(dec.constraints()) {'const1': (False, -1.0), 'const2': (True, 0.0)} >>> pprint(dec.constraints(only_broken=True)) {'const1': (False, -1.0)}
Add¶
Mul¶
Num¶
-
class
Num
(value)¶ Expression of number
Parameters: value (float) – the value of the number. Example:
Example code to create an expression.
>>> from pyqubo import Binary, Num >>> a = Binary('a') >>> a + 1 (Binary(a)+Num(1.000000)) >>> a + Num(1) (Binary(a)+Num(1.000000))
UserDefinedExpress¶
-
class
UserDefinedExpress
¶ User defined express.
User can define their own expression by inheriting
UserDefinedExpress
.Example:
Define the
LogicalAnd
class by inheritingUserDefinedExpress
.>>> from pyqubo import UserDefinedExpress, Binary >>> class LogicalAnd(UserDefinedExpress): ... def __init__(self, bit_a, bit_b): ... express = bit_a * bit_b ... super().__init__(express) >>> a, b = Binary('a'), Binary('b') >>> logical_and = LogicalAnd(a, b)
WithPenalty¶
-
class
WithPenalty
¶ You can define the custum penalty class by inheriting WithPenalty. The penalty argument will be added to the generated Hamiltonian. Integer classes with constraints, such as
OneHotEncInteger
, are defined using this class.Example:
Define the custom penalty class inheriting WithPenalty. We initialize this class with hamiltonian \(h\). The constraint term \((h-1)^2\) will be added to the generated Hamiltonian.
>>> from pyqubo import WithPenalty >>> class CustomPenalty(WithPenalty): ... def __init__(self, hamiltonian, label, strength): ... penalty = strength * (hamiltonian-1)**2 ... super().__init__(hamiltonian, penalty, label) >>> a, b = Binary("a"), Binary("b") >>> p = CustomPenalty(a+b, label="penalty", strength=2.0) >>> model = (p+1).compile() >>> qubo, offset = model.to_qubo()
Model¶
Model¶
-
class
Model
¶ Model represents binary quadratic optimization problem.
By compiling
Express
object, you get aModel
object. It contains the information about QUBO (or equivalent Ising Model), and it also has the function to decode the solution into the original variable structure.Note
We do not need to create this object directly. Instead, we get this by compiling Express objects.
Generate QUBO, Ising model, and BQM
to_qubo()
Returns QUBO and energy offset. to_ising()
Returns Ising Model and energy offset. to_bqm()
Returns dimod.BinaryQuadraticModel
.Interpret samples returned from solvers
energy()
Returns energy of the sample. decode_sample()
Returns Ising Model and energy offset. decode_sampleset()
Decode the sample represented by dimod.SampleSet
.
-
to_qubo
(index_label=False, feed_dict=None)¶ Returns QUBO and energy offset.
Parameters: - index_label (bool) – If true, the keys of returned QUBO are indexed with a positive integer number.
- feed_dict (dict[str,float]) – If the expression contains
Placeholder
objects, you have to specify the value of them byPlaceholder
. Please refer toPlaceholder
for more details.
Returns: Tuple of QUBO and energy offset. QUBO takes the form of
dict[(str, str), float]
.Return type: tuple[QUBO, float]
Examples:
This example creates the
model
from the expression, and we get the resulting QUBO by callingmodel.to_qubo()
.>>> from pyqubo import Binary >>> x, y, z = Binary("x"), Binary("y"), Binary("z") >>> model = (x*y + y*z + 3*z).compile() >>> pprint(model.to_qubo()) # doctest: +SKIP ({('x', 'x'): 0.0, ('x', 'y'): 1.0, ('y', 'y'): 0.0, ('z', 'y'): 1.0, ('z', 'z'): 3.0}, 0.0)
If you want a QUBO which has index labels, specify the argument
index_label=True
. The mapping of the indices and the corresponding labels is stored inmodel.variables
.>>> pprint(model.to_qubo(index_label=True)) # doctest: +SKIP ({(0, 0): 3.0, (0, 2): 1.0, (1, 1): 0.0, (1, 2): 1.0, (2, 2): 0.0}, 0.0) >>> model.variables ['z', 'x', 'y']
-
to_ising
(index_label=False, feed_dict=None)¶ Returns Ising Model and energy offset.
Parameters: - index_label (bool) – If true, the keys of returned QUBO are indexed with a positive integer number.
- feed_dict (dict[str,float]) – If the expression contains
Placeholder
objects, you have to specify the value of them byPlaceholder
. Please refer toPlaceholder
for more details.
Returns: Tuple of Ising Model and energy offset. Where linear takes the form of
(dict[str, float])
, and quadratic takes the form ofdict[(str, str), float]
.Return type: tuple(linear, quadratic, float)
Examples:
This example creates the
model
from the expression, and we get the resulting Ising model by callingto_ising()
.>>> from pyqubo import Binary >>> x, y, z = Binary("x"), Binary("y"), Binary("z") >>> model = (x*y + y*z + 3*z).compile() >>> pprint(model.to_ising()) # doctest: +SKIP ({'x': 0.25, 'y': 0.5, 'z': 1.75}, {('x', 'y'): 0.25, ('z', 'y'): 0.25}, 2.0)
If you want a Ising model which has index labels, specify the argument
index_label=True
. The mapping of the indices and the corresponding labels is stored inmodel.variables
.>>> pprint(model.to_ising(index_label=True)) # doctest: +SKIP ({0: 1.75, 1: 0.25, 2: 0.5}, {(0, 2): 0.25, (1, 2): 0.25}, 2.0) >>> model.variables ['z', 'x', 'y']
-
to_bqm
(index_label=False, feed_dict=None)¶ Returns
dimod.BinaryQuadraticModel
.For more details about
dimod.BinaryQuadraticModel
, see dimod.BinaryQuadraticModel.Parameters: - index_label (bool) – If true, the keys of returned QUBO are indexed with a positive integer number.
- feed_dict (dict[str,float]) – If the expression contains
Placeholder
objects, you have to specify the value of them byPlaceholder
.
Returns: dimod.BinaryQuadraticModel
with vartype set to dimod.BINARY.Return type: dimod.BinaryQuadraticModel
Examples:
>>> from pyqubo import Binary, Constraint >>> from dimod import ExactSolver >>> a, b = Binary('a'), Binary('b') >>> H = Constraint(2*a-3*b, "const1") + Constraint(a+b-1, "const2") >>> model = H.compile() >>> bqm = model.to_bqm() >>> sampleset = ExactSolver().sample(bqm) >>> decoded_samples = model.decode_sampleset(sampleset) >>> best_sample = min(decoded_samples, key=lambda s: s.energy) >>> print(best_sample.energy) -3.0 >>> pprint(best_sample.sample) {'a': 0, 'b': 1} >>> pprint(best_sample.constraints()) {'const1': (False, -3.0), 'const2': (True, 0.0)}
-
energy
(solution, vartype, feed_dict=None)¶ Returns energy of the sample.
Parameters: - sample (list[int]/dict[str,int]) – The sample returned from solvers.
- vartype (str) – Variable type of the solution. Specify either
'BINARY'
or'SPIN'
. - feed_dict (dict[str,float]) – Specify the placeholder values.
Returns: Calculated energy.
Return type: float
-
decode_sample
(sample, vartype, feed_dict=None)¶ Decode sample from solvers.
Parameters: - sample (list[int]/dict[str,int]) – The sample returned from solvers.
- vartype (str) – Variable type of the solution. Specify either
'BINARY'
or'SPIN'
. - feed_dict (dict[str,float]) – Specify the placeholder values.
Returns: DecodedSample
object.Return type: Examples
>>> from pyqubo import Binary, SubH >>> a, b = Binary('a'), Binary('b') >>> H = SubH(a+b-2, "subh1") + 2*a + b >>> model = H.compile() >>> decoded_sample = model.decode_sample({'a': 1, 'b': 0}, vartype='BINARY') >>> print(decoded_sample.energy) 1.0 >>> pprint(decoded_sample.sample) {'a': 1, 'b': 0} >>> print(decoded_sample.subh) {'subh1': -1.0}
-
decode_sampleset
(sampleset, feed_dict=None)¶ Decode the sample represented by
dimod.SampleSet
.For more details about
dimod.SampleSet
, see dimod.SampleSet.Parameters: - sample (dimod.SampleSet) – The solution returned from dimod sampler.
- feed_dict (dict[str,float]) – Specify the placeholder values. Default=None
Returns: DecodedSample
object.Return type: Examples
>>> from pyqubo import Binary, Constraint >>> from dimod import ExactSolver >>> a, b = Binary('a'), Binary('b') >>> H = Constraint(2*a-3*b, "const1") + Constraint(a+b-1, "const2") >>> model = H.compile() >>> bqm = model.to_bqm() >>> sampleset = ExactSolver().sample(bqm) >>> decoded_samples = model.decode_sampleset(sampleset) >>> best_sample = min(decoded_samples, key=lambda s: s.energy) >>> print(best_sample.energy) -3.0 >>> pprint(best_sample.sample) {'a': 0, 'b': 1} >>> pprint(best_sample.constraints()) {'const1': (False, -3.0), 'const2': (True, 0.0)}
DecodedSample¶
-
class
DecodedSample
¶ DecodedSample contains the informatin like whether the constraint is satisfied or not, or the value of the SubHamiltonian.
Examples
>>> from pyqubo import Binary, SubH >>> a, b = Binary('a'), Binary('b') >>> H = SubH(a+b-2, "subh1") + 2*a + b >>> model = H.compile() >>> decoded_sample = model.decode_sample({'a': 1, 'b': 0}, vartype='BINARY') >>> print(decoded_sample.energy) 1.0 >>> pprint(decoded_sample.sample) {'a': 1, 'b': 0} >>> print(decoded_sample.subh) {'subh1': -1.0}
Methods
array()
Get the value of the array element specified. constraints()
Returns the information about constraints.
-
array
(array_name, index)¶ Get the value of the array specified by array_name and index.
Parameters: - array_name (str) – The name of the array.
- index (int/tuple) – The index of the array.
Returns: The value of the array calculated by the sample.
Return type: float
Examples
>>> from pyqubo import Array >>> x = Array.create('x', shape=(2, 1), vartype="BINARY") >>> H = (x[0, 0] + x[1, 0] - 1)**2 >>> model = H.compile() >>> qubo, offset = model.to_qubo() >>> pprint(qubo) {('x[0][0]', 'x[0][0]'): -1.0, ('x[0][0]', 'x[1][0]'): 2.0, ('x[1][0]', 'x[1][0]'): -1.0} >>> dec = model.decode_sample({'x[0][0]': 1, 'x[1][0]': 0}, vartype='BINARY') >>> print(dec.array('x', (0, 0))) 1 >>> print(dec.array('x', (1, 0))) 0
-
constraints
(only_broken)¶ Get the value of the array specified by array_name and index.
Parameters: only_broken (bool) – Whether to select only broken constraints. Returns: Dictionary with the key being the label of the constraint and the value being the boolean and the corresponding energy value. The boolean value indicates whether the constraint is satisfied or not. Return type: dict[str, tuple[bool, float]] Examples
>>> from pyqubo import Binary, Constraint >>> a, b = Binary('a'), Binary('b') >>> H = Constraint(a+b-2, "const1") + Constraint(a+b-1, "const2") >>> model = H.compile() >>> dec = model.decode_sample({'a': 1, 'b': 0}, vartype='BINARY') >>> pprint(dec.constraints()) {'const1': (False, -1.0), 'const2': (True, 0.0)} >>> pprint(dec.constraints(only_broken=True)) {'const1': (False, -1.0)}
Array¶
-
class
Array
(bit_list)[source]¶ Multi-dimensional array.
Parameters: bit_list (list/ numpy.ndarray
) –The object from which a new array is created. Accepted input:
- (Nested) list of
Express
,Array
, int or float. - numpy.ndarray
-
shape
¶ Shape of this array.
Type: tuple[int]
Example
Create a new array with Binary.
>>> from pyqubo import Array, Binary >>> Array.create('x', shape=(2, 2), vartype='BINARY') Array([[Binary(x[0][0]), Binary(x[0][1])], [Binary(x[1][0]), Binary(x[1][1])]])
Create a new array from a nested list of
Express
.>>> array = Array([[Binary('x0'), Binary('x1')], [Binary('x2'), Binary('x3')]]) >>> array Array([[Binary(x0), Binary(x1)], [Binary(x2), Binary(x3)]])
Get the shape of the array.
>>> array.shape (2, 2)
Access an element with index.
>>> array[0, 0] # = array[(0, 0)] Binary(x0)
Use slice “:” to select a subset of the array.
>>> array[:, 1] # = array[(slice(None), 1)] Array([Binary(x1), Binary(x3)]) >>> sum(array[:, 1]) (Binary(x1)+Binary(x3))
Use list or tuple to select a subset of the array.
>>> array[[0, 1], 1] Array([Binary(x1), Binary(x3)]) >>> array[(0, 1), 1] Array([Binary(x1), Binary(x3)])
Create an array from numpy array.
>>> import numpy as np >>> Array(np.array([[1, 2], [3, 4]])) Array([[1, 2], [3, 4]])
Create an array from list of
Array
.>>> Array([Array([1, 2]), Array([3, 4])]) Array([[1, 2], [3, 4]])
- (Nested) list of
-
static
Array.
create
(name, shape, vartype)[source]¶ Create a new array with Spins or Binary.
Parameters: - name (str) – Name of the matrix. It is used as a part of the label of variables.
For example, if the name is ‘x’,
the label of (i, j) th variable will be
x[i][j]
. - shape (int/tuple[int]) – Dimensions of the array.
- vartype (
dimod.Vartype
/str/set, optional) –Variable type of the solution. Accepted input values:
Vartype.SPIN
,'SPIN'
,{-1, 1}
Vartype.BINARY
,'BINARY'
,{0, 1}
Example
>>> from pyqubo import Array >>> array = Array.create('x', shape=(2, 2), vartype='BINARY') >>> array # doctest: +SKIP Array([[Binary(x[0][0]), Binary(x[0][1])], [Binary(x[1][0]), Binary(x[1][1])]]) >>> array[0] # doctest: +SKIP Array([Binary(x[0][0]), Binary(x[0][1])])
- name (str) – Name of the matrix. It is used as a part of the label of variables.
For example, if the name is ‘x’,
the label of (i, j) th variable will be
Matrix Operation¶
Array.T |
Returns a transposed array. |
Array.dot (other) |
Returns a dot product of two arrays. |
Array.matmul (other) |
Returns a matrix product of two arrays. |
Array.reshape (new_shape) |
Returns a reshaped array. |
Arithmetic Operation¶
Array.add (other) |
Returns a sum of self and other. |
Array.subtract (other) |
Returns a difference between other and self. |
Array.mul (other) |
Returns a multiplicity of self by other. |
Array.div (other) |
Returns division of self by other. |
Construction¶
Array.fill (obj, shape) |
Create a new array with the given shape, all filled with the given object. |
Integer¶
Summary of each integer encoding, whose value takes [0, n].
Encoding | Value | Constraint | #vars | Max abs. coeff of value |
---|---|---|---|---|
UnaryEncInteger |
\(\sum_{i=1}^{n}x_{i}\) | No constraint | \(n\) | \(1\) |
LogEncInteger |
\(\sum_{i=1}^{d}2^i x_{i}\) | No constraint | \(\lceil\log_{2}n\rceil(=d)\) | \(2^d\) |
OneHotEncInteger |
\(\sum_{i=0}^{n}ix_{i}\) | \((\sum_{i=0}^{n}x_{i}-1)^2\) | \(n+1\) | \(n\) |
OrderEncInteger |
\(\sum_{i=1}^{n}x_{i}\) | \(\sum_{i=1}^{n-1}x_{i+1}(1-x_{i})\) | \(n\) | \(1\) |
UnaryEncInteger¶
-
class
UnaryEncInteger
(label, value_range)[source]¶ Unary encoded integer. The value that takes \([0, n]\) is represented by \(\sum_{i=1}^{n}x_{i}\) without any constraint.
Parameters: - label (str) – Label of the integer.
- lower (int) – Lower value of the integer.
- upper (int) – Upper value of the integer.
Examples
This example finds the value a, b such that \(a+b=3\) and \(2a-b=0\).
>>> from pyqubo import UnaryEncInteger >>> import dimod >>> a = UnaryEncInteger("a", (0, 3)) >>> b = UnaryEncInteger("b", (0, 3)) >>> M=2.0 >>> H = (2*a-b)**2 + M*(a+b-3)**2 >>> model = H.compile() >>> bqm = model.to_bqm() >>> import dimod >>> sampleset = dimod.ExactSolver().sample(bqm) >>> decoded_samples = model.decode_sampleset(sampleset) >>> best_sample = min(decoded_samples, key=lambda s: s.energy) >>> print(best_sample.subh['a']) 1.0 >>> print(best_sample.subh['b']) 2.0
LogEncInteger¶
-
class
LogEncInteger
(label, value_range)[source]¶ Log encoded integer. The value that takes \([0, n]\) is represented by \(\sum_{i=1}^{\lceil\log_{2}n\rceil}2^ix_{i}\) without any constraint.
Parameters: - label (str) – Label of the integer.
- lower (int) – Lower value of the integer.
- upper (int) – Upper value of the integer.
Examples
This example finds the value a, b such that \(a+b=5\) and \(2a-b=1\).
>>> from pyqubo import LogEncInteger >>> import dimod >>> a = LogEncInteger("a", (0, 4)) >>> b = LogEncInteger("b", (0, 4)) >>> M=2.0 >>> H = (2*a-b-1)**2 + M*(a+b-5)**2 >>> model = H.compile() >>> bqm = model.to_bqm() >>> import dimod >>> sampleset = dimod.ExactSolver().sample(bqm) >>> decoded_samples = model.decode_sampleset(sampleset) >>> best_sample = min(decoded_samples, key=lambda s: s.energy) >>> print(best_sample.subh['a']) 2.0 >>> print(best_sample.subh['b']) 3.0
OneHotEncInteger¶
-
class
OneHotEncInteger
(label, value_range, strength)[source]¶ One-hot encoded integer. The value that takes \([1, n]\) is represented by \(\sum_{i=1}^{n}ix_{i}\). Also we have the penalty function \(strength \times (\sum_{i=1}^{n}x_{i}-1)^2\) in the Hamiltonian.
Parameters: - label (str) – Label of the integer.
- lower (int) – Lower value of the integer.
- upper (int) – Upper value of the integer.
- strength (float/Placeholder) – Strength of the constraint.
Examples
This example is equivalent to the following Hamiltonian.
\[H = \left(\left(\sum_{i=1}^{3}ia_{i}+1\right) - 2\right)^2 + strength \times \left(\sum_{i=1}^{3}a_{i}-1\right)^2\]>>> from pyqubo import OneHotEncInteger >>> a = OneHotEncInteger("a", (1, 3), strength=5) >>> H = (a-2)**2 >>> model = H.compile() >>> bqm = model.to_bqm() >>> import dimod >>> sampleset = dimod.ExactSolver().sample(bqm) >>> decoded_samples = model.decode_sampleset(sampleset) >>> best_sample = min(decoded_samples, key=lambda s: s.energy) >>> print(best_sample.subh['a']) 2.0
OrderEncInteger¶
-
class
OrderEncInteger
(label, value_range, strength)[source]¶ Order encoded integer. This encoding is useful when you want to know whether the integer is more than k or not. The value that takes \([0, n]\) is represented by \(\sum_{i=1}^{n}x_{i}\). Also we have the penalty function \(strength \times \left(\sum_{i=1}^{n-1} \left(x_{i+1}-x_{i}x_{i+1}\right)\right)\) in the Hamiltonian. See the reference [TaTK09] for more details.
Parameters: - label (str) – Label of the integer.
- lower (int) – Lower value of the integer.
- upper (int) – Upper value of the integer.
- strength (float/Placeholder) – Strength of the constraint.
Examples
Create an order encoded integer a that takes [0, 3] with the strength = 5.0. Solution of a represents 2 which is the optimal solution of the Hamiltonian.
>>> from pyqubo import OrderEncInteger >>> a = OrderEncInteger("a", (0, 3), strength = 5.0) >>> model = ((a-2)**2).compile() >>> bqm = model.to_bqm() >>> import dimod >>> sampleset = dimod.ExactSolver().sample(bqm) >>> decoded_samples = model.decode_sampleset(sampleset) >>> best_sample = min(decoded_samples, key=lambda s: s.energy) >>> print(best_sample.subh['a']) 2.0
-
less_than
(k)[source]¶ Binary variable that represents whether the value is less than k.
Note
You cannot use this method alone. You should use this variable with the entire integer. See an example below.
Parameters: k (int) – Integer value. Returns: Express
Examples
This example finds the value of integer a and b such that \(a=b\) and \(a>1\) and \(b<3\). The obtained solution is \(a=b=2\).
>>> from pyqubo import OrderEncInteger >>> a = OrderEncInteger("a", (0, 4), strength = 5.0) >>> b = OrderEncInteger("b", (0, 4), strength = 5.0) >>> model = ((a-b)**2 + (1-a.more_than(1))**2 + (1-b.less_than(3))**2).compile() >>> bqm = model.to_bqm() >>> import dimod >>> sampleset = dimod.ExactSolver().sample(bqm) >>> decoded_samples = model.decode_sampleset(sampleset) >>> best_sample = min(decoded_samples, key=lambda s: s.energy) >>> print(best_sample.subh['a']) 2.0 >>> print(best_sample.subh['b']) 2.0
-
more_than
(k)[source]¶ Binary variable that represents whether the value is more than k.
Note
You cannot use this method alone. You should use this variable with the entire integer. See an example below.
Parameters: k (int) – Integer value. Returns: Express
Examples
This example finds the value of integer a and b such that \(a=b\) and \(a>1\) and \(b<3\). The obtained solution is \(a=b=2\).
>>> from pyqubo import OrderEncInteger >>> a = OrderEncInteger("a", (0, 4), strength = 5.0) >>> b = OrderEncInteger("b", (0, 4), strength = 5.0) >>> model = ((a-b)**2 + (1-a.more_than(1))**2 + (1-b.less_than(3))**2).compile() >>> bqm = model.to_bqm() >>> import dimod >>> sampleset = dimod.ExactSolver().sample(bqm) >>> decoded_samples = model.decode_sampleset(sampleset) >>> best_sample = min(decoded_samples, key=lambda s: s.energy) >>> print(best_sample.subh['a']) 2.0 >>> print(best_sample.subh['b']) 2.0
References
[TaTK09] | Tamura, N., Taga, A., Kitagawa, S., & Banbara, M. (2009). Compiling finite linear CSP into SAT. Constraints, 14(2), 254-272. |
Logical Constraint¶
NOT Constraint¶
-
class
NotConst
(a, b, label)[source]¶ Constraint: Not(a) = b.
Parameters: - a (
Express
) – expression to be binary - b (
Express
) – expression to be binary - label (str) – label to identify the constraint
Examples
In this example, when the binary variables satisfy the constraint, the energy is 0.0. On the other hand, when they break the constraint, the energy is 1.0 > 0.0.
>>> from pyqubo import NotConst, Binary >>> a, b = Binary('a'), Binary('b') >>> exp = NotConst(a, b, 'not') >>> model = exp.compile() >>> model.energy({'a': 1, 'b': 0}, vartype='BINARY') 0.0 >>> model.energy({'a': 1, 'b': 1}, vartype='BINARY') 1.0
- a (
AND Constraint¶
-
class
AndConst
(a, b, c, label)[source]¶ Constraint: AND(a, b) = c.
Parameters: - a (
Express
) – expression to be binary - b (
Express
) – expression to be binary - c (
Express
) – expression to be binary - label (str) – label to identify the constraint
Examples
In this example, when the binary variables satisfy the constraint, the energy is 0.0. On the other hand, when they break the constraint, the energy is 1.0 > 0.0.
>>> from pyqubo import AndConst, Binary >>> a, b, c = Binary('a'), Binary('b'), Binary('c') >>> exp = AndConst(a, b, c, 'and') >>> model = exp.compile() >>> model.energy({'a': 1, 'b': 0, 'c': 0}, vartype='BINARY') 0.0 >>> model.energy({'a': 0, 'b': 1, 'c': 1}, vartype='BINARY') 1.0
- a (
OR Constraint¶
-
class
OrConst
(a, b, c, label)[source]¶ Constraint: OR(a, b) = c.
Parameters: - a (
Express
) – expression to be binary - b (
Express
) – expression to be binary - c (
Express
) – expression to be binary - label (str) – label to identify the constraint
Examples
In this example, when the binary variables satisfy the constraint, the energy is 0.0. On the other hand, when they break the constraint, the energy is 1.0 > 0.0.
>>> from pyqubo import OrConst, Binary >>> a, b, c = Binary('a'), Binary('b'), Binary('c') >>> exp = OrConst(a, b, c, 'or') >>> model = exp.compile() >>> model.energy({'a': 1, 'b': 0, 'c': 1}, vartype='BINARY') 0.0 >>> model.energy({'a': 0, 'b': 1, 'c': 0}, vartype='BINARY') 1.0
- a (
XOR Constraint¶
-
class
XorConst
(a, b, c, label)[source]¶ Constraint: OR(a, b) = c.
Parameters: - a (
Express
) – expression to be binary - b (
Express
) – expression to be binary - c (
Express
) – expression to be binary - label (str) – label to identify the constraint
Examples
In this example, when the binary variables satisfy the constraint, the energy is 0.0. On the other hand, when they break the constraint, the energy is 1.0 > 0.0.
>>> from pyqubo import XorConst, Binary >>> a, b, c = Binary('a'), Binary('b'), Binary('c') >>> exp = XorConst(a, b, c, 'xor') >>> model = exp.compile() >>> model.energy({'a': 1, 'b': 0, 'c': 1, 'aux_xor': 0}, vartype='BINARY') 0.0 >>> model.energy({'a': 0, 'b': 1, 'c': 0, 'aux_xor': 0}, vartype='BINARY') 1.0
- a (
Logical Gate¶
Not¶
And¶
-
class
And
(bit_a, bit_b)[source]¶ Logical AND of inputs.
Parameters: - bit_a (
Express
) – expression to be binary - bit_b (
Express
) – expression to be binary
Examples
>>> from pyqubo import Binary, And >>> import itertools >>> a, b = Binary('a'), Binary('b') >>> exp = And(a, b) >>> model = exp.compile() >>> for a, b in itertools.product(*[(0, 1)] * 2): ... print(a, b, int(model.energy({'a': a, 'b': b}, vartype='BINARY'))) 0 0 0 0 1 0 1 0 0 1 1 1
- bit_a (
Or¶
-
class
Or
(bit_a, bit_b)[source]¶ Logical OR of inputs.
Parameters: - bit_a (
Express
) – expression to be binary - bit_b (
Express
) – expression to be binary
Examples
>>> from pyqubo import Binary, Or >>> import itertools >>> a, b = Binary('a'), Binary('b') >>> exp = Or(a, b) >>> model = exp.compile() >>> for a, b in itertools.product(*[(0, 1)] * 2): ... print(a, b, int(model.energy({'a': a, 'b': b}, vartype='BINARY'))) 0 0 0 0 1 1 1 0 1 1 1 1
- bit_a (
Xor¶
-
class
Xor
(bit_a, bit_b)[source]¶ Logical XOR of inputs.
Parameters: - bit_a (
Express
) – expression to be binary - bit_b (
Express
) – expression to be binary
Examples
>>> from pyqubo import Binary, Xor >>> import itertools >>> a, b = Binary('a'), Binary('b') >>> exp = Xor(a, b) >>> model = exp.compile() >>> for a, b in itertools.product(*[(0, 1)] * 2): ... print(a, b, int(model.energy({'a': a, 'b': b}, vartype='BINARY'))) 0 0 0 0 1 1 1 0 1 1 1 0
- bit_a (
Utils¶
Solvers¶
-
solve_ising
(linear, quad, num_reads=10, sweeps=1000, beta_range=(1.0, 50.0))[source]¶ [deprecated] Solve Ising model with Simulated Annealing (SA) provided by neal.
Parameters: - linear (dict[label, float]) – The linear parameter of the Ising model.
- quad (dict[(label, label), float]) – The quadratic parameter of the Ising model.
- num_reads (int, default=10) – Number of run repetitions of SA.
- sweeps (int, default=1000) – Number of iterations in each run of SA.
- beta_range (tuple(float, float), default=(1.0, 50.0)) – Tuple of start beta and end beta.
Note
solve_ising()
is deprecated. Use dwave-neal package instead like below.>>> from pyqubo import Spin >>> import neal >>> s1, s2, s3 = Spin("s1"), Spin("s2"), Spin("s3") >>> H = (2*s1 + 4*s2 + 6*s3)**2 >>> model = H.compile() >>> bqm = model.to_bqm() >>> sa = neal.SimulatedAnnealingSampler() >>> sampleset = sa.sample(bqm, num_reads=10) >>> samples = model.decode_sampleset(sampleset) >>> best_sample = min(samples, key=lambda s: s.energy) >>> pprint(best_sample.sample) # doctest: +SKIP {'s1': 0, 's2': 0, 's3': 1}
-
solve_qubo
(qubo, num_reads=10, sweeps=1000, beta_range=(1.0, 50.0))[source]¶ [deprecated] Solve QUBO with Simulated Annealing (SA) provided by neal.
Parameters: - qubo (dict[(label, label), float]) – The QUBO to be solved.
- num_reads (int, default=10) – Number of run repetitions of SA.
- sweeps (int, default=1000) – Number of iterations in each run of SA.
- beta_range (tuple(float, float), default=(1.0, 50.0)) – Tuple of start beta and end beta.
Returns: The solution of SA.
Return type: dict[label, bit]
Note
solve_qubo()
is deprecated. Use dwave-neal package instead like below.>>> from pyqubo import Spin >>> import neal >>> s1, s2, s3 = Spin("s1"), Spin("s2"), Spin("s3") >>> H = (2*s1 + 4*s2 + 6*s3)**2 >>> model = H.compile() >>> bqm = model.to_bqm() >>> sa = neal.SimulatedAnnealingSampler() >>> sampleset = sa.sample(bqm, num_reads=10) >>> samples = model.decode_sampleset(sampleset) >>> best_sample = min(samples, key=lambda s: s.energy) >>> pprint(best_sample.sample) # doctest: +SKIP {'s1': 0, 's2': 0, 's3': 1}