diff --git a/README.rst b/README.rst index 4bc54392..98bd32c9 100644 --- a/README.rst +++ b/README.rst @@ -44,7 +44,7 @@ The following dependencies are needed. The following are optional dependencies that allow other solvers to be used. - `cplex `__ (LP, MILP, QP, MIQP) -- `gurobipy `__ (LP, MILP (QP and MIQP support will be added in the future)) +- `gurobipy `__ (LP, MILP, QP, MIQP) - `scipy `__ (LP) @@ -134,9 +134,6 @@ Future outlook `CPLEX `__ etc.) -The optlang `trello board `__ -also provides a good overview of the project's roadmap. - .. |PyPI| image:: https://img.shields.io/pypi/v/optlang.svg?maxAge=2592000 :target: https://pypi.python.org/pypi/optlang .. |License| image:: http://img.shields.io/badge/license-APACHE2-blue.svg diff --git a/optlang/cplex_interface.py b/optlang/cplex_interface.py index 20868731..2ce5cfed 100644 --- a/optlang/cplex_interface.py +++ b/optlang/cplex_interface.py @@ -470,7 +470,7 @@ class ResultsStreamHandler(StreamHandler): def flush(self): self.logger.debug(self.getvalue()) - logger = logging.getLogger() + logger = logging.getLogger(__name__ + ".Model") # TODO: Make the logger name specific to each solver instance logger.setLevel(logging.CRITICAL) error_stream_handler = ErrorStreamHandler(logger) warning_stream_handler = WarningStreamHandler(logger) diff --git a/optlang/expression_parsing.py b/optlang/expression_parsing.py index b83c68f6..1dc0b4cc 100644 --- a/optlang/expression_parsing.py +++ b/optlang/expression_parsing.py @@ -13,7 +13,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -from optlang.symbolics import One +from optlang.symbolics import Integer + +one = Integer(1) def parse_optimization_expression(obj, linear=True, quadratic=False, expression=None, **kwargs): @@ -84,7 +86,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: diff --git a/optlang/gurobi_interface.py b/optlang/gurobi_interface.py index c8f4a345..0cba066f 100644 --- a/optlang/gurobi_interface.py +++ b/optlang/gurobi_interface.py @@ -402,10 +402,10 @@ class Configuration(interface.MathematicalProgrammingConfiguration): def __init__(self, lp_method='primal', qp_method='primal', presolve=False, verbosity=0, timeout=None, *args, **kwargs): super(Configuration, self).__init__(*args, **kwargs) + self.verbosity = verbosity self.lp_method = lp_method self.qp_method = qp_method self.presolve = presolve - self.verbosity = verbosity self.timeout = timeout @property diff --git a/optlang/interface.py b/optlang/interface.py index c86144ce..3576c7c0 100644 --- a/optlang/interface.py +++ b/optlang/interface.py @@ -33,6 +33,7 @@ import six +import optlang from optlang.exceptions import IndicatorConstraintsNotSupported from optlang.util import parse_expr, expr_to_json, is_numeric, SolverTolerances @@ -463,7 +464,8 @@ def _canonicalize(self, expression): elif isinstance(expression, int): return symbolics.Integer(expression) else: - # expression = expression.expand() This would be a good way to canonicalize, but is quite slow + if optlang._USING_SYMENGINE: + expression = expression.expand() # This is a good way to canonicalize, but is quite slow for sympy return expression @property @@ -1630,6 +1632,4 @@ def __setstate__(self, state): # model.remove(x1) - import optlang - model.interface = optlang.glpk_interface 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") 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 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): diff --git a/optlang/tests/test_expression_parsing.py b/optlang/tests/test_expression_parsing.py index 2b307b82..f03f6eb3 100644 --- a/optlang/tests/test_expression_parsing.py +++ b/optlang/tests/test_expression_parsing.py @@ -68,10 +68,18 @@ def test_parse_non_expanded_quadratic_expression(self): target = {frozenset([x]): 1, frozenset([y]): 1, frozenset([x, y]): 2, frozenset([z]): -1} linear_target = {z: 4} - offset_const, linear_terms_const, quad_terms_const = parse_optimization_expression(Constraint(expr, lb=0), quadratic=True) - offset_obj, linear_terms_obj, quad_terms_obj = parse_optimization_expression(Objective(expr), linear=False) - - self.assertEqual(offset_const, -4) + constraint = Constraint(expr, lb=0) + offset_const, linear_terms_const, quad_terms_const = parse_optimization_expression( + constraint, + quadratic=True + ) + offset_obj, linear_terms_obj, quad_terms_obj = parse_optimization_expression( + Objective(expr), + expression=expr, + linear=False + ) + + self.assertEqual(offset_const - constraint.lb, -4 + offset) self.assertEqual(offset_obj, -4 + offset) _compare_term_dicts(self, linear_terms_const, linear_target) _compare_term_dicts(self, linear_terms_obj, linear_target) 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)