-
Notifications
You must be signed in to change notification settings - Fork 65
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add DWaveCliqueSampler #313
Changes from all commits
2448305
0bd37b9
d8b797d
b3a9242
8e77d89
9af6f37
8578e0c
589156e
e7e85ac
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,197 @@ | ||
# Copyright 2020 D-Wave Systems Inc. | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
import math | ||
|
||
import dimod | ||
import dwave_networkx as dnx | ||
|
||
from minorminer.busclique import find_clique_embedding, busgraph_cache | ||
|
||
from dwave.system.samplers.dwave_sampler import DWaveSampler | ||
|
||
__all__ = ['DWaveCliqueSampler'] | ||
|
||
|
||
class DWaveCliqueSampler(dimod.Sampler): | ||
"""A sampler for solving clique problems on the D-Wave system. | ||
|
||
The `DWaveCliqueSampler` wraps | ||
:func:`minorminer.busclique.find_clique_embedding` to generate embeddings | ||
with even chain length. These embeddings will work well for dense | ||
binary quadratic models. For sparse models, using | ||
:class:`.EmbeddingComposite` with :class:`.DWaveSampler` is preferred. | ||
|
||
Args: | ||
**config: | ||
Keyword arguments, as accepted by :class:`.DWaveSampler` | ||
|
||
""" | ||
def __init__(self, **config): | ||
|
||
# get the QPU with the most qubits available, subject to other | ||
# constraints specified in **config | ||
self.child = child = DWaveSampler(order_by='-num_active_qubits', | ||
**config) | ||
|
||
# do some topology checking | ||
try: | ||
topology_type = child.properties['topology']['type'] | ||
shape = child.properties['topology']['shape'] | ||
except KeyError: | ||
raise ValueError("given sampler has unknown topology format") | ||
Comment on lines
+49
to
+53
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will fail on old solvers (with That might be acceptable, given this is a new feature, and the alternative still works. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we have anything other than C16s in production? A potential last-ditch workaround is to check if the edgelist is a subset of a C16... |
||
|
||
# We need a networkx graph with certain properties. In the | ||
# future it would be good for DWaveSampler to handle this. | ||
# See https://github.com/dwavesystems/dimod/issues/647 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Link for posterity dwavesystems/dimod#647 |
||
if topology_type == 'chimera': | ||
G = dnx.chimera_graph(*shape, | ||
node_list=child.nodelist, | ||
edge_list=child.edgelist, | ||
) | ||
elif topology_type == 'pegasus': | ||
G = dnx.pegasus_graph(shape[0], | ||
node_list=child.nodelist, | ||
edge_list=child.edgelist, | ||
) | ||
else: | ||
raise ValueError("unknown topology type") | ||
|
||
self.target_graph = G | ||
|
||
# get the energy range | ||
self.qpu_linear_range = child.properties['h_range'] | ||
self.qpu_quadratic_range = child.properties.get( | ||
'extended_j_range', child.properties['j_range']) | ||
|
||
@property | ||
def parameters(self): | ||
try: | ||
return self._parameters | ||
except AttributeError: | ||
pass | ||
|
||
self._parameters = parameters = self.child.parameters.copy() | ||
|
||
# this sampler handles scaling | ||
parameters.pop('auto_scale', None) | ||
parameters.pop('bias_range', None) | ||
parameters.pop('quadratic_range', None) | ||
|
||
return parameters | ||
|
||
@property | ||
def properties(self): | ||
try: | ||
return self._properties | ||
except AttributeError: | ||
pass | ||
|
||
self._properties = dict(qpu_properties=self.child.properties) | ||
return self.properties | ||
|
||
@property | ||
def largest_clique_size(self): | ||
"""The maximum number of variables.""" | ||
return len(self.largest_clique()) | ||
|
||
def largest_clique(self): | ||
"""Return a largest-size clique embedding.""" | ||
return busgraph_cache(self.target_graph).largest_clique() | ||
|
||
def sample(self, bqm, chain_strength=None, **kwargs): | ||
"""Sample from the specified binary quadratic model. | ||
|
||
Args: | ||
bqm (:class:`~dimod.BinaryQuadraticModel`): | ||
Any binary quadratic model with up to | ||
:attr:`.largest_clique_size` variables. This BQM is embedded | ||
using a dense clique embedding. | ||
|
||
chain_strength (float, optional): | ||
The (relative) chain strength to use in the embedding. By | ||
default a chain strength of `1.5sqrt(N)` where `N` is the size | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can use RST :math: here |
||
of the largest clique, as returned by | ||
:attr:`.largest_clique_size`. | ||
|
||
**kwargs: | ||
Optional keyword arguments for the sampling method, specified | ||
per solver in :attr:`.DWaveCliqueSampler.parameters`. | ||
D-Wave System Documentation's | ||
`solver guide <https://docs.dwavesys.com/docs/latest/doc_solver_ref.html>`_ | ||
describes the parameters and properties supported on the D-Wave | ||
system. Note that `auto_scale` is not supported by this | ||
sampler, because it scales the problem as part of the embedding | ||
process. | ||
|
||
""" | ||
|
||
# some arguments should not be overwritten | ||
if 'auto_scale' in kwargs: | ||
raise TypeError("sample() got an unexpected keyword argument " | ||
"'auto_scale'") | ||
if 'bias_range' in kwargs: | ||
raise TypeError("sample() got an unexpected keyword argument " | ||
"'bias_range'") | ||
if 'quadratic_range' in kwargs: | ||
raise TypeError("sample() got an unexpected keyword argument " | ||
"'quadratic_range'") | ||
|
||
# handle circular import. todo: fix | ||
from dwave.system.composites.embedding import FixedEmbeddingComposite | ||
|
||
# get the embedding | ||
embedding = find_clique_embedding(bqm.variables, self.target_graph, | ||
arcondello marked this conversation as resolved.
Show resolved
Hide resolved
|
||
use_cache=True) | ||
|
||
# returns an empty embedding when the BQM is too large | ||
if not embedding and bqm.num_variables: | ||
raise ValueError("Cannot embed given BQM (size {}), sampler can " | ||
"only handle problems of size {}".format( | ||
len(bqm.variables), self.largest_clique_size)) | ||
|
||
assert bqm.num_variables == len(embedding) # sanity check | ||
|
||
if chain_strength is None: | ||
# chain length determines chain strength | ||
if embedding: | ||
chain_strength = 1.5 * math.sqrt(len(embedding)) | ||
else: | ||
chain_strength = 1 # doesn't matter | ||
|
||
# scale chain strength by the problem scale | ||
scalar = max(max(map(abs, bqm.linear.values()), default=0), | ||
max(map(abs, bqm.quadratic.values()), default=0)) | ||
Comment on lines
+174
to
+175
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is where dwavesystems/dimod#402 would really pay-off. |
||
chain_strength *= scalar | ||
|
||
# # scaling only make sense in Ising space | ||
original_bqm = bqm | ||
|
||
if bqm.vartype is not dimod.SPIN: | ||
bqm = bqm.change_vartype(dimod.SPIN, inplace=False) | ||
|
||
sampler = FixedEmbeddingComposite( | ||
dimod.ScaleComposite(self.child), | ||
embedding) | ||
|
||
sampleset = sampler.sample(bqm, | ||
arcondello marked this conversation as resolved.
Show resolved
Hide resolved
|
||
bias_range=self.qpu_linear_range, | ||
quadratic_range=self.qpu_quadratic_range, | ||
arcondello marked this conversation as resolved.
Show resolved
Hide resolved
|
||
auto_scale=False, | ||
chain_strength=chain_strength, | ||
**kwargs | ||
) | ||
|
||
# change_vartype is non-blocking | ||
return sampleset.change_vartype(original_bqm.vartype) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -105,6 +105,12 @@ class DWaveSampler(dimod.Sampler, dimod.Structured): | |
then it will instead propogate the `SolverNotFoundError` to the | ||
user. | ||
|
||
order_by (callable/str/None): | ||
Solver sorting key function or (or :class:`~dwave.cloud.Solver` | ||
attribute/item dot-separated path). | ||
See :class:`~dwave.cloud.Client.get_solvers` for a more detailed | ||
description of the parameter. | ||
|
||
Comment on lines
+108
to
+113
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've just realized dwavesystems/dwave-cloud-client#405 broke the design of IMO, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Where should it be an argument? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Part of solver definition, e.g. |
||
config_file (str, optional): | ||
Path to a configuration file that identifies a D-Wave system and provides | ||
connection information. | ||
|
@@ -151,7 +157,7 @@ class DWaveSampler(dimod.Sampler, dimod.Structured): | |
for explanations of technical terms in descriptions of Ocean tools. | ||
|
||
""" | ||
def __init__(self, failover=False, retry_interval=-1, **config): | ||
def __init__(self, failover=False, retry_interval=-1, order_by=None, **config): | ||
|
||
if config.get('solver_features') is not None: | ||
warn("'solver_features' argument has been renamed to 'solver'.", DeprecationWarning) | ||
|
@@ -162,7 +168,12 @@ def __init__(self, failover=False, retry_interval=-1, **config): | |
config['solver'] = config.pop('solver_features') | ||
|
||
self.client = Client.from_config(**config) | ||
self.solver = self.client.get_solver() | ||
|
||
if order_by is None: | ||
# use the default from the cloud-client | ||
self.solver = self.client.get_solver() | ||
else: | ||
self.solver = self.client.get_solver(order_by=order_by) | ||
|
||
self.failover = failover | ||
self.retry_interval = retry_interval | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
# Copyright 2020 D-Wave Systems Inc. | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
import itertools | ||
import unittest | ||
|
||
import dimod | ||
|
||
from dwave.cloud.exceptions import ConfigFileError, SolverNotFoundError | ||
from dwave.system import DWaveCliqueSampler | ||
|
||
|
||
class TestDWaveCliqueSampler(unittest.TestCase): | ||
def test_chimera(self): | ||
try: | ||
sampler = DWaveCliqueSampler( | ||
solver=dict(topology__type='chimera', qpu=True)) | ||
except (ValueError, ConfigFileError, SolverNotFoundError): | ||
raise unittest.SkipTest("no Chimera-structured QPU available") | ||
|
||
dimod.testing.assert_sampler_api(sampler) | ||
|
||
# submit a maximum ferromagnet | ||
bqm = dimod.AdjVectorBQM('SPIN') | ||
for u, v in itertools.combinations(sampler.largest_clique(), 2): | ||
bqm.quadratic[u, v] = -1 | ||
|
||
sampler.sample(bqm).resolve() | ||
|
||
def test_pegasus(self): | ||
try: | ||
sampler = DWaveCliqueSampler( | ||
solver=dict(topology__type='pegasus', qpu=True)) | ||
except (ValueError, ConfigFileError, SolverNotFoundError): | ||
raise unittest.SkipTest("no Pegasus-structured QPU available") | ||
|
||
dimod.testing.assert_sampler_api(sampler) | ||
|
||
# submit a maximum ferromagnet | ||
bqm = dimod.AdjVectorBQM('SPIN') | ||
for u, v in itertools.combinations(sampler.largest_clique(), 2): | ||
bqm.quadratic[u, v] = -1 | ||
|
||
sampler.sample(bqm).resolve() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add a reference to https://docs.ocean.dwavesys.com/en/stable/docs_system/reference/samplers.html#dwavesampler so new users see what a DWaveSampler is.