Repository: dwavesystems/dimod
Pull request #1315 (2023-03-08 19:32:04)

Allow discrete constraints to have 0 or 1 variable
Closes https://github.com/dwavesystems/dimod/issues/1281


Patch:
diff --git a/dimod/constrained/constrained.py b/dimod/constrained/constrained.py
index db952532..b7052786 100644
--- a/dimod/constrained/constrained.py
+++ b/dimod/constrained/constrained.py
@@ -610,9 +610,6 @@ class ConstrainedQuadraticModel(cyConstrainedQuadraticModel):
 
             bqm.set_linear(v, 1)
 
-        if bqm.num_variables < 2:
-            raise ValueError("discrete constraints must have at least two variables")
-
         label = self.add_constraint_from_comparison(bqm == 1, label=label, copy=False)
         self.discrete.add(label)
         return label
@@ -667,9 +664,6 @@ class ConstrainedQuadraticModel(cyConstrainedQuadraticModel):
         if qm.num_interactions:
             raise ValueError("discrete constraints must be linear")
 
-        if qm.num_variables < 2:
-            raise ValueError("discrete constraints must have at least two variables")
-
         for v, bias in qm.iter_linear():
             if v in self.variables:
                 # it already exists, let's make sure it's not already used
diff --git a/releasenotes/notes/single-variable-discrete-constraints-1b5730928fe19041.yaml b/releasenotes/notes/single-variable-discrete-constraints-1b5730928fe19041.yaml
new file mode 100644
index 00000000..d63bb37d
--- /dev/null
+++ b/releasenotes/notes/single-variable-discrete-constraints-1b5730928fe19041.yaml
@@ -0,0 +1,5 @@
+---
+features:
+  - |
+    Allow discrete constraints added by ``ConstrainedQuadraticModel.add_discrete()``
+    to have zero or one variables.


Test patch:
diff --git a/tests/test_constrained.py b/tests/test_constrained.py
index 0722f6e7..77408e01 100644
--- a/tests/test_constrained.py
+++ b/tests/test_constrained.py
@@ -180,14 +180,33 @@ class TestAddDiscrete(unittest.TestCase):
         self.assertIn(c, cqm.discrete)
         self.assertTrue(cqm.constraints[c].lhs.is_equal(qm))
 
+    def test_empty(self):
+        # it is meaningless, but sometimes convenient to create 1 or 0 variable
+        # constraints
+        cqm = dimod.CQM()
+
+        x = dimod.Binary("x")
+
+        c1 = cqm.add_discrete([])
+        c2 = cqm.add_discrete(dimod.BQM("BINARY"))
+        c3 = cqm.add_discrete(dimod.BQM("BINARY") == 1)
+        c4 = cqm.add_discrete(x)
+        c5 = cqm.add_discrete("x")
+        c6 = cqm.add_discrete(x == 1)
+
+        for label in (c1, c2, c3):
+            self.assertTrue(cqm.constraints[label].lhs.is_equal(dimod.BQM("BINARY")))
+            self.assertEqual(cqm.constraints[label].sense, Sense.Eq)
+            self.assertEqual(cqm.constraints[label].rhs, 1)
+
+        for label in (c4, c5, c6):
+            self.assertTrue(cqm.constraints[label].lhs.is_equal(x))
+            self.assertEqual(cqm.constraints[label].sense, Sense.Eq)
+            self.assertEqual(cqm.constraints[label].rhs, 1)
+
     def test_exceptions(self):
         cqm = dimod.CQM()
         x, y, z = dimod.Binaries('xyz')
-        with self.subTest("too few variables"):
-            with self.assertRaises(ValueError):
-                cqm.add_discrete('x')
-            with self.assertRaises(ValueError):
-                cqm.add_discrete(x)
         with self.subTest("wrong sense"):
             with self.assertRaises(ValueError):
                 cqm.add_discrete(x + y <= 1)
@@ -793,6 +812,13 @@ class TestFromDQM(unittest.TestCase):
         self.assertEqual(len(cqm.variables), 0)
         self.assertEqual(len(cqm.constraints), 0)
 
+    def test_single_case_variables(self):
+        dqm = dimod.DQM()
+        u = dqm.add_variable(1)
+        v = dqm.add_variable(2)
+        cqm = dimod.CQM.from_dqm(dqm)
+        self.assertEqual(cqm.variables, [(0, 0), (1, 0), (1, 1)])
+
     def test_typical(self):
         dqm = dimod.DQM()
         u = dqm.add_variable(4)