Search code examples
pythonmathematical-optimizationlinear-programminggurobi

How can I model `if` conditionals in Gurobi Python Objective Function?


I have an objective function that has a if conditional in it. I am having trouble implementing it in Gurobi Python.

Background

There are s suppliers and p plants. x[s][p] is a variable that indicates the number of items that flow from supplier-x to plant-p. c[s][p] indicates the cost of supplying one item from a supplier to a center.

Additionally, there is a fixed cost t[s] for each supplier. If a supplier supplies to any center, this fixed cost is incurred (this fixed cost does not depend on the number of items).

I want to minimize the cost using an objective function like -

Objective Function

The first part is easy to model like sum(x[s, p] * spc[s, p] for s in range(num_suppliers) for p in range(num_center)).

For the second term, how can I model it? (The second part basically means that add the supplier's fixed cost only if the supplier is actually supplier anything to any plant).

Edit

This is the code I have now. Note: This does not produce the minimum value -

from gurobipy import *

supplier_capacity = [
    5, 10
]
plant_demand = [
    2, 4
]
num_suppliers = len(supplier_capacity)
num_plants = len(plant_demand)
t = [
    100, 1
]

c = {
    (0, 0): 1,
    (0, 1): 4,

    (1, 0): 4,
    (1, 1): 2
}

x = {}  # flow between each supplier to plant

m = Model()
xl = [(s, p) for s in range(num_suppliers) for p in range(num_plants)]
x = m.addVars(xl, vtype=GRB.INTEGER, lb=0, name='flow')

for s in range(num_suppliers):
    m.addConstr(x.sum(s, '*') <= supplier_capacity[s])
for p in range(num_plants):
    m.addConstr(x.sum('*', p) >= plant_demand[p])

m.setObjective(
    (
        sum(x[s, p] * c[s, p] for s in range(num_suppliers) for p in range(num_plants)) +
        sum(t[s] for s in range(num_suppliers) if x.sum(s, '*') >= 0)
    ), GRB.MINIMIZE
)
m.update()
m.optimize()

if m.status == GRB.Status.OPTIMAL:
    print('==== RESULTS ====')
    print('Min Cost: {}'.format(m.ObjVal))
    for v in m.getVars():
        print('{} = {}'.format(v.VarName, v.X))
else:
    print('Infeasible model')

Solution

  • Since x is a decision variable, you can not use it with a standard python if statement. Instead, you need to add a binary indicator variable (y_s) that will be forced to the value 1 whenever any of the shipment variables (x_sp) at non-zero. Then you add the indicator variable to the objective function with the coefficient t_s.

    y = [m.addVar(vtype='B', obj=t_s) for t_s in t]
    for s, y_s in enumerate(y):
        for p in range(num_plants):
             big_M = min(supplier_capacity[s], plant_demand[p])
             m.addConstr(big_M * y_s >= x[(s, p)]
    

    The constraints force each supplier to be "on" if it ships anything to any plant. The big_M value is an upper bound on the amount a supplier could ship to the plant. Since y is a binary variable, it must be 1 if any of the relevant x variables are non-zero. Conversely, if y is 1, then any or all of the relevant x variables will be effectively unconstrained. Since the coefficient on the y variables are all positive and you are minimizing you don't need an explicit constraint that y be 0 if all the x's are zero.