coin-or / python-mip

Python-MIP: collection of Python tools for the modeling and solution of Mixed-Integer Linear programs
Eclipse Public License 2.0
525 stars 92 forks source link

set_expr not working #123

Open braceal opened 4 years ago

braceal commented 4 years ago

Describe the bug The functions below are not updating the coefficients model.objective.set_expr() constraint.expr.set_expr()

To Reproduce

from mip import Model, maximize

m = Model()
v = m.add_var('v', ub=1)
m.objective = maximize(v)
print(m.objective.expr) # should print {mip.Var id : 1}
m.objective.set_expr({v: 2})
print(m.objective.expr) # should print {mip.Var id : 2}

c = m.add_constr(v <= 0.5, 'c')
print(c.expr) # should print + v - 0.5
c.expr.set_expr({v: 0.6})
print(c.expr) # should print + v - 0.6
m.optimize()

Expected behavior The output of the print statements should be as shown above in comments. However, they are not updating so it prints 1,1 and 0.5, 0.5. The expected solution to the problem is 0.6, but instead it is 0.5. Here is the ouput: image

Desktop:

h-g-s commented 4 years ago

Hi @braceal , thanks for the detailed bug report.

m.objective.set_expr should work

Changing the contents of one constraint is more complicated, since after adding/removing one non-zero element may be necessary to reorganize the matrix. Most solvers do not allow to change the contents of one constraint and usually it is necessary to remove and add a new constraint.

h-g-s commented 4 years ago

one way that works now to change the OF is:

m.objective = maximize(2 * v)

braceal commented 4 years ago

@h-g-s Thank you for your quick reply. To give a bit more context I'm working to add MIP to the optlang package so that my group can use it as part of the COBRApy package. The original issue.

The last function I am trying to optimize is called set_linear_coefficients (for both constraints and objectives). It allows the user to adjust the coefficients in the expressions (including adding new variables that already exist in the model as part of constraints). I realize that the MIP set_expr function is supposed to update the entire expression. For my problem, it would be best to only update a subset of coefficients. Is there a nice way to do this with the MIP architecture?

I'm currently trying to use m.objective.add_var(new_value + (+- old_value)). This zeroes out the old variable coefficient and then adds the new_value. However, it seems that I am seeing a similar issue with add_var as I saw in set_expr. I believe add_var is implemented correctly but it is being called when it shouldn't. I cloned a copy of MIP and added some print statements to debug. Here is the results:

Test case: image

add_var with prints: image

Output: image

Analysis: It is not clear why there are two extra calls to add_var which set the coeff to 1. It is also unclear where these functions are being called from since it appears that the var 'v' is not currently in the LinExpr since it prints "else set coef". It also appears that even though the variable coefficient was changed to 3 at some point, when the objective is printed at the end of the test case, the coefficient is 1 again.

Do you know why this behavior is happening?

VJMCallewaert commented 3 years ago

Hi,

I encountered a similar problem when trying to add terms to the objective function:

import mip

m = mip.Model()
x = m.add_var(var_type=mip.BINARY, name='x')
m.objective.add_term(x) # Has no effect on the objective
print(m.objective) # Empty

Scanning through the code, the issues seems to lie with Model.objective in model.py, which returns a new LinExpr instance. It is that LinExpr instance that is updated with the new term, but in the end does not affect the model's objective as the Model.objective's setter method is not called.

Hence the 5 calls to add_var from the example in the previous post seem to come from:

  1. maximize(v)
  2. Printing the objective
  3. Model.get_objective (called during m.objective.add_var(v, 2))
  4. add_var(v, 2) (called during m.objective.add_var(v, 2))
  5. Printing the objective.

It seems reasonable to expect that the add_var, add_expr, add_const and add_term update the objective in place. Maybe it would be an option to add a method Model.update_objective that does this?

sebheger commented 2 years ago

Reported bugs here are actually some design issues in python-mip. All provided code examples where the constraint object is modified afterward or in-place editing of the objective should not be not possible and not allowed and ideally "forbidden" by application.

As far as I have checked with v1.14.0, when in-place editing the target function the application "crashes" (because trying to set a read-only attribute) while modifying the constraint is still silently ignored.