Skip to content
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

Specialized dwave samplers #317

Merged
merged 11 commits into from
Sep 22, 2020
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
dwave.embedding.chain_breaks.MinimizeEnergy
===========================================
dwave.embedding.chain\_breaks.MinimizeEnergy
============================================

.. currentmodule:: dwave.embedding.chain_breaks

.. autoclass:: MinimizeEnergy
:members: __call__


.. automethod:: __init__


.. rubric:: Methods

.. autosummary::

~MinimizeEnergy.__init__
5 changes: 1 addition & 4 deletions dwave/system/samplers/clique.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,7 @@ class DWaveCliqueSampler(dimod.Sampler):
"""
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)
self.child = child = DWaveSampler(**config)

# do some topology checking
try:
Expand Down
75 changes: 48 additions & 27 deletions dwave/system/samplers/dwave_sampler.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@
"""
from __future__ import division

import functools
import time
import functools
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do alphabetical, you do by size 😆

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not true at all.

image

import collections.abc as abc

from warnings import warn

Expand Down Expand Up @@ -113,28 +114,43 @@ class DWaveSampler(dimod.Sampler, dimod.Structured):
See :meth:`~dwave.cloud.client.Client.get_solvers` for a more detailed
description of the parameter.

Note:
Isolated use of ``order_by`` has been deprecated in 0.10.0.
Please specify it as part of the ``solver`` argument dict.

config_file (str, optional):
Path to a configuration file that identifies a D-Wave system and provides
connection information.

profile (str, optional):
Profile to select from the configuration file.

client (str, optional, default='qpu'):
Client type used for accessing the API. Client type effectively
constraints the solver's ``category``. Supported values are
``qpu``, ``sw`` and ``hybrid`` for QPU, software and hybrid solvers
respectively. In addition, ``base`` client type doesn't filter
solvers at all.

Note:
Prior to version 0.10.0, :class:`.DWaveSampler` used the
``base`` client, allowing non-QPU solvers to be selected.
To reproduce the old behavior, set ``client='base'``.

endpoint (str, optional):
D-Wave API endpoint URL.

token (str, optional):
Authentication token for the D-Wave API to authenticate the client session.

solver (dict/str, optional):
Solver (a D-Wave system on which to run submitted problems) to select given
as a set of required features. Supported features and values are described in
Solver (a D-Wave system on which to run submitted problems) to
select given as a set of required features. Solver priority (if
multiple solvers match) can be given via ``order_by`` key. Supported
features and values are described in
:meth:`~dwave.cloud.client.Client.get_solvers`. For backward
compatibility, a solver name, formatted as a string, is accepted.

proxy (str, optional):
Proxy URL to be used for accessing the D-Wave API.

**config:
Keyword arguments passed directly to :meth:`~dwave.cloud.client.Client.from_config`.

Expand All @@ -153,7 +169,7 @@ class DWaveSampler(dimod.Sampler, dimod.Structured):

>>> from dwave.system import DWaveSampler
...
>>> sampler = DWaveSampler(solver={'qpu': True})
>>> sampler = DWaveSampler()
...
>>> qubit_a = sampler.nodelist[0]
>>> qubit_b = next(iter(sampler.adjacency[qubit_a]))
Expand All @@ -169,18 +185,25 @@ class DWaveSampler(dimod.Sampler, dimod.Structured):
"""
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)
# fold isolated order_by under solver
if order_by is not None:
warn("'order_by' has been moved under 'solver' dict.",
DeprecationWarning)

if config.get('solver') is not None:
raise ValueError("can not combine 'solver' and 'solver_features'")
# strongly prefer QPU solvers; requires kwarg-level override
config.setdefault('client', 'qpu')

config['solver'] = config.pop('solver_features')
# weakly prefer QPU solver with the highest qubit count,
# easily overridden on any config level above defaults (file/env/kwarg)
defaults = config.setdefault('defaults', {})
if not isinstance(defaults, abc.Mapping):
raise TypeError("mapping expected for 'defaults'")
defaults.update(solver=dict(order_by='-num_active_qubits'))

self.client = Client.from_config(**config)

# NOTE: split behavior until we remove `order_by` kwarg
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)
Expand All @@ -189,7 +212,7 @@ def __init__(self, failover=False, retry_interval=-1, order_by=None, **config):
self.retry_interval = retry_interval

warnings_default = WarningAction.IGNORE
"""Defines the default behabior for :meth:`.sample_ising`'s and
"""Defines the default behavior for :meth:`.sample_ising`'s and
:meth:`sample_qubo`'s `warnings` kwarg.
"""

Expand All @@ -207,7 +230,7 @@ def properties(self):
>>> from dwave.system import DWaveSampler
>>> sampler = DWaveSampler()
>>> sampler.properties # doctest: +SKIP
{u'anneal_offset_ranges': [[-0.2197463755538704, 0.03821687759418928],
{'anneal_offset_ranges': [[-0.2197463755538704, 0.03821687759418928],
[-0.2242514597680286, 0.01718456460967399],
[-0.20860153999435985, 0.05511969218508182],
# Snipped above response for brevity
Expand Down Expand Up @@ -238,12 +261,12 @@ def parameters(self):
>>> from dwave.system import DWaveSampler
>>> sampler = DWaveSampler()
>>> sampler.parameters # doctest: +SKIP
{u'anneal_offsets': ['parameters'],
u'anneal_schedule': ['parameters'],
u'annealing_time': ['parameters'],
u'answer_mode': ['parameters'],
u'auto_scale': ['parameters'],
# Snipped above response for brevity
{'anneal_offsets': ['parameters'],
'anneal_schedule': ['parameters'],
'annealing_time': ['parameters'],
'answer_mode': ['parameters'],
'auto_scale': ['parameters'],
# Snipped above response for brevity

See `Ocean Glossary <https://docs.ocean.dwavesys.com/en/stable/concepts/index.html>`_
for explanations of technical terms in descriptions of Ocean tools.
Expand Down Expand Up @@ -342,7 +365,7 @@ def sample(self, bqm, warnings=None, **kwargs):

>>> from dwave.system import DWaveSampler
...
>>> sampler = DWaveSampler(solver={'qpu': True})
>>> sampler = DWaveSampler()
...
>>> qubit_a = sampler.nodelist[0]
>>> qubit_b = next(iter(sampler.adjacency[qubit_a]))
Expand Down Expand Up @@ -496,24 +519,22 @@ def validate_anneal_schedule(self, anneal_schedule):


def to_networkx_graph(self):

"""Converts DWaveSampler's structure to a Chimera or Pegasus NetworkX graph.

Returns:
G : :class:`networkx.Graph` graph.
:class:`networkx.Graph`:
Either an (m, n, t) Chimera lattice or a Pegasus lattice of size m.

Examples:
This example converts a selected D-Wave system solver to a graph
and verifies it has over 2000 nodes.

>>> from dwave.system import DWaveSampler
...
>>> sampler = DWaveSampler(solver={'qpu': True})
>>> sampler = DWaveSampler()
>>> g = sampler.to_networkx_graph() # doctest: +SKIP
>>> len(g.nodes) > 2000 # doctest: +SKIP
True


"""

topology_type = self.properties['topology']['type']
Expand Down
29 changes: 14 additions & 15 deletions dwave/system/samplers/leap_hybrid_sampler.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

__all__ = ['LeapHybridSampler']


class LeapHybridSampler(dimod.Sampler):
"""A class for using Leap's cloud-based hybrid solvers.

Expand All @@ -40,15 +41,6 @@ class LeapHybridSampler(dimod.Sampler):
Inherits from :class:`dimod.Sampler`.

Args:
solver (dict/str, optional):
Solver (a hybrid solver on which to run submitted problems) to select
named as a string or given as a set of required features. Supported
features and values are described in
:meth:`~dwave.cloud.client.Client.get_solvers`.

connection_close (bool, optional):
Force HTTP(S) connection close after each request.

config_file (str, optional):
Path to a configuration file that identifies a hybrid solver and provides
connection information.
Expand All @@ -62,11 +54,14 @@ class LeapHybridSampler(dimod.Sampler):
token (str, optional):
Authentication token for the D-Wave API to authenticate the client session.

proxy (str, optional):
Proxy URL to be used for accessing the D-Wave API.
solver (dict/str, optional):
Solver (a hybrid solver on which to run submitted problems) to select
named as a string or given as a set of required features. Supported
features and values are described in
:meth:`~dwave.cloud.client.Client.get_solvers`.

**config:
Keyword arguments passed directly to :meth:`~dwave.cloud.client.Client.from_config`.
Keyword arguments passed to :meth:`~dwave.cloud.client.Client.from_config`.

Examples:
This example builds a random sparse graph and uses a hybrid solver to find a
Expand Down Expand Up @@ -94,22 +89,26 @@ class LeapHybridSampler(dimod.Sampler):

def __init__(self, solver=None, connection_close=True, **config):

# always use the base class (QPU client filters-out the hybrid solvers)
config['client'] = 'base'
# we want a Hybrid solver by default, but allow override
config.setdefault('client', 'hybrid')

if solver is None:
solver = {}

if isinstance(solver, abc.Mapping):
# TODO: instead of solver selection, try with user's default first
if solver.setdefault('category', 'hybrid') != 'hybrid':
raise ValueError("the only 'category' this sampler supports is 'hybrid'")
if solver.setdefault('supported_problem_types__contains', 'bqm') != 'bqm':
raise ValueError("the only problem type this sampler supports is 'bqm'")

# prefer the latest version, but allow kwarg override
solver.setdefault('order_by', '-version')

self.client = Client.from_config(
solver=solver, connection_close=connection_close, **config)

self.solver = self.client.get_solver(order_by='-version')
self.solver = self.client.get_solver()

# For explicitly named solvers:
if self.properties.get('category') != 'hybrid':
Expand Down
2 changes: 1 addition & 1 deletion dwave/system/utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def common_working_graph(graph0, graph1):
>>> import dwave_networkx as dnx
>>> from dwave.system import DWaveSampler, common_working_graph
...
>>> sampler = DWaveSampler(solver={'qpu': True})
>>> sampler = DWaveSampler()
>>> C4 = dnx.chimera_graph(4) # a 4x4 lattice of Chimera tiles
>>> c4_working_graph = common_working_graph(C4, sampler.adjacency)

Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
--extra-index-url https://pypi.dwavesys.com/simple

dimod==0.9.6
dwave-cloud-client==0.7.5
dwave-cloud-client==0.8.0
dwave-networkx==0.8.4
dwave-drivers==0.4.4
dwave-tabu==0.2.2
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@


install_requires = ['dimod>=0.9.6,<0.10.0',
'dwave-cloud-client>=0.7.5,<0.8.0',
'dwave-cloud-client>=0.8.0,<0.9.0',
'dwave-networkx>=0.8.4',
'networkx>=2.0,<3.0',
'homebase>=1.0.0,<2.0.0',
Expand Down
6 changes: 2 additions & 4 deletions tests/qpu/test_dwavecliquesampler.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@
class TestDWaveCliqueSampler(unittest.TestCase):
def test_chimera(self):
try:
sampler = DWaveCliqueSampler(
solver=dict(topology__type='chimera', qpu=True))
sampler = DWaveCliqueSampler(solver=dict(topology__type='chimera'))
except (ValueError, ConfigFileError, SolverNotFoundError):
raise unittest.SkipTest("no Chimera-structured QPU available")

Expand All @@ -40,8 +39,7 @@ def test_chimera(self):

def test_pegasus(self):
try:
sampler = DWaveCliqueSampler(
solver=dict(topology__type='pegasus', qpu=True))
sampler = DWaveCliqueSampler(solver=dict(topology__type='pegasus'))
except (ValueError, ConfigFileError, SolverNotFoundError):
raise unittest.SkipTest("no Pegasus-structured QPU available")

Expand Down
30 changes: 27 additions & 3 deletions tests/qpu/test_dwavesampler.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@
#
# =============================================================================
import unittest
from unittest import mock

import numpy

import dimod
from dwave.cloud.exceptions import ConfigFileError
from dwave.cloud.client import Client

from dwave.system.samplers import DWaveSampler

Expand All @@ -28,7 +30,7 @@ class TestDWaveSampler(unittest.TestCase):
@classmethod
def setUpClass(cls):
try:
cls.qpu = DWaveSampler(solver=dict(qpu=True))
cls.qpu = DWaveSampler()
except (ValueError, ConfigFileError):
raise unittest.SkipTest("no qpu available")

Expand Down Expand Up @@ -113,8 +115,7 @@ class TestMissingQubits(unittest.TestCase):
def setUpClass(cls):
try:
# get a QPU with less than 100% yield
cls.qpu = DWaveSampler(solver=dict(qpu=True,
num_active_qubits__lt=2048))
cls.qpu = DWaveSampler(solver=dict(num_active_qubits__lt=2048))
except (ValueError, ConfigFileError):
raise unittest.SkipTest("no qpu available")

Expand All @@ -132,3 +133,26 @@ def test_sample_ising_h_list(self):

self.assertEqual(set(sampleset.variables), set(sampler.nodelist))
assert len(sampleset.variables) < 2048 # sanity check


class TestClientSelection(unittest.TestCase):

def test_client_type(self):
with mock.patch('dwave.cloud.qpu.Client') as qpu:
self.assertEqual(DWaveSampler().client, qpu())
self.assertEqual(DWaveSampler(client='qpu').client, qpu())

with mock.patch('dwave.cloud.sw.Client') as sw:
self.assertEqual(DWaveSampler(client='sw').client, sw())

with mock.patch('dwave.cloud.hybrid.Client') as hybrid:
self.assertEqual(DWaveSampler(client='hybrid').client, hybrid())

def test_base_client(self):
# to test 'base' client instantiation offline,
# we would need a mock client and a mock solver
try:
self.assertEqual(type(DWaveSampler(client=None).client), Client)
self.assertEqual(type(DWaveSampler(client='base').client), Client)
except (ValueError, ConfigFileError):
raise unittest.SkipTest("no API token available")
4 changes: 2 additions & 2 deletions tests/qpu/test_embeddingcomposite.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ class TestEmbeddingCompositeExactSolver(unittest.TestCase):
@classmethod
def setUpClass(cls):
try:
cls.qpu = DWaveSampler(solver=dict(
qpu=True, initial_state=True, anneal_schedule=True))
cls.qpu = DWaveSampler(
solver=dict(initial_state=True, anneal_schedule=True))
except (ValueError, ConfigFileError):
raise unittest.SkipTest("no qpu available")

Expand Down
Loading