Repository: dwavesystems/dimod
Issue: Cannot pickle bqm

**Description**
Attempting to pickle a bqm (with certain versions of python) results in the error:
```
TypeError: a class that defines __slots__ without defining __getstate__ cannot be pickled
```

**Steps To Reproduce**
```python
Python 2.7.5 |Continuum Analytics, Inc.| (default, Nov  4 2013, 15:30:26) 
Type "copyright", "credits" or "license" for more information.

IPython 5.8.0 -- An enhanced Interactive Python.
?         -> Introduction and overview of IPython's features.
%quickref -> Quick reference.
help      -> Python's own help system.
object?   -> Details about 'object', use 'object??' for extra details.

In [1]: import cPickle as pickle

In [2]: import dimod

In [3]: bqm = dimod.BinaryQuadraticModel.from_qubo({})

In [4]: with open("/tmp/test.pkl", "wb") as f:
   ...:     pickle.dump(bqm, f)
   ...:     
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-4-98b674c2c655> in <module>()
      1 with open("/tmp/test.pkl", "wb") as f:
----> 2     pickle.dump(bqm, f)
      3 

/opt/miniconda3/envs/cluster/lib/python2.7/copy_reg.pyc in _reduce_ex(self, proto)
     75     except AttributeError:
     76         if getattr(self, "__slots__", None):
---> 77             raise TypeError("a class that defines __slots__ without "
     78                             "defining __getstate__ cannot be pickled")
     79         try:

TypeError: a class that defines __slots__ without defining __getstate__ cannot be pickled
```

**Expected Behavior**
bqms should be pickle-able in supported versions of python.

**Environment**
 - OS: Ubuntu 18.04.1 LTS
 - Python version: Python 2.7.5


Changed files:
tests/test_binary_quadratic_model.py, dimod/views.py

diff --git a/dimod/views.py b/dimod/views.py
index 45c12018..d83e2b9f 100644
--- a/dimod/views.py
+++ b/dimod/views.py
@@ -74,9 +74,24 @@ class IndexValuesView(abc.ValuesView):
     def __iter__(self):
         # Inherited __init__ puts the Mapping into self._mapping
         return iter(self._mapping._data.flat)
-    
 
-class LinearView(abc.MutableMapping):
+
+class BQMView(object):
+    __slots__ = '_adj',
+
+    def __init__(self, bqm):
+        self._adj = bqm._adj
+
+    # support python2 pickle
+    def __getstate__(self):
+        return {'_adj': self._adj}
+
+    # support python2 pickle
+    def __setstate__(self, state):
+        self._adj = state['_adj']
+
+
+class LinearView(BQMView, abc.MutableMapping):
     """Acts as a dictionary `{v: bias, ...}` for the linear biases.
 
     The linear biases are stored in a dict-of-dicts format, where 'self loops'
@@ -85,11 +100,6 @@ class LinearView(abc.MutableMapping):
 
     """
 
-    __slots__ = '_adj',
-
-    def __init__(self, bqm):
-        self._adj = bqm._adj
-
     def __delitem__(self, v):
         if v not in self:
             raise KeyError
@@ -130,7 +140,7 @@ class LinearItemsView(abc.ItemsView):
             yield v, neighbours[v]
 
 
-class QuadraticView(abc.MutableMapping):
+class QuadraticView(BQMView, abc.MutableMapping):
     """Acts as a dictionary `{(u, v): bias, ...}` for the quadratic biases.
 
     The quadratic biases are stored in a dict-of-dicts format. So `{(u, v): bias}` is stored as
@@ -138,11 +148,6 @@ class QuadraticView(abc.MutableMapping):
 
     """
 
-    __slots__ = '_adj',
-
-    def __init__(self, bqm):
-        self._adj = bqm._adj
-
     def __delitem__(self, interaction):
         u, v = interaction
         if u == v:
@@ -182,7 +187,7 @@ class QuadraticView(abc.MutableMapping):
             raise KeyError('{} cannot have an interaction with itself'.format(u))
 
         adj = self._adj
-        
+
         # we don't know what type we want the biases, so we require that the variables already
         # exist before we can add an interaction between them
         if u not in adj:
@@ -251,7 +256,7 @@ class NeighbourView(abc.Mapping):
         return str(dict(self))
 
 
-class AdjacencyView(abc.Mapping):
+class AdjacencyView(BQMView, abc.Mapping):
     """Acts as a dict-of-dicts `{u: {v: bias}, v: {u: bias}}` for the quadratic biases.
 
     The quadratic biases are stored in a dict-of-dicts format. So `{u: {v: bias}, v: {u: bias}}`
@@ -259,11 +264,6 @@ class AdjacencyView(abc.Mapping):
 
     """
 
-    __slots__ = '_adj',
-
-    def __init__(self, bqm):
-        self._adj = bqm._adj
-
     def __getitem__(self, v):
         if v not in self._adj:
             raise KeyError('{} is not a variable'.format(v))
diff --git a/tests/test_binary_quadratic_model.py b/tests/test_binary_quadratic_model.py
index 1d65fbc2..1d8217af 100644
--- a/tests/test_binary_quadratic_model.py
+++ b/tests/test_binary_quadratic_model.py
@@ -21,6 +21,7 @@ import tempfile
 import shutil
 import json
 import collections
+import pickle
 
 from os import path
 
@@ -1383,6 +1384,27 @@ class TestBinaryQuadraticModel(unittest.TestCase):
         self.assertIn('a', model.spin.linear)
         self.assertNotIn(0, model.spin.linear)
 
+    def test_pickle_empty(self):
+        bqm = dimod.BinaryQuadraticModel.empty(dimod.SPIN)
+        retrieved = pickle.loads(pickle.dumps(bqm))
+        self.assertEqual(bqm, retrieved)
+
+    def test_consistency_after_retrieve(self):
+        bqm = dimod.BinaryQuadraticModel.empty(dimod.SPIN)
+        retrieved = pickle.loads(pickle.dumps(bqm))
+        # make sure the retrieved one was reconstructed properly
+        retrieved.add_variable('a', -1)
+        bqm.add_variable('a', -1)
+        retrieved.add_interaction(0, 1, -.5)
+        bqm.add_interaction(0, 1, -.5)
+        self.assertEqual(bqm, retrieved)
+        self.assertConsistentBQM(retrieved)
+
+    def test_pickle_full(self):
+        bqm = dimod.BinaryQuadraticModel.from_ising({'a': -1}, {'ab': 1, 'bc': -1})
+        retrieved = pickle.loads(pickle.dumps(bqm))
+        self.assertEqual(bqm, retrieved)
+
 
 class TestConvert(unittest.TestCase):
     @unittest.skipUnless(_networkx, "No networkx installed")