For example, $TrainSample=[1,2,3,4,5]$, optimal $y=3$, the count should be $[1,1,1,0,0]$, that's what I expect

• Gurobi Staff

Are you trying to model $$\texttt{Train_Sample[k]} \leq y \iff \texttt{count[k]} = 1$$ for each $$k$$? If so, you can add two indicator constraints for each $$k$$:

for k in range(N):    m.addConstr((count[k] == 0) >> (Train_Sample[k] >= y + 1))    m.addConstr((count[k] == 1) >> (Train_Sample[k] <= y))

I'm assuming the $$\texttt{count}$$ variables are binary and $$\texttt{Train_Sample}$$ contains only integers.

Thanks for your reply. Yes, I'm trying to model $Train_Sample [k] <=y$ for each $k$. The count variables are binary and Train_Sample are contain continuous values. I tried your code, but it says "int object is not callable". I'm confused.

Let me be clear. count varaiables  vary with y. I need the values of count for each y, not just the final optimal y.

• Gurobi Staff

It's hard to say where that error comes from without seeing exactly how you define $$\texttt{count}$$, $$\texttt{Train_Sample}$$, and $$\texttt{y}$$. Can you please post a minimal, self-contained code snippet that reproduces the error? It's also not clear to me what you mean by "each $$y$$". Your code snippet only includes a single $$y$$ variable that will assume a single value at any given solution.

The following code executes without error:

import gurobipy as gpfrom gurobipy import GRBN = 10# Generate random integers for Train_SampleTrain_Sample = [k + 1 for k in range(N)]m = gp.Model()count = m.addVars(N, vtype=GRB.BINARY, name="count")y = m.addVar(name="y")for k in range(N):    m.addConstr((count[k] == 0) >> (Train_Sample[k] >= y + 1))    m.addConstr((count[k] == 1) >> (Train_Sample[k] <= y))# Fix y to 5 to illustrate how the count variables reacty.LB = 5y.UB = 5m.optimize()# Check outputprint(f"y = {y.X}")for i in range(N):    print(f"Train_Sample[{i}] = {Train_Sample[i]}, count[{i}] = {count[i].X}")

The output:

y = 5.0Train_Sample[0] = 1, count[0] = 1.0Train_Sample[1] = 2, count[1] = 1.0Train_Sample[2] = 3, count[2] = 1.0Train_Sample[3] = 4, count[3] = 1.0Train_Sample[4] = 5, count[4] = 1.0Train_Sample[5] = 6, count[5] = 0.0Train_Sample[6] = 7, count[6] = 0.0Train_Sample[7] = 8, count[7] = 0.0Train_Sample[8] = 9, count[8] = 0.0Train_Sample[9] = 10, count[9] = 0.0

Thanks for your reply. Suppose I'm considering a news vendor problem and define a function to compute the optimal order quantity.

During the process, I'm curious how will the count variables vary.

def SP_opt_P(c,r,Train_Sample):    N=len(Train_Sample)    prob=np.array([1/N]*N)    #create model    m=gp.Model('News Vendor')    M=max(Train_Sample)    #create variables    s=m.addMVar(N,name='Sells')    y=m.addVar(name='Optimal Order')    b=m.addVar(vtype=GRB.BINARY,name='b')    count=m.addMVar(N,name='count')    m.setObjective(gp.quicksum((r*s[k]-c*y)*prob[k] for k in range(N)),GRB.MAXIMIZE) #set objective    #for k in range(N): #model if Train_Sample[k]>y, then b=1, otherwise b=0    #add constraints    m.addConstrs(s[k]<=y for k in range(N))    m.addConstrs(s[k]<=Train_Sample[k] for k in range(N))    for k in range(N): #model if Train_Sample[k]>y, then b=1, otherwise b=0        m.addConstr(Train_Sample[k]>=y-M*(1-b))        m.addConstr(Train_Sample[k]<=y+M*b)        #add indicator constraints        m.addConstr((b==0)>>(count[k]==1))        m.addConstr((b==1)>>(count[k]==0))    m.optimize()    profit=round(m.Objval,3)    Order_amount=round(y.X,3)    sp=[Order_amount,profit]    SP=pd.DataFrame(sp).T    SP.columns=['Order_amount','SP_Tr_P']    print(s.X)    print(count.X)
Train_Sample=[1,2,3,4,5,6,7,8,9,10]c=5p=20SP_opt_P(c,p,Train_Sample)

The optimal order should be $y=8$, and the profit should be $64$. But when  I add the count variables, the optimal order amount becomes $10$, it's not correct.

• Gurobi Staff

When I replace the following section of your code:

for k in range(N): #model if Train_Sample[k]>y, then b=1, otherwise b=0    m.addConstr(Train_Sample[k]>=y-M*(1-b))    m.addConstr(Train_Sample[k]<=y+M*b)    #add indicator constraints    m.addConstr((b==0)>>(count[k]==1))    m.addConstr((b==1)>>(count[k]==0))

with the indicator constraints I mentioned:

for k in range(N):    m.addConstr((count[k] == 0) >> (Train_Sample[k] >= y + 1))    m.addConstr((count[k] == 1) >> (Train_Sample[k] <= y))

the code runs without error. The optimal solution value of $$y$$ is $$8$$ and the optimal objective value is $$64$$. The optimal solution values of the $$\texttt{count}$$ variables are:

[1. 1. 1. 1. 1. 1. 1. 1. 0. 0.]

Thanks for your reply. I just tried as you said. So,ethimg is wrong. Here is my code.

import numpy as npimport mathimport pandas as pdimport gurobipy as gpfrom gurobipy import GRB
def SP_opt_P(c,r,Train_Sample):    N=len(Train_Sample)    prob=np.array([1/N]*N)    #create model    m=gp.Model('News Vendor')    M=max(Train_Sample)    #create variables    s=m.addMVar(N,name='Sells')    y=m.addVar(name='Optimal Order')    b=m.addVar(vtype=GRB.BINARY,name='b')    count=m.addMVar(N,vtype=GRB.BINARY,name='count')    m.setObjective(gp.quicksum((r*s[k]-c*y)*prob[k] for k in range(N)),GRB.MAXIMIZE) #set objective    #for k in range(N): #model if Train_Sample[k]>y, then b=1, otherwise b=0    #add constraints    m.addConstrs(s[k]<=y for k in range(N))    m.addConstrs(s[k]<=Train_Sample[k] for k in range(N))    for k in range(N):        m.addConstr((count[k] == 0) >> (Train_Sample[k] >= y + 1))        m.addConstr((count[k] == 1) >> (Train_Sample[k] <= y))    m.optimize()    profit=round(m.Objval,3)    Order_amount=round(y.X,3)    sp=[Order_amount,profit]    SP=pd.DataFrame(sp).T    SP.columns=['Order_amount','SP_Tr_P']    print(s.X)    print(count.X)    return SP
Train_Sample=[1,2,3,4,5,6,7,8,9,10]c=5p=20SP_opt_P(c,p,Train_Sample)

But I got the prompt. I'm can't find the wrong place. Could you do me a favor? Thanks a lot.

Train_Sample colud contain float variables, not just integers.

• Gurobi Staff

But I got the prompt. I'm can't find the wrong place. Could you do me a favor? Thanks a lot.

Can you please try upgrading to Gurobi version 10.0.1? This unexpected error is due to a bug in Gurobi 10.0.0.

Train_Sample colud contain float variables, not just integers.

In that case, we only need to change the $$1$$ in the first indicator constraint to the smallest difference between two unique elements of $$\texttt{Train_Sample}$$. This can be done as follows:

eps = min(np.diff(np.unique(Train_Sample)))for k in range(N):    m.addConstr((count[k] == 0) >> (Train_Sample[k] >= y + eps))    m.addConstr((count[k] == 1) >> (Train_Sample[k] <= y))

Hi Eli Towle, Thanks a lot. It works after I updated the Gurobi version. But I can't tell the difference between your code and mine. I think they are following the logic, though your code is more elegant.

eps = min(np.diff(np.unique(Train_Sample)))
for k in range(N):
m.addConstr((count[k] == 0) >> (Train_Sample[k] >= y + eps))
m.addConstr((count[k] == 1) >> (Train_Sample[k] <= y))
for k in range(N): #model if Train_Sample[k]>y, then b=1, otherwise b=0
m.addConstr((b==1)>>(count[k]==0))
• Gurobi Staff

I believe your approach will work if you use a separate binary $$\texttt{b}$$ variable for each $$k$$.

Also, the first constraint in your loop enforces $$\texttt{Train_Sample[k]} \leq y$$ when $$\texttt{count[k]} = 1$$ and $$\texttt{Train_Sample[k]} \geq y$$ when $$\texttt{count[k]} = 0$$. This means $$\texttt{count[k]}$$ could equal either $$0$$ or $$1$$ if $$y = \texttt{Train_Sample[k]}$$. To ensure $$\texttt{Train_Sample[k]} \leq y \iff \texttt{count[k]} = 1$$, you should add a small positive value to the first constraint in the loop.

Altogether:

b = m.addVars(N, vtype=GRB.BINARY, name="b")eps = min(np.diff(np.unique(Train_Sample)))for k in range(N):    m.addConstr(Train_Sample[k] >= y - M * (1 - b[k]) + eps)    m.addConstr(Train_Sample[k] <= y + M * b[k])    m.addConstr((b[k] == 0) >> (count[k] == 1))    m.addConstr((b[k] == 1) >> (count[k] == 0))

That said, I don't see a reason to introduce additional $$\texttt{b}$$ variables when you can model this same logic using the existing $$\texttt{count}$$ variables.