Repository: dwavesystems/dimod
Pull request #1288 (2022-11-10 00:01:52)

Restore some removed methods for CQM manipulation
Closes https://github.com/dwavesystems/dimod/issues/1287


Patch:
diff --git a/dimod/binary/binary_quadratic_model.py b/dimod/binary/binary_quadratic_model.py
index 69ace3ef..7c227041 100644
--- a/dimod/binary/binary_quadratic_model.py
+++ b/dimod/binary/binary_quadratic_model.py
@@ -801,28 +801,6 @@ class BinaryQuadraticModel(QuadraticViewsMixin):
                                             lagrange_multiplier, -ub_c)
         return slack_terms
 
-    def add_linear_from(self, linear: Union[Iterable, Mapping]):
-        """Add variables and linear biases to a binary quadratic model.
-
-        Args:
-            linear:
-                Variables and their associated linear biases, as either a dict of
-                form ``{v: bias, ...}`` or an iterable of ``(v, bias)`` pairs,
-                where ``v`` is a variable and ``bias`` is its associated linear
-                bias.
-
-        """
-        if isinstance(linear, abc.Mapping):
-            iterator = linear.items()
-        elif isinstance(linear, abc.Iterable):
-            iterator = linear
-        else:
-            raise TypeError(
-                "expected 'linear' to be a dict or an iterable of 2-tuples.")
-
-        for v, bias in iterator:
-            self.add_linear(v, bias)
-
     def add_linear_from_array(self, linear: Sequence):
         """Add linear biases from an array-like to a binary quadratic model.
 
@@ -848,9 +826,6 @@ class BinaryQuadraticModel(QuadraticViewsMixin):
 
         self.data.add_linear_from_array(np.asarray(ldata))
 
-    add_variables_from = add_linear_from
-    """Alias for :meth:`add_linear_from`."""
-
     def add_offset(self, bias):
         """Add offset to to the model.
 
diff --git a/dimod/constrained/cyexpression.pyx b/dimod/constrained/cyexpression.pyx
index c609cdce..23d47a47 100644
--- a/dimod/constrained/cyexpression.pyx
+++ b/dimod/constrained/cyexpression.pyx
@@ -58,6 +58,10 @@ cdef class _cyExpression:
     def offset(self):
         return as_numpy_float(self.expression().offset())
 
+    @offset.setter
+    def offset(self, bias_type offset):
+        self.expression().set_offset(offset)
+
     @property
     def variables(self):
         # we could do yet another view, but that way lies madness
@@ -102,6 +106,10 @@ cdef class _cyExpression:
 
         self.expression().add_quadratic(ui, vi, bias)
 
+    def add_variable(self, *args, **kwargs):
+        """Add a variable to the model. See :meth:`ConstrainedQuadraticModel.add_variable()`."""
+        return self.parent.add_variable(*args, **kwargs)
+
     def degree(self, v):
         return self.expression().degree(self.parent.variables.index(v))
 
diff --git a/dimod/views/quadratic.py b/dimod/views/quadratic.py
index 56f34687..06ac4033 100644
--- a/dimod/views/quadratic.py
+++ b/dimod/views/quadratic.py
@@ -13,6 +13,7 @@
 #    limitations under the License.
 
 import abc
+import collections.abc
 import io
 import operator
 
@@ -400,6 +401,31 @@ 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]):
+        """Add variables and linear biases to a binary quadratic model.
+
+        Args:
+            linear:
+                Variables and their associated linear biases, as either a dict of
+                form ``{v: bias, ...}`` or an iterable of ``(v, bias)`` pairs,
+                where ``v`` is a variable and ``bias`` is its associated linear
+                bias.
+
+        """
+        if isinstance(linear, collections.abc.Mapping):
+            iterator = linear.items()
+        elif isinstance(linear, collections.abc.Iterable):
+            iterator = linear
+        else:
+            raise TypeError(
+                "expected 'linear' to be a dict or an iterable of 2-tuples.")
+
+        for v, bias in iterator:
+            self.add_linear(v, bias)
+
+    add_variables_from = add_linear_from
+    """Alias for :meth:`add_linear_from`."""
+
     def fix_variable(self, v: Variable, value: float):
         """Remove a variable by fixing its value.
 
diff --git a/releasenotes/notes/feature-restore-some-objective-and-constraint-methods-cc40b8ac28944ee2.yaml b/releasenotes/notes/feature-restore-some-objective-and-constraint-methods-cc40b8ac28944ee2.yaml
new file mode 100644
index 00000000..56899b93
--- /dev/null
+++ b/releasenotes/notes/feature-restore-some-objective-and-constraint-methods-cc40b8ac28944ee2.yaml
@@ -0,0 +1,12 @@
+---
+fixes:
+  - |
+    Add the ability to set offsets on ``ObjectiveView`` and ``ConstraintView``.
+    This was previously removed in dimod 0.12.0.
+    See `#1287 <https://github.com/dwavesystems/dimod/issues/1287>`_
+  - |
+    Add ``ObjectiveView.add_linear_from()`` and ``ConstraintView.add_linear_from()`` methods.
+    These were previously removed in dimod 0.12.0.
+    - |
+    Add ``ObjectiveView.add_variable()`` and ``ConstraintView.add_variable()`` methods.
+    These were previously removed in dimod 0.12.0.


Test patch:
diff --git a/tests/test_constrained.py b/tests/test_constrained.py
index 6579eb75..a7ecf446 100644
--- a/tests/test_constrained.py
+++ b/tests/test_constrained.py
@@ -1653,6 +1653,36 @@ class TestStr(unittest.TestCase):
 
 
 class TestViews(unittest.TestCase):
+    def test_add_linear(self):
+        x, y = dimod.Binaries('xy')
+        cqm = dimod.CQM()
+        lbl1 = cqm.add_constraint(x + y <= 1)
+        lbl2 = cqm.add_constraint(x == 1)
+
+        cqm.constraints[lbl2].lhs.set_linear('y', 3)
+        self.assertEqual(cqm.constraints[lbl2].lhs.linear, {'x': 1, 'y': 3})
+
+    def test_add_linear_from(self):
+        x, y = dimod.Binaries('xy')
+        cqm = dimod.CQM()
+        lbl = cqm.add_constraint(x + y <= 1)
+
+        cqm.objective.add_linear_from([('x', 3)])
+
+        self.assertEqual(cqm.objective.linear, {'x': 3})
+
+    def test_add_variable(self):
+        x, y = dimod.Binaries('xy')
+        cqm = dimod.CQM()
+        lbl = cqm.add_constraint(x == 1)
+        cqm.constraints[lbl].lhs.add_variable(dimod.BINARY, 'y')
+        self.assertIn('y', cqm.variables)
+        cqm.constraints[lbl].lhs.add_variable(dimod.INTEGER, 'i', lower_bound=-5, upper_bound=10)
+        self.assertIn('i', cqm.variables)
+        self.assertEqual(cqm.vartype('i'), dimod.INTEGER)
+        self.assertEqual(cqm.lower_bound('i'), -5)
+        self.assertEqual(cqm.upper_bound('i'), 10)
+
     def test_objective(self):
         cqm = dimod.CQM()
 
@@ -1686,6 +1716,16 @@ class TestViews(unittest.TestCase):
         self.assertEqual(cqm.constraints[c1].lhs.energy({'a': 1, 'b': 1}), 1)
         self.assertEqual(cqm.constraints[c1].lhs.energy({'a': 0, 'b': 0}), 0)
 
+    def test_offset(self):
+        x, y = dimod.Binaries('xy')
+        cqm = dimod.CQM()
+        lbl = cqm.add_constraint(x + y <= 1)
+
+        cqm.objective.offset = 5
+        self.assertEqual(cqm.objective.offset, 5)
+        cqm.constraints[lbl].lhs.offset = 3
+        self.assertEqual(cqm.constraints[lbl].lhs.offset, 3)
+
     def test_serialization_helpers(self):
         a, c = dimod.Binaries('ac')
         b = dimod.Integer('b', upper_bound=5)