Repository: dwavesystems/dimod
Pull request #1338 (2023-06-19 20:29:38)

Handle tuple labels (and others) when deserializing CQMs


Patch:
diff --git a/dimod/constrained/constrained.py b/dimod/constrained/constrained.py
index ca6fd597..f1bfa013 100644
--- a/dimod/constrained/constrained.py
+++ b/dimod/constrained/constrained.py
@@ -1105,7 +1105,7 @@ class ConstrainedQuadraticModel(cyConstrainedQuadraticModel):
 
             # relabel the variables if needed
             try:  # This is the only way to test whether a file exists
-                variable_labels = json.loads(zf.read("variable_labels.json"))
+                variable_labels = map(deserialize_variable, json.loads(zf.read("variable_labels.json")))
             except KeyError:
                 pass
             else:
diff --git a/releasenotes/notes/fix-cqm-serialization-tuples-fdb2702a1e6ee3e5.yaml b/releasenotes/notes/fix-cqm-serialization-tuples-fdb2702a1e6ee3e5.yaml
new file mode 100644
index 00000000..aaa983f5
--- /dev/null
+++ b/releasenotes/notes/fix-cqm-serialization-tuples-fdb2702a1e6ee3e5.yaml
@@ -0,0 +1,5 @@
+---
+fixes:
+  - |
+    Fix loading constrained quadratic models that are serialized with CQM
+    serialization version 2.0 and that have tuple labels.


Test patch:
diff --git a/tests/test_constrained.py b/tests/test_constrained.py
index cb2c7a01..97c94f57 100644
--- a/tests/test_constrained.py
+++ b/tests/test_constrained.py
@@ -1225,6 +1225,45 @@ class TestSerialization(unittest.TestCase):
             self.assertIsInstance(new._soft[label].weight, numbers.Number)
             self.assertEqual(info.penalty, new._soft[label].penalty)
 
+    def test_functional_tuple_label(self):
+        cqm = dimod.ConstrainedQuadraticModel()
+
+        dimod_vars = [
+            dimod.Integer(i, lower_bound=-5000, upper_bound=5000) for i in range(63)
+        ]
+        num_discrete = 3
+        num_cases = 7
+        dimod_vars.extend(
+            [
+                dimod.Binary((c, i))
+                for c in range(num_discrete)
+                for i in range(num_cases)
+            ]
+        )
+        dimod_vars.append(dimod.Binary(("nested", ("tuple",))))
+
+        for c in range(num_discrete):
+            cqm.add_discrete([(c, i) for i in range(num_cases)])
+
+        cqm.set_objective(dimod.QM() + sum(2 * u for u in dimod_vars))
+
+        with cqm.to_file() as f:
+            new = CQM.from_file(f)
+
+        self.assertTrue(new.objective.variables >= cqm.objective.variables)
+        for v, bias in cqm.objective.iter_linear():
+            self.assertEqual(new.objective.get_linear(v), bias)
+        for u, v, bias in cqm.objective.iter_quadratic():
+            self.assertEqual(new.objective.get_quadratic(u, v), bias)
+        self.assertEqual(new.objective.offset, cqm.objective.offset)
+
+        self.assertEqual(set(cqm.constraints), set(new.constraints))
+        for label, constraint in cqm.constraints.items():
+            self.assertTrue(constraint.lhs.is_equal(new.constraints[label].lhs))
+            self.assertEqual(constraint.rhs, new.constraints[label].rhs)
+            self.assertEqual(constraint.sense, new.constraints[label].sense)
+        self.assertSetEqual(cqm.discrete, new.discrete)
+
     def test_objective_only(self):
         cqm = dimod.CQM()
         x = dimod.Binary("x")