Repository: dwavesystems/dwave-system

Issue:
Specialized samplers for QPU & hybrid solvers + a new general one
Continuing the discussion from #278 here. In short, the proposal so far is:

- [x] (A) Specialize `DWaveSampler` for QPU solvers by using `client=qpu` and leave `solver` features undefined.

- [x] (B) Specialize `LeapHybridSampler` for hybrid solvers, with `client=hybrid`.

- [ ] ~(C) Introduce a new general sampler (name TBD), which will use the generic `client=base`, it will support structured and unstructured solvers, and it won't imply any solver features. Ideally, this sampler would be easy to subclass. Potentially implement `DWaveSampler` and `LeapHybridSampler` as such subclasses.~

- [ ] (D) Specialize `DWaveCliqueSampler` for QPU solvers by using `client=qpu` and leave `solver` features undefined.

Discussion:
codecov-commenter: # [Codecov](https://codecov.io/gh/dwavesystems/dwave-system/pull/317?src=pr&el=h1) Report
> Merging [#317](https://codecov.io/gh/dwavesystems/dwave-system/pull/317?src=pr&el=desc) into [master](https://codecov.io/gh/dwavesystems/dwave-system/commit/0cf36f5db89d8c16c4d36be56a7487235de15ea7?el=desc) will **decrease** coverage by `1.26%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/dwavesystems/dwave-system/pull/317/graphs/tree.svg?width=650&height=150&src=pr&token=t7cis9A6tt)](https://codecov.io/gh/dwavesystems/dwave-system/pull/317?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #317      +/-   ##
==========================================
- Coverage   92.85%   91.58%   -1.27%     
==========================================
  Files          22       22              
  Lines        1176     1177       +1     
==========================================
- Hits         1092     1078      -14     
- Misses         84       99      +15     
```


| [Impacted Files](https://codecov.io/gh/dwavesystems/dwave-system/pull/317?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [dwave/system/samplers/dwave\_sampler.py](https://codecov.io/gh/dwavesystems/dwave-system/pull/317/diff?src=pr&el=tree#diff-ZHdhdmUvc3lzdGVtL3NhbXBsZXJzL2R3YXZlX3NhbXBsZXIucHk=) | `78.75% <100.00%> (-8.05%)` | :arrow_down: |
| [dwave/system/samplers/leap\_hybrid\_sampler.py](https://codecov.io/gh/dwavesystems/dwave-system/pull/317/diff?src=pr&el=tree#diff-ZHdhdmUvc3lzdGVtL3NhbXBsZXJzL2xlYXBfaHlicmlkX3NhbXBsZXIucHk=) | `73.52% <100.00%> (ø)` | |
| [dwave/system/composites/embedding.py](https://codecov.io/gh/dwavesystems/dwave-system/pull/317/diff?src=pr&el=tree#diff-ZHdhdmUvc3lzdGVtL2NvbXBvc2l0ZXMvZW1iZWRkaW5nLnB5) | `95.73% <0.00%> (-1.22%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/dwavesystems/dwave-system/pull/317?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/dwavesystems/dwave-system/pull/317?src=pr&el=footer). Last update [0cf36f5...17c125e](https://codecov.io/gh/dwavesystems/dwave-system/pull/317?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/docs/reference/minimize_energy/dwave.embedding.chain_breaks.MinimizeEnergy.rst b/docs/reference/minimize_energy/dwave.embedding.chain_breaks.MinimizeEnergy.rst
index 0dcfd20..71e3b63 100644
--- a/docs/reference/minimize_energy/dwave.embedding.chain_breaks.MinimizeEnergy.rst
+++ b/docs/reference/minimize_energy/dwave.embedding.chain_breaks.MinimizeEnergy.rst
@@ -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__
diff --git a/dwave/system/samplers/clique.py b/dwave/system/samplers/clique.py
index 608cbcd..d8e1f30 100644
--- a/dwave/system/samplers/clique.py
+++ b/dwave/system/samplers/clique.py
@@ -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:
diff --git a/dwave/system/samplers/dwave_sampler.py b/dwave/system/samplers/dwave_sampler.py
index e7ba5c6..6c6183a 100644
--- a/dwave/system/samplers/dwave_sampler.py
+++ b/dwave/system/samplers/dwave_sampler.py
@@ -21,8 +21,9 @@ for explanations of technical terms in descriptions of Ocean tools.
 """
 from __future__ import division
 
-import functools
 import time
+import functools
+import collections.abc as abc
 
 from warnings import warn
 
@@ -113,6 +114,10 @@ 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.
@@ -120,6 +125,18 @@ class DWaveSampler(dimod.Sampler, dimod.Structured):
         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.
 
@@ -127,14 +144,13 @@ class DWaveSampler(dimod.Sampler, dimod.Structured):
             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`.
 
@@ -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]))
@@ -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)
@@ -189,7 +212,7 @@ class DWaveSampler(dimod.Sampler, dimod.Structured):
         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.
     """
 
@@ -207,7 +230,7 @@ class DWaveSampler(dimod.Sampler, dimod.Structured):
             >>> 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
@@ -238,12 +261,12 @@ class DWaveSampler(dimod.Sampler, dimod.Structured):
             >>> 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.
@@ -342,7 +365,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]))
@@ -496,24 +519,22 @@ class DWaveSampler(dimod.Sampler, dimod.Structured):
 
 
     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']
diff --git a/dwave/system/samplers/leap_hybrid_sampler.py b/dwave/system/samplers/leap_hybrid_sampler.py
index 55b896f..89d23c4 100644
--- a/dwave/system/samplers/leap_hybrid_sampler.py
+++ b/dwave/system/samplers/leap_hybrid_sampler.py
@@ -29,6 +29,7 @@ from dwave.cloud import Client
 
 __all__ = ['LeapHybridSampler']
 
+
 class LeapHybridSampler(dimod.Sampler):
     """A class for using Leap's cloud-based hybrid solvers.
 
@@ -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.
@@ -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
@@ -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':
diff --git a/dwave/system/utilities.py b/dwave/system/utilities.py
index ddf83f6..2dea158 100644
--- a/dwave/system/utilities.py
+++ b/dwave/system/utilities.py
@@ -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)   
 
diff --git a/requirements.txt b/requirements.txt
index eb54766..93ea17a 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -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
diff --git a/setup.py b/setup.py
index e3c1e8d..f89a34b 100644
--- a/setup.py
+++ b/setup.py
@@ -26,7 +26,7 @@ exec(open(os.path.join(".", "dwave", "system", "package_info.py")).read())
 
 
 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',


Test patch:
diff --git a/tests/qpu/test_dwavecliquesampler.py b/tests/qpu/test_dwavecliquesampler.py
index ac8dcb0..5cdd238 100644
--- a/tests/qpu/test_dwavecliquesampler.py
+++ b/tests/qpu/test_dwavecliquesampler.py
@@ -24,8 +24,7 @@ from dwave.system import DWaveCliqueSampler
 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")
 
@@ -40,8 +39,7 @@ class TestDWaveCliqueSampler(unittest.TestCase):
 
     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")
 
diff --git a/tests/qpu/test_dwavesampler.py b/tests/qpu/test_dwavesampler.py
index ac7293f..d530e81 100644
--- a/tests/qpu/test_dwavesampler.py
+++ b/tests/qpu/test_dwavesampler.py
@@ -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
 
@@ -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")
 
@@ -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")
 
@@ -132,3 +133,26 @@ class TestMissingQubits(unittest.TestCase):
 
         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")
diff --git a/tests/qpu/test_embeddingcomposite.py b/tests/qpu/test_embeddingcomposite.py
index 31f51ba..0b328f8 100644
--- a/tests/qpu/test_embeddingcomposite.py
+++ b/tests/qpu/test_embeddingcomposite.py
@@ -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")
 
diff --git a/tests/qpu/test_schedules.py b/tests/qpu/test_schedules.py
index 20dcae8..80be6f3 100644
--- a/tests/qpu/test_schedules.py
+++ b/tests/qpu/test_schedules.py
@@ -25,7 +25,7 @@ class TestRamp(unittest.TestCase):
     @classmethod
     def setUpClass(cls):
         try:
-            cls.qpu = DWaveSampler(solver=dict(qpu=True, h_gain_schedule=True))
+            cls.qpu = DWaveSampler(solver=dict(h_gain_schedule=True))
         except (ValueError, ConfigFileError):
             raise unittest.SkipTest("no qpu available")
 
diff --git a/tests/qpu/test_virtual_graph_composite.py b/tests/qpu/test_virtual_graph_composite.py
index 792a94d..f4206a2 100644
--- a/tests/qpu/test_virtual_graph_composite.py
+++ b/tests/qpu/test_virtual_graph_composite.py
@@ -30,7 +30,7 @@ class TestVirtualGraphComposite(unittest.TestCase):
     @classmethod
     def setUpClass(cls):
         try:
-            cls.qpu = DWaveSampler(solver=dict(qpu=True, flux_biases=True))
+            cls.qpu = DWaveSampler(solver=dict(flux_biases=True))
         except (ValueError, ConfigFileError):
             raise unittest.SkipTest("no qpu available")
 
diff --git a/tests/test_dwavecliquesampler.py b/tests/test_dwavecliquesampler.py
index e21fe0c..5a674bd 100644
--- a/tests/test_dwavecliquesampler.py
+++ b/tests/test_dwavecliquesampler.py
@@ -51,7 +51,7 @@ class MockDWaveSampler(dimod.RandomSampler, dimod.Structured):
 
 
 class MockChimeraDWaveSampler(MockDWaveSampler):
-    def __init__(self, order_by=None):
+    def __init__(self, **config):
         super().__init__()
 
         self.properties.update(topology=dict(shape=[4, 4, 4], type='chimera'))
@@ -75,7 +75,7 @@ class MockChimeraDWaveSampler(MockDWaveSampler):
 
 
 class MockPegasusDWaveSampler(MockDWaveSampler):
-    def __init__(self, order_by=None):
+    def __init__(self, **config):
         super().__init__()
 
         self.properties.update(topology=dict(shape=[6], type='pegasus'))
diff --git a/tests/test_dwavesampler.py b/tests/test_dwavesampler.py
index e2bc761..7268206 100644
--- a/tests/test_dwavesampler.py
+++ b/tests/test_dwavesampler.py
@@ -20,6 +20,7 @@ import warnings
 
 from collections import namedtuple
 from concurrent.futures import Future
+from unittest import mock
 from uuid import uuid4
 
 import numpy as np
@@ -32,13 +33,6 @@ from dwave.cloud.exceptions import SolverOfflineError, SolverNotFoundError
 from dwave.system.samplers import DWaveSampler
 from dwave.system.warnings import EnergyScaleWarning, TooFewSamplesWarning
 
-try:
-    # py3
-    import unittest.mock as mock
-except ImportError:
-    # py2
-    import mock
-
 
 C16 = dnx.chimera_graph(16)
 
@@ -106,25 +100,37 @@ class TestDwaveSampler(unittest.TestCase):
         self.sampler.solver = MockSolver()
 
     @mock.patch('dwave.system.samplers.dwave_sampler.Client')
-    def test_solver_init(self, MockClient):
-        """Deprecation warning is raised for `solver_features` use, but it still works."""
-
-        # assertWarns not available in py2
-        with warnings.catch_warnings(record=True) as w:
-            warnings.simplefilter("always")
-            DWaveSampler(solver_features={'qpu': True})
-            self.assertEqual(len(w), 1)
-            self.assertTrue(issubclass(w[-1].category, DeprecationWarning))
-
-        with warnings.catch_warnings(record=True) as w:
-            warnings.simplefilter("always")
-            DWaveSampler(solver={'qpu': True})
-            self.assertEqual(len(w), 0)
-
-        MockClient.reset_mock()
+    def test_init_default(self, MockClient):
+        """QPU with the highest number of qubits chosen by default."""
+
+        sampler = DWaveSampler()
+
+        MockClient.from_config.assert_called_once_with(
+            client='qpu',
+            defaults={'solver': {'order_by': '-num_active_qubits'}})
+
+    @mock.patch('dwave.system.samplers.dwave_sampler.Client')
+    def test_init_generic_behavior(self, MockClient):
+        """Generic solver behavior (default prior to 0.10.0) can be forced."""
+
+        sampler = DWaveSampler(client='base')
+
+        MockClient.from_config.assert_called_once_with(
+            client='base',
+            defaults={'solver': {'order_by': '-num_active_qubits'}})
+
+    @mock.patch('dwave.system.samplers.dwave_sampler.Client')
+    def test_init_solver(self, MockClient):
+        """QPU can be explicitly selected (old default usage example)"""
+
         solver = {'qpu': True, 'num_qubits__gt': 1000}
+
         sampler = DWaveSampler(solver=solver)
-        MockClient.from_config.assert_called_once_with(solver=solver)
+
+        MockClient.from_config.assert_called_once_with(
+            client='qpu',
+            solver=solver,
+            defaults={'solver': {'order_by': '-num_active_qubits'}})
 
     def test_sample_ising_variables(self):
 
diff --git a/tests/test_leaphybridsolver.py b/tests/test_leaphybridsolver.py
index 9964ae1..a14add6 100644
--- a/tests/test_leaphybridsolver.py
+++ b/tests/test_leaphybridsolver.py
@@ -18,6 +18,7 @@ import numpy as np
 
 import dimod
 from dwave.cloud import Client
+from dwave.cloud.exceptions import SolverNotFoundError
 
 try:
     # py3
@@ -40,13 +41,13 @@ class MockClient:
 
         self.args = kwargs
 
-    def get_solver(self, order_by=None):
+    def get_solver(self, **filters):
 
         if isinstance(self.args['solver'], str) and self.args['solver'] == 'not_hybrid_solver':
             return MockBadLeapHybridSolver()
 
         if self.args.get('client', 'base') not in ['base', 'hybrid']:
-            return MockBadLeapHybridSolver()
+            raise SolverNotFoundError
 
         return MockLeapHybridSolver()
 
@@ -61,21 +62,25 @@ class TestLeapHybridSampler(unittest.TestCase):
         mock_client.reset_mock()
         LeapHybridSampler()
         mock_client.from_config.assert_called_once_with(
-            client='base', connection_close=True,
+            client='hybrid', connection_close=True,
             solver={'category': 'hybrid',
-                    'supported_problem_types__contains': 'bqm'})
+                    'supported_problem_types__contains': 'bqm',
+                    'order_by': '-version'})
 
-        # Non-hybrid client setting is ignored?
+        # Non-hybrid client setting
         mock_client.reset_mock()
-        LeapHybridSampler(client='qpu')
+        with self.assertRaises(SolverNotFoundError):
+            LeapHybridSampler(client='qpu')
 
         # Explicitly set category to hybrid
         mock_client.reset_mock()
-        LeapHybridSampler(solver={'category': 'hybrid', 'supported_problem_types__contains': 'bqm'})
+        LeapHybridSampler(solver={'category': 'hybrid',
+                                  'supported_problem_types__contains': 'bqm'})
         mock_client.from_config.assert_called_once_with(
-            client='base', connection_close=True,
+            client='hybrid', connection_close=True,
             solver={'category': 'hybrid',
-                    'supported_problem_types__contains': 'bqm'})
+                    'supported_problem_types__contains': 'bqm',
+                    'order_by': '-version'})
 
         # Explicitly set category to not hybrid
         with self.assertRaises(ValueError):
@@ -85,27 +90,29 @@ class TestLeapHybridSampler(unittest.TestCase):
         mock_client.reset_mock()
         LeapHybridSampler(solver={'qpu': True})
         mock_client.from_config.assert_called_once_with(
-            client='base', connection_close=True,
+            client='hybrid', connection_close=True,
             solver={'qpu': True, 'category': 'hybrid',
-                    'supported_problem_types__contains': 'bqm'})
+                    'supported_problem_types__contains': 'bqm',
+                    'order_by': '-version'})
 
         mock_client.reset_mock()
-        LeapHybridSampler(solver={'qpu': True, 'anneal_schedule' :False})
+        LeapHybridSampler(solver={'qpu': True, 'anneal_schedule': False})
         mock_client.from_config.assert_called_once_with(
-            client='base', connection_close=True,
+            client='hybrid', connection_close=True,
             solver={'anneal_schedule': False, 'qpu': True, 'category': 'hybrid',
-                    'supported_problem_types__contains': 'bqm'})
+                    'supported_problem_types__contains': 'bqm',
+                    'order_by': '-version'})
 
         # Named solver: hybrid
         mock_client.reset_mock()
         LeapHybridSampler(solver="hybrid_solver")
         mock_client.from_config.assert_called_once_with(
-            client='base', connection_close=True, solver="hybrid_solver")
+            client='hybrid', connection_close=True, solver="hybrid_solver")
 
         mock_client.reset_mock()
         LeapHybridSampler(connection_close=False, solver="hybrid_solver")
         mock_client.from_config.assert_called_once_with(
-            client='base', connection_close=False, solver="hybrid_solver")
+            client='hybrid', connection_close=False, solver="hybrid_solver")
 
         # Named solver: non-hybrid
         with self.assertRaises(ValueError):
@@ -115,18 +122,20 @@ class TestLeapHybridSampler(unittest.TestCase):
         mock_client.reset_mock()
         LeapHybridSampler(connection_close=False)
         mock_client.from_config.assert_called_once_with(
-            client='base', connection_close=False,
+            client='hybrid', connection_close=False,
             solver={'category': 'hybrid',
-                    'supported_problem_types__contains': 'bqm'})
+                    'supported_problem_types__contains': 'bqm',
+                    'order_by': '-version'})
 
         mock_client.reset_mock()
         LeapHybridSampler(connection_close=False,
                           solver={'category': 'hybrid',
                                   'supported_problem_types__contains': 'bqm'})
         mock_client.from_config.assert_called_once_with(
-            client='base', connection_close=False,
+            client='hybrid', connection_close=False,
             solver={'category': 'hybrid',
-                    'supported_problem_types__contains': 'bqm'})
+                    'supported_problem_types__contains': 'bqm',
+                    'order_by': '-version'})
 
     @mock.patch('dwave.system.samplers.leap_hybrid_sampler.Client')
     def test_sample_bqm(self, mock_client):