Binary Quadratic Model, Qubo & Ising

Any qlassf function can be transformed to a binary quadratic model using the to_bqm function. In this example we create a generic function that factorize num, and we bind num with 15.

from qlasskit import qlassf, Qint, Parameter


@qlassf
def test_factor_generic(num: Parameter[Qint[4]], a: Qint[3], b: Qint[3]) -> Qint[4]:
    return num - (a * b)


test_factor = test_factor_generic.bind(num=15)
bqm = test_factor.to_bqm()

print("Vars:", bqm.num_variables, "\nInteractions:", bqm.num_interactions)
Vars: 14 
Interactions: 41

A qlassf function can also be exported as a QUBO model using to_bqm('qubo') or as an Ising model using to_bqm('ising').

Running on simulated sampler annealer

Now we can run a simulated sampler annealer to minimize this function; qlasskit offer a decode_samples helper function that translates sample result to the high level types of qlasskit.

import neal
from qlasskit.bqm import decode_samples

sa = neal.SimulatedAnnealingSampler()
sampleset = sa.sample(bqm, num_reads=10)
decoded_samples = decode_samples(test_factor, sampleset)
best_sample = min(decoded_samples, key=lambda x: x.energy)
print(best_sample.sample)
{'a': 3, 'b': 5}

The result is 5 and 3 as expected (since 5 times 3 is equal to 15).

Running on DWave annealer

If we have a valid DWave account, we can run our problem on a real quantum annealer as follow:

from dwave.system import DWaveSampler, EmbeddingComposite

sampler = EmbeddingComposite(DWaveSampler())
sampleset = sampler.sample(bqm, num_reads=10)
decoded_samples = decode_samples(test_factor, sampleset)
best_sample = min(decoded_samples, key=lambda x: x.energy)
print(best_sample.sample)
---------------------------------------------------------------------------
SolverFailureError                        Traceback (most recent call last)
Cell In[3], line 5
      3 sampler = EmbeddingComposite(DWaveSampler())
      4 sampleset = sampler.sample(bqm, num_reads=10)
----> 5 decoded_samples = decode_samples(test_factor, sampleset)
      6 best_sample = min(decoded_samples, key=lambda x: x.energy)
      7 print(best_sample.sample)

File ~/.pyenv/versions/3.10.13/envs/qlasskit_310-env/lib/python3.10/site-packages/qlasskit-0.1.18-py3.10.egg/qlasskit/bqm.py:142, in decode_samples(qf, sampleset)
    140 """Get dimod sampleset and return an high level decoded solution"""
    141 model = qf.to_bqm("pq_model")
--> 142 decoded = model.decode_sampleset(sampleset)
    144 new_dec = []
    145 for el in decoded:

File ~/.pyenv/versions/3.10.13/envs/qlasskit_310-env/lib/python3.10/site-packages/dimod/sampleset.py:1121, in SampleSet.record(self)
   1105 @property
   1106 def record(self):
   1107     """:obj:`numpy.recarray` containing the samples, energies, number of occurences, and other sample data.
   1108 
   1109     Examples:
   (...)
   1119 
   1120     """
-> 1121     self.resolve()
   1122     return self._record

File ~/.pyenv/versions/3.10.13/envs/qlasskit_310-env/lib/python3.10/site-packages/dimod/sampleset.py:1485, in SampleSet.resolve(self)
   1483 # if it doesn't have the attribute then it is already resolved
   1484 if hasattr(self, '_future'):
-> 1485     samples = self._result_hook(self._future)
   1486     self.__init__(samples.record, samples.variables, samples.info, samples.vartype)
   1487     del self._future

File ~/.pyenv/versions/3.10.13/envs/qlasskit_310-env/lib/python3.10/site-packages/dwave/system/composites/embedding.py:284, in EmbeddingComposite.sample.<locals>.async_unembed(response)
    279 def async_unembed(response):
    280     # unembed the sampleset aysnchronously.
    282     warninghandler.chain_break(response, embedding)
--> 284     sampleset = unembed_sampleset(response, embedding, source_bqm=bqm,
    285                                   chain_break_method=chain_break_method,
    286                                   chain_break_fraction=chain_break_fraction,
    287                                   return_embedding=return_embedding)
    289     if return_embedding:
    290         sampleset.info['embedding_context'].update(
    291             embedding_parameters=embedding_parameters,
    292             chain_strength=embedding.chain_strength)

File ~/.pyenv/versions/3.10.13/envs/qlasskit_310-env/lib/python3.10/site-packages/dwave/embedding/transforms.py:606, in unembed_sampleset(target_sampleset, embedding, source_bqm, chain_break_method, chain_break_fraction, return_embedding)
    603 except KeyError:
    604     raise ValueError("given bqm does not match the embedding")
--> 606 record = target_sampleset.record
    608 unembedded, idxs = chain_break_method(target_sampleset, chains)
    610 reserved = {'sample', 'energy'}

File ~/.pyenv/versions/3.10.13/envs/qlasskit_310-env/lib/python3.10/site-packages/dimod/sampleset.py:1121, in SampleSet.record(self)
   1105 @property
   1106 def record(self):
   1107     """:obj:`numpy.recarray` containing the samples, energies, number of occurences, and other sample data.
   1108 
   1109     Examples:
   (...)
   1119 
   1120     """
-> 1121     self.resolve()
   1122     return self._record

File ~/.pyenv/versions/3.10.13/envs/qlasskit_310-env/lib/python3.10/site-packages/dimod/sampleset.py:1485, in SampleSet.resolve(self)
   1483 # if it doesn't have the attribute then it is already resolved
   1484 if hasattr(self, '_future'):
-> 1485     samples = self._result_hook(self._future)
   1486     self.__init__(samples.record, samples.variables, samples.info, samples.vartype)
   1487     del self._future

File ~/.pyenv/versions/3.10.13/envs/qlasskit_310-env/lib/python3.10/site-packages/dwave/system/samplers/dwave_sampler.py:452, in DWaveSampler.sample.<locals>._hook(computation)
    450 except (SolverError, InvalidAPIResponseError) as exc:
    451     if not self.failover:
--> 452         raise exc
    453     if isinstance(exc, SolverAuthenticationError):
    454         raise exc

File ~/.pyenv/versions/3.10.13/envs/qlasskit_310-env/lib/python3.10/site-packages/dwave/system/samplers/dwave_sampler.py:439, in DWaveSampler.sample.<locals>._hook(computation)
    436     return sampleset
    438 try:
--> 439     return resolve(computation)
    441 except (ProblemUploadError, RequestTimeout, PollingTimeout) as exc:
    442     if not self.failover:

File ~/.pyenv/versions/3.10.13/envs/qlasskit_310-env/lib/python3.10/site-packages/dwave/system/samplers/dwave_sampler.py:429, in DWaveSampler.sample.<locals>._hook.<locals>.resolve(computation)
    427 def resolve(computation):
    428     sampleset = computation.sampleset
--> 429     sampleset.resolve()
    431     if warninghandler is not None:
    432         warninghandler.too_few_samples(sampleset)

File ~/.pyenv/versions/3.10.13/envs/qlasskit_310-env/lib/python3.10/site-packages/dimod/sampleset.py:1485, in SampleSet.resolve(self)
   1483 # if it doesn't have the attribute then it is already resolved
   1484 if hasattr(self, '_future'):
-> 1485     samples = self._result_hook(self._future)
   1486     self.__init__(samples.record, samples.variables, samples.info, samples.vartype)
   1487     del self._future

File ~/.pyenv/versions/3.10.13/envs/qlasskit_310-env/lib/python3.10/site-packages/dwave/cloud/computation.py:823, in Future.sampleset.<locals>.<lambda>(f)
    818 except ImportError:
    819     raise RuntimeError("Can't construct SampleSet without dimod. "
    820                        "Re-install the library with 'bqm' support.")
    822 self._sampleset = sampleset = dimod.SampleSet.from_future(
--> 823     self, lambda f: f.wait_sampleset())
    825 # propagate id to sampleset as well
    826 # note: this requires dimod>=0.8.21 (before that version SampleSet
    827 # had slots set which prevented dynamic addition of attributes).
    828 sampleset.wait_id = self.wait_id

File ~/.pyenv/versions/3.10.13/envs/qlasskit_310-env/lib/python3.10/site-packages/dwave/cloud/computation.py:755, in Future.wait_sampleset(self)
    752 """Blocking sampleset getter."""
    754 # blocking result get
--> 755 result = self._load_result()
    757 # common problem info: id/label
    758 problem_info = dict(problem_id=self.id)

File ~/.pyenv/versions/3.10.13/envs/qlasskit_310-env/lib/python3.10/site-packages/dwave/cloud/computation.py:893, in Future._load_result(self)
    891 # Check for other error conditions
    892 if self._exception is not None:
--> 893     raise self._exception
    895 # If someone else took care of this while we were waiting
    896 if self._result is not None:

File ~/.pyenv/versions/3.10.13/envs/qlasskit_310-env/lib/python3.10/site-packages/dwave/cloud/client/base.py:1309, in Client._handle_problem_status(self, message, future)
   1307 if 'error_code' in message and 'error_msg' in message:
   1308     logger.debug("Error response received: %r", message)
-> 1309     raise SolverFailureError(message['error_msg'])
   1311 if 'status' not in message:
   1312     raise InvalidAPIResponseError("'status' missing in problem description response")

SolverFailureError: Problem not accepted because user has insufficient remaining solver access time in project DEV.