Struggling with Lazy constraints
AnsweredHello, I am new to Gurobi and actually struggling with the implementation of lazy subtour elimination constraints for solving an LSNDP instance. In the documentation I found two different methods to do so: enumerating using the .Lazy attribute or using callbacks.
With the first one,
#variables definition ///
sb1 = m.addConstrs((sum(departureport_r_i[r,i] for i in data.list_ports) == 1 for r in data.R), name ='sb1')
#... other constr of same type
sb4 = m.addConstrs((y_r_i_j[r,i,j]==1)>>(eta1_r_i_j[r,i,j] == departuretime_r_i[r,i]) for r in data.R for i in data.list_ports for j in data.list_ports if i != j) #SUBTOUR 2
#... other constr of same type
m.update()
for r in data.R:
for i in data.list_ports:
for j in data.list_ports:
if i != j:
#...
sb4[r,i,j].Lazy = 1
#...
I get the error AttributeError: 'gurobipy.GenConstr' object has no attribute 'Lazy'
And with the callback method,
y_r_i_j = m.addVars(rij, vtype=GRB.BINARY, name='y_r_i_j')
def subtourelim(model, where):
if where == GRB.Callback.MIPSOL:
y = model.cbGetSolution(model.getVarByName('y_r_i_j'))
I get TypeError: object of type 'NoneType' has no len()... I think It is because of that specific error on model.getVarByName('y_r_i_j') when I am testing it outside the callback function: GurobiError: No variable names available to index
I am trying to use getVarByName because I have several different variables and I do not want to get them all (as in the tsp.py) I only want variables for route generation of my problem
Could anyone help me with one or both method ? Thanks a lot :)
-
Official comment
This post is more than three years old. Some information may not be up to date. For current information, please check the Gurobi Documentation or Knowledge Base. If you need more help, please create a new post in the community forum. Or why not try our AI Gurobot?. -
Regarding the "'gurobipy.GenConstr' object has no attribute 'Lazy'" error: The Lazy attribute can only be used with linear constraints (Constr objects), not general constraints (GenConstr objects) like indicator constraints. To resolve the issue, you must reformulate your general constraint using linear constraints.
Let \( z \) be a binary variable, \( y \in [\ell, u] \) a continuous variable, and \( c \) a constant. The indicator constraint
$$\begin{align*} z = 1 \implies y = c\end{align*}$$
can be linearized with the following two constraints:
$$\begin{align*} y &\leq u + (c - u)z \\ y &\geq \ell + (c - \ell)z.\end{align*}$$
If \( z = 0 \), the above two constraints become \( \ell \leq y \leq u \). In this case, \( y \) is not restricted by these constraints. If \( z = 1 \), the two constraints together imply \( y = c \), as desired.
Regarding the "object of type 'NoneType' has no len()" error: there are no variables in your model with a name exactly equal to \( \texttt{y_r_i_j} \). As a result, your call to Model.getVarByName() returns \( \texttt{None} \). Model.addVars() uses the \( \texttt{name} \) keyword argument as a base for constructing unique variable names. The actual variable names will be something like \( \texttt{y_r_i_j[0,0,0]} \), \( \texttt{y_r_i_j[0,0,1]} \), etc. For example:
>>> x = m.addVars(3, name='x')
>>> m.update()
>>> m.getAttr('VarName', m.getVars())
['x[0]', 'x[1]', 'x[2]']An easier way to access your \( y \) variable in a callback is to create a new attribute for your Model object and assign the \( \texttt{y} \) variables to it. Then, you can simply query this new attribute inside the callback function:
y = m.addVars(rij, vtype=GRB.BINARY, name='y')
m._y = y
def subtourelim(model, where):
if where == GRB.Callback.MIPSOL:
yvals = model.cbGetSolution(model._y)Note that lazy constraints added via callbacks must also be linear. So in this case, you will also have to linearize your indicator constraints.
Regarding the "No variable names available to index" error: Model.getVarByName() queries the variables' VarName attributes. However, these attributes are not available until the model is updated. To resolve the issue, call Model.update() first:
m.update()
0 -
Thank you for your clear and complete support, it helped a lot and my script works fine now !
I would just like to mention some strange error I faced and solved:
rij = [list of tuples (r,i,j)]
z = model.addVars(rij, vtype = GRB.CONTINUOUS, name = 'z')
model._z = z
def mycallback(where, model):
if where == GRB.Callback.MIPSOL:
zvals = model.cbGetSolution(model._z)
print(zvals) #everything looks fine, it prints the dict I expect
for r,i,j in zvals.keys():
model.cbLazy(zvals[r,i,j] >= 0)Message error at the constraint: Unsupported operand type(s) for -: 'bool' and 'NoneType' my dict changed to NoneType once I used it in the loop. I replaced zvals by model._z and it solved the problem but I can not understand why ? Is is still a linearity pb ? My constraint seems classic...
I thought It was because z was not used anywhere else so Gurobi discarded it during the resolution and affected no value to it. But I red here, (https://support.gurobi.com/hc/en-us/community/posts/360078009912-set-the-value-of-unused-variables- https://support.gurobi.com/hc/en-us/community/posts/360078009912-set-the-value-of-unused-variables- ) that it can not be the case.
Any idea ?
In any case, thank you again for help :)
0 -
The \( \texttt{MIPSOL} \) callback is called whenever Gurobi finds a new incumbent solution. Model.cbGetSolution() returns the values of your variables at this new solution.
Consider any \( (r, i, j) \) tuple. In the new incumbent solution found by Gurobi, \( z_{rij} \) might have a value of \( 0 \). In this case, \( \texttt{zvals[r,i,j]} \) equals \( 0 \). So, \( \texttt{zvals[r,i,j] >= 0} \) is really the expression \( \texttt{0 >= 0} \). In Python, this evaluates to \( \texttt{True} \). As a result, the following call to Model.cbLazy()
model.cbLazy(zvals[r,i,j] >= 0)is equivalent to
model.cbLazy(True)
which isn't a valid way of calling Model.cbLazy().
As you noted, your code works when you use \( \texttt{model._z} \) instead of \( \texttt{zvals} \) in your call to Model.cbLazy(). This is because \( \texttt{model._z} \) stores the actual \( z \) variables instead of their solution values. Thus, the code
model.cbLazy(model._z[r,i,j] >= 0)adds the lazy constraint \( z_{rij} \geq 0 \), which I assume is what you're trying to do.
I'll also note the lower bound on the \( z \) variables is \( 0 \) by default. This means in any solution found by Gurobi, all of the \( z \) variables are nonnegative. Consequently, the lazy constraint \( z_{rij} \geq 0 \) is always redundant.
0
Post is closed for comments.
Comments
4 comments