Skip to main content

loop for conditioanl constraint

Answered

Comments

14 comments

  • JUN ZHOU
    Gurobi-versary
    Conversationalist
    First Question

    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

    0
  • Eli Towle
    Gurobi Staff 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.

    0
  • JUN ZHOU
    Gurobi-versary
    Conversationalist
    First Question

    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.

    0
  • JUN ZHOU
    Gurobi-versary
    Conversationalist
    First Question

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

    0
  • Eli Towle
    Gurobi Staff 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 gp
    from gurobipy import GRB

    N = 10

    # Generate random integers for Train_Sample
    Train_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 react
    y.LB = 5
    y.UB = 5

    m.optimize()

    # Check output
    print(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.0
    Train_Sample[0] = 1, count[0] = 1.0
    Train_Sample[1] = 2, count[1] = 1.0
    Train_Sample[2] = 3, count[2] = 1.0
    Train_Sample[3] = 4, count[3] = 1.0
    Train_Sample[4] = 5, count[4] = 1.0
    Train_Sample[5] = 6, count[5] = 0.0
    Train_Sample[6] = 7, count[6] = 0.0
    Train_Sample[7] = 8, count[7] = 0.0
    Train_Sample[8] = 9, count[8] = 0.0
    Train_Sample[9] = 10, count[9] = 0.0
    0
  • JUN ZHOU
    Gurobi-versary
    Conversationalist
    First Question

    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)

    0
  • JUN ZHOU
    Gurobi-versary
    Conversationalist
    First Question
    Train_Sample=[1,2,3,4,5,6,7,8,9,10]
    c=5
    p=20
    SP_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.

    0
  • Eli Towle
    Gurobi Staff 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.]
    0
  • JUN ZHOU
    Gurobi-versary
    Conversationalist
    First Question

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

    import numpy as np
    import math
    import pandas as pd
    import gurobipy as gp
    from 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=5
    p=20
    SP_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.

    0
  • JUN ZHOU
    Gurobi-versary
    Conversationalist
    First Question

    Train_Sample colud contain float variables, not just integers.

    0
  • Eli Towle
    Gurobi Staff 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))
    0
  • JUN ZHOU
    Gurobi-versary
    Conversationalist
    First Question

    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(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))
    0
  • Eli Towle
    Gurobi Staff 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.

    0
  • JUN ZHOU
    Gurobi-versary
    Conversationalist
    First Question

    Hi, Eli Towel. Thanks a lot for your reply. I see your point. Thank you again.

    0

Please sign in to leave a comment.