Repository: dwavesystems/dimod

Issue:
SampleSet from_serializable not handling "tuple" variable labels
**Description**
Common methods for serialization (json, bson) will convert tuples to lists. When using these with the BQM serialization, everything works well because when deserializing, variable labels that are lists get converted to tuples. However, this behavior is not implemented for SampleSet.

**Steps To Reproduce**
```python
Python 3.7.2 (default, Dec 29 2018, 06:19:36) 
Type 'copyright', 'credits' or 'license' for more information
IPython 7.2.0 -- An enhanced Interactive Python. Type '?' for help.

[ins] In [1]: import dimod, bson                                                                                                                                                               

[ins] In [2]: bqm = dimod.BinaryQuadraticModel.from_ising({(0, 1): 1}, {})                                                                                                                     

[ins] In [3]: sampleset = dimod.ExactSolver().sample(bqm)                                                                                                                                      

[ins] In [4]: dimod.BQM.from_serializable(bson.BSON.decode(bson.BSON.encode(bqm.to_serializable(use_bytes=True))))                                                                             
Out[4]: BinaryQuadraticModel({(0, 1): 1.0}, {}, 0.0, Vartype.SPIN)

[ins] In [5]: dimod.SampleSet.from_serializable(bson.BSON.decode(bson.BSON.encode(sampleset.to_serializable(use_bytes=True))))                                                                 
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-5-171af1df8e03> in <module>
----> 1 dimod.SampleSet.from_serializable(bson.BSON.decode(bson.BSON.encode(sampleset.to_serializable(use_bytes=True))))

/opt/miniconda3/envs/three/lib/python3.7/site-packages/dimod/sampleset.py in from_serializable(cls, obj)
   1252         variables = obj['variable_labels']
   1253 
-> 1254         return cls.from_samples((sample, variables), vartype, **vectors)
   1255 
   1256     ###############################################################################################

/opt/miniconda3/envs/three/lib/python3.7/site-packages/dimod/sampleset.py in from_samples(cls, samples_like, vartype, energy, info, num_occurrences, aggregate_samples, sort_labels, **vectors)
    430             info = {}
    431 
--> 432         return cls(record, variables, info, vartype)
    433 
    434     @classmethod

/opt/miniconda3/envs/three/lib/python3.7/site-packages/dimod/decorators.py in new_f(*args, **kwargs)
    262                 _enforce_single_arg(name, final_args, final_kwargs)
    263 
--> 264             return f(*final_args, **final_kwargs)
    265 
    266         return new_f

/opt/miniconda3/envs/three/lib/python3.7/site-packages/dimod/sampleset.py in __init__(self, record, variables, info, vartype)
    312         num_samples, num_variables = record.sample.shape
    313 
--> 314         self._variables = variables = Variables(variables)
    315         if len(variables) != num_variables:
    316             msg = ("mismatch between number of variables in record.sample ({}) "

/opt/miniconda3/envs/three/lib/python3.7/site-packages/dimod/variables.py in __init__(self, iterable)
     57                 idx += 1
     58                 yield v
---> 59         self._label = list(_iter())
     60 
     61     def __getitem__(self, i):

/opt/miniconda3/envs/three/lib/python3.7/site-packages/dimod/variables.py in _iter()
     52             idx = 0
     53             for v in iterable:
---> 54                 if v in index:
     55                     continue
     56                 index[v] = idx

TypeError: unhashable type: 'list'
```

**Expected Behavior**
SampleSet serialization should behave the same way for variable labels as BQMs.


Discussion:

Patch:
diff --git a/dimod/sampleset.py b/dimod/sampleset.py
index 03d7ca95..40c050d4 100644
--- a/dimod/sampleset.py
+++ b/dimod/sampleset.py
@@ -1307,7 +1307,8 @@ class SampleSet(abc.Iterable, abc.Sized):
         if vartype is Vartype.SPIN:
             sample = np.asarray(2*sample-1, dtype=dtype)
 
-        variables = obj['variable_labels']
+        variables = [tuple(v) if isinstance(v, list) else v
+                     for v in obj["variable_labels"]]
 
         info = obj['info']
 


Test patch:
diff --git a/tests/test_sampleset.py b/tests/test_sampleset.py
index bed841fd..141552a8 100644
--- a/tests/test_sampleset.py
+++ b/tests/test_sampleset.py
@@ -501,6 +501,15 @@ class TestSerialization(unittest.TestCase):
 
         self.assertEqual(samples, dimod.SampleSet.from_serializable(json.loads(s)))
 
+    def test_tuple_variable_labels(self):
+        sampleset = dimod.SampleSet.from_samples(([], [(0, 0), (0, 1), ("a", "b", 2)]), dimod.BINARY, energy=[])
+
+        json_str = json.dumps(sampleset.to_serializable())
+
+        new = dimod.SampleSet.from_serializable(json.loads(json_str))
+
+        self.assertEqual(sampleset, new)
+
 
 @unittest.skipUnless(_pandas, "no pandas present")
 class TestPandas(unittest.TestCase):