Repository: dwavesystems/dimod
Pull request #1265 (2022-10-18 17:03:44)

Make BQM.vartype callable
There are many places in the code like https://github.com/dwavesystems/dimod/blob/273fb86cdb3af495b490db11a1ecc941e44da0d2/dimod/constrained.py#L209 that could be generalized by the ability to call `bqm.vartype(v)`.

It would also allow us to make `QuadraticViewsMixin` much more full-featured.

See also #849 


Patch:
diff --git a/dimod/vartypes.py b/dimod/vartypes.py
index 1c4e9cfb..fb427a51 100644
--- a/dimod/vartypes.py
+++ b/dimod/vartypes.py
@@ -52,12 +52,16 @@ Examples:
     True
 
 """
+from __future__ import annotations
+
 import enum
 import typing
 import warnings
 
 from collections.abc import Container, Iterable
 
+import dimod.typing
+
 __all__ = ['as_vartype',
            'Vartype', 'ExtendedVartype',
            'SPIN', 'BINARY', 'DISCRETE', 'INTEGER', 'REAL',
@@ -117,6 +121,16 @@ class Vartype(enum.Enum):
     INTEGER = DISCRETE = Integers()  # DISCRETE is an alias for INTEGER
     REAL = Real()
 
+    # Dev note:
+    # This allows us to treat QMs and BQMs in the same way, i.e. we can
+    # do bqm.vartype(v).
+    # It would be better to make BQM.vartype a method, but that would be an
+    # enormous backward compatibility break.
+    # I tried instead making a proxy object, but that doesn't allow syntax like
+    # bqm.vartype is SPIN
+    def __call__(self, v: typing.Optional[dimod.typing.Variable] = None) -> Vartype:
+        return self
+
 
 # Deprecated alias
 ExtendedVartype = Vartype
diff --git a/releasenotes/notes/callable-BQM.vartype-65fae40b690bd8a5.yaml b/releasenotes/notes/callable-BQM.vartype-65fae40b690bd8a5.yaml
new file mode 100644
index 00000000..1b350c0d
--- /dev/null
+++ b/releasenotes/notes/callable-BQM.vartype-65fae40b690bd8a5.yaml
@@ -0,0 +1,6 @@
+---
+features:
+  - |
+    Make ``BinaryQuadraticModel.vartype`` callable. This allows for more
+    uniform syntax between binary quadratic models, quadratic models and
+    constrained quadratic models.


Test patch:
diff --git a/tests/test_bqm.py b/tests/test_bqm.py
index ca0047f0..6e6c040c 100644
--- a/tests/test_bqm.py
+++ b/tests/test_bqm.py
@@ -2757,6 +2757,31 @@ class TestToPolyString(unittest.TestCase):
         self.assertIn(bqm.to_polystring(), ['3*a*b + 7*a*c', '3*b*a + 7*c*a'])
 
 
+class TestVartype(unittest.TestCase):
+    @parameterized.expand(BQMs.items())
+    def test_method_and_property(self, _, BQM):
+        bqm = BQM({'a': 1}, {'ab': 1}, 1.5, dimod.BINARY)
+        self.assertEqual(bqm.vartype, bqm.vartype())
+        self.assertIs(bqm.vartype, bqm.vartype())
+        self.assertEqual(str(bqm.vartype), str(bqm.vartype()))
+        self.assertEqual(repr(bqm.vartype), repr(bqm.vartype()))
+
+        self.assertEqual(bqm.vartype, bqm.vartype('a'))
+        self.assertIs(bqm.vartype, bqm.vartype('a'))
+        self.assertEqual(str(bqm.vartype), str(bqm.vartype('a')))
+        self.assertEqual(repr(bqm.vartype), repr(bqm.vartype('a')))
+
+    # # unfortunately, because the vartype objects are singletonss,
+    # # we don't get this behaviour which would be desired
+    # @parameterized.expand(BQMs.items())
+    # def test_vartype_reference(self, _, BQM):
+    #     bqm = BQM({'a': 1}, {'ab': 1}, 1.5, dimod.BINARY)
+    #     vartype = bqm.vartype
+    #     self.assertEqual(vartype(), dimod.BINARY)
+    #     bqm.change_vartype("SPIN", inplace=True)
+    #     self.assertEqual(vartype(), dimod.SPIN)
+
+
 class TestVartypeViews(unittest.TestCase):
     @parameterized.expand(BQMs.items())
     def test_add_offset_binary(self, name, BQM):