The smaller exercises embedded in Multi-Period Inventory Planning, Robust Regression and Solvers and How to Help Them check what you just read. This notebook collects the larger exercises for Lecture 10: independent problems worth more time.
Homework Exercises¶
Solution to Exercise 1 #
a. is the order size at time , so from the output: order 9 units in period 1, 10 units in period 4, and 7 units in period 5 (all other ). The holding costs, from the values, are euro... more precisely, using : euro. Since the total cost is 284 euro, the ordering cost is euro.
b. The stock levels are , so the maximum stock level is 12, reached in period 1.
c. Since and for all , the optimizer wants and as small as possible. Without the nonnegativity constraint, it would drive them to , making the total cost unbounded below; pulp reports the model as unbounded.
d. The extra constraint forbids ordering in periods 1 and 2. With initial stock 8 and
demand over those two periods, demand cannot be met, so the model is
infeasible (pulp reports Infeasible). Using only the constraints: nonnegativity of
requires ; substituting gives
, so is indeed
infeasible.
e. i. Add ILO_problem += stock[t] <= 10 for every (or pass upBound=10 when
creating the stock variables).
ii. Extend the data lists: demands = [None, 5, 4, 8, 10, 4, 2, 1, 8],
holding_costs = [None, 1, 1, 1, 1, 1, 1, 1, 3],
order_costs = [None, 10, 13, 13, 10, 10, 13, 13, 2].
iii. holding_costs = [None, 1, 1, 1, 2, 2, 2, 1].
iv. Add 3 * stock[-1] to the objective.
v. Introduce a binary leftover_costs variable, add 30 * leftover_costs to the
objective, and enforce stock[-1] <= leftover_costs * 100000 (a big-M
constraint) so it is forced to 1 whenever stock remains at time .
vi. Introduce integer variables order_multiple[t] and add
supply[t] == 5 * order_multiple[t] for every .
vii. Add supply[t] == 0 for every .
f. Take, period by period, the worst case (maximum) demand across the four scenarios: , and re-optimize the model with this conservative demand vector. This guarantees every scenario’s demand is met.
g. Remove the holding-cost term from the objective, then solve the model once per warehouse (using as the stock upper bound) and add the corresponding to each optimal cost. The warehouse with the lowest total cost is best, and its solution gives the ordering strategy to use.
h. Introduce a second set of parameters and variables for the new product, marked with a prime: , , , , . The model becomes
This is the same model as before (minus holding costs) duplicated for the second product, with the two products’ stock levels linked through the shared capacity constraint .
Solution to Exercise 2 #
a. The objective value of the incumbent is 237 and the best bound is 14.34, so the gap is , or 94%. The schedule is not proven optimal: all we know is that the optimal total weighted tardiness lies between 14.34 and 237. It may still be optimal, but the solver cannot show it (a longer run finds a schedule with 202, so here it is not).
b. Proving is the hard part. The best bound does not move at all after the root node (14.34 throughout), so the gap can never close in this time. The incumbent keeps improving until about 21 seconds (its value drops from 412 to 237), so finding better schedules is not easy either, but at least it makes progress.
c. Neither helps. With gapRel=0.05, CBC stops once the gap is below 5%, but the gap stays around 94%, so the solve runs until the time limit as before. The warm start gives a first incumbent with value 428, which is worse than the incumbent with value 412 that CBC finds by itself after 0.02 seconds, and a warm start does not raise the best bound. What could help is more time, or a formulation with a stronger LO relaxation.
d. Open-source: for example CBC, HiGHS or SCIP. Commercial: for example Gurobi, CPLEX or FICO Xpress. The commercial solvers are the fastest, but they are expensive outside academia (there are free academic licenses), which limits their use in practice.
Solution to Exercise 3 #
a. The two errors:
The
stockvariables have nolowBound=0, so stock levels can be negative: demand is then met from stock that does not exist, and negative stock even lowers the holding cost.range(1, len(demand))only runs over , so the balance constraint of the first period ( in Python) is missing: nothing links to , and .
Together, can be made as negative as you like (with and following through their balance constraints), which lowers the cost without limit: Unbounded. The corrected code:
import pulp
demand = [4, 6, 3]
holding = [1, 1, 1]
order_cost = [3, 5, 4]
s0 = 2
periods = range(len(demand))
inventory = pulp.LpProblem(name="inventory", sense=pulp.LpMinimize)
order = [pulp.LpVariable(name=f"x_{t + 1}", lowBound=0) for t in periods]
stock = [pulp.LpVariable(name=f"s_{t + 1}", lowBound=0) for t in periods]
inventory += pulp.lpSum(
order_cost[t] * order[t] + holding[t] * stock[t] for t in periods
)
for t in periods:
prev = s0 if t == 0 else stock[t - 1]
inventory += stock[t] == prev + order[t] - demand[t], f"balance_{t + 1}"
inventory.solve(pulp.PULP_CBC_CMD(msg=False))
print(pulp.LpStatus[inventory.status])
print("orders:", [order[t].value() for t in periods])
print("end-of-period stock:", [stock[t].value() for t in periods])
print("total cost:", inventory.objective.value())It prints:
Optimal
orders: [8.0, 0.0, 3.0]
end-of-period stock: [6.0, 0.0, 0.0]
total cost: 42.0Fixing only one of the two errors is not enough. With only the lower bounds added, the first period’s balance is still missing, and the model takes 9 units of stock in period 1 from nowhere:
Optimal
orders: [0.0, 0.0, 0.0]
end-of-period stock: [9.0, 3.0, 0.0]
total cost: 12.0With only the loop fixed, stock is allowed to be negative, and ordering nothing is “optimal”:
Optimal
orders: [0.0, 0.0, 0.0]
end-of-period stock: [-2.0, -8.0, -11.0]
total cost: -21.0b. Add an integer variable for the number of batches ordered in period , and the constraints . Added to the corrected code, before the solve line:
batches = [
pulp.LpVariable(name=f"n_{t + 1}", lowBound=0, cat="Integer")
for t in periods
]
for t in periods:
inventory += order[t] == 5 * batches[t], f"batch_{t + 1}"The plan changes: period 1 orders 10 units (8 needed), and in period 3 the one missing unit requires a full batch of 5:
Optimal
orders: [10.0, 0.0, 5.0]
end-of-period stock: [8.0, 2.0, 4.0]
total cost: 64.0