Repository: dwavesystems/dimod

Issue:
Feature Request: Throw warning when unembedding results in too many chain breaks
Proposed syntax:
```
resp = unembed_response(response, embedding, bqm, warning_threshold=.1)
```
In this case a `UserWarning` will be raised if more than 10% of the chains are broken.


Discussion:

Patch:
diff --git a/dimod/embedding/transforms.py b/dimod/embedding/transforms.py
index b72dbda6..4acba9fe 100644
--- a/dimod/embedding/transforms.py
+++ b/dimod/embedding/transforms.py
@@ -23,7 +23,7 @@ import numpy as np
 from six import iteritems, itervalues
 
 from dimod.binary_quadratic_model import BinaryQuadraticModel
-from dimod.embedding.chain_breaks import majority_vote
+from dimod.embedding.chain_breaks import majority_vote, broken_chains
 from dimod.embedding.utils import chain_to_quadratic
 from dimod.response import Response
 from dimod.vartypes import Vartype
@@ -330,7 +330,8 @@ def embed_qubo(source_Q, embedding, target_adjacency, chain_strength=1.0):
     return target_Q
 
 
-def unembed_response(target_response, embedding, source_bqm, chain_break_method=None):
+def unembed_response(target_response, embedding, source_bqm,
+                     chain_break_method=None, chain_break_fraction=False):
     """Unembed the response.
 
     Construct a response for the source binary quadratic model (BQM) by unembedding the given
@@ -351,6 +352,10 @@ def unembed_response(target_response, embedding, source_bqm, chain_break_method=
             Method used to resolve chain breaks. Must be an iterator which accepts a sample and
             an embedding and yields unembedded samples.
 
+        chain_break_fraction (bool, optional, default=False):
+            If True, a 'chain_break_fraction' field is added to the unembedded response which report
+            what fraction of the chains were broken before unembedding.
+
     Returns:
         :obj:`.Response`:
             Response for the source binary quadratic model.
@@ -444,6 +449,9 @@ def unembed_response(target_response, embedding, source_bqm, chain_break_method=
     datatypes.extend((name, record[name].dtype, record[name].shape[1:]) for name in record.dtype.names
                      if name not in {'sample', 'energy'})
 
+    if chain_break_fraction:
+        datatypes.append(('chain_break_fraction', np.float64))
+
     data = np.rec.array(np.empty(num_samples, dtype=datatypes))
 
     data.sample = unembedded
@@ -452,4 +460,7 @@ def unembed_response(target_response, embedding, source_bqm, chain_break_method=
         if name not in {'sample', 'energy'}:
             data[name] = record[name][idxs]
 
+    if chain_break_fraction:
+        data['chain_break_fraction'] = broken_chains(record.sample, chain_idxs).mean(axis=1)[idxs]
+
     return Response(data, variables, target_response.info, target_response.vartype)


Test patch:
diff --git a/tests/test_embedding_transforms.py b/tests/test_embedding_transforms.py
index f022020d..0a50fb25 100644
--- a/tests/test_embedding_transforms.py
+++ b/tests/test_embedding_transforms.py
@@ -202,6 +202,29 @@ class TestUnembedResponse(unittest.TestCase):
 
         np.testing.assert_array_equal(arr, unembedded.record)
 
+    def test_chain_break_statistics_discard(self):
+        sample0 = {0: -1, 1: -1, 2: +1}
+        sample1 = {0: +1, 1: -1, 2: +1}
+        samples = [sample0, sample1]
+
+        # now set up an embedding that works for one sample and not the other
+        embedding = {'a': {0, 1}, 'b': {2}}
+
+        bqm = dimod.BinaryQuadraticModel.from_ising({'a': 1}, {('a', 'b'): -1})
+
+        resp = dimod.Response.from_samples(samples, {'energy': [-1, 1]}, {}, dimod.SPIN)
+
+        resp = dimod.unembed_response(resp, embedding, bqm, chain_break_method=dimod.embedding.discard,
+                                      chain_break_fraction=True)
+
+        source_samples = list(resp)
+
+        # only the first sample should be returned
+        self.assertEqual(len(source_samples), 1)
+        self.assertEqual(source_samples, [{'a': -1, 'b': +1}])
+
+        self.assertEqual(resp.record.chain_break_fraction.sum(), 0)
+
 
 class TestEmbedBQM(unittest.TestCase):
     def test_embed_bqm_empty(self):