Skip to content

Commit 866ef51

Browse files
committed
improve model_builder code
1 parent 7352ffc commit 866ef51

File tree

3 files changed

+103
-25
lines changed

3 files changed

+103
-25
lines changed

ortools/linear_solver/python/model_builder.py

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,9 @@
5353
_VariableOrConstraint = Union["LinearConstraint", mbh.Variable]
5454

5555
# Forward solve statuses.
56+
AffineExpr = mbh.AffineExpr
5657
BoundedLinearExpression = mbh.BoundedLinearExpression
58+
FlatExpr = mbh.FlatExpr
5759
LinearExpr = mbh.LinearExpr
5860
SolveStatus = mbh.SolveStatus
5961
Variable = mbh.Variable
@@ -107,7 +109,7 @@ def _add_linear_constraint_to_helper(
107109
if name is not None:
108110
helper.set_constraint_name(c.index, name)
109111
return c
110-
raise TypeError("invalid type={}".format(type(bounded_expr)))
112+
raise TypeError(f"invalid type={type(bounded_expr).__name__!r}")
111113

112114

113115
def _add_enforced_linear_constraint_to_helper(
@@ -171,7 +173,7 @@ def _add_enforced_linear_constraint_to_helper(
171173
helper.set_constraint_name(c.index, name)
172174
return c
173175

174-
raise TypeError("invalid type={}".format(type(bounded_expr)))
176+
raise TypeError(f"invalid type={type(bounded_expr).__name__!r}")
175177

176178

177179
class LinearConstraint:
@@ -631,8 +633,10 @@ def new_var(
631633
Returns:
632634
a variable whose domain is [lb, ub].
633635
"""
634-
635-
return Variable(self.__helper, lb, ub, is_integer, name)
636+
if name:
637+
return Variable(self.__helper, lb, ub, is_integer, name)
638+
else:
639+
return Variable(self.__helper, lb, ub, is_integer)
636640

637641
def new_int_var(
638642
self, lb: NumberT, ub: NumberT, name: Optional[str] = None
@@ -712,16 +716,15 @@ def new_var_series(
712716
if not isinstance(index, pd.Index):
713717
raise TypeError("Non-index object is used as index")
714718
if not name.isidentifier():
715-
raise ValueError("name={} is not a valid identifier".format(name))
719+
raise ValueError(f"name={name!r} is not a valid identifier")
716720
if (
717721
mbn.is_a_number(lower_bounds)
718722
and mbn.is_a_number(upper_bounds)
719723
and lower_bounds > upper_bounds
720724
):
721725
raise ValueError(
722-
"lower_bound={} is greater than upper_bound={} for variable set={}".format(
723-
lower_bounds, upper_bounds, name
724-
)
726+
f"lower_bound={lower_bounds} is greater than"
727+
f" upper_bound={upper_bounds} for variable set={name!r}"
725728
)
726729
if (
727730
isinstance(is_integral, bool)
@@ -733,10 +736,9 @@ def new_var_series(
733736
and math.ceil(lower_bounds) > math.floor(upper_bounds)
734737
):
735738
raise ValueError(
736-
"ceil(lower_bound={})={}".format(lower_bounds, math.ceil(lower_bounds))
737-
+ " is greater than floor("
738-
+ "upper_bound={})={}".format(upper_bounds, math.floor(upper_bounds))
739-
+ " for variable set={}".format(name)
739+
f"ceil(lower_bound={lower_bounds})={math.ceil(lower_bounds)}"
740+
f" is greater than floor({upper_bounds}) = {math.floor(upper_bounds)}"
741+
f" for variable set={name!r}"
740742
)
741743
lower_bounds = _convert_to_series_and_validate_index(lower_bounds, index)
742744
upper_bounds = _convert_to_series_and_validate_index(upper_bounds, index)
@@ -871,8 +873,8 @@ def add_linear_constraint( # pytype: disable=annotation-type-mismatch # numpy-
871873
)
872874
else:
873875
raise TypeError(
874-
f"Not supported: Model.add_linear_constraint({linear_expr})"
875-
f" with type {type(linear_expr)}"
876+
"Not supported:"
877+
f" Model.add_linear_constraint({type(linear_expr).__name__!r})"
876878
)
877879
return ct
878880

@@ -917,7 +919,7 @@ def add(
917919
],
918920
)
919921
else:
920-
raise TypeError("Not supported: Model.add(" + str(ct) + ")")
922+
raise TypeError(f"Not supported: Model.add({type(ct).__name__!r})")
921923

922924
def linear_constraint_from_index(self, index: IntegerT) -> LinearConstraint:
923925
"""Rebuilds a linear constraint object from the model and its index."""
@@ -954,8 +956,7 @@ def add_enforced_linear_constraint( # pytype: disable=annotation-type-mismatch
954956
else:
955957
raise TypeError(
956958
"Not supported:"
957-
f" Model.add_enforced_linear_constraint({linear_expr}) with"
958-
f" type {type(linear_expr)}"
959+
f" Model.add_enforced_linear_constraint({type(linear_expr).__name__!r})"
959960
)
960961
return ct
961962

@@ -1018,7 +1019,7 @@ def add_enforced(
10181019
],
10191020
)
10201021
else:
1021-
raise TypeError("Not supported: Model.add_enforced(" + str(ct) + ")")
1022+
raise TypeError(f"Not supported: Model.add_enforced({type(ct).__name__!r}")
10221023

10231024
def enforced_linear_constraint_from_index(
10241025
self, index: IntegerT
@@ -1050,7 +1051,10 @@ def __optimize(self, linear_expr: LinearExprT, maximize: bool) -> None:
10501051
var_indices = [var.index for var in flat_expr.vars]
10511052
self.helper.set_objective_coefficients(var_indices, flat_expr.coeffs)
10521053
else:
1053-
raise TypeError(f"Not supported: Model.minimize/maximize({linear_expr})")
1054+
raise TypeError(
1055+
"Not supported:"
1056+
f" Model.minimize/maximize({type(linear_expr).__name__!r})"
1057+
)
10541058

10551059
@property
10561060
def objective_offset(self) -> np.double:
@@ -1233,7 +1237,7 @@ def value(self, expr: LinearExprT) -> np.double:
12331237
elif isinstance(expr, LinearExpr):
12341238
return self.__solve_helper.expression_value(expr)
12351239
else:
1236-
raise TypeError(f"Unknown expression {expr!r} of type {type(expr)}")
1240+
raise TypeError(f"Unknown expression {type(expr).__name__!r}")
12371241

12381242
def values(self, variables: _IndexOrSeries) -> pd.Series:
12391243
"""Returns the values of the input variables.
@@ -1401,7 +1405,7 @@ def _convert_to_series_and_validate_index(
14011405
else:
14021406
raise ValueError("index does not match")
14031407
else:
1404-
raise TypeError("invalid type={}".format(type(value_or_series)))
1408+
raise TypeError("invalid type={type(value_or_series).__name!r}")
14051409
return result
14061410

14071411

@@ -1429,7 +1433,7 @@ def _convert_to_var_series_and_validate_index(
14291433
else:
14301434
raise ValueError("index does not match")
14311435
else:
1432-
raise TypeError("invalid type={}".format(type(var_or_series)))
1436+
raise TypeError("invalid type={type(value_or_series).__name!r}")
14331437
return result
14341438

14351439

ortools/linear_solver/python/model_builder_test.py

Lines changed: 77 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
# limitations under the License.
1414

1515
import math
16+
import sys
1617
from typing import Any, Callable, Dict, Mapping, Union
1718

1819
from absl.testing import absltest
@@ -32,9 +33,7 @@
3233
def build_dict(expr: mb.LinearExprT) -> Dict[mbh.Variable, float]:
3334
res = {}
3435
flat_expr = mbh.FlatExpr(expr)
35-
print(f"expr = {expr} flat_expr = {flat_expr}", flush=True)
3636
for var, coeff in zip(flat_expr.vars, flat_expr.coeffs):
37-
print(f"process {var} * {coeff}", flush=True)
3837
if not coeff:
3938
continue
4039
res[var] = coeff
@@ -46,6 +45,10 @@ class ModelBuilderTest(absltest.TestCase):
4645
# checking primal, dual, objective values and other values.
4746
NUM_PLACES = 5
4847

48+
def tearDown(self) -> None:
49+
super().tearDown()
50+
sys.stdout.flush()
51+
4952
# pylint: disable=too-many-statements
5053
def run_minimal_linear_example(self, solver_name):
5154
"""Minimal Linear Example."""
@@ -318,6 +321,49 @@ def test_class_api(self):
318321
self.assertEqual(flat_e14.offset, 0.8)
319322
self.assertEqual(e14.__str__(), "(x - t + 0.8)")
320323

324+
e15 = mb.LinearExpr.weighted_sum([1, x, 1], [1, 1, -1])
325+
self.assertIsInstance(e15, mb.Variable)
326+
self.assertEqual(x.index, e15.index)
327+
328+
e16 = mb.LinearExpr.affine(x, 1.0, 0.0)
329+
self.assertIsInstance(e16, mb.Variable)
330+
self.assertEqual(x.index, e16.index)
331+
332+
e17 = -x
333+
self.assertIsInstance(e17, mb.AffineExpr)
334+
self.assertEqual(str(e17), "(-x)")
335+
336+
e18 = mb.LinearExpr.affine(x, 1.0, -2.0)
337+
self.assertIsInstance(e18, mb.AffineExpr)
338+
self.assertEqual(str(e18), "(x - 2)")
339+
340+
e19 = mb.LinearExpr.weighted_sum([1, x, 1], [1, 1, -2])
341+
self.assertIsInstance(e19, mb.AffineExpr)
342+
self.assertEqual(str(e19), "(x - 1)")
343+
344+
e20 = mb.LinearExpr.affine(x, -2.0, 0.0)
345+
self.assertIsInstance(e20, mb.AffineExpr)
346+
self.assertEqual(str(e20), "(-2 * x)")
347+
348+
e21 = mb.LinearExpr.weighted_sum([1, x, 1], [1, 2, -1])
349+
self.assertIsInstance(e21, mb.AffineExpr)
350+
self.assertEqual(str(e21), "(2 * x)")
351+
352+
c1 = x == 2
353+
self.assertEqual(str(c1), "x == 2")
354+
355+
c2 = -x == 3
356+
self.assertEqual(str(c2), "-x == 3")
357+
358+
c3 = x + y == 3
359+
self.assertEqual(str(c3), "(x + y) == 3")
360+
361+
c4 = -x + y == 3
362+
self.assertEqual(str(c4), "(-x + y) == 3")
363+
364+
c5 = x - y == 3
365+
self.assertEqual(str(c5), "(x - y) == 3")
366+
321367
def test_variables(self):
322368
model = mb.Model()
323369
x = model.new_int_var(0.0, 4.0, "x")
@@ -330,6 +376,10 @@ def test_variables(self):
330376
self.assertEqual(1.0, x.lower_bound)
331377
self.assertEqual(3.0, x.upper_bound)
332378
self.assertTrue(x.is_integral)
379+
n1 = model.new_int_var(0, 4)
380+
self.assertEqual(n1.name, "variable#1")
381+
n2 = model.new_int_var(0, 4, None)
382+
self.assertEqual(n2.name, "variable#2")
333383

334384
# Tests the equality operator.
335385
y = model.new_int_var(0.0, 4.0, "y")
@@ -449,6 +499,10 @@ def test_create_true_ct(self):
449499

450500
class InternalHelperTest(absltest.TestCase):
451501

502+
def tearDown(self) -> None:
503+
super().tearDown()
504+
sys.stdout.flush()
505+
452506
def test_anonymous_variables(self):
453507
helper = mb.Model().helper
454508
index = helper.add_var()
@@ -641,6 +695,10 @@ def test_str(self, expr, expected_str):
641695

642696
class LinearBaseErrorsTest(absltest.TestCase):
643697

698+
def tearDown(self) -> None:
699+
super().tearDown()
700+
sys.stdout.flush()
701+
644702
def test_unknown_linear_type(self):
645703
with self.assertRaises(TypeError):
646704

@@ -758,6 +816,10 @@ def test_var_eq_var_as_bool(self):
758816

759817
class BoundedLinearBaseErrorsTest(absltest.TestCase):
760818

819+
def tearDown(self) -> None:
820+
super().tearDown()
821+
sys.stdout.flush()
822+
761823
def test_single_var_bounded_linear_expression_as_bool(self):
762824
with self.assertRaisesRegex(
763825
NotImplementedError, "Evaluating a BoundedLinearExpression"
@@ -775,6 +837,10 @@ def test_bounded_linear_expression_as_bool(self):
775837

776838
class ModelBuilderErrorsTest(absltest.TestCase):
777839

840+
def tearDown(self) -> None:
841+
super().tearDown()
842+
sys.stdout.flush()
843+
778844
def test_new_var_series_errors(self):
779845
with self.assertRaisesRegex(TypeError, r"Non-index object"):
780846
model = mb.Model()
@@ -1607,6 +1673,10 @@ def test_maximize(
16071673

16081674
class ModelBuilderProtoTest(absltest.TestCase):
16091675

1676+
def tearDown(self) -> None:
1677+
super().tearDown()
1678+
sys.stdout.flush()
1679+
16101680
def test_export_to_proto(self):
16111681
expected = linear_solver_pb2.MPModelProto()
16121682
text_format.Parse(
@@ -1970,6 +2040,11 @@ def test_get_objective_value(
19702040

19712041

19722042
class ModelBuilderExamplesTest(absltest.TestCase):
2043+
2044+
def tearDown(self) -> None:
2045+
super().tearDown()
2046+
sys.stdout.flush()
2047+
19732048
def test_simple_problem(self):
19742049
# max 5x1 + 4x2 + 3x3
19752050
# s.t 2x1 + 3x2 + x3 <= 5

ortools/linear_solver/wrappers/model_builder_helper.cc

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -866,7 +866,6 @@ double ExprFlattener::Flatten(std::vector<const Variable*>* vars,
866866
}
867867

868868
void ExprEvaluator::AddVarCoeff(const Variable* var, double coeff) {
869-
if (coeff == 0.0) return;
870869
offset_ += coeff * helper_->variable_value(var->index());
871870
}
872871

0 commit comments

Comments
 (0)