Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion mip/entities.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,17 @@ def __init__(
"You should pass eiter 'expr' or 'variables and coeffs' to the"
"constructor, not the three simultaneously."
)
self.__expr = dict(zip(variables, coeffs))
# dict(zip(...)) silently drops all but the last coefficient when
# the same variable appears more than once, so fall back to an
# accumulating loop only when duplicates are actually present.
# This keeps the common (duplicate-free) case as fast as before.
if len(variables) == len(set(variables)):
self.__expr = dict(zip(variables, coeffs))
else:
expr_dict = {} # type: dict[mip.Var, mip.Numeric]
for var, coeff in zip(variables, coeffs):
expr_dict[var] = expr_dict.get(var, 0) + coeff
self.__expr = expr_dict

elif expr is not None:
self.__expr = expr.copy()
Expand Down
38 changes: 38 additions & 0 deletions test/mip_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import mip.gurobi
import mip.highs
from mip import Model, xsum, OptimizationStatus, MAXIMIZE, BINARY, INTEGER
from mip.entities import LinExpr
from mip import ConstrsGenerator, CutPool, maximize, CBC, GUROBI, HIGHS, Column, Constr
from os import environ
from util import skip_on, has_gurobi_license
Expand Down Expand Up @@ -588,6 +589,43 @@ def test_obj_const2(self, solver: str):
assert model.objective_const == 1


@skip_on(NotImplementedError)
@pytest.mark.parametrize("solver", SOLVERS)
@pytest.mark.parametrize(
"constraint, lb, ub",
[
(lambda x: x + x >= 3, 1, 2),
(lambda x: x - x >= 0, 1, 2),
(lambda x: x == x, 1, 2),
(lambda x: x >= x, 1, 2),
(lambda x: x <= x, -2, -1),
(lambda x: LinExpr([x, x, x], [2, -1, -1], sense="="), 1, 2),
],
)
def test_identical_vars(solver: str, constraint, lb, ub):
"""Try if constraints are correctly added when variables are identical"""
m = Model(solver_name=solver)
x = m.add_var(name="x", lb=lb, ub=ub, obj=1)

m.add_constr(constraint(x))

m.optimize()
assert m.status == OptimizationStatus.OPTIMAL
assert lb - TOL <= x.x <= ub + TOL


def test_linexpr_duplicate_variables_constructor():
"""LinExpr constructed directly with duplicate variables should sum
coefficients instead of silently dropping earlier ones (issue #396)."""
m = Model()
x = m.add_var(name="x")
y = m.add_var(name="y")

expr = LinExpr([x, y, x], [1, 2, 3])
assert expr.expr[x] == 4
assert expr.expr[y] == 2


@skip_on(NotImplementedError)
@pytest.mark.parametrize("val", range(1, 4))
@pytest.mark.parametrize("solver", SOLVERS)
Expand Down
Loading