Repository: dwavesystems/dimod
Pull request #1368 (2024-03-15 15:23:34)

add method `ConstrainedQuadraticModel.objective.add_quadartic_from()` 
While there is a method `ConstrainedQuadraticModel.objective.add_linear_from()`, which adds linear biases to the objective, there is no corresponding method for quadratic biases.

Here, we add a method `ConstrainedQuadraticModel.objective.add_quadartic_from()` to allow adding quadratic terms to the objective in a similar way to the linear biases. We also add an abstract method `ConstrainedQuadraticModel.objective.add_quadartic()` to be overloaded in the subclasses.

Closes the issue https://github.com/dwavesystems/dimod/issues/1367




Patch:
diff --git a/dimod/views/quadratic.py b/dimod/views/quadratic.py
index 06ac4033..31cd4dd4 100644
--- a/dimod/views/quadratic.py
+++ b/dimod/views/quadratic.py
@@ -352,6 +352,10 @@ class QuadraticViewsMixin(abc.ABC):
     def add_linear(self, v: Variable, bias: Bias):
         raise NotImplementedError
 
+    @abc.abstractmethod
+    def add_quadratic(self, u: Variable, v: Variable, bias: Bias):
+        raise NotImplementedError
+
     @abc.abstractmethod
     def degree(self, v: Variable) -> int:
         raise NotImplementedError
@@ -401,7 +405,8 @@ class QuadraticViewsMixin(abc.ABC):
     def set_quadratic(self, u: Variable, v: Variable, bias: Bias):
         raise NotImplementedError
 
-    def add_linear_from(self, linear: Union[Iterable, Mapping]):
+    def add_linear_from(self, linear: Union[Mapping[Variable, Bias],
+                                            Iterable[Tuple[Variable, Bias]]]):
         """Add variables and linear biases to a binary quadratic model.
 
         Args:
@@ -426,6 +431,29 @@ class QuadraticViewsMixin(abc.ABC):
     add_variables_from = add_linear_from
     """Alias for :meth:`add_linear_from`."""
 
+    def add_quadratic_from(self, quadratic: Union[Mapping[Tuple[Variable, Variable], Bias],
+                                                  Iterable[Tuple[Variable, Variable, Bias]]]):
+        """Add variables and quadratic biases to a binary quadratic model.
+
+        Args:
+            quadratic:
+                Variables and their associated quadratic biases, as either a dict of
+                form ``{u, v: bias, ...}`` or an iterable of ``(u, v, bias)`` triple,
+                where ``u``, ``v`` are variables and ``bias`` is its associated
+                quadratic bias.
+
+        """
+        if isinstance(quadratic, collections.abc.Mapping):            
+            for (u, v), bias in quadratic.items():
+                self.add_quadratic(u, v, bias)
+        elif isinstance(quadratic, collections.abc.Iterable):
+            for u, v, bias in quadratic:
+                self.add_quadratic(u, v, bias)
+        else:
+            raise TypeError(
+                "expected 'quadratic' to be a dict or an iterable of 3-tuples.")
+
+
     def fix_variable(self, v: Variable, value: float):
         """Remove a variable by fixing its value.
 
diff --git a/releasenotes/notes/add-quadratic-from-52f923c89ce762cb.yaml b/releasenotes/notes/add-quadratic-from-52f923c89ce762cb.yaml
new file mode 100644
index 00000000..165d2c6b
--- /dev/null
+++ b/releasenotes/notes/add-quadratic-from-52f923c89ce762cb.yaml
@@ -0,0 +1,14 @@
+---
+features:
+  - |
+    Add an abstract method ``QuadraticViewsMixin.add_quadratic()``.
+  - |
+    Add a method ``QuadraticViewsMixin.add_quadratic_from()`` to add quadratic
+    interactions. This method is intended to be used as follows:
+    ``ConstrainedQuadraticModel.objective.add_quadratic_from()``, similarly to
+    ``ConstrainedQuadraticModel.objective.add_linear_from()``.
+upgrade:
+  - |
+    All sublcasses of ``QuadraticViewsMixin``, including ``ObjectiveView``, 
+    ``QuadraticModel`` and ``BinaryQuadraticModel``, now require to have 
+    the method ``add_quadratic()``.


Test patch:
diff --git a/tests/test_constrained.py b/tests/test_constrained.py
index 8387196e..e0e2eec5 100644
--- a/tests/test_constrained.py
+++ b/tests/test_constrained.py
@@ -1889,12 +1889,43 @@ class TestViews(unittest.TestCase):
 
     def test_add_linear_from(self):
         x, y = dimod.Binaries('xy')
+        # test iterable
         cqm = dimod.CQM()
         lbl = cqm.add_constraint(x + y <= 1)
 
         cqm.objective.add_linear_from([('x', 3)])
 
         self.assertEqual(cqm.objective.linear, {'x': 3})
+        # test mapping     
+        cqm = dimod.CQM()
+        lbl = cqm.add_constraint(x + y <= 1)
+        cqm.objective.add_linear_from({'x': 3})
+        self.assertEqual(cqm.objective.linear, {'x': 3})
+
+        # test missing variable
+        cqm = dimod.CQM()
+        with self.assertRaises(ValueError):
+            cqm.objective.add_linear_from([('x', 3)])
+
+    def test_add_quadratic_from(self):
+        x, y = dimod.Binaries('xy')
+        # test iterable
+        cqm = dimod.CQM()
+        lbl = cqm.add_constraint(x + y <= 1)
+
+        cqm.objective.add_quadratic_from([('x', 'y', 3)])
+
+        self.assertEqual(cqm.objective.quadratic, {('x', 'y'): 3})
+        # test mapping        
+        cqm = dimod.CQM()
+        lbl = cqm.add_constraint(x + y <= 1)
+        cqm.objective.add_quadratic_from({('x', 'y'): 3})
+        self.assertEqual(cqm.objective.quadratic, {('x', 'y'): 3})
+        
+        # test missing variable
+        cqm = dimod.CQM()        
+        with self.assertRaises(ValueError):
+            cqm.objective.add_quadratic_from([('x', 'y', 3)])        
 
     def test_add_variable(self):
         x, y = dimod.Binaries('xy')