1

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')
David Nehme
  • 21,379
  • 8
  • 78
  • 117
Siddharth
  • 5,009
  • 11
  • 49
  • 71

2 Answers2

2

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.

David Nehme
  • 21,379
  • 8
  • 78
  • 117
  • Did you mean - `m.addConstr(big_m * y[s] >= x[(s, p)]`? I tried adding this constraint (and not changing the objective function), and `y` has all `1`s even though the both `x[0, 0]` and `x[0, 1]` are zeros. – Siddharth Jun 24 '19 at 18:36
  • I spoke too early. If I add `sum(y[s] * t[s] for s in range(num_suppliers))` to the objective function (in addition to what I have now), this seems to produce the right answer. Thank you! – Siddharth Jun 24 '19 at 18:39
0

Would you be looking for something like this? here you only want the 1st and last element of an array that looks like this. So then the sum across columns by row of only the first and last rows are >= 1.

array([[ 0,  1,  2,  3],
       [ 4,  -5,  -6,  -7],
       [ 8,  9, 10, 11]])
num_suppliers, num_center = 3, 4

t = [1,2,3]
x = {
     (0, 0): 0,
     (0, 1): 1,
     (0, 2): 2,
     (0, 3): 3,
     (1, 0): 4,
     (1, 1): -5,
     (1, 2): -6,
     (1, 3): -7,
     (2, 0): 8,
     (2, 1): 9,
     (2, 2): 10,
     (2, 3): 11
     }

sum(t[s] for s in range(num_suppliers) if sum(x[s, p] for p in range(num_center)) >= 1)

Output: 4

Buckeye14Guy
  • 831
  • 6
  • 12