In [1] the question was posed: "Can we formulate a (Q,R) inventory model as an integer programming model?" Of course, I said: Yes. But we need to be a bit careful if we want to make it a linear model.
(Q,R) Inventory Policy
The (Q,R) inventory model is as follows: if the inventory falls below R, place an order for Q. In addition, we deal with:
- Lead times: when we placed an order it takes some time to be delivered
- When inventory becomes zero, additional demand will cause a backlog. Backlogged demand will be fulfilled when replenishments arrive but at a cost.
- Costs are:
- Fixed ordering cost
- Holding cost related to inventory
- Penalties related to backlogs
Data
---- 11 PARAMETER demand random data
t1 17, t2 84, t3 55, t4 30, t5 29, t6 22, t7 34, t8 85, t9 6, t10 50
t11 99, t12 57, t13 99, t14 76, t15 13, t16 63, t17 15, t18 25, t19 66, t20 43
t21 35, t22 35, t23 13, t24 15, t25 58, t26 83, t27 23, t28 66, t29 77, t30 30
t31 11, t32 50, t33 16, t34 87, t35 26, t36 28, t37 59, t38 72, t39 62, t40 46
t41 41, t42 11, t43 31, t44 4, t45 33, t46 18, t47 64, t48 56, t49 76, t50 29
t51 66, t52 75, t53 62, t54 28, t55 8, t56 10, t57 64, t58 54, t59 3, t60 79
t61 7, t62 17, t63 52, t64 75, t65 17, t66 3, t67 58, t68 62, t69 38, t70 35
t71 24, t72 24, t73 13, t74 93, t75 37, t76 78, t77 30, t78 12, t79 74, t80 6
t81 20, t83 26, t84 49, t85 15, t86 17, t87 33, t88 31, t89 32, t90 96, t91 99
t92 36, t93 37, t94 77, t95 39, t96 91, t97 11, t98 73, t99 5, t100 57
We assume demand occurs during period \(t\), i.e. it is a "flow" quantity (opposed to "stock variables").
The other data for the problem looks like:
---- 28 PARAMETER invCap = 1000.000 inventory capacity
PARAMETER maxQ = 1000.000 max order quantity
PARAMETER orderCost = 500.000 cost for each order
PARAMETER holdCost = 2.000 holding cost (per unit, per time period)
PARAMETER leadTime = 10.000 lead time
PARAMETER backLogCost = 10.000 backlogged orders penalty (per unit, per time period)
PARAMETER maxBackLogged = 1000.000 big-M
---- 28 PARAMETER initInv intial inventory available at t1
t1 500.000
Variables
I need a bunch of decision variables to model this. Let's list them:
| Decision variables | |
|---|---|
| \(\color{darkred}Q \in [0,\color{darkblue}{\mathit{maxQ}}]\) | Reorder quantity. Each time we place an order, this is the size of the order. |
| \(\color{darkred}R \in [0,\color{darkblue}{\mathit{invCap}}]\) | Reorder point. We place an order when the inventory drops below \(\color{darkred}R\). |
| \(\color{darkred}{\mathit{inv}}_t \in [0,\color{darkblue}{\mathit{invCap}}] \) | Inventory at beginning of period \(t\). |
| \(\color{darkred}{\mathit{back}}_t \in [0,\color{darkblue}{\mathit{maxBackLogged}}]\) | Backlog, unmet demand we fulfill once new inventory arrives, again at beginning of period \(t\). |
| \(\color{darkred}{\mathit{low}}_t \in \{0,1\}\) | Inventory is low, defined by \(\color{darkred}{\mathit{inv}}_t \le \color{darkred}R\). |
| \(\color{darkred}{\mathit{order}}_t \in \{0,1\}\) | An order is placed. We place an order when \(\color{darkred}{\mathit{inv}}_t\) drops below \(\color{darkred}R\). |
| \(\color{darkred}{\mathit{repl}}_t \in \{0,\color{darkred}Q\}\) | Replenishment. Inventory is replenished by a previously placed order (there is a lead time). This replenishment is also used to handle backlogged demand. \(\color{darkred}{\mathit{repl}}_t\) is either zero or equal to the reorder quantity \(\color{darkred}Q\). |
In the question, it was required that the decision variables are integer-valued. So, I declared variables as integer where needed.
Model equations
1. The objective has three parts: fixed ordering cost,holding cost, and backlog penalities. So we have \[\min \> \color{darkred} z = \color{darkblue}{\mathit{orderCost}}\cdot\sum_t \color{darkred}{\mathit{order}}_t+\color{darkblue}{\mathit{holdCost}}\cdot \sum_t \color{darkred}{\mathit{inv}}_t+\color{darkblue}{\mathit{backlogCost}} \cdot\sum_t \color{darkred}{\mathit{back}}_t\]
2. The inventory balance equation is a bit complex as we allow backlog: \[\color{darkred}{\mathit{inv}}_t-\color{darkred}{\mathit{back}}_t = \color{darkred}{\mathit{inv}}_{t-1}-\color{darkred}{\mathit{back}}_{t-1} - \color{darkblue}{\mathit{demand}}_t + \color{darkred}{\mathit{repl}}_t + \color{darkblue}{\mathit{initInv}}_t \] Instead of just inventory, we update the quantity \(\color{darkred}{\mathit{inv}}_t-\color{darkred}{\mathit{back}}_t \). We require that only one of these variables can be non-zero. That means we need to add the complementarity condition \[\color{darkred}{\mathit{inv}}_t\cdot \color{darkred}{\mathit{back}}_t = 0\] In many cases when we minimize cost this condition is not needed, as the model will automatically have not both variables nonzero. Unfortunately, in this case the model may not always do this, just to get some early reordering, so we need to enforce this explicitly. The complementarity condition can be linearized as: \[\begin{align} & \color{darkred}{\mathit{inv}}_t \le \color{darkblue}{\mathit{invCap}} \cdot \color{darkred}\delta_t \\ & \color{darkred}{\mathit{back}}_t \le \color{darkblue}{\mathit{maxBackLogged}} \cdot (1-\color{darkred}\delta_t) \\ & \color{darkred}\delta_t \in \{0,1\}\end{align} \] The term \(\color{darkblue}{\mathit{initInv}}_t\) deals with initial inventory available at \(t=1\). This is a sparse vector with only a nonzero value in the first position. This trick makes it easy to have a single constraint for all time periods as opposed to handling the first period differently.
3. To detect low inventory, i.e. when \(\color{darkred}{\mathit{inv}}_t \le \color{darkred}R\), we add a binary variable \(\color{darkred}{\mathit{low}}_t \in \{0,1\}\). We need to make sure: \[\begin{cases}\color{darkred}{\mathit{inv}}_t \le \color{darkred}R \Rightarrow \color{darkred}{\mathit{low}}_t = 1 \\ \color{darkred}{\mathit{inv}}_t \ge \color{darkred}R + 1 \Rightarrow \color{darkred}{\mathit{low}}_t = 0 \end{cases}\] I implemented this as \[\begin{align} & \color{darkred}{\mathit{inv}}_t \le \color{darkred}R + \color{darkblue}{\mathit{invCap}} \cdot (1-\color{darkred}{\mathit{low}}_t ) \\ &\color{darkred}{\mathit{inv}}_t \ge \color{darkred}R + 1 - (\color{darkblue}{\mathit{invCap}}+1)\cdot \color{darkred}{\mathit{low}}_t\end{align} \] This implies \(\color{darkred}R \le \color{darkblue}{\mathit{invCap}} \), which is a proper bound.
4. A reorder event takes place when inventory drops below \(\color{darkred}R\). This means we should trigger a reorder only when \(\color{darkred}{\mathit{low}}_{t-1}=0\) and \(\color{darkred}{\mathit{low}}_{t}=1\). In terms of our model, this condition can be written as \[\color{darkred}{\mathit{order}}_t = (1-\color{darkred}{\mathit{low}}_{t-1}) \cdot \color{darkred}{\mathit{low}}_t\] I implemented a linearization of this as: \[\begin{align} & \color{darkred}{\mathit{order}}_t \le 1-\color{darkred}{\mathit{low}}_{t-1} \\ & \color{darkred}{\mathit{order}}_t \le \color{darkred}{\mathit{low}}_t \\ & \color{darkred}{\mathit{order}}_t \ge \color{darkred}{\mathit{low}}_t -\color{darkred}{\mathit{low}}_{t-1} \end{align}\]
5. Inventory replenishment takes place after an order was placed and when the lead time passed. We can write this as \[\color{darkred}{\mathit{repl}}_t = \color{darkred}Q \cdot \color{darkred}{\mathit{order}}_{t-\color{darkblue}{\mathit{leadTime}}}\] with \(\color{darkred}{\mathit{repl}}_t \in [0,\color{darkblue}{\mathit{maxQ}}]\). I linearized this as: \[ \begin{align} & \color{darkred}{\mathit{repl}}_t \le \color{darkblue}{\mathit{maxQ}} \cdot \color{darkred}{\mathit{order}}_{t-\color{darkblue}{\mathit{leadTime}}} \\ & \color{darkred}{\mathit{repl}}_t \le \color{darkred}Q \\ & \color{darkred}{\mathit{repl}}_t \ge \color{darkred}Q - \color{darkblue}{\mathit{maxQ}}\cdot (1-\color{darkred}{\mathit{order}}_{t-\color{darkblue}{\mathit{leadTime}}}) \end{align}\] For simplicity, I will assume that no ordering happened just before \(t=1\). In other words, all \(\color{darkred}{\mathit{order}}_t=0\) when \(t \le 0\). That will make a model a little bit messier, but you will need to address this in a real, practical model.
OK, this was a lot of things to digest. Let's summarize the model:
| MIP Model |
|---|
| \[\begin{align} \min \> & \color{darkred} z = \color{darkblue}{\mathit{orderCost}}\cdot\sum_t \color{darkred}{\mathit{order}}_t+\color{darkblue}{\mathit{holdCost}}\cdot \sum_t \color{darkred}{\mathit{inv}}_t+\color{darkblue}{\mathit{backlogCost}} \cdot\sum_t \color{darkred}{\mathit{back}}_t && (1) \\ & \color{darkred}{\mathit{inv}}_t-\color{darkred}{\mathit{back}}_t = \color{darkred}{\mathit{inv}}_{t-1}-\color{darkred}{\mathit{back}}_{t-1} - \color{darkblue}{\mathit{demand}}_t + \color{darkred}{\mathit{repl}}_t + \color{darkblue}{\mathit{initInv}}_t && (2) \\ & \color{darkred}{\mathit{inv}}_t \le \color{darkblue}{\mathit{invCap}} \cdot \color{darkred}\delta_t \\ & \color{darkred}{\mathit{back}}_t \le \color{darkblue}{\mathit{maxBackLogged}} \cdot (1-\color{darkred}\delta_t) \\ & \color{darkred}{\mathit{inv}}_t \le \color{darkred}R + \color{darkblue}{\mathit{invCap}} \cdot (1-\color{darkred}{\mathit{low}}_t ) && (3) \\ &\color{darkred}{\mathit{inv}}_t \ge \color{darkred}R + 1 - (\color{darkblue}{\mathit{invCap}}+1)\cdot \color{darkred}{\mathit{low}}_t \\ & \color{darkred}{\mathit{order}}_t \le 1-\color{darkred}{\mathit{low}}_{t-1} && (4) \\ &\color{darkred}{\mathit{order}}_t \le \color{darkred}{\mathit{low}}_t \\ & \color{darkred}{\mathit{order}}_t \ge \color{darkred}{\mathit{low}}_t -\color{darkred}{\mathit{low}}_{t-1} \\ & \color{darkred}{\mathit{repl}}_t \le \color{darkblue}{\mathit{maxQ}} \cdot \color{darkred}{\mathit{order}}_{t-\color{darkblue}{\mathit{leadTime}}} && (5) \\ & \color{darkred}{\mathit{repl}}_t \le \color{darkred}Q \\ & \color{darkred}{\mathit{repl}}_t \ge \color{darkred}Q - \color{darkblue}{\mathit{maxQ}}\cdot (1-\color{darkred}{\mathit{order}}_{t-\color{darkblue}{\mathit{leadTime}}}) \\ & \color{darkred}Q \in [0,\color{darkblue}{\mathit{maxQ}}] \\ & \color{darkred}R \in [0,\color{darkblue}{\mathit{invCap}}] \\ & \color{darkred}{\mathit{inv}}_t \in [0,\color{darkblue}{\mathit{invCap}}] \\ & \color{darkred}{\mathit{back}}_t \in [0,\color{darkblue}{\mathit{maxBackLogged}}] \\ & \color{darkred}\delta_t \in \{0,1\} \\ & \color{darkred}{\mathit{low}}_t \in \{0,1\} \\ & \color{darkred}{\mathit{order}}_t \in \{0,1\} \\ & \color{darkred}{\mathit{repl}}_t \in [0,\color{darkblue}{\mathit{maxQ}}] \end{align}\] |
Results
---- 163 PARAMETER result
demand inv backlog below reorder replenish
t1 17483
t2 84399
t3 5534411
t4 303141
t5 292851
t6 222631
t7 342291
t8 851441
t9 61381
t10 50881
t11 99111
t12 57681
t13 99345512
t14 7626911
t15 132561
t16 631931
t17 151781
t18 251531
t19 66871
t20 43441
t21 3591
t22 35261
t23 13391
t24 15458512
t25 58400
t26 8331711
t27 232941
t28 662281
t29 771511
t30 301211
t31 111101
t32 50601
t33 16441
t34 87431
t35 26691
t36 28415512
t37 59356
t38 7228411
t39 622221
t40 461761
t41 411351
t42 111241
t43 31931
t44 4891
t45 33561
t46 18381
t47 64261
t48 56430512
t49 76354
t50 2932511
t51 662591
t52 751841
t53 621221
t54 28941
t55 8861
t56 10761
t57 64121
t58 54421
t59 3451
t60 79388512
t61 7381
t62 17364
t63 5231211
t64 752371
t65 172201
t66 32171
t67 581591
t68 62971
t69 38591
t70 35241
t71 241
t72 24241
t73 13475512
t74 93382
t75 37345
t76 7826711
t77 302371
t78 122251
t79 741511
t80 61451
t81 201251
t82 1251
t83 26991
t84 49501
t85 15351
t86 17530512
t87 33497
t88 31466
t89 32434
t90 9633811
t91 992391
t92 362031
t93 371661
t94 77891
t95 39501
t96 91411
t97 11521
t98 731251
t99 51301
t100 573251512
Q 512
R 344
numorder 8
holdcost 37580
ordercost 4000
backlogcost 7410
A picture of this:
This looks reasonable to me.
References
- Minimize an Integer Programming problem with nested decision variables, https://stackoverflow.com/questions/64652771/minimize-an-integer-programming-problem-with-nested-decision-variables