62. Production Smoothing via Inventories#
Contents
In addition to what’s in Anaconda, this lecture employs the following library:
!pip install quantecon
Show code cell output
Requirement already satisfied: quantecon in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (0.8.1)
Requirement already satisfied: numba>=0.49.0 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from quantecon) (0.60.0)
Requirement already satisfied: numpy>=1.17.0 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from quantecon) (1.26.4)
Requirement already satisfied: requests in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from quantecon) (2.32.3)
Requirement already satisfied: scipy>=1.5.0 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from quantecon) (1.13.1)
Requirement already satisfied: sympy in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from quantecon) (1.14.0)
Requirement already satisfied: llvmlite<0.44,>=0.43.0dev0 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from numba>=0.49.0->quantecon) (0.43.0)
Requirement already satisfied: charset-normalizer<4,>=2 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from requests->quantecon) (3.3.2)
Requirement already satisfied: idna<4,>=2.5 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from requests->quantecon) (3.7)
Requirement already satisfied: urllib3<3,>=1.21.1 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from requests->quantecon) (2.2.3)
Requirement already satisfied: certifi>=2017.4.17 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from requests->quantecon) (2024.8.30)
Requirement already satisfied: mpmath<1.4,>=1.1.0 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from sympy->quantecon) (1.3.0)
62.1. Overview#
This lecture can be viewed as an application of this quantecon lecture about linear quadratic control theory.
It formulates a discounted dynamic program for a firm that chooses a production schedule to balance
minimizing costs of production across time, against
keeping costs of holding inventories low
In the tradition of a classic book by Holt, Modigliani, Muth, and Simon [Holt et al., 1960], we simplify the firm’s problem by formulating it as a linear quadratic discounted dynamic programming problem of the type studied in this quantecon lecture.
Because its costs of production are increasing and quadratic in production, the firm holds inventories as a buffer stock in order to smooth production across time, provided that holding inventories is not too costly.
But the firm also wants to make its sales out of existing inventories, a preference that we represent by a cost that is quadratic in the difference between sales in a period and the firm’s beginning of period inventories.
We compute examples designed to indicate how the firm optimally smooths production while keeping inventories close to sales.
To introduce components of the model, let
be sales at time be production at time be inventories at the beginning of time be a discount factor , be a cost of production function, where , be an inventory cost function , where , be a cost-of-holding-inventories function, consisting of two components:a cost
of carrying inventories, anda cost
of having inventories deviate from sales
be an inverse demand function for a firm’s product, where and is a demand shock at time be the firm’s profits at time be the present value of the firm’s profits at time be the law of motion of inventories be a law of motion for an exogenous state vector that contains time information useful for predicting the demand shock link the demand shock to the information setthe constant
be the first component of
To map our problem into a linear-quadratic discounted dynamic
programming problem (also known as an optimal linear regulator), we
define the state vector at time
and the control vector as
The law of motion for the state vector
or
(At this point, we ask that you please forgive us for using
We can express the firm’s profit as a function of states and controls as
To form the matrices
where
Remark on notation: The notation for cross product term in the
QuantEcon library is
The firms’ optimum decision rule takes the form
and the evolution of the state under the optimal decision rule is
The firm chooses a decision rule for
subject to a given
This is a stochastic discounted LQ dynamic program.
Here is code for computing an optimal decision rule and for analyzing its consequences.
import matplotlib.pyplot as plt
plt.rcParams["figure.figsize"] = (11, 5) #set default figure size
import numpy as np
import quantecon as qe
class SmoothingExample:
"""
Class for constructing, solving, and plotting results for
inventories and sales smoothing problem.
"""
def __init__(self,
β=0.96, # Discount factor
c1=1, # Cost-of-production
c2=1,
d1=1, # Cost-of-holding inventories
d2=1,
a0=10, # Inverse demand function
a1=1,
A22=[[1, 0], # z process
[1, 0.9]],
C2=[[0], [1]],
G=[0, 1]):
self.β = β
self.c1, self.c2 = c1, c2
self.d1, self.d2 = d1, d2
self.a0, self.a1 = a0, a1
self.A22 = np.atleast_2d(A22)
self.C2 = np.atleast_2d(C2)
self.G = np.atleast_2d(G)
# Dimensions
k, j = self.C2.shape # Dimensions for randomness part
n = k + 1 # Number of states
m = 2 # Number of controls
Sc = np.zeros(k)
Sc[0] = 1
# Construct matrices of transition law
A = np.zeros((n, n))
A[0, 0] = 1
A[1:, 1:] = self.A22
B = np.zeros((n, m))
B[0, :] = 1, -1
C = np.zeros((n, j))
C[1:, :] = self.C2
self.A, self.B, self.C = A, B, C
# Construct matrices of one period profit function
R = np.zeros((n, n))
R[0, 0] = d2
R[1:, 0] = d1 / 2 * Sc
R[0, 1:] = d1 / 2 * Sc
Q = np.zeros((m, m))
Q[0, 0] = c2
Q[1, 1] = a1 + d2
N = np.zeros((m, n))
N[1, 0] = - d2
N[0, 1:] = c1 / 2 * Sc
N[1, 1:] = - a0 / 2 * Sc - self.G / 2
self.R, self.Q, self.N = R, Q, N
# Construct LQ instance
self.LQ = qe.LQ(Q, R, A, B, C, N, beta=β)
self.LQ.stationary_values()
def simulate(self, x0, T=100):
c1, c2 = self.c1, self.c2
d1, d2 = self.d1, self.d2
a0, a1 = self.a0, self.a1
G = self.G
x_path, u_path, w_path = self.LQ.compute_sequence(x0, ts_length=T)
I_path = x_path[0, :-1]
z_path = x_path[1:, :-1]
𝜈_path = (G @ z_path)[0, :]
Q_path = u_path[0, :]
S_path = u_path[1, :]
revenue = (a0 - a1 * S_path + 𝜈_path) * S_path
cost_production = c1 * Q_path + c2 * Q_path ** 2
cost_inventories = d1 * I_path + d2 * (S_path - I_path) ** 2
Q_no_inventory = (a0 + 𝜈_path - c1) / (2 * (a1 + c2))
Q_hardwired = (a0 + 𝜈_path - c1) / (2 * (a1 + c2 + d2))
fig, ax = plt.subplots(2, 2, figsize=(15, 10))
ax[0, 0].plot(range(T), I_path, label="inventories")
ax[0, 0].plot(range(T), S_path, label="sales")
ax[0, 0].plot(range(T), Q_path, label="production")
ax[0, 0].legend(loc=1)
ax[0, 0].set_title("inventories, sales, and production")
ax[0, 1].plot(range(T), (Q_path - S_path), color='b')
ax[0, 1].set_ylabel("change in inventories", color='b')
span = max(abs(Q_path - S_path))
ax[0, 1].set_ylim(0-span*1.1, 0+span*1.1)
ax[0, 1].set_title("demand shock and change in inventories")
ax1_ = ax[0, 1].twinx()
ax1_.plot(range(T), 𝜈_path, color='r')
ax1_.set_ylabel("demand shock", color='r')
span = max(abs(𝜈_path))
ax1_.set_ylim(0-span*1.1, 0+span*1.1)
ax1_.plot([0, T], [0, 0], '--', color='k')
ax[1, 0].plot(range(T), revenue, label="revenue")
ax[1, 0].plot(range(T), cost_production, label="cost_production")
ax[1, 0].plot(range(T), cost_inventories, label="cost_inventories")
ax[1, 0].legend(loc=1)
ax[1, 0].set_title("profits decomposition")
ax[1, 1].plot(range(T), Q_path, label="production")
ax[1, 1].plot(range(T), Q_hardwired, label='production when $I_t$ \
forced to be zero')
ax[1, 1].plot(range(T), Q_no_inventory, label='production when \
inventories not useful')
ax[1, 1].legend(loc=1)
ax[1, 1].set_title('three production concepts')
plt.show()
Notice that the above code sets parameters at the following default values
discount factor
,inverse demand function:
cost of production
costs of holding inventories
In the examples below, we alter some or all of these parameter values.
62.2. Example 1#
In this example, the demand shock follows AR(1) process:
which implies
We set
We’ll calculate and display outcomes, then discuss them below the pertinent figures.
ex1 = SmoothingExample()
x0 = [0, 1, 0]
ex1.simulate(x0)

The figures above illustrate various features of an optimal production plan.
Starting from zero inventories, the firm builds up a stock of inventories and uses them to smooth costly production in the face of demand shocks.
Optimal decisions evidently respond to demand shocks.
Inventories are always less than sales, so some sales come from current
production, a consequence of the cost,
The lower right panel shows differences between optimal production and two alternative production concepts that come from altering the firm’s cost structure – i.e., its technology.
These two concepts correspond to these distinct altered firm problems.
a setting in which inventories are not needed
a setting in which they are needed but we arbitrarily prevent the firm from holding inventories by forcing it to set
always
We use these two alternative production concepts in order to shed light on the baseline model.
62.3. Inventories Not Useful#
Let’s turn first to the setting in which inventories aren’t needed.
In this problem, the firm forms an output plan that maximizes the expected value of
It turns out that the optimal plan for
When inventories aren’t required or used, sales always equal production.
This simplifies the problem and the optimal no-inventory production maximizes the expected value of
The optimum decision rule is
62.4. Inventories Useful but are Hardwired to be Zero Always#
Next, we turn to a distinct problem in which inventories are useful –
meaning that there are costs of
Here the firm’s maximization problem is
subject to the restrictions that
The restriction that
Here the optimal production plan is
We introduce this
The bottom right panel displays a production path for the original
problem that we are interested in (the blue line) as well with an
optimal production path for the model in which inventories are not
useful (the green path) and also for the model in which, although
inventories are useful, they are hardwired to zero and the firm pays
cost
Notice that it is typically optimal for the firm to produce more when inventories aren’t useful. Here there is no requirement to sell out of inventories and no costs from having sales deviate from inventories.
But “typical” does not mean “always”.
Thus, if we look closely, we notice that for small
High optimal production in the original model early on occurs because the firm wants to accumulate inventories quickly in order to acquire high inventories for use in later periods.
But how the green line compares to the blue line early on depends on the evolution of the demand shock, as we will see in a deterministically seasonal demand shock example to be analyzed below.
In that example, the original firm optimally accumulates inventories slowly because the next positive demand shock is in the distant future.
To make the green-blue model production comparison easier to see, let’s confine the graphs to the first 10 periods:
ex1.simulate(x0, T=10)

62.5. Example 2#
Next, we shut down randomness in demand and assume that the demand shock
Again, we’ll compute and display outcomes in some figures
ex2 = SmoothingExample(C2=[[0], [0]])
x0 = [0, 1, 0]
ex2.simulate(x0)

62.6. Example 3#
Now we’ll put randomness back into the demand shock process and also assume that there are zero costs of holding inventories.
In particular, we’ll look at a situation in which
Now it becomes optimal to set sales approximately equal to inventories and to use inventories to smooth production quite well, as the following figures confirm
ex3 = SmoothingExample(d1=0)
x0 = [0, 1, 0]
ex3.simulate(x0)

62.7. Example 4#
To bring out some features of the optimal policy that are related to some technical issues in linear control theory, we’ll now temporarily assume that it is costless to hold inventories.
When we completely shut down the cost of holding inventories by setting
(Technically, we have set parameters that end up violating conditions needed to assure stability of the optimally controlled state.)
The firm finds it optimal to set
Recall the law of motion for inventories
So when
for almost all values of
The dynamic program instructs the firm to set production costs to zero and to run a Ponzi scheme by running inventories down forever.
(We can interpret this as the firm somehow going short in or borrowing inventories)
The following figures confirm that inventories head south without limit
ex4 = SmoothingExample(d1=0, d2=0)
x0 = [0, 1, 0]
ex4.simulate(x0)

Let’s shorten the time span displayed in order to highlight what is going on.
We’ll set the horizon
# shorter period
ex4.simulate(x0, T=30)

62.8. Example 5#
Now we’ll assume that the demand shock that follows a linear time trend
To represent this, we set
# Set parameters
a = 0.5
b = 3.
ex5 = SmoothingExample(A22=[[1, 0], [1, 1]], C2=[[0], [0]], G=[b, a])
x0 = [0, 1, 0] # set the initial inventory as 0
ex5.simulate(x0, T=10)

62.9. Example 6#
Now we’ll assume a deterministically seasonal demand shock.
To represent this we’ll set
where
ex6 = SmoothingExample(A22=[[1, 0, 0, 0, 0],
[0, 0, 0, 0, 1],
[0, 1, 0, 0, 0],
[0, 0, 1, 0, 0],
[0, 0, 0, 1, 0]],
C2=[[0], [0], [0], [0], [0]],
G=[b, a, 0, 0, 0])
x00 = [0, 1, 0, 1, 0, 0] # Set the initial inventory as 0
ex6.simulate(x00, T=20)

Now we’ll generate some more examples that differ simply from the initial season of the year in which we begin the demand shock
x01 = [0, 1, 1, 0, 0, 0]
ex6.simulate(x01, T=20)

x02 = [0, 1, 0, 0, 1, 0]
ex6.simulate(x02, T=20)

x03 = [0, 1, 0, 0, 0, 1]
ex6.simulate(x03, T=20)

62.10. Exercises#
Please try to analyze some inventory sales smoothing problems using the
SmoothingExample
class.
Exercise 62.1
Assume that the demand shock follows AR(2) process below:
where SmoothingExample
class. Simulate paths starting from the initial
condition
After this, try to construct a very similar SmoothingExample
with
the same demand shock process but exclude the randomness
Solution to Exercise 62.1
# set parameters
α = 1
ρ1 = 1.2
ρ2 = -.3
# construct matrices
A22 =[[1, 0, 0],
[1, ρ1, ρ2],
[0, 1, 0]]
C2 = [[0], [1], [0]]
G = [0, 1, 0]
ex1 = SmoothingExample(A22=A22, C2=C2, G=G)
x0 = [0, 1, 0, 0] # initial condition
ex1.simulate(x0)

# now silence the noise
ex1_no_noise = SmoothingExample(A22=A22, C2=[[0], [0], [0]], G=G)
# initial condition
x0 = [0, 1, 0, 0]
# compute stationary states
x_bar = ex1_no_noise.LQ.compute_sequence(x0, ts_length=250)[0][:, -1]
x_bar
array([ 3.69387755, 1. , 10. , 10. ])
In the following, we add small and large shocks to
T = 40
# small shock
x_bar1 = x_bar.copy()
x_bar1[2] += 2
ex1_no_noise.simulate(x_bar1, T=T)

# large shock
x_bar1 = x_bar.copy()
x_bar1[2] += 10
ex1_no_noise.simulate(x_bar1, T=T)

Exercise 62.2
Change parameters of
Make production more costly, by setting
.Increase the cost of having inventories deviate from sales, by setting
.
Solution to Exercise 62.2
x0 = [0, 1, 0]
SmoothingExample(c2=5).simulate(x0)

SmoothingExample(d2=5).simulate(x0)
