Repository: dwavesystems/dwave-system

Issue:
LeapHybridSampler fails on default QPU-only config
Since `LeapHybridSampler` instantiates the cloud client instance based on user config, but does not override the client classname setting, if `client=qpu` is set in the `dwave.conf`, or in the environment variable, the instantiation will fail because a hybrid solver will not be found with a QPU client.

Discussion:

Patch:
diff --git a/dwave/system/composites/embedding.py b/dwave/system/composites/embedding.py
index 060906b..5c4e60d 100644
--- a/dwave/system/composites/embedding.py
+++ b/dwave/system/composites/embedding.py
@@ -235,7 +235,7 @@ class EmbeddingComposite(dimod.ComposedSampler):
 
         warninghandler = WarningHandler(warnings)
 
-        warninghandler.chain_strength(bqm, chain_strength, embedding)
+        warninghandler.chain_strength(bqm, chain_strength)
         warninghandler.chain_length(embedding)
 
         if bqm and not embedding:
diff --git a/dwave/system/samplers/leap_hybrid_sampler.py b/dwave/system/samplers/leap_hybrid_sampler.py
index 860752d..8e56430 100644
--- a/dwave/system/samplers/leap_hybrid_sampler.py
+++ b/dwave/system/samplers/leap_hybrid_sampler.py
@@ -87,6 +87,9 @@ 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'
+
         if solver is None:
             solver = {}
 
@@ -94,12 +97,13 @@ class LeapHybridSampler(dimod.Sampler):
             if solver.setdefault('category', 'hybrid') != 'hybrid':
                 raise ValueError("the only 'category' this sampler supports is 'hybrid'")
 
-        self.client = Client.from_config(solver=solver, connection_close=connection_close, **config)
+        self.client = Client.from_config(
+            solver=solver, connection_close=connection_close, **config)
+
         self.solver = self.client.get_solver()
 
         # For explicitly named solvers:
-        if ('category' not in self.properties.keys()) or (
-                      not self.properties['category'] == 'hybrid'):
+        if self.properties.get('category') != 'hybrid':
             raise ValueError("selected solver is not a hybrid solver.")
 
     @property
diff --git a/dwave/system/warnings.py b/dwave/system/warnings.py
index e646ce3..fac3baf 100644
--- a/dwave/system/warnings.py
+++ b/dwave/system/warnings.py
@@ -174,18 +174,12 @@ class WarningHandler(object):
                                      sample_index=row),
                            )
 
-    def chain_strength(self, bqm, chain_strength, embedding=None):
+    def chain_strength(self, bqm, chain_strength):
         """Issues a warning when any quadratic biases are greater than the given
         chain strength."""
         if as_action(self.action) is IGNORE:
             return
 
-        if embedding is not None:
-            if not embedding or all(len(chain) <= 1 for chain in embedding.values()):
-                # the chains are all length 1 so don't have to worry about
-                # strength
-                return
-
         interactions = [uv for uv, bias in bqm.quadratic.items()
                         if abs(bias) >= chain_strength]
 


Test patch:
diff --git a/tests/test_embedding_composite.py b/tests/test_embedding_composite.py
index bec8048..b00a740 100644
--- a/tests/test_embedding_composite.py
+++ b/tests/test_embedding_composite.py
@@ -293,24 +293,6 @@ class TestEmbeddingComposite(unittest.TestCase):
     def test_warning_chain_strength(self):
         G = dnx.chimera_graph(12)
 
-        sampler = EmbeddingComposite(
-            dimod.StructureComposite(dimod.RandomSampler(), G.nodes, G.edges))
-
-        # use a triangle so there is a chain of length 2
-        J = {(0, 1): 100, (1, 2): 100, (0, 2): 1}
-
-        ss = sampler.sample_ising({}, J, warnings='SAVE')
-        self.assertIn('warnings', ss.info)
-
-        count = 0
-        for warning in ss.info['warnings']:
-            if issubclass(warning['type'], ChainStrengthWarning):
-                count += 1
-        self.assertEqual(count, 1)
-
-    def test_warnings_chain_strength_len1(self):
-        G = dnx.chimera_graph(12)
-
         sampler = EmbeddingComposite(
             dimod.StructureComposite(dimod.RandomSampler(), G.nodes, G.edges))
 
@@ -319,12 +301,11 @@ class TestEmbeddingComposite(unittest.TestCase):
         ss = sampler.sample_ising({}, J, warnings='SAVE')
         self.assertIn('warnings', ss.info)
 
-        # should have no chain length
         count = 0
         for warning in ss.info['warnings']:
             if issubclass(warning['type'], ChainStrengthWarning):
                 count += 1
-        self.assertEqual(count, 0)
+        self.assertEqual(count, 1)
 
     def test_warnings_as_class_variable(self):
         G = dnx.chimera_graph(12)
diff --git a/tests/test_leaphybridsolver.py b/tests/test_leaphybridsolver.py
index 31644cb..cdb39cb 100644
--- a/tests/test_leaphybridsolver.py
+++ b/tests/test_leaphybridsolver.py
@@ -45,6 +45,9 @@ class MockClient:
         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()
+
         return MockLeapHybridSolver()
 
 class TestLeapHybridSampler(unittest.TestCase):
@@ -57,14 +60,18 @@ class TestLeapHybridSampler(unittest.TestCase):
         # Default call
         mock_client.reset_mock()
         LeapHybridSampler()
-        mock_client.from_config.assert_called_once_with(connection_close=True,
-                                                 solver={'category': 'hybrid'})
+        mock_client.from_config.assert_called_once_with(
+            client='base', connection_close=True, solver={'category': 'hybrid'})
+
+        # Non-hybrid client setting is ignored?
+        mock_client.reset_mock()
+        LeapHybridSampler(client='qpu')
 
         # Explicitly set category to hybrid
         mock_client.reset_mock()
         LeapHybridSampler(solver={'category': 'hybrid'})
-        mock_client.from_config.assert_called_once_with(connection_close=True,
-                                                 solver={'category': 'hybrid'})
+        mock_client.from_config.assert_called_once_with(
+            client='base', connection_close=True, solver={'category': 'hybrid'})
 
         # Explicitly set category to not hybrid
         with self.assertRaises(ValueError):
@@ -73,26 +80,25 @@ class TestLeapHybridSampler(unittest.TestCase):
         # Set irrelevant paremeters
         mock_client.reset_mock()
         LeapHybridSampler(solver={'qpu': True})
-        mock_client.from_config.assert_called_once_with(connection_close=True,
-                                                 solver={'qpu': True, 'category': 'hybrid'})
+        mock_client.from_config.assert_called_once_with(
+            client='base', connection_close=True, solver={'qpu': True, 'category': 'hybrid'})
 
         mock_client.reset_mock()
         LeapHybridSampler(solver={'qpu': True, 'anneal_schedule' :False})
-        mock_client.from_config.assert_called_once_with(connection_close=True,
-                                                 solver={'anneal_schedule' :False,
-                                                         'qpu': True,
-                                                         'category': 'hybrid'})
+        mock_client.from_config.assert_called_once_with(
+            client='base', connection_close=True,
+            solver={'anneal_schedule': False, 'qpu': True, 'category': 'hybrid'})
 
         # Named solver: hybrid
         mock_client.reset_mock()
         LeapHybridSampler(solver="hybrid_solver")
-        mock_client.from_config.assert_called_once_with(connection_close=True,
-                                                 solver="hybrid_solver")
+        mock_client.from_config.assert_called_once_with(
+            client='base', 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(connection_close=False,
-                                                 solver="hybrid_solver")
+        mock_client.from_config.assert_called_once_with(
+            client='base', connection_close=False, solver="hybrid_solver")
 
         # Named solver: non-hybrid
         with self.assertRaises(ValueError):
@@ -101,13 +107,13 @@ class TestLeapHybridSampler(unittest.TestCase):
         # Set connection_close to False
         mock_client.reset_mock()
         LeapHybridSampler(connection_close=False)
-        mock_client.from_config.assert_called_once_with(connection_close=False,
-                                                 solver={'category': 'hybrid'})
+        mock_client.from_config.assert_called_once_with(
+            client='base', connection_close=False, solver={'category': 'hybrid'})
 
         mock_client.reset_mock()
         LeapHybridSampler(connection_close=False, solver={'category': 'hybrid'})
-        mock_client.from_config.assert_called_once_with(connection_close=False,
-                                                 solver={'category': 'hybrid'})
+        mock_client.from_config.assert_called_once_with(
+            client='base', connection_close=False, solver={'category': 'hybrid'})
 
     @mock.patch('dwave.system.samplers.leap_hybrid_sampler.Client')
     def test_sample_bqm(self, mock_client):