From 4a6f1ac5d854cf2d1cf774212ebcdcd42e3375f6 Mon Sep 17 00:00:00 2001 From: "Moritz E. Beber" Date: Sat, 19 May 2018 21:23:05 +0200 Subject: [PATCH 1/6] refactor: make base constants real numbers Using real numbers avoids certain corner cases where integers are either undesired, e.g., in `set_linear_coefficients` or they would be simplified out as in `Mul(One, variable)`. --- optlang/symbolics.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/optlang/symbolics.py b/optlang/symbolics.py index f9bdeb5e..228eda86 100644 --- a/optlang/symbolics.py +++ b/optlang/symbolics.py @@ -58,9 +58,9 @@ Real = symengine.RealDouble Basic = symengine.Basic Number = symengine.Number - Zero = Integer(0) - One = Integer(1) - NegativeOne = Integer(-1) + Zero = Real(0) + One = Real(1) + NegativeOne = Real(-1) sympify = symengine.sympy_compat.sympify Add = symengine.Add @@ -113,9 +113,9 @@ def mul(*args): Real = sympy.RealNumber Basic = sympy.Basic Number = sympy.Number - Zero = Integer(0) - One = Integer(1) - NegativeOne = Integer(-1) + Zero = Real(0) + One = Real(1) + NegativeOne = Real(-1) sympify = sympy.sympify Add = sympy.Add From 64edec5dba9c554991b877046e5f07a52b94bd9a Mon Sep 17 00:00:00 2001 From: "Moritz E. Beber" Date: Sat, 19 May 2018 21:42:20 +0200 Subject: [PATCH 2/6] fix: correct test cases accordingly --- optlang/tests/test_symbolics.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/optlang/tests/test_symbolics.py b/optlang/tests/test_symbolics.py index 22655344..92fd06d7 100644 --- a/optlang/tests/test_symbolics.py +++ b/optlang/tests/test_symbolics.py @@ -5,7 +5,7 @@ class SymbolicsTestCase(unittest.TestCase): def test_add_identity(self): - self.assertEqual(optlang.symbolics.add(), 0) + self.assertEqual(optlang.symbolics.add(), 0.0) def test_mul_identity(self): - self.assertEqual(optlang.symbolics.mul(), 1) + self.assertEqual(optlang.symbolics.mul(), 1.0) From a30541b1e14305b0057c2657162a129b2218fc90 Mon Sep 17 00:00:00 2001 From: "Moritz E. Beber" Date: Sun, 20 May 2018 11:50:46 +0200 Subject: [PATCH 3/6] fix: return statement to integer comparison --- optlang/expression_parsing.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/optlang/expression_parsing.py b/optlang/expression_parsing.py index b83c68f6..22192731 100644 --- a/optlang/expression_parsing.py +++ b/optlang/expression_parsing.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from optlang.symbolics import One +from optlang.symbolics import Integer def parse_optimization_expression(obj, linear=True, quadratic=False, expression=None, **kwargs): @@ -70,6 +70,7 @@ def _parse_linear_expression(expression, expanded=False, **kwargs): """ offset = 0 constant = None + one = Integer(1) if expression.is_Add: coefficients = expression.as_coefficients_dict() @@ -84,7 +85,7 @@ def _parse_linear_expression(expression, expanded=False, **kwargs): for var in coefficients: if not (var.is_Symbol): - if var == One: + if var == one: constant = var offset = float(coefficients[var]) elif expanded: From ba904a9aa8d34b75a4481663bd8662f87280fb51 Mon Sep 17 00:00:00 2001 From: Kristian Jensen Date: Wed, 4 Jul 2018 14:36:33 +0200 Subject: [PATCH 4/6] chore: only construct Integer(1) once --- optlang/expression_parsing.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/optlang/expression_parsing.py b/optlang/expression_parsing.py index 22192731..1dc0b4cc 100644 --- a/optlang/expression_parsing.py +++ b/optlang/expression_parsing.py @@ -15,6 +15,8 @@ from optlang.symbolics import Integer +one = Integer(1) + def parse_optimization_expression(obj, linear=True, quadratic=False, expression=None, **kwargs): """ @@ -70,7 +72,6 @@ def _parse_linear_expression(expression, expanded=False, **kwargs): """ offset = 0 constant = None - one = Integer(1) if expression.is_Add: coefficients = expression.as_coefficients_dict() From 05476395b979aed8d21a5e6d055523eb58e18757 Mon Sep 17 00:00:00 2001 From: Kristian Jensen Date: Wed, 4 Jul 2018 14:49:20 +0200 Subject: [PATCH 5/6] test: add constraint and objective construction tests with sloppy --- optlang/tests/abstract_test_cases.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/optlang/tests/abstract_test_cases.py b/optlang/tests/abstract_test_cases.py index 9dc49a75..133cf53d 100644 --- a/optlang/tests/abstract_test_cases.py +++ b/optlang/tests/abstract_test_cases.py @@ -20,6 +20,7 @@ import six from optlang import interface +from optlang import symbolics import optlang import pickle import json @@ -350,6 +351,18 @@ def test_new_invalid_name_raises(self): with self.assertRaises(Exception): const.name = "This\ttab" + def test_construct_with_sloppy(self): + x, y, z, w = self.model.variables[:4] + const = self.interface.Constraint( + symbolics.add([symbolics.mul(symbolics.One, var) for var in [x, y, z]]), + lb=0, + sloppy=True + ) + self.model.add(const) + self.model.update() + + self.assertTrue(const.get_linear_coefficients([x, y, z, w]) == {x: 1, y: 1, z: 1, w: 0}) + @six.add_metaclass(abc.ABCMeta) class AbstractObjectiveTestCase(unittest.TestCase): @@ -390,6 +403,17 @@ def test_new_invalid_name_raises(self): with self.assertRaises(Exception): obj.name = "This\ttab" + def test_construct_with_sloppy(self): + x, y, z, w = self.model.variables[:4] + obj = self.interface.Objective( + symbolics.add([symbolics.mul((symbolics.One, var)) for var in [x, y, z]]), + direction="min", + sloppy=True + ) + self.model.objective = obj + + self.assertTrue(obj.get_linear_coefficients([x, y, z, w]) == {x: 1, y: 1, z: 1, w: 0}) + @six.add_metaclass(abc.ABCMeta) class AbstractModelTestCase(unittest.TestCase): From 5b8a098db28b84f74742ba82361cffc31197881a Mon Sep 17 00:00:00 2001 From: Kristian Jensen Date: Wed, 4 Jul 2018 15:41:09 +0200 Subject: [PATCH 6/6] fix: make scipy get_linear_coefficients return regular numeric types --- optlang/scipy_interface.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/optlang/scipy_interface.py b/optlang/scipy_interface.py index 0d82b878..dea8c556 100644 --- a/optlang/scipy_interface.py +++ b/optlang/scipy_interface.py @@ -418,6 +418,7 @@ def coefficient_dict(self, names=True): if self.expression.is_Add: coefficient_dict = {variable: coef for variable, coef in self.expression.as_coefficients_dict().items() if variable.is_Symbol} + coefficient_dict = {var: float(coef) for var, coef in coefficient_dict.items()} elif self.expression.is_Atom and self.expression.is_Symbol: coefficient_dict = {self.expression: 1} elif self.expression.is_Mul and len(self.expression.args) <= 2: @@ -496,7 +497,7 @@ def coefficient_dict(self): coefficient_dict = {} else: raise ValueError("Invalid expression: " + str(self.expression)) - coefficient_dict = {var.name: coef for var, coef in coefficient_dict.items()} + coefficient_dict = {var.name: float(coef) for var, coef in coefficient_dict.items()} return coefficient_dict def set_linear_coefficients(self, coefficients): @@ -509,7 +510,7 @@ def set_linear_coefficients(self, coefficients): def get_linear_coefficients(self, variables): if self.problem is not None: self.problem.update() - return {v: self.problem.problem.objective.get(v.name, 0) for v in variables} + return {v: float(self.problem.problem.objective.get(v.name, 0)) for v in variables} else: raise Exception("Can't get coefficients from solver if objective is not in a model")