Creating only necessary variables (associated with a constraint)
AnsweredI'm trying to create variables if and only if they are associated with a specific constraint. Normally, it is a straightforward but my constraint contains if statement with a for loop connected with an upper and lower bound. Here is my example:
nd = np.arange(0, 13, 1, dtype=int)
time = np.arange(0, 48, 1, dtype=int)
evbee = np.arange(0, 12, 1, dtype=int)
z_msea = {}
y_msea = {}
u_msea = {}
for i in nd:
for f in evbee:
for t in time:
z_msea[i,f,t] = model.addVar(vtype=GRB.BINARY, name="z_msea-i%d-f%d-t%d" %(i,f,t))
y_msea[i,f,t] = model.addVar(vtype=GRB.BINARY, name="y_msea-i%d-f%d-t%d" %(i,f,t))
u_msea[i,f,t] = model.addVar(vtype=GRB.BINARY, name="u_msea-i%d-f%d-t%d" %(i,f,t))
for t in time:
for i in nd:
for j in nd:
for f in evbee:
if i!=j and t-t_cost.iloc[i][j]+1>0:
model.addConstr(gp.quicksum(z_msea[i,f,t] for t in range(t-t_cost.iloc[i][j]+1,t+1) )
<= 1-y_msea[j,f,t], name="cost_t")
where t_cost is:
C | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
0 | 0 | 2 | 3 | 4 | 5 | 3 | 4 | 3 | 4 | 2 | 3 | 4 | 5 |
1 | 2 | 0 | 2 | 3 | 4 | 2 | 3 | 3 | 4 | 2 | 3 | 4 | 5 |
2 | 3 | 2 | 0 | 2 | 3 | 3 | 2 | 4 | 4 | 3 | 2 | 3 | 4 |
3 | 4 | 3 | 2 | 0 | 2 | 4 | 3 | 4 | 3 | 4 | 3 | 2 | 3 |
4 | 5 | 4 | 3 | 2 | 0 | 4 | 2 | 5 | 4 | 5 | 4 | 3 | 2 |
5 | 3 | 2 | 3 | 4 | 4 | 0 | 2 | 4 | 5 | 3 | 4 | 5 | 5 |
6 | 4 | 3 | 2 | 3 | 2 | 2 | 0 | 5 | 4 | 4 | 3 | 4 | 4 |
7 | 3 | 3 | 4 | 4 | 5 | 4 | 5 | 0 | 2 | 2 | 3 | 3 | 4 |
8 | 4 | 4 | 4 | 3 | 4 | 5 | 4 | 2 | 0 | 3 | 3 | 2 | 3 |
9 | 2 | 2 | 3 | 4 | 5 | 3 | 4 | 2 | 3 | 0 | 2 | 3 | 4 |
10 | 3 | 3 | 2 | 3 | 4 | 4 | 3 | 3 | 3 | 2 | 0 | 2 | 3 |
11 | 4 | 4 | 3 | 2 | 3 | 5 | 4 | 3 | 2 | 3 | 2 | 0 | 2 |
12 | 5 | 5 | 4 | 3 | 2 | 5 | 4 | 4 | 3 | 4 | 3 | 2 | 0 |
So, as you can see z_msea is connected to "i" where y_msea is connected to "j" which belong to same subset. Additionaly, z_msea sum also has lower and upper bounds which makes things complicated. In this form, not used (in a constraint) y_msea and z_msea variables can get random values and result with a wrong calculation later. Hence, I need to create only necessary ones. I can do this by,
for t in time:
for i in nd:
for j in nd:
for f in evbee:
if i!=j and t-t_cost.iloc[i][j]+1>0:
model.addConstr(gp.quicksum(model.addVar(vtype=GRB.BINARY, name="z_msea-i%d-f%d-t%d" %(i,f,t)) for t in range(t-t_cost.iloc[i][j]+1,t+1) )
<= 1-model.addVar(vtype=GRB.BINARY, name="y_msea-j%d-f%d-t%d" %(j,f,t)), name="cost_t")
But this time, using those variables in an another constraint is not useful like this:
for t in time:
for i in nd:
for f in evbee:
model.addConstr(y_msea[i,f,t] + z_msea[i,f,t] <= 1)
if t>0:
model.addConstr(y_msea[i,f,t] - z_msea[i,f,t] == u_msea[i,f,t] - u_msea[i,f,t-1])
Is there any way to accomplish this easier?
-
Hi Selim,
I would not expect variables that don't appear in a constraint to take random values, I'd expect them to take one of their bounds, or 0 in the case they are not bounded. But to answer your questions I can think of two approaches to create a model without unused variables.
The first is to create all variables as you are currently doing, then remove any which don't appear in a constraint. To do this you can add the following two lines before calling optimize():
model.update()
model.remove([v for v in model.getVars() if model.getCol(v).size() == 0])The second approach is to extend our tupledict to create a new class which generates variables on the fly:
import gurobipy as gp
class td_auto(gp.tupledict):
def __init__(self, model, *args, name="", **kwargs):
self.model = model
self.args = args
self.name = name
self.kwargs=kwargs
super().__init__()
def __missing__(self, key):
v = self.model.addVar(*self.args, name=self.name%key, **self.kwargs)
self.__setitem__(key, v)
return v
m = gp.Model()
x = td_auto(m, vtype=gp.GRB.BINARY, name="z_msea-i%d-f%d-t%d")
example = gp.quicksum([x[i,2,3] for i in range(10)])
m.update()
print(example)
print(x)This does avoid creating unnecessary variables as you requested, but it's more complicated than the first, so in this case I'd suggest that the first approach is used unless it turns out to be too slow and the second approach is faster.
- Riley
0 -
Thanks for your detailed reply Riley!
Yes, you are right. Maybe I explained it wrong.
The problem is that the unused variables take their bounds (in that case 0 or 1 since they are binary). But then I calculate u_msea based on them (which is my essential constraint for cost). So if they take random values (0 or 1) my calculations will be wrong.
In your first approach, instead of deleting them, I set them to 0. So they are not gonna break any other constraint.
[model.addConstr(v==0) for v in model.getVars() if model.getCol(v).size() == 0]
model.update()In GAMS, I assume it is easier to create such constraints since it does not need if condition;
sum (t$((ord(t) >= ord(t) – t_cost(i,j)+1) and (ord(t) <= ord(t))), z_msea(i,f,t)) =l= 1 - y_msea(j,f,t)
As far as I know, GAMS do not create such variables without using
if t-t_cost.iloc[i][j]+1>0:
and ignore adding constraint if variable is not exist (such as z_msea[-1,0,0] etc.).
My constraint is:
The weird thing is; if I create mVar instead of Var, the constraint adding process does not give any error for non exist variables (with negative index etc.).
0 -
Hi Selim,
I can't really comment on GAMS as I've never used it.
However I do need to comment on your last statement:
The weird thing is; if I create mVar instead of Var, the constraint adding process does not give any error for non exist variables (with negative index etc.).
Our Matrix API is built on top of numpy and scipy. What you are seeing with negative indices is a consequence of numpy indexing. Eg
>> import numpy as np
>> arr = np.array([1,2,3])
>> arr[-1]
3
>> arr[-2]
2So if you have a MVar and use a negative index it won't error, it won't ignore it, it will give you back a variable and if you're unaware of numpy indexing then I'd guess you're creating incorrect constraints.
- Riley
0
Please sign in to leave a comment.
Comments
3 comments